使用平台Invoke (c#)在非托管代码中分配和释放内存

本文关键字:非托管代码 分配 内存 释放 平台 Invoke | 更新日期: 2023-09-27 18:07:20

我想在非托管代码(c++)中分配和释放内存,我们称它们为托管代码(c#)中的函数。我不确定以下代码是否没有内存泄漏?

c#代码:

[DllImport("SampleDLL.dll")]
public extern void getString([MarshalAs(UnmanagedType.LPStr)] out String strbuilder);
[DllImport("SampleDLL.dll")]
public extern void freeMemory([MarshalAs(UnmanagedType.LPStr)] out String strBuilder);
....
//call to unmanaged code
getString(out str);
Console.WriteLine(str);
freeMemory(out str);

c++代码:

extern void _cdecl getString(char **str)
{
    *str = new char[20];
    std::string temp = "Hello world";
    strncpy(*str,temp.c_str(),temp.length()+1);
}
extern void _cdecl freeMemory(char **str)
{
    if(*str)
        delete []*str;
    *str=NULL;
}

使用平台Invoke (c#)在非托管代码中分配和释放内存

不行,这行不通。pinvoke编组程序将尝试使用CoTaskMemFree()释放字符串的内存。否则它不知道你有一个释放函数。那不会很好地工作,你没有用CoTaskMemAlloc分配字符串。这会在XP中造成无声的内存泄漏,在Vista和更高版本中造成崩溃。

您必须阻止编组程序尝试做正确的工作:

[DllImport("SampleDLL.dll")]
public extern void getString(out IntPtr strptr);
[DllImport("SampleDLL.dll")]
public extern void freeMemory(IntPtr strptr);

然后需要c#代码中的marshal . ptrtostringansi()自己从返回的指针中封送字符串。

我个人认为使用BSTR最容易做到这一点,从而避免了导出deallocator的需要。

c++

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 __stdcall getString()
{
    return ANSItoBSTR("Hello world");
}

当然,如果你使用的是Unicode字符串,那就更容易了。

BSTR __stdcall getString()
{
    return ::SysAllocString(L"Hello world");
}
c#

[DllImport(@"test.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string getString();

在c#方面就是这样。你只需调用getString(),它返回一个。net string,你不需要编组任何东西或调用deallocator