[转]Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 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 the techniques for exchanging arrays between managed and unmanaged code by way of SAFEARRAYs.

1.2 The knowledge that can be gained from the first 3 parts of this series is sufficient for general development purposes.

1.3 From part 4 onwards, I shall be covering cases where an outer structure is used to contain the array of structures.

1.4 The use of SAFEARRAYs for marshaling managed arrays is particularly useful when the array is meant to be variable in length.

1.5 I have previously written about marshaling managed structures that contain arrays (see the “Passing Managed Structures With Arrays To Unmanaged Code” series beginning from part 1).

1.6 However, the array contained in the example structure is an integer array whereas the arrays that are used in this series of articles will be arrays of structures, hence serving an alternate (and more complex) example.

2. A New Structure – ArrayContainer.

2.1 Along with using the same TestStructure struct which was first defined in part 1, we shall be using a new structure : ArrayContainer :

[ComVisible(true)]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Guid("42D386A1-AAE1-445e-A755-00AA7B2C1753")]
public struct ArrayContainer
{
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_RECORD)]
    public TestStructure[] array_of_test_structures;
}

2.2 For the purpose of this article, I have defined this structure inside the source codes of the C# console client application CSConsoleApp which was first introduced in part 1.

2.3 After compiling the CSConsoleApp project into an output EXE assembly, we must call on the Type Library Exporter (TLBEXP.EXE) to produce a type library that will contain the COM-visible constructs defined in CSConsoleApp.exe :

tlbexp CSConsoleApp.exe /out:CSConsoleApp.tlb

2.4 After this is done, we can examine CSConsoleApp.tlb via OLEVIEW.EXE and observe the following definitions contained inside the type library :

// Generated .IDL file (by the OLE/COM Object Viewer)
// 

[
  uuid(4E765D3B-C1F5-4CDE-8095-1E9614E0AE3F),
  version(1.0)
]
library CSConsoleApp
{
    	// TLib :     // Forward declare all types defined in this typelib
	typedef
	[
		uuid(42D386A1-AAE1-445E-A755-00AA7B2C1753), version(1.0)    ,
		custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSConsoleApp.ArrayContainer")
	]
	struct tagArrayContainer
	{
		SAFEARRAY(TestStructure) array_of_test_structures;
	} ArrayContainer;

	typedef
	[
		uuid(1979BCD7-1062-44D8-B3FC-A2686C61E715), version(1.0)    ,
		custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSConsoleApp.TestStructure")
	]
	struct tagTestStructure
	{
		long m_integer;
		double m_double;
		BSTR m_string;
	} TestStructure;
};

We can see that the ArrayContainer structure contains a SAFEARRAY of TestStructure structs.

2.5 That the ArrayContainer structure is a wrapper for a SAFEARRAY of TestStructure does not add very much more complexity to its management unless ArrayContainer also contains other fields.

2.6 I have, however, defined it in the simple way it is so that when we reach later parts of this series of articles where we actually define a structure that contains an array of ArrayContainer structures, things do not get overly complicated and difficult to understand.

3. Unmanaged API that takes an ArrayContainer Structure as an “In” Parameter.

3.1 In this section, I shall present a new function to be exported from the UnmanagedDll.dll that we have been using since part 1. This function takes an ArrayContainer structure as input parameter and then displays the field values of each TestStructure struct contained inside the “array_of_test_structures” SAFEARRAY field.

3.2 Full source codes listed below :

extern "C" __declspec(dllexport) void __stdcall SetArrayContainer
(
  /*[in]*/ ArrayContainer array_container
)
{
	std::vector<TestStructure> vecTestStructure;

	// Copy the TestStructure elements of the SAFEARRAY
	// in "array_container.array_of_test_structures"
	// into a vector of TestStructure structs.
	CopySafeArrayToVector<TestStructure, VT_RECORD>
	(
		array_container.array_of_test_structures,
		vecTestStructure
	);

	// Display the field values of each TestStructure
	// within the "vecTestStructure" vector.
	std::for_each
	(
		vecTestStructure.begin(),
		vecTestStructure.end(),
		display_test_structure()
	);

	// Obtain the IRccordInfo interface associated with
	// the TestStructure UDT.
	IRecordInfoPtr spIRecordInfoTestStructure = NULL;

	SafeArrayGetRecordInfo
	(
		array_container.array_of_test_structures,
		&spIRecordInfoTestStructure
	);

	// Before the end of this function, each of the
	// TestStructure structs inside vecTestStructure
	// must be cleared.
	std::for_each
	(
		vecTestStructure.begin(),
		vecTestStructure.end(),
		clear_test_structure(spIRecordInfoTestStructure)
	);
}

