将 c++ 函数包装为 c#,具有结构的总动态大小

Wrapping c++ function to c# with total dynamic size of struct

本文关键字:结构 动态 函数 c++ 包装      更新时间:2023-10-16

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

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

static void GetInfos(LibraryInfo& infos);

当然,我知道有一个类 图书馆信息

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 字段。
  • 第三:我为什么要用元帅副本,这个任意长度的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")] 私有静态外空 GetInfos(out IntPtr test);

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

  • 如果我在我的结构中使用 IntPtr,我没有字符串的长度,所以 我真的不能元帅。复制,也不是 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);

[编辑 2 基于 jdweng 2snd 评论]

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

我终于找到了方法,这要归功于@jdweng的解释和@PaulF的链接

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

链接: MSDN : 封送-类-结构-和联合

解释(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

[根据大卫·赫弗南的评论编辑1]

下面是一个调用示例的 C 代码:

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