//
you're reading...
Interop Marshaling, Managed Structures, SAFEARRAYs

Marshaling a SAFEARRAY of Managed Structures by P/Invoke Part 7.

1. Introduction.

1.1 We have reached the most complex part of this series of articles on the use of SAFEARRAY to marshal arrays of managed structures between managed and unmanaged code.

1.2 I do hope that I have maintained the reader’s interest up to this point.

1.3 In the first 3 parts of this series, we studied how to marshal an array of TestStructure structs.

1.4 Then from part 4 through 6, we examined how to marshal an array of TestStructure structs which are contained within a container structure (ArrayContainer).

1.5 From part 7 onwards, we shall be marshaling an array of ArrayContainer structures. Since each ArrayContainer struct contains an array of TestStructure structs, we will be actually marshaling an array of arrays of TestStructure structs.

2. A New Structure – ContainerOfArrayContainers.

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 Along with the above 2 structures, we shall hereby be defining a new structure : ContainerOfArrayContainers :

[ComVisible(true)]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Guid("E9456034-E3F2-4bec-82D4-EC30C99954DA")]
public struct ContainerOfArrayContainers
{
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_RECORD)]
    public ArrayContainer[] array_of_array_containers;
}

2.3 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)
//
// typelib filename: 

[
  uuid(4E765D3B-C1F5-4CDE-8095-1E9614E0AE3F),
  version(1.0)
]
library CSConsoleApp
{
    	// TLib :     // Forward declare all types defined in this typelib

    	typedef
	[
		uuid(E9456034-E3F2-4BEC-82D4-EC30C99954DA), version(1.0),
    		custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSConsoleApp.ContainerOfArrayContainers")
	]
	struct tagContainerOfArrayContainers
	{
		SAFEARRAY(ArrayContainer) array_of_array_containers;
	}
	ContainerOfArrayContainers;

    	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 ContainerOfArrayContainers structure contains a SAFEARRAY of ArrayContainer structs. And each ArrayContainer structure contains a SAFEARRAY of TestStructure structs.

2.5 As mentioned in part 4, the ArrayContainer structure, as a wrapper for a SAFEARRAY of TestStructure structs, is not very difficult to manage unless ArrayContainer also contains other fields. However, it is deliberately kept simple so that now that we have reached this stage of this series, things have been kept to a minimum of complexity towards easier understanding.

3. Unmanaged API that takes a ContainerOfArrayContainers 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.

3.2 This function takes a ContainerOfArrayContainers structure as input parameter. It then takes the “array_of_array_containers” field and iterates through each ArrayContainer structure inside this SAFEARRAY field.

3.3 For each ArrayContainer structure inside the “array_of_array_containers” SAFEARRAY, we loop through its “array_of_test_structures” field and display the field values of each TestStructure SAFEARRAY element.

3.4 Full source codes listed below :

extern "C" __declspec(dllexport) void __stdcall SetContainerOfArrayContainers
(
  /*[out]*/ ContainerOfArrayContainers container_of_array_containers
)
{
	std::vector<ArrayContainer> vecArrayContainer;

	// Copy the ArrayContainer elements of the SAFEARRAY
	// in "container_of_array_containers.array_of_array_containers"
	// into a vector of ArrayContainer structs.
	CopySafeArrayToVector<ArrayContainer, VT_RECORD>
	(
	  container_of_array_containers.array_of_array_containers,
	  vecArrayContainer
	);

	// Process each ArrayContainer structure contained
	// within the "vecArrayContainer" vector.
	//
	// For each ArrayContainer structure, process its
	// "array_of_test_structures" SAFEARRAY field.
	//
	// For each TestStructure contained within the
	// "array_of_test_structures" SAFEARRAY field,
	// display its field values.
	std::for_each
	(
		vecArrayContainer.begin(),
		vecArrayContainer.end(),
		display_contents_of_array_container()
	);

	// Obtain the IRccordInfo interface associated with the ArrayContainer UDT.
	IRecordInfoPtr spIRecordInfoArrayContainer = NULL;

	SafeArrayGetRecordInfo
	(
		container_of_array_containers.array_of_array_containers,
		&spIRecordInfoArrayContainer
	);

	// Before the end of this function, each of the ArrayContainer
	// structs inside vecArrayContainer must be cleared.
	std::for_each
	(
		vecArrayContainer.begin(),
		vecArrayContainer.end(),
		record_clear<ArrayContainer>(spIRecordInfoArrayContainer)
	);
}

The following is a general description of the function above :

  • It defines a vector of ArrayContainer structs “vecArrayContainer”.
  • A helper function CopySafeArrayToVector<>() is used to copy the ArrayContainer elements of “container_of_array_containers.array_of_array_containers” (i.e. the SAFEARRAY of ArrayContainer structs contained in the input ContainerOfArrayContainers structure “container_of_array_containers”) to the “vecArrayContainer” vector.
  • It then use the for_each() algorithm function to loop through the elements of the “vecArrayContainer” vector. For each ArrayContainer structure, process its “array_of_test_structures” SAFEARRAY field. Then, for each TestStructure contained within the “array_of_test_structures” SAFEARRAY field, display its field values.
  • It then calls on SafeArrayGetRecordInfo() to obtain a pointer to the IRecordInfo interface associated with the ArrayContainer UDT.
  • Another for_each() loop is called, this time to clear each ArrayContainer struct element contained inside “vecArrayContainer”.
  • This last action is necessary because each ArrayContainer struct element contained inside “vecArrayContainer” is a copy of its corresponding ArrayContainer contained inside “container_of_array_containers.array_of_array_containers”.
  • Note however that the input “container_of_array_containers” UDT parameter is owned by the interop marshaler and so it (the UDT) will be destroyed (along with all the contained SAFEARRAYs and SAFEARRAYs within SAFEARRAYs) when the SetContainerOfArrayContainers() function returns.

3.5 The CopySafeArrayToVector<>() helper function has been documented in part 4. The display_contents_of_array_container() functor source codes are displayed below :

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

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

	void operator () (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
		);

		printf ("Displaying TestStructure structs of ArrayContainer element [%d].\r\n", m_iCounter);

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

		// Before the end of this function, each of the TestStructure
		// structs inside "vecTestStructure" must be cleared.

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

		SafeArrayGetRecordInfo
		(
			array_container.array_of_test_structures,
			&spIRecordInfoTestStructure
		);

		std::for_each
		(
			vecTestStructure.begin(),
			vecTestStructure.end(),
			record_clear<TestStructure>(spIRecordInfoTestStructure)
		);

		m_iCounter++;
	};

	int m_iCounter;
};

