1. Introduction.

1.1 In part
1
of this series of articles, I demonstrated how to transfer managed
arrays to unmanaged code as SAFEARRAYs. The transfer was single-directional
“into” the unmanaged function and the SAFEARRAY that was passed to the unmanaged
function is treated as “read-only”.

1.2 Then in part
2
, I showed how to return a SAFEARRAY from unmanaged code to
managed code as an “out” parameter. The transfer this time was also
single-directional but the direction is “outwards” towards the managed
code caller. The SAFEARRAY that was returned is used to generate a managed
array.

1.3 Here in part 3, I shall explain how to pass a
managed array “to and from” unmanaged code as both an “in” and “out” (i.e. by
reference) parameter.

1.4 A parameter passed this way is marshaled
“into” the unmanaged function when it is called and when the function returns,
the same parameter is marshaled “out of” the unmanaged function. The parameter
is also subject to modification by the target function.

1.5 As usual, throughout this article, we shall be
working only with single-dimensional managed
arrays and SAFEARRAYs.

2. TestStructure, CSConsoleApp.tlb Type
Library and UnmanagedDll.DLL

2.1 We shall be using the same TestStructure struct that
we have developed in part 1.

2.2 We shall also continue to use
the CSConsoleApp.tlb type
library that was produced from the CSConsoleApp console application
solution that was presented in part 1.

2.3 We shall augment UnmanagedDll.dll with
some helper functions as well as a new exported API to be called in
the CSConsoleApp console application.

2.4 The source codes of the CSConsoleApp console
application will also be updated with new test codes.

3. Unmanaged API that References a SAFEARRAY of
TestStructure.

3.1 The new exported function that we will
expose to C# takes as parameter a double pointer to a SAFEARRAY of
TestStructure structures.

3.2 This double pointer will be used to point to a
SAFEARRAY of TestStructure UDTs that derives from a managed array. It will also
be used to return a SAFEARRAY of TestStructure UDTs to the caller.

3.3 In our case, the caller will be the interop
marshaler which will first transform a managed array into a SAFEARRAY in order
to pass to the exported function. The interop marshaler will then transform the
returned SAFEARRAY back into a managed array.

3.4 Note the protocol on memory ownership : because the
interop marshaler is the eventual receiver of the SAFEARRAY which is
returned, the interop marshaler owns the SAFEARRAY and is at liberty
to destroy it when it no longer needs it.

3.5 The following is a full code listing of this
function :

// ModifyArrayOfTestStructure() will modify the contents
// of the input SAFEARRAY. It will also add new elements
// to the SAFEARRAY.
extern "C" __declspec(dllexport) void __stdcall ModifyArrayOfTestStructure
(
/*[in, out]*/ SAFEARRAY** ppSafeArrayToModify
)
{
IRecordInfoPtr spIRecordInfo = NULL; // We obtain a pointer to the IRecordInfo interface associated
// with the SAFEARRAY. It is used to clear TestStructure structs.
SafeArrayGetRecordInfo(*ppSafeArrayToModify, &spIRecordInfo); // If we are unable to obtain such an interface,
if (spIRecordInfo == NULL)
{
return;
} // First determine how many elements there are inside
// "ppSafeArrayToModify".
long LBound = 0;
long UBound = 0; SafeArrayGetLBound(*ppSafeArrayToModify, 1, &LBound);
SafeArrayGetUBound(*ppSafeArrayToModify, 1, &UBound); ULONG ulSafeArraySize = UBound - LBound + 1; if (ulSafeArraySize > 0)
{
// The SAFEARRAY must have at least one element
// for us to modify.
TestStructure test_structure;
long rgIndices[1]; // The UDT receiving structure must be cleared
// before calling on SafeArrayGetElement() to
// obtain a copy of an element.
memset(&test_structure, 0, sizeof(TestStructure)); // We will modify the very first element.
rgIndices[0] = 0; SafeArrayGetElement
(
*ppSafeArrayToModify,
rgIndices,
(void FAR*)&test_structure
); // Increment the values of the
// "m_integer" and "m_double"
// fields.
test_structure.m_integer += 100;
test_structure.m_double += 100;
// Clear the original BSTR and
// set it to a different value.
::SysFreeString(test_structure.m_string);
test_structure.m_string = ::SysAllocString(L"Modified String"); // Insert "test_structure" into the SAFEARRAY
// at the same index position (i.e. 0);
SafeArrayPutElement
(
(SAFEARRAY*)*ppSafeArrayToModify,
(long*)rgIndices,
(void*)(&test_structure)
); // After "inserting" "test_structure" into
// the SAFEARRAY, we must remember to clear it.
spIRecordInfo -> RecordClear((void*)(&test_structure));
} // Now modify the size of the SAFEARRAY.
// Increase it 3 more elements.
SAFEARRAYBOUND rgsabound[1]; rgsabound[0].lLbound = 0;
rgsabound[0].cElements = ulSafeArraySize + 3; HRESULT hrRetTemp = SafeArrayRedim
(
*ppSafeArrayToModify,
rgsabound
); if (SUCCEEDED(hrRetTemp))
{
for (ULONG ulIndex = ulSafeArraySize; ulIndex < (ulSafeArraySize + 3); ulIndex++)
{
long rgIndices[1];
TestStructure test_structure; rgIndices[0] = ulIndex; memset(&test_structure, 0, sizeof(TestStructure)); // Add simple values to the fields of the new TestStructure
// structs that are added to the SAFEARRAY.
test_structure.m_integer = (int)ulIndex;
test_structure.m_double = (double)ulIndex;
test_structure.m_string = ::SysAllocString(L"New String"); SafeArrayPutElement
(
(SAFEARRAY*)*ppSafeArrayToModify,
(long*)rgIndices,
(void*)(&test_structure)
); spIRecordInfo -> RecordClear((void*)(&test_structure));
}
}
}

