【问题标题】:How to marshal to ANSI string via attribute?如何通过属性编组为 ANSI 字符串?
【发布时间】:2013-07-16 03:39:05
【问题描述】:

这行得通:

[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,那么有什么关系呢?


正如 Daniel pointed out,它可能崩溃了,因为编组器正在尝试释放内存。文章还指出,

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

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

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

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

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

【问题讨论】:

  • 是的,如果是静态的,那么内存一定不能被释放。所以唯一的方法是手动编组,否则 CLR 编组器将释放内存。

标签: c# c marshalling


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    const char *
    SDL_GetError(void)
    {
        static char errmsg[SDL_ERRBUFIZE];
    
        return SDL_GetErrorMsg(errmsg, SDL_ERRBUFIZE);
    }
    

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

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

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

    【讨论】:

      【解决方案3】:

      使用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;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-09
        • 1970-01-01
        • 2013-03-11
        相关资源
        最近更新 更多