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

The 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

First I 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 *outPrivileges = new
    unmanagedStruct[myStructSize]; 

    size = myStructSize; 
    //Do whatever your function is supposed to do
    return outPrivileges; 
}

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

'Set the cleanup pointer to point on the same memory block
cleanUpPtr = p1
'Call the unmanaged function and get intpointer
p1 = unmanagedFunction(numberOfResults) 
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 the managed memory by using the CoTaskMemFree() attribute.

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.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"