C# 中的字符 * 编组
Posted
技术标签:
【中文标题】C# 中的字符 * 编组【英文标题】:Char * marshalling in C# 【发布时间】:2009-11-27 12:47:31 【问题描述】:我在 Visual C++ DLL 中有这个函数
char * __stdcall GetMessage(int id)
char buffer[250];
.
.
.
strcpy(buffer, entry); // entry is a char * that has allocated 250 chars
return (char *)buffer;
我正在尝试使用以下代码从 C# 导入此函数
[DllImport("mydll.dll", CharSet=CharSet.Ansi)]
public static extern string GetMessage(int id);
我试图在 MessageBox 中显示它,它的字符串末尾总是有奇怪的符号。有什么建议可以解决这个问题吗?
【问题讨论】:
我没有提到我调试的DLL char *buffer是一个正确的字符串,最后是0。 我不知道返回值是怎样的,但是对于参数,以下规则适用:对 const char* 使用字符串,对 char* 使用 StringBuilder。另一方面,您正在返回一个指向作为局部变量的缓冲区的指针。这听起来不是一个好主意。这个问题可能是你的朋友,但是:***.com/questions/370079/… @OreghonGost,不,规则是:使用string
用于您不变异的内容,并使用StringBuilder
用于您所做的事情。 const
给出了提示,但并非严格要求。
【参考方案1】:
我将再次强调您的 C++ 代码无效这一事实。您正在返回一个指向堆栈帧上的局部变量的指针,该指针在函数返回时不再有效。它现在起作用只是一个意外。一旦你调用另一个函数,堆栈空间将被重用,破坏缓冲区内容。当您从 C# 调用此代码时,肯定会发生这种情况,P/Invoke marshaller 实现将覆盖堆栈帧并破坏缓冲区。
更糟糕的是,P/Invoke 编组器会尝试释放缓冲区。它将假定缓冲区由 CoTaskMemAlloc() 分配并调用 CoTaskMemFree() 以释放缓冲区。这将在 Windows XP 及更早版本上静默失败,但在 Vista 及更高版本上会使您的程序崩溃。
这是您的问题的一种解决方案,使用 CoTaskMemAlloc() 分配缓冲区而不是使用局部变量。全方位更好的解决方案是让函数的调用者通过缓冲区,用字符串填充。这样,调用者可以决定如何分配缓冲区并在必要时清理它。函数签名应如下所示:
extern "C" __declspec(dllexport)
void __stdcall GetMessage(int id, char* buffer, size_t bufferSize);
使用 strcpy_s() 安全地填充缓冲区并避免任何缓冲区溢出。相应的 C# 声明将是:
[DllImport("mydll.dll")]
private static extern void GetMessage(int id, StringBuilder buffer, int bufferSize);
并像这样调用:
var sb = new StringBuilder(250);
GetMessage(id, sb, sb.Capacity);
string retval = sb.ToString();
【讨论】:
【参考方案2】:从 DLL 中,您返回一个字符数组,即在 GetMessage 函数内的堆栈上的缓冲区,当执行返回时,数据被弄乱了,因为缓冲区从堆栈中弹出并且在堆栈中是本地的此外,因此您看到的奇怪字符。有两种方法可以解决此问题,将缓冲区变量声明更改为指针并在返回之前对其进行 malloc 或将其声明为静态。
字符 * 缓冲区; if (!(buffer = (char *)malloc((250 * sizeof(char)) + 1)) // 处理内存不足的情况 // 现在缓冲区有 250 + 1 用于空终止字符。或者
静态字符缓冲区[250];您还可以澄清入口指针变量 - 是全局的吗?
希望这会有所帮助, 最好的祝福, 汤姆。
【讨论】:
【参考方案3】:尝试使用 StringBuilder 作为返回类型并省略 CharSet 属性:
[DllImport("mydll.dll")]
public static extern StringBuilder GetMessage(int id);
【讨论】:
以上是关于C# 中的字符 * 编组的主要内容,如果未能解决你的问题,请参考以下文章