将字符串从 C# 发送到 C++ dll 文件
Posted
技术标签:
【中文标题】将字符串从 C# 发送到 C++ dll 文件【英文标题】:Sending string from C# to C++ dll file 【发布时间】:2018-03-08 13:26:14 【问题描述】:我在 socket.dll 库中有一个函数如下:
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
wstring ws(pib_key_chars);
std::string pib_key(ws.begin(), ws.end());
cout << "In GetPib" << " and pib_key in string=" << pib_key << endl;
Pib pb;
return std::to_string(pb.Get(phyFskTxPacket, 0));
当我使用“dumpbin /exports socket.dll”检查 socket.dll 签名的 GetPib 函数时,它会返回
1 0 00011370 ?GetPib@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?
$allocator@D@2@@std@@V12@@Z = @ILT+875(?GetPib@@YA?AV?$basic_string@DU?
$char_traits@D@std@@V?$allocator@D@2@@std@@V12@@Z)
我在 C# 项目 (WindowsFormsApp1) 中使用相同的签名来调用/调用 GetPib 函数。
下面是调用GetPib函数的C#源代码:
namespace WindowsFormsApp1
public partial class Form1 : Form
[DllImport("socket.dll", EntryPoint = "?GetPib@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PB_W@Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
public Form1()
InitializeComponent();
private void GetPib_button_Click(object sender, EventArgs e)
String str = pib_id_tbox.Text;
pib_uvalue_tbox.Text = GetPib(str);
当我调用GetPib(0x1004xxx4)之类的GetPib函数时,它会调用socket.dll的GetPib函数,但值因特殊字符而异
str=0x10040004 tr=268697604-----> in C# file
In GetPib and pib_key in string=h䤰„------> in C++ .dll file
如何解决这个问题。
【问题讨论】:
hmm 可能为 c++ dll 做一个包装器(我的意思是控制台应用程序来执行 c++ 函数)并从 c# 应用程序执行它们? 您不能通过 P/INVOKE 使用std::string
或其他类似的 C++ 类。您要么需要将签名更改为有效的东西,要么需要创建一个包装器。 std::string in C#?
@crashmstr:我的代码已经遵循您提供的示例。你能告诉我还需要做哪些代码更改,使用新代码 sn-p。
您对宽字符串使用了错误的UnmanagedType
值。 String Marshaling
How to convert wstring into string? 的可能重复项(你去吧,从宽字符串转换为字符串的代码)。一旦你遇到 p/invoke 的问题,回来看看我的 cmets。
【参考方案1】:
首先,将非托管函数声明包装在 extern "C"
中以消除重整,并将非托管函数重写为更适合与托管代码互操作的东西。
extern "C"
__declspec(dllexport) int GetPib(const char* pib_key_chars, char *result, int len)
cout << "In GetPib" << " and pib_key in string=" << pib_key_chars << endl;
Pib pb;
string packet = std:to_string(pb.Get(phyFskTxPacket, 0);
int n = (int)packet.length;
if (n < len)
packet.copy(result, n, 0);
result[n] = '\0';
return n + 1;
在这个重写中,我们传递了托管字符串、一个分配的用于保存结果的缓冲区以及该缓冲区的长度。这个版本处理char*
字符串。如果您需要使用wchar_t*
宽字符串,转换它应该很简单。需要注意的主要一点是,我们没有使用任何 C++ 类型作为函数的参数,因为互操作编组器无法知道如何处理这些类型。我们只使用语言原语。
托管的 P/Invoke 签名如下所示:
[DllImport("socket.dll", EntryPoint="GetPib")]
public static extern int GetPib(([MarshalAs(UnmanagedType.LPStr)] string keyChars, IntPtr buffer, int len);
称呼它:
private void GetPib_button_Click(object sender, EventArgs e)
int needed = GetPib(pib_id_tbox.Text, IntPtr.Zero, 0);
IntPtr p = Marshal.AllocHGlobal(needed);
GetPib(pib_id_tbox.Text, p, needed);
pib_uvalue_tbox.Text = Marshal.PtrToStringAuto(p);
Marshal.FreeHGlobal(p);
这里我们分配非托管内存,以便在我们与非托管代码互操作时 GC 不会移动它。非托管函数将使用生成的char*
填充缓冲区,我们使用PtrToStringAuto()
将其转换回托管字符串。然后我们释放非托管内存。
【讨论】:
【参考方案2】:我点击了这个链接
https://answers.unity.com/questions/142150/passing-strings-to-and-from-a-c-dll.html
我在 C++ 中修改了 GetPib api
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
到
extern BSTR __declspec(dllexport) GetPib(BSTR pib_key_chars)
修改的 C# 文件来自
[DllImport("socket.dll", EntryPoint = "?GetPib@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PB_W@Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
到
[DllImport("socket.dll", EntryPoint = "?GetPib@@YAPA_WPA_W@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetPib(string str);
它解决了我的问题!!!!!!
【讨论】:
以上是关于将字符串从 C# 发送到 C++ dll 文件的主要内容,如果未能解决你的问题,请参考以下文章