Posted tagged ‘P/Invoke’

Explicit P/Invoke: Memory management for return types and ref parameters

September 6, 2009

In one of my recent assignments at work I was to communicate with native code from my C# application. Micros0ft provides two kinds of platform invoke functionality i.e.

Implicit p/invoke: This is supported by VC++ only so managed C++ can call unmanaged code.

Explicit p/invoke: This is supported by .NET platform and any code written in C# or VB.NET can execute native code and vice versa.

Other than these, if native code is implemented as COM servers, .NET runtime provides COM Callable Wrappers to communicate with the managed code. Similarly it provides Runtime Callable Wrappers where managed code is exposed as COM objects for native code to communicate with the managed code.

In this article my focus will be on explicit p/invoke providing callback functionality and related memory issues.

To call unmanaged code from managed is pretty straight forward. All you have to do is to export the functions in the DLL. Then provide the declaration matching exact signature with Dllimport attribute and if custom structures or classes are being used as parameters provide their definition as well in the managed code.

For example if some dll Example.dll exports a function “int nStatus GetUpdate(char* strParam)”. To call this funciton in your .NET application declare it as follows

[Dllimport("Path\\Example.dll")]

public extern static int GetUpdate(MarshalAs(UnmanagedType.LPStr)] string strParam);

Then call it as follows

string strParam = "test data";

int iStatus = GetUpdate(strParam);

The native code would look like

int GetUpdate(char* strParam)

{

//Do some processing on strParam here...

// calculate status and return

return nStatus;

}

Lets involve custom structures first as value parameters and later on as ref parameter so that native application will allocate memory and managed code will consume.

[StructLayout(LayoutKind.Sequential)]
struct BridgeMember
{
public int nMember;
[MarshalAs(UnmanagedType.LPStr)]
public string strMember;
}
[StructLayout(LayoutKind.Sequential)]
struct BridgeData
{
public int nData;
public uint dwData;
[MarshalAs(UnmanagedType.LPStr)]
public string strData;
public eType eData;
public BridgeMember BMData;
}
enum eType { one = 1, two };

[DllImport(@"path\Example.dll", ExactSpelling = true, CharSet = CharSet.Ansi)]
public static extern int ProcessObjectByVal(BridgeData BInData);

Use it as follows
BridgeData oBridgeData = new BridgeData();
oBridgeData.BMData.nMember = 10;
oBridgeData.BMData.strMember = "From C# assembly";
oBridgeData.dwData = 20;
oBridgeData.eData = eType.one;
oBridgeData.nData = 30;
oBridgeData.strData = "From C#";

int n = ProcessObjectByVal(oBridgeData);

The native code will include the definitions of the structures exactly as above and the ProcessObjectByVal() will look like as follows


int ProcessObjectByVal(BridgeData BInData)
{
//get values from BInData and process...
return iSomeStatus;
}

Now if we want to pass BridgeData as ref or out parameter, for strings allocations has to be done using COM memory functions such as CoTaskMemAlloc. Because .NET runtime expect native code to allocate memory using CoTaskMemAlloc and it frees the pointers using CoTaskMemFree. If memory is allocated using other functions like new or malloc; the memory will not be freed or worse crash can occur as .NET runtime will try to free it using CoTaskMemFree in any case.
For details please this article on MSDN.
Following example illustrate the correct use of memory functions for p/invoke.
Native Code:

int ProcessObjectByRef(BridgeData* pBOutData)
{
pBOutData->BMData.nMember = 1;
int size = 400;
//pBOutData->BMData.strMember = new char[size]; //who will free it? using coTaskMemAlloc will fix this leak
pBOutData->BMData.strMember = (char*)CoTaskMemAlloc(size);
memset(pBOutData->BMData.strMember,0,size);
strcpy_s(pBOutData->BMData.strMember,size,"This is medium string");
pBOutData->dwData = 55;
pBOutData->eData = two;
pBOutData->nData = 14;
pBOutData->strData = (char*)CoTaskMemAlloc(20); //who will free it? will using coTaskMemAlloc fix this leak?
memset(pBOutData->strData,0,20);
strcpy_s(pBOutData->strData,20,"small string");
return pBOutData->nData;
}

Managed code:

[DllImport(@"path\Example.dll", ExactSpelling = true, CharSet = CharSet.Ansi)]
public static extern int ProcessObjectByRef(out BridgeData BInData);

BridgeData oBridgeDataOut;
n = ProcessObjectByRef(out oBridgeDataOut);
//use oBridgeDataOut here...

We just discussed out to process objects by ref where native code will allocate memory as required instead of passing String or StringBuilder with pre-allocated memory which might not be a good option for all cases where data is created by native application.

In the above example if IntPtr is used in place of attributed string, the managed code has to de-allocate the memory itself rather than relying on the Runtime for example

IntPtr IntPtrData;
String strData = Marshal.PtrToStringAuto(IntPtrData);
Marshal.FreeCoTaskMem(IntPtrData);

Advertisements