//
you're reading...
Programming Issues/Tips

How to Implement Pointer To Pointer in C# Without Using Unsafe Code.

1. Introduction.

1.1 In C#, pointer management has traditionally been done using the IntPtr type.

1.2 There is a large number of Marshal class methods that work with IntPtrs and they are very useful indeed.

1.3 However, IntPtr dereferencing (i.e. the referencing of data pointed to by a pointer which is itself pointed to by another pointer) is something for which a direct solution is not readily available.

1.4 But after doing a little bit of research and experimentation, I found that this can be achieved by the judicious use of GCHandle and Marshal class methods.

1.5 This article presents one such technique. A set of example codes will be provided.

2. Synopsis of Example Code.

2.1 Let’s say we have a managed integer array.

2.2 We allocate data for this managed array and want to pass it to an unmanaged API in a DLL as a double int pointer (i.e. int**), not a SAFEARRAY. 

2.3 We expect the unmanaged API to display values from this array.

2.4 Thereafter, we want the API to de-allocate the array. re-allocate a new one, fill it with values and return this new array to the managed code, all via the double int pointer.

2.5 The unmanaged API should have the following signature :

extern "C" __declspec(dllexport) void ReferenceDoublePointerToIntegerArray
(
  /*[in]*/ int** ppIntegerArray, 
  /*[in, out]*/ int* pnElementCount
);

The first parameter “ppIntegerArray” is the double pointer to an integer array.

The second parameter “pnElementCount” contains the count of elements in the array on input. The array will be re-allocated by the API and so it sets “pnElementCount” to the latest count of elements in the array on output.

3. Implementation of ReferenceDoublePointerToIntegerArray() API.

3.1 The following is a sample implementation for ReferenceDoublePointerToIntegerArray() :

#include <iostream>
#include <objbase.h>

extern "C" __declspec(dllexport) void ReferenceDoublePointerToIntegerArray
(
  /*[in]*/ int** ppIntegerArray, 
  /*[in, out]*/ int* pnElementCount
)
{
	for (int i = 0; i < *pnElementCount; i++)
	{
		std::cout << (int)((*ppIntegerArray)[i]) << std::endl;
	}

	::CoTaskMemFree(*ppIntegerArray);

	*pnElementCount = 5;
	*ppIntegerArray = (int*)::CoTaskMemAlloc(*pnElementCount * sizeof(int));

	for (int i = 0; i < *pnElementCount; i++)
	{
		(*ppIntegerArray)[i] = 100 + i;
	}
}

The function will perform the following :

  • It displays all values from the input array which is referenced from the double integer pointer “ppIntegerArray”.
  • The integer array is then freed using CoTaskMemFree().
  • It is then re-allocated using CoTaskMemAlloc() and then filled with new values.
  • The integer referenced by the “pnElementCount” pointer is assigned a new value which indicates the size of the new array.

3.2 Notice that we must use CoTaskMemAlloc() to allocate memory and CoTaskMemFree() to free it. These APIs work together with the managed Marshal.AllocCoTaskMem() and Marshal.FreeCoTaskMem() methods.

3.3 As I have very often advised in my articles, the C++ new and delete keywords cannot be used. These are C++ compiler-dependent and the CLR have no intrinsic knowledge on how to work with memory that is associated with the C++ new and delete keywords.

3.4 Neither are the C library malloc() and free() functions compatible with the CLR. These are C-library-specific and are not universally implemented.

3.5 Let’s assume that the above API has been compiled into a DLL named HelperDLL.dll.

4. C# Client Code and a Demonstration of Double Pointer Dereferencing.

4.1 In this section, I shall present C# code that shows how double pointer dereferencing can be accomplished.

4.2 The ReferenceDoublePointerToIntegerArray() API should be declared in C# in the following way :

