Search code examples
c#cdllmarshalling

using nested structure pointers in C/C++ DLL in C#


let say we have following code in a dll written in C-lang, where i try to map some functions defined in the dll as functionpointers, map to their actual functions, i follow following link to get till here https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-different-types-of-arrays

Dlltest.h

typedef struct VersionInfo
{
  UINT uMajor;
  UINT uMinor;
  UINT uMRevision;
} STRUCT_VERSION_INFO;

typedef struct DllTestFPStruct
{
  int(*Close) (void);
  int(*Init) (STRUCT_VERSION_INFO *sVersInfo);
  int(*RegisterClient) (int id);
} STRUCT_DLL_TEST_FP_STRUCT;

typedef struct DllTestMgr
{
  STRUCT_DLL_TEST_FP_STRUCT *psItf;
  int iDummy;
} STRUCT_DLL_TEST_MGR;

extern "C"
{ __declspec(dllexport) void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP); }

Dlltest.c

static int Close(void);
static int Init(STRUCT_VERSION_INFO *sVersInfo);
static int RegisterClient(int id);

STRUCT_DLL_TEST_FP_STRUCT sFP =
{
  &Close,
  &Init,
  &RegisterClient,
};

DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
{ psFP->psItf = &sFP; }

static int Close(void)
{ printf("Closed called.\n"); }

static int Init(STRUCT_VERSION_INFO *sVersInfo)
{ printf("Init called.\n");}

static int RegisterClient(STRUCT_VERSION_INFO *sVersInfo)
{  printf("RegisterClient called.\n");}

Now i want to write a c# application which uses this DLL, specially it should make use of the "GetDllTestFP"-Function which maps the functionpointers to their actuall function. right now my C#-Application is as follow:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int FP_DLL_TEST_CLOSE(ref VersionInfo sVersInfo);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int FP_DLL_TEST_INIT(ref VersionInfo sVersInfo);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int FP_DLL_TEST_RC(ref VersionInfo sVersInfo);

    [StructLayout(LayoutKind.Sequential)]
    public struct DllTestFPStruct
    {
        public FP_DLL_TEST_CLOSE Close;   
        public FP_DLL_TEST_INIT Init;
        public FP_DLL_TEST_RC RegisterClient;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DllTestMgr
    {
        public IntPtr psItf;
        public int iDummy;
    }

    [DllImport("DllTestC.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
    public static extern void GetDllTestFP(ref DllTestMgr ps);

    static void Main(string[] args)
    {
        VersionInfo vInfo = new VersionInfo();
        DllTestFPStruct dllFPs = new DllTestFPStruct();
        DllTestMgr dllTest = new DllTestMgr();
        IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(dllFPs));
        Marshal.StructureToPtr(dllFPs, buffer, false);
        dllTest.psItf = buffer;
        GetDllTestFP(ref dllTest);  // Funtionpointers are not mapped, still null
        dllFPs.Close(ref vInfo);
  }

The problem is, that the functions do not get mapped to their actuall functions in the dll. Any idea how can i achieve my goal?

Thanks


Solution

  • The C# code is:

    DllTestMgr dllTest = new DllTestMgr();
    GetDllTestFP(ref dllTest);
    DllTestFPStruct dllFPs = (DllTestFPStruct)Marshal.PtrToStructure(dllTest.psItf, typeof(DllTestFPStruct));
    
    VersionInfo vInfo = new VersionInfo();
    dllFPs.Close(ref vInfo);
    

    You don't need to allocate dllTest.psItf becase in GetDllTestFP you do:

    DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
    {
        psFP->psItf = &sFP;
    }
    

    So you copy the address of sFP.

    Note that in general this is a bad idea, because you are giving to the "client" direct access to your data (the sFP struct). The alternative is that the client passes the memory (as you wrote before) and then you do:

    (*psFP->psItf) = sFP; 
    

    (but then remember to free the allocated memory!)

    Third alternative, the C-side allocates a block of memory through a shared allocator (one that can be used by C#, so no malloc/new here) and then the C# has to deallocate it.

    wrong solution is

    STRUCT_DLL_TEST_FP_STRUCT sFP2 = sFP;
    psFP->psItf = &sFP2;
    

    The lifetime of sFP2 ends when the method returns. psFP->psItf is now pointing to a piece of stack that doesn't exist anymore. don't do it!

    Ah... as written by @Hans Passant, depending on who allocates the memory, the GetDllTestFP can be ref or out. If the memory is allocated by C# then it must be ref, if it isn't allocated (as is now) or is allocated by C++, then out is ok and you'll save on the marshaling in one direction.