The () operator for this functor class performs very similarly to the SetArrayContainer() function that we met back in part 4. Please refer to section 3 of part 4 for in-depth explanation.

The main difference between the () operator and the SetArrayContainer() function is that SetArrayContainer() used the clear_test_structure() function to perform the clearing of the vector of TestStructure structs whereas the the () operator uses the new record_clear functor class.

3.6 The record_clear functor class is listed below :

template <typename T>
struct record_clear : public std::unary_function<T, void>
{
  // Constructor.
  record_clear(IRecordInfoPtr& refIRecordInfoPtr) :
    m_refIRecordInfoPtr(refIRecordInfoPtr)
  {
  }

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

  void operator () (T& t)
  {
	m_refIRecordInfoPtr -> RecordClear((PVOID)&t);
  }

  IRecordInfoPtr& m_refIRecordInfoPtr;
};

I have deviced a new record_clear functor class instead of using the previously defined clear_test_structure functor class so as to provide a generic functor class that is able to invoke the IRecordInfo::RecordClear() on any UDT instead of a specific one.

4. Example C# Call to SetContainerOfArrayContainers().

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

[DllImport("UnmanagedDll.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void SetContainerOfArrayContainers
(
	[In] ContainerOfArrayContainers container_of_array_containers
);

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

  • The ContainerOfArrayContainers parameter “container_of_array_containers”, when passed to the unmanaged world, will be transformed into the following “unmanaged equivalent” format :
struct ContainerOfArrayContainers
{
    SAFEARRAY * array_of_array_containers;
};
  • When the interop marshaler is about to call SetContainerOfArrayContainers(), it first creates such an unmanaged structure and then translates the fields of the managed ContainerOfArrayContainers structure to their equivalents in the unmanaged ContainerOfArrayContainers structure.
  • In this process an amazing number of SAFEARRAYs are created : the array of ArrayContainer structures in the “array_of_array_containers” field of the ContainerOfArrayContainers structure (to be passed as parameter) will need to be transformed into a SAFEARRAY of unmanaged ArrayContainer structs. And then each unmanaged ArrayContainer struct will point to a SAFEARRAY of TestStructure structs.
  • The InAttribute indicates that the unmanaged ContainerOfArrayContainers structure will be passed as a read-only parameter. It is not to be modified by the target SetContainerOfArrayContainers() function.
  • This also indicates that the interop marshaler will remain the owner of the unmanaged ContainerOfArrayContainers structure that will get passed as parameter to SetContainerOfArrayContainers().
  • The interop marshaler will, when SetContainerOfArrayContainers() returns, destroy this unmanaged structure along with all the SAFEARRAYs and UDTs previously created for it.

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

static void DoTest_SetContainerOfArrayContainers()
{
    ContainerOfArrayContainers container_of_array_containers = new ContainerOfArrayContainers();

    container_of_array_containers.array_of_array_containers = new ArrayContainer[3];

    for (int i = 0; i < container_of_array_containers.array_of_array_containers.Length; i++)
    {
        container_of_array_containers.array_of_array_containers[i].array_of_test_structures
            = new TestStructure[3];

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

    SetContainerOfArrayContainers(container_of_array_containers);
}

The following is a synopsis of the function above :

  • It instantiates a ContainerOfArrayContainers structure.
  • The “array_of_array_containers” member is instantiated to an array of 3 ArrayContainer structs.
  • It then loops through each ArrayContainer of the “array_of_array_containers” array and for each ArrayContainer struct in this array, it instantiates an array of 3 TestStructure structs for its “array_of_test_structures” field.
  • An internal loop is then performed iterating through this “array_of_test_structures” array and for each TestStructure contained, its fields are given values.
  • At the end of the entire first loop, there will be a total of 9 (i.e. 3×3) TestStructure structs each with
  • The SetContainerOfArrayContainers() function is then called.

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

Displaying TestStructure structs of ArrayContainer element [0].
TestStructure[0].m_integer : [0]
TestStructure[0].m_double  : [0.000000]
TestStructure[0].m_string  : [Hello World [0][0]]
TestStructure[1].m_integer : [1]
TestStructure[1].m_double  : [1.000000]
TestStructure[1].m_string  : [Hello World [0][1]]
TestStructure[2].m_integer : [2]
TestStructure[2].m_double  : [2.000000]
TestStructure[2].m_string  : [Hello World [0][2]]
Displaying TestStructure structs of ArrayContainer element [1].
TestStructure[0].m_integer : [1]
TestStructure[0].m_double  : [1.000000]
TestStructure[0].m_string  : [Hello World [1][0]]
TestStructure[1].m_integer : [2]
TestStructure[1].m_double  : [2.000000]
TestStructure[1].m_string  : [Hello World [1][1]]
TestStructure[2].m_integer : [3]
TestStructure[2].m_double  : [3.000000]
TestStructure[2].m_string  : [Hello World [1][2]]
Displaying TestStructure structs of ArrayContainer element [2].
TestStructure[0].m_integer : [2]
TestStructure[0].m_double  : [2.000000]
TestStructure[0].m_string  : [Hello World [2][0]]
TestStructure[1].m_integer : [3]
TestStructure[1].m_double  : [3.000000]
TestStructure[1].m_string  : [Hello World [2][1]]
TestStructure[2].m_integer : [4]
TestStructure[2].m_double  : [4.000000]
TestStructure[2].m_string  : [Hello World [2][2]]

5. In Conclusion.

5.1 Here in part 7, we have increased complexity and looked at how managed structures may be marshaled to unmanaged code by way of a SAFEARRAY contained in an outer wrapping structure which is itself part of a SAFEARRAY of outer wrapping structures.

5.2 I have done by best to iterate through the 2 levels of SAFEARRAYs as simply as possible. I do hope the reader has benefitted from this.

5.3 In the next part, I shall be discussing how to marshal the 2 level SAFEARRAYs of unmanaged structures from unmanaged code to managed code as a return value.

 

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: