pinvoke:如何释放malloc分配的字符串?

9
在一个C的动态链接库中,我有一个像这样的函数:
char* GetSomeText(char* szInputText)
{
      char* ptrReturnValue = (char*) malloc(strlen(szInputText) * 1000); // Actually done after parsemarkup with the proper length
      init_parser(); // Allocates an internal processing buffer for ParseMarkup result, which I need to copy
      sprintf(ptrReturnValue, "%s", ParseMarkup(szInputText) );
      terminate_parser(); // Frees the internal processing buffer
      return ptrReturnValue;
}

我希望您使用P/Invoke从C#中调用它。
[DllImport("MyDll.dll")]
private static extern string GetSomeText(string strInput);

如何正确释放已分配的内存?

我正在编写跨平台代码,目标是Windows和Linux。

编辑: 像这样

[DllImport("MyDll.dll")]
private static extern System.IntPtr GetSomeText(string strInput);

[DllImport("MyDll.dll")]
private static extern void FreePointer(System.IntPtr ptrInput);

IntPtr ptr = GetSomeText("SomeText");
string result = Marshal.PtrToStringAuto(ptr);
FreePointer(ptr);

有趣的是,C#会尝试使用CoTaskMemFree来释放它 xD - Armen Tsirunyan
1
@Armen 那么你只需要使用 CoTaskMemAlloc() 分配它,正确的事情就会发生? - David Heffernan
请不要强制转换malloc()的返回值。 - Pete Wilson
@David Heffernan:不,我正在使用gcc编译C,而不是C ++。但是,你是对的,我添加了char *的强制转换,这样它也可以在g ++编译器上编译。 - Stefan Steiger
@davidheffernan:最终,这是否重要?这是有效的C和有效的C++。只有C++需要在接口函数中使用extern "C"。 - Stefan Steiger
3个回答

7
您应该将返回的字符串作为IntPtr进行编排,否则CLR可能会使用错误的分配器释放内存,从而可能导致堆损坏和各种问题。

请参见这个几乎(但不完全)重复的问题PInvoke for C function that returns char *.

理想情况下,您的C dll还应该公开一个FreeText函数供您在需要释放字符串时使用。这可以确保字符串以正确的方式被释放(即使C dll发生更改)。


我很担心这是正确的答案。我将把IntPtr转换为字符串,然后使用此IntPtr在libc.so/msvcr80.dll中调用pinvoke free函数,之后将IntPtr设置为IntPtr.Zero。嗯,你说得对,编写一个FreeText函数并进行pinvoke可能会更容易。否则,我必须编写一个跨平台的malloc包装器(根据操作系统在libc.so和msvcr80.dll之间切换),这将使我陷入msvcr80.dll的DLL版本地狱。 - Stefan Steiger
1
你说得很对,应该导出自己的 FreeMemory 函数。不要尝试直接链接到 C 运行时库,因为你可能得不到正确的运行时库。导出一个释放内存的函数可以保证你使用与分配内存时相同的运行时库。 - David Heffernan

1

如果您返回使用本地malloc分配的.net内存,则还必须导出解除分配器。我不认为这是一种理想的操作,而是更喜欢将文本导出为BSTR。这可以由C#运行时释放,因为它知道BSTR是由COM分配器分配的。 C#编码变得简单了很多。

唯一的问题是BSTR使用Unicode字符,而您的C++代码使用ANSI。我会这样解决:

C++

#include <comutil.h>
BSTR ANSItoBSTR(const char* input)
{
    BSTR result = NULL;
    int lenA = lstrlenA(input);
    int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
    if (lenW > 0)
    {
        result = ::SysAllocStringLen(0, lenW);
        ::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
    } 
    return result;
}

BSTR GetSomeText(char* szInputText)
{
      return ANSItoBSTR(szInputText);
}

C#

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

我在Windows和Linux上都使用了共享对象。你的代码没问题,但它使用了WinAPI,在Windows上没问题,但在Linux上不行。此外,编译器会自动转换ASCII和Unicode字符集之间的转换。只要dll不支持Unicode,bstr也就没有意义了。 - Stefan Steiger
你真的应该一开始就告诉我们所有的信息。现在你告诉我们你正在使用Linux!显然这改变了事情。在Windows上,char*实际上是ANSI而不是ASCII。 - David Heffernan
抱歉,我同时使用Windows和Linux。不,ANSI仅适用于Windows应用程序。控制台应用程序(以前是DOS)是ASCII。 - Stefan Steiger
在.NET中,控制台应用程序无法与DOS通信。实际上,现在已经没有DOS了。Windows控制台应用程序是合适的Windows应用程序,而char是ANSI格式的。在Linux中,char可能是utf8格式的。无论如何,我现在理解了你的问题。下次提到Mono! - David Heffernan
char* 实际上就是一个二进制的8位缓冲区。ANSI、ASCII和Unicode 是文本输出例程解释这些缓冲区的方式。 - Петър Петров

1
添加另一个函数ReturnSomeText,调用free或其他必要的方法来释放内存。

“ReturnSomeText”函数难道不应该在与“GetSomeText”相同的dll中声明吗? - KevinDTimm
是的,那是一般的想法。使用“private static extern”的C#或其他语言需要另一个函数将内存返回到正确的运行时。 - Bo Persson
这样做可以完成工作,但是导出一个解分配器总让我感到不舒服! - David Heffernan

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