【问题标题】:Marshalling char* C function to C#将 char* C 函数编组为 C#
【发布时间】:2021-08-20 10:29:53
【问题描述】:

我试图了解如何通过在托管和非托管代码之间来回传递和修改字符串来编组 char* 类型。托管到非托管代码似乎可以正常工作,但反之则不行。 IntPtr 适合这种情况吗?

C

EXPORT char* CharTest(char* ptchar, unsigned char* ptuchar)
{
    ptchar[0] = 'x';
    ptchar[1] = 'y';
    printf("%s    %s\n", ptchar, ptuchar);
    return(ptchar);
}

C#

[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest(string ptchar, string ptuchar);

static void Main()
{
    string ptchar = "ptchar";
    string ptuchar = "ptuchar";

    Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptchar, ptuchar)));
}

输出

xychar    ptuchar
x?J

谢谢!

【问题讨论】:

  • 这里发生了一大堆关于谁拥有哪块内存的问题。编组器正在为ptcharptuchar 分配新的char* 缓冲区,并将您的字符串复制到其中。它在本机调用返回后立即释放这些缓冲区。但是,您随后会从您的函数中返回其中一个缓冲区——但它刚刚被释放!
  • 我的第一反应是将 C# 中的方法声明为 extern static string CharTest(...); 并让互操作层处理所有转换。你试过了吗?
  • 这并不能解决问题,因为你搞砸了谁拥有什么。如果你没有遵循 marshaller 默认为你提供的所有权语义,你真的需要回退到传递 IntPtr 并自己进行内存管理
  • 先生们您好,谢谢您的回答,@Heinzi 声明为字符串而不是 IntPtr 似乎已经解决了这个问题,我对 char* 和 unsigned char* “转换”感到有点不安作为一个字符串。我不应该在函数声明中添加额外的编组信息,例如 In Out 或 MarshalAs 吗?
  • @canton7 可能是错误的,但如果 C 的返回类型是 string,编组器不会在对参数缓冲区执行任何 free 之前复制字符串吗?

标签: c# c marshalling unmanaged managed


【解决方案1】:

您可以将导入函数的返回类型声明为string

[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static string CharTest(string ptchar, string ptuchar);

但由于您实际上是在返回参数之一,因此您必须依靠编组器在复制返回缓冲区之前不释放参数缓冲区。

您还有两个选择:

  • 自己编组。确保将其放在try/finally 中以防万一
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest(IntPtr ptchar, string ptuchar);

static void Main()
{
    string ptchar = "ptchar";
    string ptuchar = "ptuchar";
    IntPtr ptcharPtr = IntPtr.Zero;

    try
    {
        ptcharPtr = Marshal.StringToHGlobalAnsi(ptchar);
        Console.WriteLine(Marshal.PtrToStringAnsi(CharTest(ptcharPtr, ptuchar)));
    }
    finally
    {
        Marshal.FreeHGlobal(ptcharPtr);
    }
}
  • 将参数声明为StringBuilder,这意味着它将被双向复制。在这种情况下,您无需查看返回值,因为它与参数相同。
[DllImport("Sandbox.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
extern static IntPtr CharTest([In, Out] StringBuilder ptchar, string ptuchar);

static void Main()
{
    StringBuilder ptchar = new StringBuilder("ptchar");
    string ptuchar = "ptuchar";

    CharTest(ptchar, ptuchar);
    Console.WriteLine(ptchar);
}

【讨论】:

  • 我很确定您的第二个解决方案需要将 C 参数声明为 char** ptchar...
  • @Heinzi Hum 你可能是对的。我认为StringBuilder 应该可以工作,但已修改
猜你喜欢
  • 2013-09-13
  • 1970-01-01
  • 1970-01-01
  • 2015-03-03
  • 2017-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-07
相关资源
最近更新 更多