如何安全地将字符串引用从 c# 传递到 c++?
Posted
技术标签:
【中文标题】如何安全地将字符串引用从 c# 传递到 c++?【英文标题】:How to safely pass string reference from c# to c++? 【发布时间】:2014-04-05 02:36:45 【问题描述】:我正在用 C++ dll 项目开发一个 C# dll 项目。
假设 C++ dll 登录到某个网站,并在 Web 服务器上进行一些查询。
而 C++ dll 必须返回网站的 html 代码。
同时,C++ dll 必须保存来自网站的 cookie 数据。
所以,我将 StringBuilder 对象传递给 C++ 函数。
我已经知道如何使用 C# 中的 HttpWebRequest 和 HttpWebResponse 从网站获取 html 代码,但不幸的是我必须在 C++ dll 项目中进行。
请记住,我不需要任何 C# 代码。
我试过Passing a string variable as a ref between a c# dll and c++ dll。
从 C# 传递 StringBuilder 并将其作为 LPTSTR 获取。
它工作正常,但结果中缺少一些字符串。
我找不到原因。
无论如何,这是我的代码。
C++
extern "C" __declspec(dllexport) BSTR LoginQuery(const char* UserID, const char* UserPW, char Cookies[])
std::string html;
try
std::map<std::string, std::string> cookies;
MyClass *myclass = new MyClass();
html = myclass->LoginQuery(UserID, UserPW, cookies);
// Response cookies
std::string cookieData;
for (std::map<std::string, std::string>::iterator iterator = cookies.begin(); iterator != cookies.end(); iterator++)
cookieData += iterator->first;
cookieData += "=";
cookieData += iterator->second;
cookieData += ";";
sprintf(Cookies, cookieData.c_str(), 0);
delete myclass;
catch (...)
return ::SysAllocString(CComBSTR(html.c_str()).Detach());
C#
[DllImport(@"MyDll.dll", EntryPoint="LoginQuery", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern void LoginQuery(string UserID, string UserPW, StringBuilder Cookies);
void SomeThing()
StringBuilder _cookies = new StringBuilder(1024);
string result = LoginQuery("test", "1234", _cookies);
效果很好。
将StringBuilder作为cookie,我可以进行网站的下一个url。
(我在 C++ 项目中使用 libcurl。)
但问题是我有大约 100 个 id。
当它运行大约 3~40 时,它返回堆错误。
Debug Assertion Failed!
Program: ~~~~mics\dbgheap.c
Line: 1424
Expression: _pFirstBlock == pHead
我无法单击中止、重试或忽略按钮。
看起来 C# 应用程序挂起。
我阅读了很多关于 dbgheap 调试断言失败的文章。
类似于另一个堆中的空闲内存对象。
我是 C++ 的新手。
我在http://bytes.com/topic/c-sharp/answers/812465-best-way-interop-c-system-string-c-std-string 上阅读了 Edson 的问题。
但错误并不总是在特定时间出现。
所以我猜测,当 .NET 运行垃圾收集器时会发生这种情况。
.NET 垃圾收集器尝试释放一些从 C++ dll 创建的内存,我得到了堆错误。
我说的对吗?
我认为他和我的问题一样。
避免堆错误并将正确字符串从 C++ dll 返回到 C# dll 的最佳方法是什么?
P/S:只有当我在调试模式下运行时才会出现堆错误。我运行发布模式时没有收到错误消息。
编辑
根据 WhozCraig 的回答,我将代码更改如下。
//return ::SysAllocString(CComBSTR(html.c_str()).Detach());
CComBSTR res(html.c_str());
return res.Detach();
但运气不好,我仍然收到堆错误。
【问题讨论】:
相关,这个:return ::SysAllocString(CComBSTR(html.c_str()).Detach());
是内存泄漏。您正在分离 CComBSTR(这意味着它现在是原始 BSTR)然后返回 that 的 SysAllocString,永远泄漏分离的 BSTR。只需这样做:CComBSTR res(html.c_str()); return res.Detach();
您是否有理由不将 C++ 置于 C++/CLR 模式并传入 system.string 并在 C++ 级别处理此问题? ;) 这使得事情更容易使用。您使用的互操作旨在与“遗留纯 C”接口集成 - 如果您拥有 C++ 代码,只需打开集成。
当 cookie 过多时,您的 sprintf() 函数调用会损坏堆。 Cookies 参数除了使您的程序崩溃之外没有任何用处。去掉它。请注意,它也不会在 C++ 代码中执行任何操作,因此只需删除 sprintf() 调用即可。在尝试调用之前对 C++ 代码进行彻底的单元测试。
@WhozCraig 我会尝试更改我的代码。
@TomTom C++/CLR 是否可以使用 JustDecompile 等反编译器进行反编译?如果是这样,我不能使用它。
【参考方案1】:
您的问题是标题询问有关将字符串引用从 c# 传递到 c++,但稍后在文本中您询问如何将字符串从 C++ 返回到 C#。此外,您告诉您是 C++ 新手。考虑到这一点,我会告诉我上次我必须这样做时我是如何进行这种交互的。我刚刚让 C++ 端分配并释放所有内存,只传递给 C# IntPtr
s 成为 Marshal.PtrToStringAnsi(p)
ed。在我的例子中,在 C++ 中存储线程本地的 recerences 并在每个函数调用时释放它们就足够了,但是你可以创建一个 C++ 函数来释放它给出的任何 ref。不是很聪明,也不一定是最有效的方法,但它确实有效。
更新:
它所做的正是they say 它所做的。一些快速的谷歌搜索出现了this article。我觉得还不错,你可以参考一下,而不是我的建议。如果指针不能自行释放(例如旧的 Delphi/C++Builder 样式字符串),并且您更喜欢在托管端而不是在本机端受到更多困扰,则传递原始 IntPtr
s 很好.
作为一个例子,一段代码做 Delphi 交互(也适用于 C++ Builder):
// function Get_TI_TC(AuraFileName:PAnsiChar; var TI,TC:PAnsiChar):Boolean; stdcall; external 'AuraToIec104.dll' name 'Get_TI_TC';
[DllImport("AuraToIec104")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool Get_TI_TC(string AuraFileName, out IntPtr TI, out IntPtr TC);
public static bool Get_TI_TC(string AuraFileName, out string TI, out string TC)
IntPtr pTI, pTC;
bool result = Get_TI_TC(AuraFileName, out pTI, out pTC);
TI = Marshal.PtrToStringAnsi(pTI);
TC = Marshal.PtrToStringAnsi(pTC);
return result;
【讨论】:
你能给我看一篇 Marshal.PtrToStringAnsi 的文章吗? 感谢您提供的优质信息。我读过那篇文章。现在,我想知道如何从 C++ 函数中获得“out IntPtr”。我收到了“const char *”类型,我在 c# 中得到了 IntPtr.Zero。该网站上没有任何示例显示我传递“out IntPtr”。 嗯...我想你只是重新解释。我的例子是在 Delpni 中,它有var
关键字 meaninc C# 的 ref
,它认为它处理 PAnsiChar
s。在你的 C++ 函数中......我会说你返回 char *
并有一个 char **
参数。在您的 C# extern
声明中,您说您的 C++ 函数返回 IntPtr
并具有 out IntPtr
参数。如果这可行,您可能需要更多的 Unicode(wchar_t * 和 Marshal.PtrToStringUni),但想法是一样的。【参考方案2】:
看起来你的问题很简单。您正在创建一个StringBuilder
,它可以容纳多达 1024 个字符。如果您的 C++ 函数返回的值超过此值,您的应用程序将崩溃(迟早)。
因此,要解决您的问题,请将StringBuilder
的大小增加到可能的最大输出长度。更多详情:Passing StringBuilder to PInvoke function 其中引用:
唯一的 需要注意的是 StringBuilder 必须 被分配足够的空间 返回值,否则文本将 溢出,导致异常 由 P/Invoke 抛出。
在动态字符串长度的情况下,在 C++ 函数中使用 BSTR 参数实际上可能会更好。然后,您可以在 C# 中使用 [MarshalAs(UnmanagedType.AnsiBStr), Out] ref string ...
,在 C++ 中使用 BSTR*
。
【讨论】:
StringBuilder 大小过小,会抛出缓冲区溢出异常。不是堆错误。因此,与 StringBuilder 无关。实际上cookie(StringBuilder)的大小只有20个字符。 "实际上cookie(StringBuilder)的大小只有20个字符。" - 你是什么意思?如果您说的是“100 个 id”,我认为 20 个字符不足以容纳它们。 @JoshuaSon 另外,我怀疑你会得到 BufferOverFlowException。 sprintf() 在写入太小的缓冲区时肯定不会抛出异常,它会惨遭失败。请仔细检查。 如果你看到 void SomeThing 函数,我总是用 new 关键字重新创建 StringBuilder。 @JoshuaSon 可能是这样,但我仍然看不到这个缓冲区足够大。以上是关于如何安全地将字符串引用从 c# 传递到 c++?的主要内容,如果未能解决你的问题,请参考以下文章
如何有效地将大字符串从 Python 传递到 C++ 扩展方法?