The following is a synopsis of this function
:

  • The function first tries to obtain a pointer to
    the IRecordInfo interface associated with the UDT contained in the
    SAFEARRAY. This is done by using SafeArrayGetRecordInfo().
  • It then calculates the total number of elements in the
    SAFEARRAY (using SafeArrayGetLBound() and
    SafeArrayGetUBound()).
  • Assuming that the SAFEARRAY contains at least one
    element, this function will modify the field values of the very first
    element.
  • Note that SafeArrayGetElement() is used to obtain a copy
    of the target element and then SafeArrayPutElement() is used to replace the
    target element.
  • When SafeArrayPutElement() is called, it will first
    clear the fields of the existing element residing at the target
    index.
  • Then the onus is on the ModifyArrayOfTestStructure()
    function to clear the TestStructure struct which was used to insert the UDT at
    the target index. For this, IRecordInfo::RecordClear() is
    used.
  • After that, the ModifyArrayOfTestStructure() function
    uses SafeArrayRedim() to resize the SAFEARRAY to a larger
    size.
  • A loop is then performed to insert new additional UDTs
    into the SAFEARRAY.
  • Note that as each UDT is inserted into the SAFEARRAY, a
    copy of the UDT is made and stored in the SAFEARRAY.
  • Hence the original TestStructure UDT must be cleared
    after each call to SafeArrayPutElement().

4. Example C# Call to
ModifyArrayOfTestStructure().

4.1 The following shows how the
ModifyArrayOfTestStructure() API should be declared in a C# program :

[DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void ModifyArrayOfTestStructure
(
[In][Out] [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_RECORD)]
ref TestStructure[] SafeArrayToModify
);

Now note the use of the various attributes :

  • The presence of the InAttribute and the OutAttribute
    indicate that the managed array parameter (i.e. “SafeArrayToModify”) is to be
    marshaled into and out of the ModifyArrayOfTestStructure() function. The “ref”
    keyword further indicates this to the C# compiler.
  • These attributes also indicate to the interop
    marshaler that whatever form the counterpart parameter (i.e. the parameter
    of the unmanaged function) takes when it is passed to the target function, it
    may be eventually modified before its return.
  • However, it is nevertheless owned by the caller
    which is at liberty to destroy it when it is returned from the unmanaged
    function.
  • The way the MarshalAsAttribute is specified as well as
    the presence of the OutAttribute indicate to the interop marshaler that the
    counterpart parameter will take the form of a double pointer to a
    SAFEARRAY.
  • The “SafeArraySubType” field for the MarshalAsAttribute,
    being equal to “VarEnum.VT_RECORD”, indicates to the interop marshaler that the
    SAFEARRAY will contain UDTs.
  • And since the “SafeArrayToModify” parameter is typed as
    an array of TestStructure, the UDT must be the equivalent of the
    TestStructure.

4.2 The following is a sample C# function that makes a
call to GetArrayOfTestStructure() :

