【问题标题】:C++ dll returned string is corrupted in the C# caller, why?C++ dll 返回的字符串在 C# 调用者中已损坏,为什么?
【发布时间】:2011-09-15 07:27:14
【问题描述】:

我有一个调用 C++ DLL 的 C# 应用程序。

在 C# 中,我的代码如下:

[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
public extern static String Func1(String arg);
......
String arg = "test text";
String retstring = Func1(arg);

在 CPP 中,我的函数定义如下:

extern "C"
{
__declspec(dllexport) LPWSTR Func1(LPWSTR arg)
{
          ....
          LPWSTR ret1 = L"1?2?3?4?5";
          LPWSTR ret2 = SomeActualFunction(arg);
          retturn ret1; // return ret2;
     }
}

如果我在 C++ 的 Func1() 中返回 ret1,一切正常。在 VS2008 的内存窗口中,我可以看到正确的 Unicode 二进制文件。在 C++ 中,ret1 的二进制文件是

“31 00 3f 00 32 00 3f 00 33 00 3f 00 34 00 3f 00 35 00”

,而在 C# 中,retstring 的二进制文件是

“28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00 31 00 3f 00 32 00 3f 00 33 00 3f 00 34 00 3f 00 35 00”

。我认为 C# 二进制文件

“28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00”

是 System.String 类型的标头。

更重要的是,如果我在 CPP 代码中的 return 之前添加以下行,我也可以在 C# 代码中获得正确的 retstring:

ret2 = L"1?2?3?4?5";

但是当我在 C++ DLL 中返回 ret2 时,C# 中返回的字符串 ret 似乎已损坏。根据我的检查,C++ DLL 中的二进制文件是正确的 Unicode。但是 C# 代码中 retstring 的二进制文件是

“28 67 a3 f7 fe 07 00 00 0a 00 00 00 09 00 00 00 dd dd dd dd dd dd dd dd dd dd dd ....”。

我只能注意到 ret2 比 ret1 长 - ret2 有数百个 WCHAR。

有什么想法吗?提前谢谢。

【问题讨论】:

  • 这种编组在我看来不合适。由于您使用的是宽字符串,因此最好使用 BSTR 进行互操作。
  • c 尖字符串与 c 字符串不同,您需要查看它的转换。

标签: c# c++ string dll return


【解决方案1】:
[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPStr)]
public extern static String Func1(String arg);

或

[DllImport(@"111.dll", CharSet = CharSet.Unicode)]
public extern static IntPtr Func1(String arg);
// In your calling code
string result = Marshal.PtrToStringUni(Func1("somestring"));

【讨论】:

  • LPSTR 用于 ANSI。我尝试了[return: MarshalAs(UnmanagedType.LPWStr)],但结果是一样的“dd dd dd dd....”。
【解决方案2】:

你的 func 是 __cdecl 还是 __stdcall 调用约定? IIRC,C# 的默认值为 __stdcall,但 C++ 的默认值为 __cdecl。尝试添加CallingConvention=CallingConvention.Cdecl。


另一种可能:既然你说返回静态字符串有效,那么SomeActualFunction返回的指针是否仍然有效?如果它指向该函数中的本地缓冲区,则从函数返回后将不再有效。

【讨论】:

  • 这不起作用...我也尝试使用 __stdcall 重新编译 CPP DLL,但仍然失败...
  • 第一个建议不太可能,因为您说静态字符串有效。更有可能SomeActualFunction 是错误的。您是否仅使用 C++ 测试过该函数?
  • 是的,我已经使用 CPP 控制台测试了 CPP DLL。 SomeActualFunction() 一切正常。
【解决方案3】:

所以,如果你返回指向硬编码字符串 L"1?2?3?4?5" 的指针,一切正常。但是,如果您返回 SomeActualFunction,则答案不正确。也许 C++ 代码不正确? SomeActualFunction 是如何工作的?例如,它可能会从此时销毁的某个堆栈分配的对象返回 LPWSTR。尝试先用 C++ 客户端对此进行测试。

