【问题标题】:Marshalling a char** in C#在 C# 中编组一个 char**
【发布时间】:2011-01-04 16:15:40
【问题描述】:

我正在与采用char**(即指向字符串的指针)的代码交互:

int DoSomething(Whatever* handle, char** error);

基本上,它需要一个句柄来处理它的状态,如果出现问题,它会返回一个错误代码和可选的错误消息(内存是在外部分配的,并通过第二个函数释放。那部分我已经弄清楚了: ) )。

但是,我不确定如何在 C# 中处理。我目前拥有的:

[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)]
private static unsafe extern int DoSomething(IntPtr handle, byte** error);

public static unsafe int DoSomething(IntPtr handle, out string error) {
    byte* buff;

    int ret = DoSomething(handle, &buff);

    if(buff != 0) {
        // ???
    } else {
        error = "";
    }

    return ret;
}

我翻来覆去,不知道怎么把它变成byte[],适合喂给UTF8Encoding.UTF8.GetString()

我走对了吗?

编辑:更明确地说,库函数分配内存,必须通过调用另一个库函数来释放内存。如果一个解决方案没有给我留下一个我可以释放的指针,那么这个解决方案是不可接受的。

额外问题:如上所述,该库使用 UTF-8 作为其字符串。我需要在我的 P/Invokes 中做任何特别的事情,还是只使用 string 来处理普通的 const char* 参数?

【问题讨论】:

  • 你试过out StringBuilder吗?
  • 我添加了一个额外的编辑来澄清根本问题。

标签: c# c string pinvoke marshalling


【解决方案1】:

您应该能够使用ref string 并让运行时默认编组器为您处理此转换。您可以使用[MarshalAs(UnmanagedType.LPStr)] 提示参数上的字符宽度,以确保您使用的是 8 位字符。

由于您有一个特殊的释放方法要调用,因此您需要保留指针,就像您在问题示例中已经显示的那样。

我是这样写的:

[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)] 
private static unsafe extern int DoSomething(
    MySafeHandle handle, void** error); // byte** should work, too, I'm just lazy

然后你可以得到一个字符串:

var errorMsg = Marshal.PtrToStringAnsi(new IntPtr(*error));

和清理:

[DllImport("mydll.dll", CallingConvention = CallingConvention.Cdecl)] 
private static extern int FreeMyMemory(IntPtr h);

// ...

FreeMyMemory(new IntPtr(error));

现在我们有了编组错误,所以只需返回它。

return errorMsg;

还要注意 MySafeHandle 类型,它将继承自 System.Runtime.InteropServices.SafeHandle。虽然不是严格需要(您可以使用 IntPtr),但它在与本机代码互操作时为您提供了更好的句柄管理。在此处阅读:http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.aspx

【讨论】:

  • 如何处理内存?重申一下,DoSomething 将分配内存并将其存储在*error 中。我需要释放那段记忆……
  • 你应该知道它是如何分配的:全局堆还是本地?
  • 它是堆,但由库分配。我需要在mydll.dll 中调用FreeMyMemory 来释放它。我无法从 ref string 获取地址。
  • 另外,我知道一定有类似SafeHandle的东西!谢谢!
  • 啊,你需要调用一个特殊的 dealloc 方法——那么,默认的 marshaller 不会为你做这件事。您需要在第二个参数上传递一个指针。我会更新答案。
【解决方案2】:

作为参考,这里是编译的代码(但尚未测试,正在处理下一个测试,工作 100%)可以满足我的需要。如果有人可以做得更好,那就是我所追求的:D

public static unsafe int DoSomething(IntPtr handle, out string error) {
    byte* buff;

    int ret = DoSomething(handle, &buff);

    if(buff != null) {
        int i = 0;

        //count the number of bytes in the error message
        while (buff[++i] != 0) ;

        //allocate a managed array to store the data
        byte[] tmp = new byte[i];

        //(Marshal only works with IntPtrs)
        IntPtr errPtr = new IntPtr(buff);

        //copy the unmanaged array over
        Marshal.Copy(buff, tmp, 0, i);

        //get the string from the managed array
        error = UTF8Encoding.UTF8.GetString(buff);

        //free the unmanaged array
        //omitted, since it's not important

        //take a shot of whiskey
    } else {
        error = "";
    }

    return ret;
}

编辑:修复了 while 循环中的逻辑,它有一个错误。

【讨论】:

    猜你喜欢
    • 2012-01-23
    • 1970-01-01
    • 2012-02-14
    • 2011-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-13
    相关资源
    最近更新 更多