static void DoTest_ModifyArrayOfTestStructure()
{
// Define and instantiate a managed array of 3
// TestStructure structs.
TestStructure[] SafeArrayOfTestStructure = new TestStructure[3]; // Assign simple values to the elements of the array.
for (int i = 0; i < SafeArrayOfTestStructure.Length; i++)
{
SafeArrayOfTestStructure[i].m_integer = i;
SafeArrayOfTestStructure[i].m_double = (double)i;
SafeArrayOfTestStructure[i].m_string = string.Format("Hello World [{0}]", i);
} // Call on ModifyArrayOfTestStructure() to modify
// this array.
ModifyArrayOfTestStructure(ref SafeArrayOfTestStructure); // Display the contents of the array.
for (int i = 0; i < SafeArrayOfTestStructure.Length; i++)
{
Console.WriteLine("SafeArrayOfTestStructure[{0}].m_integer : [{1}]", i, SafeArrayOfTestStructure[i].m_integer);
Console.WriteLine("SafeArrayOfTestStructure[{0}].m_double : [{1}]", i, SafeArrayOfTestStructure[i].m_double);
Console.WriteLine("SafeArrayOfTestStructure[{0}].m_string : [{1:S}]", i, SafeArrayOfTestStructure[i].m_string);
}
}

The following is a synopsis :

  • A managed array of 3 TestStructure structs is defined
    and instantiated.
  • Simple values are assigned to the field values of each
    element.
  • The ModifyArrayOfTestStructure() API is then invoked and
    the managed array is passed as a parameter by reference.
  • Thereafter, we display all the contents of the managed
    array.

4.3 The following is what happened under the
covers :

  • When ModifyArrayOfTestStructure() is called, the interop
    marshaler will internally prepare a SAFEARRAY and fill it with the (unmanaged)
    UDT equivalent of each of the (managed) TestStructure structs contained in the
    “SafeArrayOfTestStructure” array.
  • Then, sensing that the “SafeArrayToModify” parameter of
    the ModifyArrayOfTestStructure() function has been designated as an “in” and
    “out” parameter, the interop marshaler passes a double pointer to the SAFEARRAY
    as parameter when it calls the ModifyArrayOfTestStructure()
    function.
  • The ModifyArrayOfTestStructure() function is free to
    modify the SAFEARRAY in whatever way it deems fit.
  • When ModifyArrayOfTestStructure() returns, the interop
    marshaler will use the returned SAFEARRAY to internally modify the managed
    array.
  • However, very likely, the interop marshaler will simply
    delete the entire original managed array and re-create a new one from the latest
    contents of the SAFEARRAY.
  • When the managed array of TestStructure is finally
    modified/re-created, the returned SAFEARRAY which was shared by both the interop
    marshaler and the ModifyArrayOfTestStructure() API will be destroyed. Each
    TestStructure contained inside the SAFEARRAY will be destroyed by calling on the
    RecordDestroy() method using the IRecordInfo pointer which is already contained
    within the SAFEARRAY.

4.4 At runtime, the C# function
DoTest_GetArrayOfTestStructure() will produce the following expected output
:

SafeArrayOfTestStructure[0].m_integer : [100]
SafeArrayOfTestStructure[0].m_double : [100]
SafeArrayOfTestStructure[0].m_string : [Modified String]
SafeArrayOfTestStructure[1].m_integer : [1]
SafeArrayOfTestStructure[1].m_double : [1]
SafeArrayOfTestStructure[1].m_string : [Hello World [1]]
SafeArrayOfTestStructure[2].m_integer : [2]
SafeArrayOfTestStructure[2].m_double : [2]
SafeArrayOfTestStructure[2].m_string : [Hello World [2]]
SafeArrayOfTestStructure[3].m_integer : [3]
SafeArrayOfTestStructure[3].m_double : [3]
SafeArrayOfTestStructure[3].m_string : [New String]
SafeArrayOfTestStructure[4].m_integer : [4]
SafeArrayOfTestStructure[4].m_double : [4]
SafeArrayOfTestStructure[4].m_string : [New String]
SafeArrayOfTestStructure[5].m_integer : [5]
SafeArrayOfTestStructure[5].m_double : [5]
SafeArrayOfTestStructure[5].m_string : [New String]

The following is a general summary for the results shown
above :

  • At the start of the DoTest_ModifyArrayOfTestStructure()
    function, a managed array of 3 TestStructure structs is
    created.
  • Then the ModifyArrayOfTestStructure() function modified
    the very first element to contain values 100, 100 and “Modified String” for its
    “m_integer”, “m_double” and “m_string” field values
    respectively.
  • The ModifyArrayOfTestStructure() function also added 3
    new TestStructure UDTs with numeric values that correspond with their SAFEARRAY
    index positions and a standard string value of “New String”.
  • Hence eventually, the “SafeArrayOfTestStructure” array
    contained 6 elements with values as shown.

5. In Conclusion.

5.1 Here in part 3, I have demonstrated sending and
returning a SAFEARRAY to and from an unmanaged function in managed
code.

