将字符串转换为char*再转换为字符串(C# -> C -> C++)

5
我正在开发一个带有 C 包装器的 C++ DLL,以便在不同的语言中使用。目前,我也在开发一个用 C# 编写的插件来调用我的 DLL。
我想要的是将一个字符串(文件路径)作为我的 DLL 的参数传递,以便在我的 DLL 中使用。 C#
[DllImport(DllName, CallingConvention = DllCallingConvention)]
public static extern IntPtr AllocateHandle(string filename);

C包装器

LPVOID SAMPLEDLL_API CALLCONV_API AllocateHandle(char* filename);

C++类的构造函数

CustomData::CustomData(char* filename)
{
    _filename = filename; // string _filename;
}

当我在文件上保存_filename(因为我没有找到使用DLL断点调试的方法),我得到了类似于ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ0à×的结果。我尝试了不同的解决方案将char*转换为字符串,但结果仍然相同。
谢谢您的帮助。

这篇https://dev59.com/aWzXa4cB1Zd3GeqPS2Pb#13993608有帮助吗? - mjwills
我想您需要使用[MarshalAs(UnmanagedType.LPStr)]来修饰您的filename - Evk
不好意思,我刚刚发现了DLL、插件等世界,所以MarshalAs对我来说还没有什么意义,我宁愿先尝试第一个答案,而且它也起作用了,所以谢谢大家。 - Mathieu Gauquelin
2个回答

2
问题在于,C#中的字符串是Unicode格式的,而C++中的字符串是ansi格式的。因此,你需要告诉C#这个字符串必须是ansi格式的:
[DllImport(DllName.dll, CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Ansi)]
static extern IntPtr AllocateHandle(string filename);

您还可以将字符串长度作为第二个参数传递,这样您就可以在cpp侧知道字符串的长度。
[编辑]
根据一些评论,您还可以尝试将[char *]更改为[ wchar_t *],它是Unicode。然后,您应该在C#端使用适当的属性:CharSet = CharSet.Unicode。

3
默认不是Ansi吗?https://msdn.microsoft.com/zh-cn/library/system.runtime.interopservices.dllimportattribute.charset(v=vs.110).aspx - mjwills
2
@Bigiansen Ansi是每个字符1字节,而Unicode使用2字节来表示字符串中的字符。如果操作者想要使用英语以外的字符,则应该使用Unicode。 - Markiian Benovskyi
从MSDN:Ansi =将字符串作为多字节字符字符串进行编组。https://msdn.microsoft.com/es-es/library/system.runtime.interopservices.charset(v=vs.110).aspx - babu646
3
这个链接说明只有一些语言支持多字节字符串,例如日语或中文。通常 ANSI 是每个字符一个字节。Unicode 字符集更宽,它们用多个字节对每种语言的每个字符进行编码。UTF8 可以每个字符使用 1 到 4 个字节不等。 - Adam Jachocki
1
使用CharSet.Unicodewchar_t配合wchar_t * 转换成字符串,现在已经可以工作了,谢谢 ;) - Mathieu Gauquelin
显示剩余6条评论

1
似乎您正在将从托管代码传递的字符串存储在非托管类的成员字段中。这样做是行不通的,因为垃圾回收器会在某个时刻移动或处理托管字符串,这将使得非托管端上的字符串无效。如果您想要保留该字符串以备后用,必须在非托管端上复制它(分配在非托管堆上)。
CustomData::CustomData(char *filename)
{
  // _filename will need to be freed at some point; might
  // want to think about using std::string instead
  // like _filename = new std::string (filename);
  _filename = strdup(filename);
}

现在,非托管代码有它自己的(非托管的)字符串副本,因此当GC处理托管字符串时,这并不重要。如果您还编写了非托管代码,则这是处理情况的最简单方法。还有其他措施可以防止GC干扰非托管交互操作,但这些措施很棘手且耗时,并且仅在您无法修改非托管代码时才是必需的。

这比你第一段描述的还要糟糕,因为用于交互操作的缓冲区可以在关联的托管字符串之前被垃圾回收。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接