【问题标题】:Wrapping c++ function to c# with total dynamic size of struct用结构的总动态大小将 c++ 函数包装到 c#
【发布时间】:2019-11-04 01:23:49
【问题描述】:

我正在努力将 C++ 函数包装到 C#。 我对这类包装有非常基本的了解,但在这里我试图找到“最佳解决方案”。

假设我只有一个包含 C++ 函数的 .dll。我只知道有这个签名的函数:

static void GetInfos(LibraryInfo& infos);

当然我知道有一个类 LibraryInfos

class LIBRARY_EXPORT_CLASS LibraryInfo
{
    public:
        const char* libVersion;
        const char* libName;
        const char* libDate; 
    };
};

现在我尝试在 C# 测试项目中使用这个函数:

static void Main(string[] args)
{
     // Create Pointer
     IntPtr ptr;

     // Call function
     GetInfos(out ptr);

     // Get the 100 first byte (used only to demonstrate)
     byte[] buffer = new byte[100];
     Marshal.Copy(ptr, buffer, 0, 100);

     // Display memory content
     Console.WriteLine(Encoding.ASCII.GetString(buffer));

     Console.ReadLine();
}

[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

这段代码给我作为输出

v.1.2.5                                                                   ?I9               
  • 首先:我知道这是非常糟糕的做法,编组一个 任意长度作为 byte[] 在这里只是为了演示。
  • 第二:我只有版本,但如果我调用的是同一个 dll 从 C++ 项目中,我有 3 字段与数据。
  • 第三:为什么我用了 Marshal 副本,而且这个任意长度的 100 ?因为我没有成功调用 PtrToStruct,所以这里是我的 试过了:

    [StructLayout(LayoutKind.Sequential)]
    private struct LibInformation
    {
        public IntPtr Version; // I tried, char[], string, and IntPtr
        public IntPtr Name;
        public IntPtr Date;
    }
    
    static void Main(string[] args)
    {
    // Create Pointer
    IntPtr ptr;
    
    // Call function
    GetInfos(out ptr);
    
    if (ptr != IntPtr.Zero)
    {
         LibInformation infos = (LibInformation)Marshal.PtrToStructure(ptr, typeof(LibInformation));   
    }
    
    Console.ReadLine();
    }
    

    [DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")] private static extern void GetInfos(out IntPtr test);

然后我无法检索我的版本、名称和日期。

  • 如果我在我的结构中使用 IntPtr,我没有字符串的长度,所以 我不能真正 marshal.Copy,也不能 PtrToStringAuto。
  • 如果我使用 Char[] 或字符串,它不起作用。

我认为我的问题在于不知道最终响应的大小。所以我现在最好的选择是制作 C++ 项目,从那里调用这个函数,然后把这个结构包装在一个更好的结构中,我可以在另一边编组(每个成员的长度作为其他成员)。

有什么想法吗?

[编辑 1 基于 jdweng 评论]

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version; // I tried, char[], string, and IntPtr
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create Pointer
    IntPtr ptr;

    // Call function
    GetInfos(out ptr);

    var data = Marshal.PtrToStructure<LibInformation>(ptr);
    var version = Marshal.PtrToStringAnsi(data.Version);
    Console.WriteLine(version) // result : ""

    // Use Ptr directly as string instead of struct
    var version2 = Marshal.PtrToStringAnsi(ptr);
    Console.WriteLine(version2) // result : "v1.2.5" but how can i access other field ?

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

[EDITS 2 based on jdweng 2snd comment]

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version;
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create Pointer for my structure
    IntPtr ptr;

    // Create 3 pointers and allocate them
    IntPtr ptrVersion = Marshal.AllocHGlobal(100);
    IntPtr ptrName = Marshal.AllocHGlobal(100);
    IntPtr ptrDate = Marshal.AllocHGlobal(100);

    // Then declare LibInformation and assign
    LibInformation infos = new LibInformation();

    // Here is probably my missunderstanding (an my error)
    // As I need a ptr to call my function I have to get the Ptr of my struct
    IntPtr ptr = Marshal.AllocHGlobal(300);
    Marshal.StructureToPtr(infos, ptr, false);

    // Assign
    infos.Version = ptrVersion;
    infos.Name = ptrName;
    infos.Date = ptrDate;

    // Call function
    GetInfos(out ptr);

    var data = Marshal.PtrToStructure<LibInformation>(ptr);
    var version = Marshal.PtrToStringAnsi(data.Version);
    Console.WriteLine(version) // result : still ""

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

【问题讨论】:

  • 你的类有三个项目(版本名称日期),它们都是指针。所以结构大小是 3 x 4(Intptr 是 4 个字节)= 12。当你复制 100 个字节时,你应该会看到。前 12 个。所以在 c# 中你需要两个步骤。 1) 获取 LibraryInfo 2) 然后您使用 Marshal PtrToStr 三次从那里的指针获取三个项目的值。 LibraryInfo 三个对象都应该定义为 IntPtr。
  • @PaulF 谢谢,但我已经用过那个页面了。
  • @jdweng 谢谢,但这就是我尝试过的,但不起作用。如果我调用 GetLibraryInfo,然后调用 ptrTostruct,那么我有另外 3 个 IntPtr,然后我对它们每个调用 IntPtrToString,我得到空字符串。如果我直接在传递参数的指针上调用 IntPtrTostring,我会得到版本号。我将根据您的评论用我尝试过的内容更新我的原始帖子
  • 方法的参数列表在执行栈上。从方法返回后,参数列表无效,因为它可以被父代码覆盖。只有方法的返回值是有效的。所以你必须在调用方法之前分配所有数据。因此,您需要在非托管内存中分配变量版本、名称和日期。然后声明 LibInformation 并将版本、名称和日期设置为分配的内存位置。最后调用方法。然后要获取三个变量,您必须调用 Marshal.PtrToStruct 从非托管内存中复制结果。

标签: c# c++ dll marshalling


【解决方案1】:

终于找到办法了,感谢@jdweng的解释和@PaulF的链接

唯一的坏消息是我必须在调用我的函数之前任意分配 n 个字节

链接: MSDN : marshaling-classes-structures-and-unions

解释(jdweng):

方法的参数列表在执行堆栈上。从方法返回后,参数列表无效,因为它可以被父代码覆盖。只有方法的返回值是有效的。所以你必须在调用方法之前分配所有数据。因此,您需要在非托管内存中分配变量版本、名称和日期。然后声明 LibInformation 并将版本、名称和日期设置为分配的内存位置。最后调用方法。然后要获取三个变量,您必须调用 Marshal.PtrToStruct 从非托管内存中复制结果。

最终代码:

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version;
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create 3 pointers and allocate them
    IntPtr ptrVersion = Marshal.AllocHGlobal(100);
    IntPtr ptrName = Marshal.AllocHGlobal(100);
    IntPtr ptrDate = Marshal.AllocHGlobal(100);

    // Then declare LibInformation and assign
    LibInformation infos = new LibInformation();

    // Assign
    infos.Version = ptrVersion;
    infos.Name = ptrName;
    infos.Date = ptrDate;

    // Call function
    GetInfos(out infos);

    var version = Marshal.PtrToStringAnsi(data.Version);
    var name = Marshal.PtrToStringAnsi(data.Name);
    var date = Marshal.PtrToStringAnsi(data.Date);

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out LibInformation test); // Changing IntPtr to LibInformation

[根据 David Heffernan 的评论编辑 1]

这是调用示例的 C 代码:

HComplexCTXPoint::LibraryInfo versionInfo;
HComplexCTXPoint::GetInfos(versionInfo);
std::cout << versionInfo.libName << std::endl;

【讨论】:

  • 我不认为这是正确的,我必须警告你,不幸的是,jdweng 是一个连续提供有关 pinvoke 主题的错误建议的人。库返回指向库中声明的 const 字符串的可能性更大。如果我们能看到用 C 语言调用代码的示例,那将解决任何疑问。
  • @DavidHeffernan Ho,我明白了,我的意思是,这对我现在有效,但我知道这不是正确的方法。如果您有更好的方法或建议,我总是很乐意学习。我根据您的评论更新了我的答案。
  • 现在代码太多了。在问题和答案中。在它被挑选成minimal reproducible example 之前,我不会花时间在这上面。
猜你喜欢
  • 2018-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 2019-06-27
相关资源
最近更新 更多