【发布时间】: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