[DllImport("HelperDLL.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void ReferenceDoublePointerToIntegerArray
(
  [In] IntPtr ppIntegerArray, 
  ref Int32 pnElementCount
);

Notice the following :

  • The first parameter “ppIntegerArray” is declared as of IntPtr type.
  • Note that even though its unmanaged counterpart type is a double pointer to an integer : int**, it is not declared as passed by reference. Instead, it is an “in” parameter.
  • This is because what we want to pass to the API is actually just a number (albeit a number that indicates a memory address).
  • We do not expect the unmanaged API to modify this number for us. More on this later.
  • The second parameter “pnElementCount” is indeed an integer that we want to pass to the unmanaged code by reference.
  • This is because we do want the unmanaged API to change this value for us.

4.3 The following is an example C# function that prepares an array to pass to ReferenceDoublePointerToIntegerArray() :

private static void DoTest()
{
    // Allocate a standard array of 10 32-bit integers.
    Int32[] IntegerArray = new Int32[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    Int32 nElementCount = IntegerArray.Length;
    // Allocate a memory buffer (that can be accessed and modified by unmanaged code)
    // to store values from the IntegerArray array.
    IntPtr pUnmanagedIntegerArray = (IntPtr)Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(Int32)) * IntegerArray.Length);

    // Copy values from IntegerArray to this buffer (i.e. pUnmanagedIntegerArray).
    Marshal.Copy(IntegerArray, 0, pUnmanagedIntegerArray, IntegerArray.Length);

    // Allocate a GCHandle in order to allocate an IntPtr
    // that stores the memory address of pUnmanagedIntegerArray.
    GCHandle gch = GCHandle.Alloc(pUnmanagedIntegerArray, GCHandleType.Pinned);
    // Use GCHandle.AddrOfPinnedObject() to obtain a pointer 
    // to a pointer to the integer array of pUnmanagedIntegerArray.
    // It is ppUnmanagedIntegerArray that will be passed to the API.
    IntPtr ppUnmanagedIntegerArray = (IntPtr)gch.AddrOfPinnedObject();

    // Call the ReferenceDoublePointerToIntegerArray() API.
    // The ReferenceDoublePointerToIntegerArray() API will not
    // change the value of ppUnmanagedIntegerArray. 
    ReferenceDoublePointerToIntegerArray(ppUnmanagedIntegerArray, ref nElementCount);

    // We must now find a way to dereference the memory address
    // contained inside ppUnmanagedIntegerArray.

    // Declare an array (of one single value) of IntPtr.
    IntPtr[] pNewUnmanagedIntegerArray = new IntPtr[1];

    // Copy the value contained inside ppUnmanagedIntegerArray
    // to pNewUnmanagedIntegerArray.
    Marshal.Copy(ppUnmanagedIntegerArray, pNewUnmanagedIntegerArray, 0, 1);

    // Allocate a new integer array to be filled with 
    // values from the array pointed to by pNewUnmanagedIntegerArray[0]
    Int32[] NewIntegerArray = new Int32[nElementCount];

    // Copy the integer array values pointed to by pNewUnmanagedIntegerArray[0]
    // to NewIntegerArray.
    Marshal.Copy(pNewUnmanagedIntegerArray[0], NewIntegerArray, 0, nElementCount);

    // Display the new array values.
    for (int i = 0; i < (NewIntegerArray.Length); i++)
    {
        Console.WriteLine("{0:D}", NewIntegerArray[i]);
    }

    // Free the new integer array memory.
    Marshal.FreeCoTaskMem(pNewUnmanagedIntegerArray[0]);
    // Free the memory area for ppUnmanagedIntegerArray itself.
    gch.Free();
}

The following is a summary description of what DoTest() does :

  • A managed integer array of 10 elements “IntegerArray” is allocated and filled with values.
  • An integer “nElementCount” is set to the number of elements contained in “IntegerArray”.
  • Then, an unmanaged memory buffer is allocated using Marshal.AllocCoTaskMem() and will be pointed to by “pUnmanagedIntegerArray”.
  • This unmanaged memory buffer is large enough to store 10 integers.
  • The Marshal.Copy() method is then used to copy values from IntegerArray to this buffer.
  • Next comes the first technique that we use to manage a double pointer : by using the GCHandle class to create one. More details on this will be provided in the dedicated section later on.
  • For now, suffice it to say that we will hold in “ppUnmanagedIntegerArray” (an IntPtr) a pointer to a pointer to the unmanaged integer array “pUnmanagedIntegerArray”.
  • The ReferenceDoublePointerToIntegerArray() API is called and “ppUnmanagedIntegerArray” is passed as the first parameter. A reference to “nElementCount” is passed as second parameter.
  • Once ReferenceDoublePointerToIntegerArray() returns, the data in the memory area pointed to by “ppUnmanagedIntegerArray” will be changed.
  • Whereas previously it has pointed to the “pUnmanagedIntegerArray” integer array, it will now point to a new array which has just been allocated by the ReferenceDoublePointerToIntegerArray() API.
  • Next comes the second technique that we use to manage a double pointer : by using 2 versions of the Marshal.Copy() method.
  • More details on this will be provided in a dedicated section below.
  • For now, suffice it to say that after we have acquired an IntPtr that points to the new (unmanaged) integer array, we will copy its values to a new managed array “NewIntegerArray”.
  • The values in the new array are displayed and the new unmanaged integer array is freed by using Marshal.FreeCoTaskMem().

5. Technique One : How to Create a Pointer to a Pointer to an Integer Array.

5.1 This is accomplished by using the GCHandle class. Specifically, its static GCHandle.Alloc() method (the version that takes a GCHandleType parameter).

5.2 We specifically use this method in order to obtain an IntPtr that will contain the current IntPtr value of “pUnmanagedIntegerArray”. This IntPtr that we seek is obtained via the GCHandle.AddrOfPinnedObject() method.

5.3 The code below is a re-print of a code fragment shown earlier :

    // Allocate a GCHandle in order to allocate an IntPtr
    // that stores the memory address of pUnmanagedIntegerArray.
    GCHandle gch = GCHandle.Alloc(pUnmanagedIntegerArray, GCHandleType.Pinned);
    // Use GCHandle.AddrOfPinnedObject() to obtain a pointer 
    // to a pointer to the integer array of pUnmanagedIntegerArray.
    // It is ppUnmanagedIntegerArray that will be passed to the API.
    IntPtr ppUnmanagedIntegerArray = (IntPtr)gch.AddrOfPinnedObject();

The IntPtr that we want is “ppUnmanagedIntegerArray” which is the value we pass as the parameter to the call to ReferenceDoublePointerToIntegerArray().

5.4 By allocating a GCHandle to wrap “pUnmanagedIntegerArray”, we create an object that contains it. This generates one level of indirection. In other words, we obtained a “pointer” to “pUnmanagedIntegerArray”.

5.5 Then, by calling GCHandle.AddrOfPinnedObject(), we obtain an IntPtr that points to the memory area inside the GCHandle that contains “pUnmanagedIntegerArray”. In other words, we obtained a “pointer” to a “pointer” to “pUnmanagedIntegerArray”, i.e. the double pointer that we want.

5.6 The situation can be illustrated by the following diagram :

5.7 Now, later on inside the ReferenceDoublePointerToIntegerArray() API, what happens is that the memory area pointed to by the pointer inside “ppUnmanagedIntegerArray” will be assigned a new value – a pointer to a new unmanaged integer array. The old one will of course by freed by the API first.

5.8 This can be illustrated by the diagram below :

 

6. Technique Two : How to Dereference a Pointer to a Pointer to an Integer Array.

6.1 This technique is used after the call to the ReferenceDoublePointerToIntegerArray() API. This is accomplished using 2 versions of the Marshal.Copy() method :

  • The Marshal.Copy() method that takes an IntPtr as source as an IntPtr array as destination.
  • The Marshal.Copy() method that takes an IntPtr as source as an Int32 array as destination.

6.2 The first Marshal.Copy() method, whose declaration is :

public static void Copy
(
	IntPtr source,
	IntPtr[] destination,
	int startIndex,
	int length
);

is used to copy the value contained inside “ppUnmanagedIntegerArray” into an array of IntPtr “pNewUnmanagedIntegerArray”.

6.3 Now “pNewUnmanagedIntegerArray” is an IntPtr array of only one element. And we have called Marshal.Copy() to copy only one IntPtr element from “ppUnmanagedIntegerArray”.

6.4 Recall that “ppUnmanagedIntegerArray” contains a pointer to a pointer to an unmanaged integer array. By copying the value from “ppUnmanagedIntegerArray” to “pNewUnmanagedIntegerArray”, “pNewUnmanagedIntegerArray[0]” will be made to contain a value which can be interpreted as a pointer to the GCHandle’s memory area that holds the new unmanaged array which is allocated by the ReferenceDoublePointerToIntegerArray() API.

6.5 Then, by treatung “pNewUnmanagedIntegerArray[0]” as a single IntPtr, we use the second version of Marshal.Copy() :

public static void Copy
(
	IntPtr source,
	int[] destination,
	int startIndex,
	int length
);

to copy the values of the unmanaged array (newly allocated by the API) to our new managed array “NewIntegerArray”.

6.6 This can be illustrated by the diagram below :

7. Memory Release and the Need to Preserve the GCHandle Until The End.

7.1 When the new managed array “NewIntegerArray” has been initialized properly from the data of the new unmanaged integer array, it is time to release the memory occuppied by the new unmanaged integer array.

7.2 This is done by calling Marshal.FreeCoTaskMem() on the new unmanaged integer array whose address is held in pNewUnmanagedIntegerArray[0].

7.3 Thereafter, the GCHandle itself which was allocated earlier to hold the original pointer to the unmanaged integer array must itself be freed by calling GCHandle.Free().

7.4 This GCHandle must be held in memory not because the original object which was held in it (i.e. “pUnmanagedIntegerArray”) had to be pinned down in memory. Indeed, “pUnmanagedIntegerArray” is merely a number (albeit it is a number which indicates the memory address of the unmanaged integer array).

7.5 Rather GCHandle must be held in memory to artificially create a storage space to contain the address of the unmanaged integer array – first the one allocated before the call to the API and later the one allocated by the API.

7.6 If the freeing was done before the API call or even right after the API call, this “artificial” memory storage area may be overwritten.

7.7 To see the effects of this, try inserting the following code before the call to ReferenceDoublePointerToIntegerArray() :

gch.Free();
GC.Collect();

as in :

IntPtr ppUnmanagedIntegerArray = (IntPtr)gch.AddrOfPinnedObject();

// temp.
gch.Free();
GC.Collect();
// temp.

// Call the ReferenceDoublePointerToIntegerArray() API.
// The ReferenceDoublePointerToIntegerArray() API will not
// change the value of ppUnmanagedIntegerArray. 
ReferenceDoublePointerToIntegerArray(ppUnmanagedIntegerArray, ref nElementCount);

7.8 Another way to demonstrate the problem, insert the GCHandle.Free() and GC.Collect() call right after the call to ReferenceDoublePointerToIntegerArray() :

ReferenceDoublePointerToIntegerArray(ppUnmanagedIntegerArray, ref nElementCount);

// temp.
gch.Free();
GC.Collect();
// temp.

// We must now find a way to dereference the memory address
// contained inside ppUnmanagedIntegerArray.

// Declare an array (of one single value) of IntPtr.
IntPtr[] pNewUnmanagedIntegerArray = new IntPtr[1];

7.9 In both cases, the “artificial” IntPtr area may end up being cleared or filled with garbage value and so we will have a pointer to an invalid (or NULL) pointer.

8. In Conclusion.

8.1 I hope that the reader has benefitted from this short article.

8.2 With some clever use of the GCHandle and the Marshal classes, double pointer management can certainly be achieved.

8.3 It may seem unintuitive and convoluted initially (the usage of unsafe code certainly makes the code clearer). However, with practiced usage, familiarity kicks in and unsafe code may be avoided.

 

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

One thought on “How to Implement Pointer To Pointer in C# Without Using Unsafe Code.

  1. Je vous remercie beaucoup!

    Posted by squirrels22 | February 11, 2013, 7:03 pm

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: