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

1.2 We have seen in part 4 an example for marshaling such a container structure to unmanaged code “one way” (i.e. as an “in” parameter).

1.3 In part 5 we learned how to marshal such a container structure from an unmanaged function to managed code as an “out” (return) parameter.

1.4 Here in part 6, we shall look into how to marshal such a container structure bi-directionally, i.e. as both an “in” and an “out” (or reference) parameter.

1.5 Just as I did in part 5, I shall take this opportunity 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 a Reference 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 modify the underlying ArrayContainer structure with new values for its fields. This ArrayContainer structure is in this way returned to the calling function with modified field data.

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

extern "C" __declspec(dllexport) void __stdcall ModifyArrayContainer
(
  /*[in, out]*/ ArrayContainer* pArrayContainerToModify
)
{
	IRecordInfoPtr spIRecordInfoArrayContainer = NULL;
	HRESULT hrRet;

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

	// We do something drastic : we completely clear the contents of
	// "pArrayContainerToModify". This will release object references
	// and other values of the UDT without deallocating the UDT.
	//
	// As a result, the "array_of_test_structures" SAFEARRAY contained
	// inside "pArrayContainerToModify" will be destroyed.
	spIRecordInfoArrayContainer -> RecordClear((PVOID)pArrayContainerToModify);

	// We now create an STL vector of TestStructure structs.
	std::vector<TestStructure> vecTestStructure;
	TestStructure test_structure;

	test_structure.m_integer = 100;
	test_structure.m_double = 100.0;
	test_structure.m_string = ::SysAllocString(L"New string");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 101;
	test_structure.m_double = 101.0;
	test_structure.m_string = ::SysAllocString(L"New string");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 102;
	test_structure.m_double = 102.0;
	test_structure.m_string = ::SysAllocString(L"New string");
	vecTestStructure.push_back(test_structure);

	test_structure.m_integer = 103;
	test_structure.m_double = 103.0;
	test_structure.m_string = ::SysAllocString(L"New string");
	vecTestStructure.push_back(test_structure);

	IRecordInfoPtr spIRecordInfoTestStructure = NULL;

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

	// Define a receiver of the SAFEARRAY that we want
	// to now create using CreateSafeArrayEx<>().
	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".

	// Define a VARIANT that will contain "pSafeArrayOfTestStructure".
	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.
	//
	// As an alternative to PutFieldNoCopy() as
	// used in GetArrayContainer(), we now use
	// the PutField() method which will make a
	// complete copy of the data contained
	// inside "varFieldValue" to set as the
	// field value of "array_of_test_structures".
	//
	// Hence later on, we must either destroy
	// "pSafeArrayOfTestStructure" by a call to
	// SafeArrayDestroy() or by calling VariantClear().
	spIRecordInfoArrayContainer -> PutField
	(
		INVOKE_PROPERTYPUT,
		pArrayContainerToModify,
		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;

		spIRecordInfoTestStructure -> RecordClear((PVOID)&test_structure);
	}

	// Because we have used IRecordInfo::PutField()
	// to insert "pSafeArrayOfTestStructure" as a field
	// value into "ArrayContainer", we must now destroy
	// "pSafeArrayOfTestStructure" either by calling
	// SafeArrayDestroy() or by calling VariantClear()
	// but not both.
	//
	VariantClear(&varFieldValue);
}

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

  • The function begins by completely clearing the fields of “pArrayContainerToModify”.
  • This will result in the complete destruction of the “array_of_test_structures” SAFEARRAY field of “pArrayContainerToModify”.
  • Later on in the function, a new SAFEARRAY will be created and will replace the one that has just been destroyed.
  • This will present no problem to the corresponding “array_of_test_structures” field of the managed ArrayContainer structure that was passed to the ModifyArrayContainer() function in the managed client application. This is because a brand new managed array will be created and then initialized with new values from the new “array_of_test_structures” SAFEARRAY. But more on this when we get there later.
  • A STL vector of TestStructure structs (i.e. “vecTestStructure”) will then be instantiated and filled with values.
  • Then a new SAFEARRAY (i.e. “pSafeArrayOfTestStructure”) will be created using the CreateSafeArrayEx<>() helper function. The SAFEARRAY will be filled with TestStructure structs copied from the earlier “vecTestStructure” vector.
  • And then the IRecordInfo::PutField() method will be called on the IRecordInfo interface associated with the ArrayContainer UDT to insert the values of “pSafeArrayOfTestStructure” into the “array_of_test_structures” field of the input “pArrayContainerToModify”.
  • At this point, the whole purpose of the ModifyArrayContainer() function has been accomplished.
  • What remains is clean-up.
  • The TestStructure structs contained inside “vecTestStructure” must now each be destroyed. We loop through the elements of “vecTestStructure” and call on the IRecordInfo interface associated with the TestStructure UDT to clear each structure.
  • Note that we must not call IRecordInfo::RecordDestroy() because this will cause the memory area occuppied by each TestStructure struct to be freed.
  • This is not permitted because each TestStructure struct contained inside “vecTestStructure” remains owned by “vecTestStructure”. Only “vecTestStructure” can de-allocate each struct.
  • Finally, we call on VariantClear() to clear the “varFieldValue” VARIANT that was used in the call to IRecordInfo::PutField().
  • Alternatively, SafeArrayDestroy() can also be used to directly destroy “pSafeArrayOfTestStructure”.
  • However, do not call both VariantClear() and SafeArrayDestroy().

4. Example Call to ModifyArrayContainer().

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

[DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void ModifyArrayContainer
(
  [In][Out] ref ArrayContainer ArrayContainerToModify
);
  • When the interop marshaler internally makes a call to the ModifyArrayContainer() function, it will create the unmanaged version of the ArrayContainer structure and then pass a pointer to it as parameter to the ModifyArrayContainer() function.
  • 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 ModifyArrayContainer() function. The ModifyArrayContainer() function has the right to do so because the OutAttribute indicates that the parameter is to be returned from it.
  • Now due to the presence of the InAttribute, the existing contents of the managed ArrayContainer structure will be transformed into respective fields of the unmanaged version of the ArrayContainer structure which is then passed along to the ModifyArrayContainer() function.

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

static void DoTest_ModifyArrayContainer()
{
    ArrayContainer ArrayContainerToModify = new ArrayContainer();
    // Define and instantiate a managed array of 3
    // TestStructure structs for the "array_of_test_structures"
    // field.
    ArrayContainerToModify.array_of_test_structures = new TestStructure[3];

    // Assign simple values to the elements of the array.
    for (int i = 0; i < ArrayContainerToModify.array_of_test_structures.Length; i++)
    {
        ArrayContainerToModify.array_of_test_structures[i].m_integer
		= i;
        ArrayContainerToModify.array_of_test_structures[i].m_double
		= (double)i;
        ArrayContainerToModify.array_of_test_structures[i].m_string
		= string.Format("Hello World [{0}]", i);
    }

    // Call on ModifyArrayContainer() to modify ArrayContainerToModify.
    ModifyArrayContainer(ref ArrayContainerToModify);

    // Display the contents of the "array_of_test_structures" array
    // inside ArrayContainerToModify.
    for (int i = 0; i < ArrayContainerToModify.array_of_test_structures.Length; i++)
    {
        Console.WriteLine("ArrayContainerToModify.array_of_test_structures[{0}].m_integer : [{1}]",
		i, ArrayContainerToModify.array_of_test_structures[i].m_integer);
        Console.WriteLine("ArrayContainerToModify.array_of_test_structures[{0}].m_double : [{1}]",
		i, ArrayContainerToModify.array_of_test_structures[i].m_double);
        Console.WriteLine("ArrayContainerToModify.array_of_test_structures[{0}].m_string : [{1:S}]",
		i, ArrayContainerToModify.array_of_test_structures[i].m_string);
    }
}

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

  • An ArrayContainer structure “ArrayContainerToModify” is defined and instantiated.
  • The “array_of_test_structures” field of “ArrayContainerToModify” is instantiated to an array of 3 TestStructure structs.
  • Simple values are then assigned to the elements of the array.
  • The ModifyArrayContainer() function is then called with “ArrayContainerToModify” passed as a reference parameter.
  • Under the covers, when the ModifyArrayContainer() is called, an unmanaged counterpart of the “ArrayContainerToModify” structure is allocated by the interop marshaler and filled with actual field values based on those of the managed “ArrayContainerToModify” structure.
  • A pointer to this unmanaged “ArrayContainerToModify” structure is then passed to the ModifyArrayContainer() function.
  • When the ModifyArrayContainer() returns, the latest contents of the unmanaged “ArrayContainerToModify” structure is used to rebuild the managed “ArrayContainerToModify” structure.
  • The DoTest_ModifyArrayContainer() function then loops through the contents of the new “ArrayContainerToModify” structure and display all its new contents.

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

ArrayContainerToModify.array_of_test_structures[0].m_integer : [100]
ArrayContainerToModify.array_of_test_structures[0].m_double : [100]
ArrayContainerToModify.array_of_test_structures[0].m_string : [New string]
ArrayContainerToModify.array_of_test_structures[1].m_integer : [101]
ArrayContainerToModify.array_of_test_structures[1].m_double : [101]
ArrayContainerToModify.array_of_test_structures[1].m_string : [New string]
ArrayContainerToModify.array_of_test_structures[2].m_integer : [102]
ArrayContainerToModify.array_of_test_structures[2].m_double : [102]
ArrayContainerToModify.array_of_test_structures[2].m_string : [New string]
ArrayContainerToModify.array_of_test_structures[3].m_integer : [103]
ArrayContainerToModify.array_of_test_structures[3].m_double : [103]
ArrayContainerToModify.array_of_test_structures[3].m_string : [New string]

5. In Conclusion.

5.1 Here in part 6, we have looked at how managed structures may be passed to and returned from unmanaged code bi-directionally by way of a SAFEARRAY contained in an outer wrapping structure.

5.2 More good demonstrations of methods of the IRecordInfo interface (RecordClear(), PutField()) was given. The VariantClear() function was also used to completely clear a SAFEARRAY. I certainly hope that the reader has benefitted from this.

5.3 In part 7, we shall shift gears again and up the ante in complexity. We shall be explore marshaling a structure that contains an array of ArrayContainer structures.

5.4 Noting that each ArrayContainer structue contains an array of TestStructure structs, we are talking about marshaling a potentially great number of TestStructure structs.

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