如何安全地将字符串引用从 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# IntPtrs 成为 Marshal.PtrToStringAnsi(p)ed。在我的例子中,在 C++ 中存储线程本地的 recerences 并在每个函数调用时释放它们就足够了,但是你可以创建一个 C++ 函数来释放它给出的任何 ref。不是很聪明,也不一定是最有效的方法,但它确实有效。

更新: 它所做的正是they say 它所做的。一些快速的谷歌搜索出现了this article。我觉得还不错,你可以参考一下,而不是我的建议。如果指针不能自行释放(例如旧的 Delphi/C++Builder 样式字符串),并且您更喜欢在托管端而不是在本机端受到更多困扰,则传递原始 IntPtrs 很好.

作为一个例子,一段代码做 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,它认为它处理 PAnsiChars。在你的 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++?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过引用从 c# 到 c++ 传递字节数组

如何有效地将大字符串从 Python 传递到 C++ 扩展方法?

将字符串的引用从 C# 传递给 C++ 函数

如何使用 DLLImport 将字符串从 C# 传递到 C++(以及从 C++ 到 C#)?

如何将二维数组从 C# 传递到 C++?

如何正确地将日期时间从 c# 传递到 sql?