如何通过属性编组到ANSI字符串?

时间:2013-07-16 03:39:06

标签: c# c marshalling

这有效:

[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();

This article表明return属性基本上就像调用Marshal.PtrToStringAnsi一样,那么交易是什么?


作为丹尼尔pointed out,它可能会崩溃,因为编组人员试图释放记忆。文章还指出,

  

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

我尝试使用Marshal.FreeHGlobalMarshal.FreeCoTaskMemMarshal.FreeBSTR释放字符指针 - 它们都崩溃了。没有任何其他方法可以释放内存AFAIK,因此我猜测内存是通过newmalloc()分配的。那么现在,我哼了什么?我的程序中有永久性内存泄漏?

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

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

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

3 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:3)

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

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

    return SDL_GetErrorMsg(errmsg, SDL_ERRBUFIZE);
}

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

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

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

答案 2 :(得分: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;
    }
}