【讨论】:

  • 和所有的 cmets 一样,我认为问题应该出在 CPP DLL 中释放的内存上。我想在 C# 调用者中分配字符串缓冲区并使其成为 Fun1() 中的引用,如何定义 C++ 函数 Fun1? Func1(LPWSTR 输入,LPWSTR 输出)???在 C# 中,我将定义 Func1(string in, ref string out)。
【解决方案4】:

我总是会为此使用BSTR,因为它使内存分配/释放的责任变得透明。

C++

#include <comutil.h>
BSTR GetSomeText()
{
    return ::SysAllocString(L"Greetings from the native world!");
}

C#

[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText();

你没有说你的字符串是如何分配的,但是一旦你使用了一个动态分配的字符串,你就需要解决这个问题。 BSTR 的伟大之处在于它使用共享 COM 分配器,这使 C# 编组器能够使用与分配它的 C++ 代码相同的分配器来解除分配字符串。

【讨论】:

  • 感谢大家关心这个话题。感谢您的所有 cmets!
【解决方案5】:

我已经在 SkyDrive (http://cid-48a119f5ed65483e.office.live.com/self.aspx/.Public/MarshalString.zip) 上上传了一个示例代码。 它演示了如何将托管字符串传递给非托管代码以及如何操作它。

【讨论】:

    【解决方案6】:

    我想我得到了这个工作:对 C++ DLL 的 C# 调用,将一个字符串返回给 C#。我将分享我认为使它发挥作用的因素:

    在 C# 中

    using System.Runtime.InteropServices;
    
    [DllImport(
        @"C:\Users\Ron\Documents\Visual Studio 2013\ etc. ...(The whole path)... my.dll,
        CallingConvention = CallingConvention.Cdecl
        )]
    [return: MarshalAs(UnmanagedType.BStr)]
    public static extern string cpp_brand_files(string home_dir, string xml_lic);
    

    以及稍后在 C# 中的调用:

          string a_str = cpp_brand_files(home_dir, xml_license);
    

    在 C++ DLL 中:

      #using <mscorlib.dll>
    
       using namespace System;
    
       extern "C" __declspec(dllexport)
       wchar_t* cpp_brand_files(char* home_dir, char* xml_lic);
    

    在 C++ 中更进一步:

      wchar_t* cpp_brand_files(char* home_dir, char* xml_lic)
      {
    
          BSTR samp = ::SysAllocString( L"This is the long, long string." );
          return samp;
      }
    

    另外,在配置属性中使用 /cli(公共语言运行时支持)编译 C++ DLL。右键单击 C++ 项目,然后属性 -> 配置属性 -> 常规 -> 公共语言运行时支持。

    所以这就是我认为使它工作的基本原理(返回一个字符串)。

    我正在使用 Visual Studio 2013 RC。专家,请评论额外或缺失的内容。

    【讨论】:

    • 提示:当问题是获取 C++ DLL 导出签名以匹配函数的 C# 导入签名时,调试器中的错误消息没有太大帮助。 (签名意味着匹配 C++ 和 C# 之间的函数参数类型和返回值。)似乎整个事情只是得到导入/导出匹配。
    【解决方案7】:

    如果您需要编组其他数据类型,这里有一个链接供您参考 .NET 4.0 的更新链接 https://msdn.microsoft.com/en-us/library/sak564ww(v=vs.100).aspx

    【讨论】:

    • 这个链接是死路
    • 感谢@Badgerspot。链接已更新。请注意,由于某种原因,在 MSDN 上,相同的交叉引用似乎不适用于 .NET 4.5 及更高版本。可能是因为 COM 是一项如此古老的技术,而且用例并不完全存在。数据类型也交叉引用了 UInt32 等核心 .NET 数据类型,不会改变。
    猜你喜欢
    • 1970-01-01
    • 2017-02-23
    • 1970-01-01
    • 2013-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多