5.2 Various SAFEARRAY APIs are used to demonstrate ways
to modify both the contents of SAFEARRAYs as well as the size of
SAFEARRAYs.

5.3 I have also shown that the modified contents of a
SAFEARRAY is used by the interop marshaler to modify/re-create the original
managed array.

5.4 Again I emphasized that with memory ownership,
the interop marshaler is at liberty to destroy the returned
SAFEARRAY.

5.5 This part marks the end of my basic treatment
of marshaling a managed array to and from unmanaged functions by way of
SAFEARRAYs.

5.6 Further parts of this series of articles are
not required reading for those who are not interested in more complex
containment of managed arrays as UDTs in SAFEARRAYs.

[转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 3.的更多相关文章

  1. [转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 1.

    1. Introduction. 1.1 I have previously written about exchanging SAFEARRAYs of managed structures wit ...

  2. [转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 6.

    1. Introduction. 1.1 Starting from part 4 I have started to discuss how to interop marshal a managed ...

  3. [转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 5.

    1. Introduction. 1.1 In part 4, I have started to discuss how to interop marshal a managed array tha ...

  4. [转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 4.

    1. Introduction. 1.1 In parts 1 through 3 of this series of articles, I have thoroughly discussed th ...

  5. [转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 2.

    1. Introduction. 1.1 In part 1 of this series of articles, I explained how managed arrays may be tra ...

  6. [转]Passing Managed Structures With Strings To Unmanaged Code Part 2

    1. Introduction. 1.1 In part 1 of this series of blogs we studied how to pass a managed structure (w ...

  7. [转]Passing Managed Structures With Strings To Unmanaged Code Part 1

    1. Introduction. 1.1 Managed structures that contain strings are a common sight. The trouble is that ...

  8. [转]Passing Managed Structures With Strings To Unmanaged Code Part 3

    1. Introduction. 1.1 In part 1 of this series of blogs we studied how to pass a managed structure (w ...

  9. Passing JavaScript Objects to Managed Code

    Silverlight If the target managed property or input parameter is strongly typed (that is, not typed ...

随机推荐

  1. angular的$q,defer,promise

    $q是Angular的一种内置服务,它可以使你异步地执行函数,并且当函数执行完成时它允许你使用函数的返回值(或异常). 官网:http://docs.angularjs.cn/api/ng/servi ...

  2. Azure的CentOS上安装LIS (Linux Integration Service)

    Azure上虚拟化技术都是采用的Hyper-v,每台Linux虚拟机都安装了LIS(Linux Integration Service).LIS的功能是为VM提供各种虚拟设备的驱动.所以LIS直接影响 ...

  3. EMC (电磁兼容性)

    电磁兼容性EMC(Electro Magnetic Compatibility),是指设备或系统在其电磁环境中符合要求运行并不对其环境中的任何设备产生无法忍受的电磁干扰的能力.因此,EMC包括两个方面 ...

  4. (转)C# -- 扩展方法的应用(Extension Methods)

    本文转载自:http://blog.csdn.net/zxz414644665/article/details/9793205 当你有下面这样一个需求的时候,扩展方法就会起到作用:在项目中,类A需要添 ...

  5. 机器学习:模型泛化(LASSO 回归)

    一.基础理解 LASSO 回归(Least Absolute Shrinkage and Selection Operator Regression)是模型正则化的一定方式: 功能:与岭回归一样,解决 ...

  6. Java-API:java.util.map

    ylbtech-Java-API:java.util.map compact1, compact2, compact3 java.util Interface Map<K,V> Type ...

  7. 查看,修改ceph节点的ceph配置命令

    标签(空格分隔): ceph,ceph运维,ceph配置 查看ceph配置 1. 查看ceph默认配置: # ceph --show-config 2. 查看 type.num 的ceph默认配置: ...

  8. 系统监控磁盘分区 homework

    作业一: 1) 开启Linux系统前添加一块大小为15G的SCSI硬盘 2) 开启系统,右击桌面,打开终端 3) 为新加的硬盘分区,一个主分区大小为5G,剩余空间给扩展分区,在扩展分区上划分1个逻辑分 ...

  9. Windows系统上release版本程序bug跟踪解决方案-.dmp文件。

    使用场景: Win32程序在release模式下编译完成,发送给最终用户使用时,我们的程序有时候也会出现崩溃的情况,这个时候如果能快速定位崩溃原因或提供一些程序崩溃时的状态信息,对我们解决问题将会带来 ...

  10. viewpagerindicator+UnderlinePageIndicator+ viewpage切换

    布局文件activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/androi ...