Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Marshal variable length array of structs from C to VB.NET

0.00/5 (No votes)
24 Jul 2011 1  
How to pass a variable length array of structs from C to VB.NET.

Introduction

This article presents an example of how to create a VB.NET program that retrieves data in the form of a variable length array of structs from a DLL file written in C. The challenge here is how to pass data between the safe world of managed code executed by the Common Language Runtime (CLR) and unmanaged code.

Background

Code which is developed using the .NET Framework is known as managed code. This code is directly executed by CLR with the help of managed code execution. Any code that is written in .NET Framework is managed code. Managed code uses CLR which in turns looks after your applications by managing memory, handling security, allowing cross-language debugging, and so on. Code which is developed outside the .NET Framework is known as unmanaged code. Applications that do not run under the control of the CLR are said to be unmanaged, and therefore don’t have the benefits of the CLR. I won’t get any deeper into the definitions of managed and unmanaged code since that is out of the scope for this article and there are plenty of sources for that on the internet. Just Google it :)

Marshalling

The solution for passing data between managed and unmanaged code is of course marshalling. What made the assignment a bit tricky was that some of the unmanaged functions returned an array of structs whose size I didn’t know when I was calling the function.

The Code

I will begin with the unmanaged DLL. Here is the struct declaration in C:

typedef struct { 
    Char id1[10]; 
    Char id2[20];               
}unmanagedStruct;

Here is the declaration of the function:

extern "C" __declspec(dllexport) unmanagedStruct* unmanagedFunction(int &size) 
{ 
    unmanagedStruct *outStruct = new
    unmanagedStruct[myStructSize]; 
 
    size = myStructSize; 
    //Do whatever your function is supposed to do
    return outStruct; 
}

As you can see, the function returns a pointer since what I need on the managed side is a pointer to the unmanaged memory I have allocated. The next thing to note is that I am taking the integer size as a reference call. This variable enables me to get the size of the array on the managed side. How the elements of the array are retrieved will be explained in detail when I present the managed code. Now I will just show the relevant parts of the unmanaged function. Of course, the function should do a lot more such as fill the array with values and so on, but that is out of the scope for this article. So as you can see, I am declaring my array of structs and allocating memory for it. Then I set the size variable to the size of the array so that the managed code will be able to know the size. Finally, I return the pointer to the caller of the function.

Now on the managed side, in Visual Studio, I start by declaring the struct I intent to use. Notice that I am using the MarshalAs attribute class to specify the length of my fields in the structure. These lengths have to be the same as the lengths declared in the unmanaged DLL.

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _ 
Public Structure managedStruct 
 
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _ 
   Public id1 As String  
 
   <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _ 
   Public id2 As String  
End Structure

I continue by declaring a DllImport to my unmanaged DLL and the function which I intend to use.

<DllImport("myUnmanagedDll.dll")> _  
Public Shared Function unmanagedFunction(ByRef size As Integer) As IntPtr 
End Function

And finally, here comes the part where I call the unmanaged function from my managed code, retrieve the pointer to the unmanaged array of structs, and retrieve the values.

Dim p1 As IntPtr  'My struct pointer
'pointer to be used to cleanup the unmanaged memory 
Dim cleanUpPtr as IntPtr
Dim numberOfResults As Integer 'Size of array from dll
Dim resultArray() As managedStruct
'The array where i will store the retrieved values
 
'Call the unmanaged function and get intpointer
p1 = unmanagedFunction(numberOfResults) 
'Set the pointer to be used for cleanup to point on the same memory block
cleanUpPtr = p1
 
ReDim resultArray(numberOfResults - 1) 'Resize my array  
 
Dim size As Integer = Marshal.SizeOf(GetType(managedStruct))
Dim start As Integer = (CType(p1, Integer) + _
(Marshal.SizeOf(GetType(managedStruct)) - size)) 
p1 = New IntPtr(start) 'Set the pointer at the first element of array 
Dim i As Integer = 0 
 
Do While (i < numberOfResults) 
    resultArray(i) = CType(Marshal.PtrToStructure(p1, _
                           GetType(managedStruct)), managedStruct)
 
    If (i < (count - 1)) The
        p1 = New IntPtr((CType(p1, Integer) + size)) 
    End If  
 
    i = (i + 1)
Loop

After the call to the unmanaged function, I have the number of posts I have retrieved in the array. In the variable called size, I store the size of every array element so I know how many bytes I have to increment to retrieve the next post. Then I proceed to set the pointer p1 to the first array element, and finally use Marshal.PtrToStructure to retrieve the values. Once I have retrieved an array element, I continue to set the pointer to the next element. This step is repeated until I have retrieved all the elements and now I have the entire unmanaged array in the managed memory.

Now as a last thing, remember to clean up the unmanaged memory. That’s why I created an extra pointer called cleanupPtr since I will loop over the array with the original pointer. cleanUpPtr is still pointing to the beginning of the unmanaged memory. The cleanup operation can be performed directly from managed memory by using the CoTaskMemFree() attribute or by using the delete[] operator in the unmanaged code.

Final Words

As a final word, I would like to answer the question I guess many of you would ask: Why do it in VB.NET, and the answer to that question is that was part of the requirement when I got the task. If it was up to me, I would have done it in C#. I am a C# guy.

History

  • 24 July, 2011: Fixed some copy-paste error in the code part of the article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here