2

I have a common construct in an unmanaged Win32 C++ DLL:

// FirstElemPtrContainer.h
#include "stdafx.h"

typedef unsigned char elem_type; // a byte

typedef struct FirstElemPtrContainer {
    unsigned char num_elems;
    void *allocd_ary;
} FirstElemPtrContainer;

The void* in the struct is meant to contain a pointer to the first element of an allocated byte array.

The DLL that uses this definition then exports functions to allocate, populate, and deallocate the struct:

// The exported allocator function.
extern "C" _declspec(dllexport) 
    FirstElemPtrContainer *BuildStruct(int elem_count)
{
    FirstElemPtrContainer *fepc_ptr = new FirstElemPtrContainer;
    fepc_ptr->num_elems = elem_count;
    elem_type *ary = new elem_type[fepc_ptr->num_elems];
    for (int i = 0; i < fepc_ptr->num_elems; i++)
    {
        ary[i] = ((i + 1) * 5); // multiples of 5
    }
    fepc_ptr->allocd_ary = ary;

    return fepc_ptr;
}

// The exported deallocator function.
extern "C" _declspec(dllexport) void 
    DestroyStruct(FirstElemPtrContainer *fepc_ptr)
{
    delete[] fepc_ptr->allocd_ary;
    delete fepc_ptr;
}

These work just fine for a native caller.

In C#, I try to describe this same structure via PInvoke:

[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
    public byte num_elems;
    [MarshalAs(UnmanagedType.LPArray, 
        ArraySubType = UnmanagedType.U1, SizeConst = 4)]
    public IntPtr allocd_ary;
}

... and describe the call interface like so:

public static class Imports
{
    [DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
    public static extern IntPtr BuildStruct(int elem_count);

    [DllImport("MyLib", CallingConvention = CallingConvention.Winapi)]
    public static extern void DestroyStruct(IntPtr fepc_ptr);
}

Now I attempt to call my interface:

class Program
{
    const int NUM_ELEMS = 4;
    static void Main(string[] args)
    {
        IntPtr fepc_ptr = Imports.BuildStruct(NUM_ELEMS);
        if ( fepc_ptr == IntPtr.Zero ) 
        {
            Console.WriteLine("Error getting struct from PInvoke.");
            return;
        }

        FirstElemPtrContainer fepc =
            (FirstElemPtrContainer)Marshal.PtrToStructure(fepc_ptr, 
        typeof(FirstElemPtrContainer));
        //...
    }
}

The PtrToStructure() call gives the error "Cannot marshal field 'allocd_ary' of type 'MyLibInvoke.FirstElemPtrContainer': Invalid managed/unmanaged type combination (Int/UInt must be paired with SysInt or SysUInt)."

You can see that I've hard-coded a particular number of elements, which we'll assume the caller adheres to. I've also added an ArraySubType clause, though it seems not to make a difference. Why the type mismatch complaint?

Buggieboy
  • 4,636
  • 4
  • 55
  • 79

1 Answers1

3

Your struct should be declared like this:

[StructLayout(LayoutKind.Sequential)]
public struct FirstElemPtrContainer
{
    public byte num_elems;
    public IntPtr allocd_ary;
}

it has to be done this way because allocd_ary is a pointer to unmanaged memory and cannot be marshalled by the p/invoke marshaller.

In order to read the contents of allocd_ary you can use Marshal.Copy.

FirstElemPtrContainer fepc = (FirstElemPtrContainer)Marshal.
    PtrToStructure(fepc_ptr, typeof(FirstElemPtrContainer));
byte[] ary = new byte[fepc.num_elems];
Marshal.Copy(fepc.allocd_ary, ary, 0, ary.Length);

I suspect that CallingConvention.Winapi is wrong and that you should be using CallingConvention.Cdecl.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • That's right. I tried your original suggestion of changing the PInvoke definition to contain a byte[], and got a different error: "Mismatch has occurred between the runtime type of the array and the sub type recorded in the metadata." I was already doing the Marshall.Copy() in my code, so literally all I did was to comment out the [MarshalAs()] gobbledygook that I had above and everything worked. Then, I came back here and saw that you'd suggested exactly the same thing that I'd just done. – Buggieboy Apr 09 '12 at 19:32
  • @Buggieboy I apologise for leading you astray. I hadn't read the question closely enough first time around. But what about calling convention? If you don't explicitly set it in your C++ code, it will be `Cdecl`. – David Heffernan Apr 09 '12 at 19:39
  • David - Well, I had Visual Studio 2005 make me a C++ Win32 DLL project and have 'extern "C" _declspec(dllexport)' preceding my function definitions, if that tells you anything. The "CallingConvention.Winapi" didn't seem to harm anything, so I left it as is. – Buggieboy Apr 09 '12 at 20:10
  • You should use Cdecl as per my answer then. – David Heffernan Apr 09 '12 at 20:18
  • Just checked the Advanced properties in VS, and it says that "Compile as C++ Code" (/TP) is used. I'll make the change. Thanks! – Buggieboy Apr 09 '12 at 20:30