views:

371

answers:

3

I have problems with marshalling output parameter of c++ function returning array of data to c#.

Here is C++ declaration:

#define DLL_API __declspec(dllexport)

typedef TPARAMETER_DATA
{
    char        *parameter;
    int     size;
} PARAMETER_DATA;

int DLL_API GetParameters(PARAMETER_DATA *outputData);

The function allocates memory for char array, places the data there and returns the number of allocated bytes in "size" field. Here is my c# declaration:

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 50000)]        
    public byte[] data;   // tried also SizeParamIndex = 1 instead of SizeConst

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}

[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
public extern static uint GetParameters(ref PARAMETER_DATA outputData); // tried also  'out' parameter

When calling the function in c# I get empty structure (size=0, empty array). I tried passing outputData parameter with data feld initialized to new byte[50000] but no data is returned anyway.

Every other function in this dll (some with complex input structures) are working fine, but this is the only function that allocates memory to return data. I tried many other C# marshalling declarations (with LPArray, LPString) with no luck - always empty data structure is returned or memory access exception is thrown. Am I missing something simple here?

EDIT:

I cannot change the c++ code - it's external library.

+1  A: 

The problem you're facing is that a pointer is returned - not really a string or an array. There is no way for the marshaller to convert the pointer to an array or string, because the length is unknown.

The solution might be to do the pointer handling in c#. You should also figure out if you're responsible for freeing the pointer, or that the library will do that for you.

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    public IntPtr data;   // tried also SizeParamIndex = 1 instead of SizeConst

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}

[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
private extern static uint GetParameters(ref PARAMETER_DATA outputData);

public static uint GetParameters(out String result)
{
    PARAMETER_DATA outputData = new PARAMETER_DATA();
    result= Marshal.PtrToStringAnsi(outputData.data, outputData.size );
    Marshal.FreeHGlobal(outputData.data); // not sure about this
}
Koert
Is really pointer is returned? The c++ function argument is \*outputData but it's a pointer passed TO a function and it should point to a already allocated structure (with 4 bytes for char\* and 4 bytes for int size). So how the invocation of the function should look in C#?
PanJanek
A: 

Your marshaling declarations for the C# structure don't match your C++ structure, they correspond to this c++ structure

typedef TPARAMETER_DATA
{
    char    parameter[50000];
    int     size;
} PARAMETER_DATA;

You might be able to get that to work with some special Marshaling code, but I think an easier way is to change the way you do the allocation on the C++ side.

I'm sure there are other ways to do this, but one way that I got to work was to use a SAFEARRAY. SAFEARRAY is part of the standard COM API, created originally for interop with VB (I think). http://msdn.microsoft.com/en-us/library/ms221145.aspx

A SAFEARRAY knows it's size and datatype, so it's easy for the marshaler to handle. I must be locked before you can write into it in C++ and then unlocked before you try and marshal it.

So your new parameter structure is this, (I don't think size is needed, the SAFEARRAY already knows)

typedef TPARAMETER_DATA
{
    SAFEARRAY * parameter;
    int         size; // I think this is redundant.
}

In your C++ code you allocate the array using

SAFEARRAY * psa = SafeArrayCreateVector(VT_UI1, 0, 50000);
if ( ! psa)
    return E_OUTOFMEMORY;

HRESULT hr = SafeArrayLock(psa);
if (FAILED(hr))
{
    SafeArrayDestroy(psa);
    return hr;
}

CopyMemory(psa->pvData, mydataptr, 50000);
SafeArrayUnlock(psa);

PARAMETER_DATA pda = {psa, 50000};

Then the C# declaration for the structure is

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    [MarshalAs(UnmanagedType.SafeArray)]        
    public byte[] data; // I used System.Array here, but I think byte[] is OK

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}
John Knoeller
The problem is I cannot change the c++ code - it's external library.
PanJanek
A: 

Is this one of the things you tried? LPArray and SizeParamIndex = 1

[StructLayout (LayoutKind.Sequential)]
public struct PARAMETER_DATA
{
  [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 1)]
  public byte [] data; 

  [MarshalAs (UnmanagedType.I4)]
  public int size;
}
John Knoeller
Yes. No success. byte[] data is intact after tha call.
PanJanek
Then there is no way to describe your structure to the marshaler. You will have to do custom marshaling, or write some C++ code to change the structure before it is marshalled.
John Knoeller
Koehrt's solution is close to what you need. You should start there.
John Knoeller