[英]How to import an exported char* from C++ dll in C# project
我正在嘗試在我的 C# 項目中打開從 Dll 導出的 char*。
由於代碼太多,我將編寫一個與我正在處理的代碼相似的簡短代碼。
C++:
__declspec(dllexport) typedef struct Kid {
char* _name;
int _age;
int _grade;
} Kid;
Kid get_default_kid()
{
char name[] = { "Quinn" };
return Kid{
name, 2,7
};
}
extern "C" __declspec(dllexport) Kid get_default_kid();
C#:
public struct Kid
{
public string _name;
public int _age;
public int _grade;
}
private const string Dll2 = @"C:\Users\Me\source\repos\Tom\x64\Release\JerryDll.dll";
[DllImport(Dll2, CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid();
public static void Main(string[] args){
Kid defaultKid = get_default_kid();
Console.WriteLine(defaultKid._name);
Console.WriteLine(defaultKid._age);
Console.WriteLine(defaultKid._grade);
}
此代碼在控制台中寫入一些隨機字符,例如“♠”,而不是從 dll 導出的名稱。
我試過的:
嘗試將char*
作為IntPtr
導入,然后使用以下命令讀取它:
Marshal.PtrToStringAnsi()/BSTR/Auto/Uni
和Marshal.ReadIntPtr
在嘗試使用上面的行讀取它之前。
從那以后,我嘗試在 C# 中將字符串轉換為 UTF-8 。
在谷歌搜索了很多。
一般來說,當我們談到 C/C++ 和 C# 之間的編組字符串(或其他分配的內存)時,最重要和最復雜的問題是:如何釋放 memory? 如果可能,最簡單的解決方案是從 C/C++ 端導出一個或多個釋放器方法。 這里我舉幾個例子:
請注意,您不能
public struct Kid
{
public string _name;
public int _age;
public int _grade;
}
你會得到一個錯誤。 .NET 封送拆收器不想封送作為返回值的非 blittable 結構(因此具有復雜類型的struct
,如string
s)。 出於這個原因,我使用IntPtr
,然后手動將char*
編組為string
。
C++:
extern "C"
{
const char* pstrInternal = "John";
typedef struct Kid
{
char* _name;
int _age;
int _grade;
};
// WRONG IDEA!!! HOW WILL THE "USER" KNOW IF THEY SHOULD
// DEALLOCATE OR NOT?
__declspec(dllexport) Kid get_default_kid_const()
{
// MUSTN'T DEALLOCATE!!!
return Kid { (char*)pstrInternal, 2,7 };
}
__declspec(dllexport) Kid get_a_kid()
{
// this string must be freed with free()
char* pstr = strdup(pstrInternal);
return Kid { pstr, 2,7 };
}
__declspec(dllexport) void free_memory(void* ptr)
{
free(ptr);
}
__declspec(dllexport) void free_kid(Kid* ptr)
{
if (ptr != NULL)
{
free(ptr->_name);
}
}
}
C#:
public struct Kid
{
public IntPtr _namePtr;
public int _age;
public int _grade;
public string _name { get => Marshal.PtrToStringAnsi(_namePtr); }
}
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid_const();
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_a_kid();
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_memory(IntPtr ptr);
[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_kid(ref Kid kid);
public static void Main(string[] args)
{
Kid kid = get_default_kid_const();
Console.WriteLine($"{kid._name}, {kid._age}, {kid._grade}");
// MUSTN'T FREE this Kid!!!
Kid kid2 = get_a_kid();
Console.WriteLine($"{kid2._name}, {kid2._age}, {kid2._grade}");
free_memory(kid2._namePtr);
Kid kid3 = get_a_kid();
Console.WriteLine($"{kid3._name}, {kid3._age}, {kid3._grade}");
free_kid(ref kid3);
}
請注意,對於那些在 C/C++ 和 C# 之間編組字符串卻不知道它是如何工作的人來說,有一個痛苦的世界(隨機崩潰和 memory 泄漏造成的痛苦)。
字符串的默認編組行為是寬字符串,例如LPWSTR
。 您將不得不添加一個編組指令來告訴 .NET 編組器您真正提供的是一個窄字符串(例如LPSTR
或char*
)。
在 MSDN 上閱讀更多信息。
該頁面上有一個與您想要的非常接近的示例 - 您幾乎可以只復制編組指令:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
[MarshalAs(UnmanagedType.LPStr)] public string f1;
}
所以你的將是:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Kid
{
[MarshalAs(UnmanagedType.LPStr)]
public string _name;
public int _age;
public int _grade;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.