7

这有效:

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetError")]
private static extern IntPtr SDL_GetError();

public static string GetError()
{
    return Marshal.PtrToStringAnsi(SDL_GetError());
}

这崩溃了:

[DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetError")]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern string GetError();

这篇文章建议 return 属性本质上就像 call 一样Marshal.PtrToStringAnsi,那么有什么关系呢?


正如 Daniel指出的那样,它可能会崩溃,因为编组器正在尝试释放内存。文章还指出,

注意:请注意,非托管端不得使用“new”关键字或“malloc()”C 函数来分配内存。在这些情况下,Interop Marshaler 将无法释放内存。这是因为“new”关键字依赖于编译器,而“malloc”函数依赖于 C 库。

我试过用 , 释放 char 指针Marshal.FreeHGlobalMarshal.FreeCoTaskMem而且Marshal.FreeBSTR——它们都崩溃了。没有任何其他方法可以释放内存 AFAIK,所以我猜内存是通过newor分配的malloc()。那现在怎么办,我被圈套了?我的程序中有永久性内存泄漏?

我检查了来源。该字符串是通过创建的static char errmsg[SDL_ERRBUFIZE]。我的 C 生锈了,但我猜它被声明为static当它超出函数范围时不会被释放。我不记得静态数组在内存中的位置;有什么方法可以释放它们吗?

编辑:等等......它是静态的。这意味着每次出现新错误时,它都会覆盖旧的错误消息,因此为什么SDL_GetError()只返回最新的错误消息。因此,我不必担心释放它。

因此,如果所有return: MarshalAs...选项都试图释放内存,那么唯一的解决方案就是我目前的解决方案。毕竟这是最优的。

4

3 回答 3

4

如链接文章中所述,使用 时[return: MarshalAs(UnmanagedType.LPStr)],CLR 使用 释放本机字符串的内存FreeCoTaskMem()。如果您通过 手动创建托管字符串对象Marshal.PtrToStringAnsi(),则根本不会释放内存。

如果它崩溃,那么可能不是通过在非托管端创建字符串CoTaskMemAlloc(),而是通过 new() 或 malloc() (例如)。的 APISDL_GetError()应该说明释放本机字符串的工作是谁以及如何释放。

于 2013-07-16T05:06:03.300 回答
3

我做了一些挖掘。的来源SDL_GetError是:

const char *
SDL_GetError(void)
{
    static char errmsg[SDL_ERRBUFIZE];

    return SDL_GetErrorMsg(errmsg, SDL_ERRBUFIZE);
}

我们可以看到字符串的内存被分配为一个静态字符数组。每次SDL_GetError调用它都会被覆盖。因此,我们不能也不需要释放它。

由于这些[return: MarshalAs.*]方法都试图在编组类型后释放内存,因此它们将不起作用(并进一步导致程序崩溃)。

因此,您(我的)原始解决方案是最佳的。

于 2013-07-16T06:27:25.980 回答
1

另一种使用ICustomMarshaler. 用法:

[DllImport(OpenAL.Name, EntryPoint = "alGetString")]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(OpenALStringMarshaler))]
public static extern string GetString(int name);

internal class OpenALStringMarshaler : ICustomMarshaler
{
    #region ICustomMarshaler Members

    public void CleanUpManagedData(object ManagedObj) { }

    public void CleanUpNativeData(IntPtr pNativeData) { }

    public int GetNativeDataSize()
        => -1;

    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        throw new NotSupportedException();
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
        => Marshal.PtrToStringAnsi(pNativeData);

    #endregion

    public static ICustomMarshaler GetInstance(string cookie)
    {
        if (cookie == null)
        {
            throw new ArgumentNullException(nameof(cookie));
        }

        var result = new OpenALStringMarshaler();

        return result;
    }
}
于 2019-03-10T16:08:31.953 回答