[转]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 that is contained within a structure.

1.2 I have given a specific working example for marshaling such a container structure to unmanaged code “one way” (i.e. as an “in” parameter).

1.3 Here in part 5, I shall demonstrate how to marshal such a container structure from an unmanaged function to managed code as an “out” (return) parameter.

1.4 Just like the case in part 4, the example codes that will be presented here are practically not much more complicated than direct marshaling of a SAFEARRAY from unmanaged code to managed code, to be eventually transformed into a managed array.

1.5 I have endeavoured to cover this aspect of marshaling in order to pave the way for a later installment in which an array of an array of structures are to be marshaled.

1.6 It is also an opportunity for me to demonstrate the use of several IRecordInfo methods which pertain to UDTs. Through these IRecordInfo methods I hope to show strong consistency and robustness of both the IRecordInfo interface as well as the SAFEARRAY.

2. The TestStructure, ArrayContainer Structures and the CSConsoleApp.tlb Type Library.

2.1 We shall continue to use the TestStructure and the ArrayContainer structures that we have previously defined in part 1 and part 4 respectively.

2.2 We shall also be using the CSConsoleApp.tlb type library that we have updated in part 4.

3. Unmanaged API that Returns an ArrayContainer Structure as an “Out” Parameter.

3.1 In this section, I shall present a new unmanaged function to be exported from the UnmanagedDll.dll that we have been using since part 1. This function takes a pointer to an ArrayContainer structure as input parameter.

3.2 The intension of this function is to fill the underlying ArrayContainer structure with values for its fields. This ArrayContainer structure is in this way returned to the calling function.

3.3 Full source codes of this unmanaged function is listed below :

extern "C" __declspec(dllexport) void __stdcall GetArrayContainer
(
  /*[out]*/ ArrayContainer* pArrayContainerReceiver
)
{
	std::vector<TestStructure> vecTestStructure;
	TestStructure test_structure;

	test_structure.m_integer = 0;
	test_structure.m_double = 0;
	test_structure.m_string = ::SysAllocString(L"Hello World");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 1;
	test_structure.m_double = 1.0;
	test_structure.m_string = ::SysAllocString(L"Hello World");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 2;
	test_structure.m_double = 2.0;
	test_structure.m_string = ::SysAllocString(L"Hello World");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 3;
	test_structure.m_double = 3.0;
	test_structure.m_string = ::SysAllocString(L"Hello World");
	vecTestStructure.push_back(test_structure);

	HRESULT hrRet;
	IRecordInfoPtr spIRecordInfoTestStructure = NULL;

	hrRet = GetIRecordType
	(
		TEXT("CSConsoleApp.tlb"),
		__uuidof(TestStructure),
		&spIRecordInfoTestStructure
	);

	// Define a receiver of the SAFEARRAY.
	SAFEARRAY* pSafeArrayOfTestStructure = NULL;

	CreateSafeArrayEx<TestStructure, VT_RECORD>
	(
		(TestStructure*)&(vecTestStructure[0]),
		vecTestStructure.size(),
		(PVOID)spIRecordInfoTestStructure,
		pSafeArrayOfTestStructure
	);

	// At this point, "pSafeArrayOfTestStructures" contains
	// copies of TestStructure structs from "vecTestStructure".

	IRecordInfoPtr spIRecordInfoArrayContainer = NULL;

	hrRet = GetIRecordType
	(
		TEXT("CSConsoleApp.tlb"),
		__uuidof(ArrayContainer),
		&spIRecordInfoArrayContainer
	);

	spIRecordInfoArrayContainer -> RecordClear((PVOID)pArrayContainerReceiver);		

	// Define a VARIANT that will contain "pSafeArrayOfTestStructures".
	VARIANT varFieldValue;

	VariantInit(&varFieldValue);
	V_VT(&varFieldValue) = (VT_ARRAY | VT_RECORD);
	V_ARRAY(&varFieldValue) = pSafeArrayOfTestStructure;

	// Set the "array_of_test_structures" field
	// of the "pArrayContainerReceiver" structure.
	spIRecordInfoArrayContainer -> PutFieldNoCopy
	(
		INVOKE_PROPERTYPUT,
		pArrayContainerReceiver,
		L"array_of_test_structures",
		&varFieldValue
	);

	std::vector<TestStructure>::iterator theIterator;

	// The members of structures which are reference types
	// (e.g. m_string which is BSTR) must be freed here.
	// This is because SAFEARRAYs use copy-semantics.
	// That is, they store an entire copy of the structures
	// that we insert into it.
	//
	// Therefore each TestStructure structure inside the
	// SAFEARRAY will contain a BSTR copy in its m_string
	// member.
	for
	(
		theIterator = vecTestStructure.begin();
		theIterator != vecTestStructure.end();
		theIterator++
	)
	{
		TestStructure& test_structure = *theIterator;

		::SysFreeString(test_structure.m_string);
	}

	// Because we have used IRecordInfo::PutFieldNoCopy()
	// to insert "pSafeArrayOfTestStructure" as a field
	// value into "ArrayContainer", we must not destroy
	// "pSafeArrayOfTestStructure".
	//
	// If we had used IRecordInfo::PutField() then we
	// must call SafeArrayDestroy() on "pSafeArrayOfTestStructure"
	// or use VariantClear() on the VARIANT that was
	// used in the IRecordInfo::PutField() call.
	// Otherwise there will be a memory leakage.
	//
	//::SafeArrayDestroy(pSafeArrayOfTestStructure);
	//pSafeArrayOfTestStructure = NULL;
	//VariantClear(&varFieldValue);
}