The following is a synopsis of the function above :

  • It defines a vector of TestStructure structs “vecTestStructure”.
  • A helper function CopySafeArrayToVector<>() is used to copy the TestStructure elements of “array_container.array_of_test_structures” (i.e. the SAFEARRAY of TestStructure structs contained in the input ArrayContainer “array_container”) to the “vecTestStructure” vector.
  • It then use the for_each() algorithm function to loop through the elements of the “vecTestStructure” vector and display the field values of each TestStructure struct element.
  • It then calls on SafeArrayGetRecordInfo() to obtain a pointer to the IRecordInfo interface associated with the TestStructure UDT.
  • Another for_each() loop is called, this time to clear each TestStructure struct element contained inside “vecTestStructure”.
  • This last action is necessary because each TestStructure struct element contained inside “vecTestStructure” is a copy of its corresponding TestStructure contained inside “array_container.array_of_test_structures”.
  • While “array_container.array_of_test_structures” will eventually be destroyed by the interop marshaler (it owns the entire ArrayContainer structure passed to the SetArrayContainer() function, the TestStructure structs contained inside “vecTestStructure” must be cleared manually.

3.3 The CopySafeArrayToVector<>() helper function source codes are listed below :

template <class T, VARTYPE v>
void CopySafeArrayToVector
(
  /*[in]*/ SAFEARRAY*& pSafeArray,
  /*[out]*/ std::vector<T>& vecReceiver
)
{
	VARTYPE vt;	

	SafeArrayGetVartype(pSafeArray, &vt);

	// We assert that the Variant Type is "v".
	_ASSERT(vt == v);

	long LBound = 0;
	long UBound = 0;

	SafeArrayGetLBound(pSafeArray, 1, &LBound);
	SafeArrayGetUBound(pSafeArray, 1, &UBound);

	ULONG ulSafeArraySize = UBound - LBound + 1;

	for (ULONG ulIndex = 0; ulIndex < ulSafeArraySize; ulIndex++)
	{
		long lIndexVector[1];
		T data;
		HRESULT hrRetTemp;

		lIndexVector[0] = ulIndex;

		// Must initialize memory area of "data"
		// before calling SafeArrayGetElement().
		memset(&data, 0, sizeof(T));

		hrRetTemp = SafeArrayGetElement
		(
			(SAFEARRAY*)pSafeArray,
			(long*)lIndexVector,
			(void*)(&data)
		);

		if (SUCCEEDED(hrRetTemp))
		{
			vecReceiver.push_back(data);
		}
	}
}

The following is a summary of this helper function :

  • This function takes 2 template parameters “T” which specifies the type of data contained in the SAFEARRAY parameter. “T” is also the data type of the vector paraneter that receives a copy of the contents of the SAFEARRAY.
  • It first ues the SafeArrayGetVartype() function to obtain the variant type of the elements contained in the SAFEARRAY.
  • This is asserted to match the VARTYPE “v” which is specified by template parameter.
  • The SafeArrayGetLBound() and SafeArrayGetUBound() functions are then used to calculate the number of elements contained inside the SAFEARRAY.
  • The SAFEARRAY is then looped through and with each iteration, SafeArrayGetElement() is called to obtain a copy of an element.
  • The element is then pushed into the receiving vector.

3.4 The “display_test_structure” functor class which was used in the for_each() loop is listed below :

struct display_test_structure :
	public std::unary_function<TestStructure, void>
{
	// Constructor
	display_test_structure() :
		m_iCounter(0)
	{
	}

	// Copy constructor.
	display_test_structure(const display_test_structure& rhs) :
		m_iCounter(rhs.m_iCounter)
	{
	}

	void operator () (TestStructure& test_structure)
	{
		printf ("TestStructure[%d].m_integer : [%d]
",
			m_iCounter, test_structure.m_integer);
		printf ("TestStructure[%d].m_double  : [%f]
",
			m_iCounter, test_structure.m_double);
		printf ("TestStructure[%d].m_string  : [%S]
",
			m_iCounter, test_structure.m_string);
		m_iCounter++;
	};

	int m_iCounter;
};

3.5 The “clear_test_structure” functor class which was used in a for_loop to clear the TestStructure struct elements of the “vecTestStructure” vector is listed below :

struct clear_test_structure :
	public std::unary_function<TestStructure, void>
{
  // Constructor.
  clear_test_structure(IRecordInfoPtr& refIRecordInfoPtr) :
    m_refIRecordInfoPtr(refIRecordInfoPtr)
  {
  }

  // Copy constructor.
  clear_test_structure(const clear_test_structure& rhs) :
    m_refIRecordInfoPtr(rhs.m_refIRecordInfoPtr)
  {
  }

  void operator () (TestStructure& test_structure)
  {
	m_refIRecordInfoPtr -> RecordClear((PVOID)&test_structure);
  }

  IRecordInfoPtr& m_refIRecordInfoPtr;
};

4. Example C# Call to SetArrayContainer().

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

[DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void SetArrayContainer([In] ArrayContainer array_container);

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

  • The ArrayContainer parameter “array_container”, when passed to the unmanaged world, will be transformed into the following “unmanaged equivalent” format :
struct ArrayContainer
{
    SAFEARRAY * array_of_test_structures;
};
  • When the interop marshaler is about to call SetArrayContainer(), it first creates such an unmanaged structure and then translates the fields of the managed ArrayContainer structure to their equivalents in the unmanaged ArrayContainer structure.
  • For example, the “array_of_test_structures” field which is a managed array of TestStructure structs is transformed into an equivalent SAFEARRAY and the “array_of_test_structures” field of the unmanaged ArrayContainer struct will point to it.
  • The InAttribute indicates that the unmanaged ArrayContainer structure will be passed as a read-only parameter. It is not to be modified by the SetArrayContainer() function.

4.2 The following is a sample function that calls SetArrayContainer() :

static void DoTest_SetArrayContainer()
{
    ArrayContainer array_container = new ArrayContainer();

    array_container.array_of_test_structures = new TestStructure[3];

    for (int i = 0; i < array_container.array_of_test_structures.Length; i++)
    {
        array_container.array_of_test_structures[i].m_integer = i;
        array_container.array_of_test_structures[i].m_double = (double)i;
        array_container.array_of_test_structures[i].m_string = string.Format("Hello World [{0}]", i);
    }

    SetArrayContainer(array_container);
}

The following is a synopsis of the function above :

  • It instantiates a ArrayContainer structure.
  • The “array_of_test_structures” member is instantiated to an array of 3 TestStructure structs.
  • The “array_of_test_structures” array is filled with values.
  • SetArrayContainer() is called.

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

TestStructure[0].m_integer : [0]
TestStructure[0].m_double  : [0.000000]
TestStructure[0].m_string  : [Hello World [0]]
TestStructure[1].m_integer : [1]
TestStructure[1].m_double  : [1.000000]
TestStructure[1].m_string  : [Hello World [1]]
TestStructure[2].m_integer : [2]
TestStructure[2].m_double  : [2.000000]
TestStructure[2].m_string  : [Hello World [2]]

5. In Conclusion.

5.1 Here in part 4, we have shifted gears and looked at how managed structures may be marshaled to unmanaged code by way of a SAFEARRAY contained in an outer wrapping structure.

5.2 Although the concept is practically not much more complicated than direct marshaling of a managed array to unmanaged code via SAFEARRAYs, I have endeavoured to cover this in order to pave the way for a later installment in which an array of an array of structures are to be marshaled.

5.3 It is also an opportunity for me to introduce and expound on some useful helper functions for the benefit of the reader.