在 C# 的 C/C++ DLL 中使用嵌套结构指针

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

假设我们在用 C-lang 编写的 dll 中有以下代码,我尝试将 dll 中定义的一些函数映射为函数指针,映射到它们的实际函数,我按照以下 link 得到到这里 https://docs.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");}

现在我想编写一个使用此 DLL 的 C# 应用程序,特别是它应该使用 "GetDllTestFP"-Function 将函数指针映射到它们的实际函数。现在我的 C# 应用程序如下:

    [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);
  }

问题是,这些函数没有映射到它们在 dll 中的实际函数。 知道我怎样才能实现我的目标吗?

谢谢

C#代码是:

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

VersionInfo vInfo = new VersionInfo();
dllFPs.Close(ref vInfo);

你不需要分配 dllTest.psItf 因为在 GetDllTestFP 你这样做:

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

所以你复制sFP地址

请注意,一般来说这是一个坏主意,因为您让 "client" 直接访问您的数据(sFP 结构)。另一种方法是客户端传递内存(如您之前所写),然后您执行:

(*psFP->psItf) = sFP; 

(但记得释放分配的内存!)

第三种方案,C端通过shared allocator分配一块内存(C#可以使用的,所以这里没有malloc/new)然后C#有取消分配它。

错误的解决方法是

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

sFP2 的生命周期在方法 returns 时结束。 psFP->psItf 现在指向一块不再存在的堆栈。 不要这样做!

啊...正如@Hans Passant 所写,根据谁分配内存,GetDllTestFP 可以是 refout。如果内存是由 C# 分配的,那么它必须是 ref,如果它没有分配(像现在这样)或者是由 C++ 分配的,那么 out 就可以了,你将节省封送处理朝一个方向。