The following is a summary of the workings of this function :

  • An STL vector “vecTestStructure” is defined.
  • The vector is used to insert 4 copies of TestStructure structs each of which has different values for its “m_integer” and “m_double” fields. The “m_string” fields are set to a standard value.
  • The GetIRecordType() helper function (first introduced in part 2) is used to obtain a pointer to the IRecordInfo interface which is associated with the TestStructure UDT.
  • The CreateSafeArrayEx<>() helper function is then used to copy each of the TestStructure UDTs from “vecTestStructure” to a SAFEARRAY.
  • Instead of directly using the SAFEARRAY field of the input “pArrayContainerReceiver” ArrayContainer structure to hold the SAFEARRAY created inside CreateSafeArrayEx<>(), I have used a separate SAFEARRAY “pSafeArrayOfTestStructure” to point to it.
  • What I intend to do is to use the IRecordInfo::PutFieldNoCopy() method to insert the SAFEARRAY directly into the “array_of_test_structures” field of “pArrayContainerReceiver”.
  • To do this, we need to use a VARIANT to contain “pSafeArrayOfTestStructure”.
  • Before the function completes and returns to the caller, we must clear the TestStructure structs contained inside “vecTestStructure”.
  • This is necessary because the SAFEARRAY that was eventually inserted into “pArrayContainerReceiver” holds a complete copy of each of the UDTs contained inside “vecTestStructure”.
  • Hence the UDTs inside “vecTestStructure” are no longer needed anywhere and if we do not clear them, memory leakage will result.
  • However, note well that because we have used IRecordInfo::PutFieldNoCopy() to insert “pSafeArrayOfTestStructure” as a field value into “pArrayContainerReceiver”, we must not destroy “pSafeArrayOfTestStructure”.
  • As the documentation of IRecordInfo::PutFieldNoCopy() indicated, the ownership of “pSafeArrayOfTestStructure” was transferred to the receiving “pArrayContainerReceiver”.
  • If we had used IRecordInfo::PutField() then we must call SafeArrayDestroy() on “pSafeArrayOfTestStructure” or use VariantClear() on the VARIANT that was used in the IRecordInfo::PutField() call. Otherwise there will be a memory leak.

4. Example Call to GetArrayContainer().

4.1 The following is how the GetArrayContainer() function is declared in a client C# code :

[DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void GetArrayContainer([Out] out ArrayContainer ArrayContainerReceiver);

The following are some important points pertaining to the code above :

  • When the interop marshaler internally makes a call to the GetArrayContainer() function, it will create the unmanaged version of the ArrayContainer structure and then pass a pointer to it as parameter to the GetArrayContainer() function.
  • The unmanaged version of the ArrayContainer structure is the one which is created by the Visual C++ compiler when it processed the “CSConsoleApp.tlb” type library via an #import statement in a C++ project :
struct __declspec(uuid("42d386a1-aae1-445e-a755-00aa7b2c1753"))
ArrayContainer
{
    SAFEARRAY * array_of_test_structures;
};
  • A pointer to this unmanaged ArrayContainer structure is passed instead of a complete structure itself due to the use of the OutAttribute as well as the out keyword.
  • This is so that the underlying structure can be modified by the GetArrayContainer() function.

4.2 The following is a sample C# function that calls GetArrayContainer() :

static void DoTest_GetArrayContainer()
{
    ArrayContainer ArrayContainerReceiver;

    GetArrayContainer(out ArrayContainerReceiver);

    for (int i = 0; i < ArrayContainerReceiver.array_of_test_structures.Length; i++)
    {
        Console.WriteLine("ArrayContainerReceiver.array_of_test_structures[{0}].m_integer : [{1}]",
            i, ArrayContainerReceiver.array_of_test_structures[i].m_integer);
        Console.WriteLine("ArrayContainerReceiver.array_of_test_structures[{0}].m_double : [{1}]",
            i, ArrayContainerReceiver.array_of_test_structures[i].m_double);
        Console.WriteLine("ArrayContainerReceiver.array_of_test_structures[{0}].m_string : [{1:S}]",
            i, ArrayContainerReceiver.array_of_test_structures[i].m_string);
    }
}

The following are some important points pertaining to the code above :

  • An ArrayContainer structure “ArrayContainerReceiver” is defined but is not instantiated.
  • This is acceptable because it is used as an “out” parameter to a call to GetArrayContainer().
  • Under the covers, when the GetArrayContainer() returns, a new ArrayContainer structure will be created by the interop marshaler and the unmanaged ArrayContainer structure the pointer to which was passed to the function would be filled with actual field values.
  • These field values will be used to set the corresponding ones in the newly created managed ArrayContainer structure.
  • The new field values of the managed ArrayContainer structure will be displayed via a loop.

4.3 At runtime, the above function will produce the following console output :

ArrayContainerReceiver.array_of_test_structures[0].m_integer : [0]
ArrayContainerReceiver.array_of_test_structures[0].m_double : [0]
ArrayContainerReceiver.array_of_test_structures[0].m_string : [Hello World]
ArrayContainerReceiver.array_of_test_structures[1].m_integer : [1]
ArrayContainerReceiver.array_of_test_structures[1].m_double : [1]
ArrayContainerReceiver.array_of_test_structures[1].m_string : [Hello World]
ArrayContainerReceiver.array_of_test_structures[2].m_integer : [2]
ArrayContainerReceiver.array_of_test_structures[2].m_double : [2]
ArrayContainerReceiver.array_of_test_structures[2].m_string : [Hello World]
ArrayContainerReceiver.array_of_test_structures[3].m_integer : [3]
ArrayContainerReceiver.array_of_test_structures[3].m_double : [3]
ArrayContainerReceiver.array_of_test_structures[3].m_string : [Hello World]

5. In Conclusion.

5.1 Here in part 5, we have looked at how managed structures may be derived from unmanaged code by way of a SAFEARRAY contained in an outer wrapping structure.

5.2 I have made a good attempt at demonstrating the use of the IRecordInfo::PutFieldNoCopy() method to insert a field value into a UDT.

5.3 Part of my aim was to demontrate a solid example of memory ownership.

5.4 I certainly hope that the reader has benefitted from this demonstration.

原文地址:https://www.cnblogs.com/czytcn/p/7928087.html