返回 char * 的 C 函数的 PInvoke

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/370079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 00:49:40  来源:igfitidea点击:

PInvoke for C function that returns char *

c#pinvoke

提问by Brandon

I'm trying to write some C# code that calls a method from an unmanaged DLL. The prototype for the function in the dll is:

我正在尝试编写一些从非托管 DLL 调用方法的 C# 代码。dll中函数的原型为:

extern "C" __declspec(dllexport) char *foo(void);

In C#, I first used:

在 C# 中,我首先使用:

[DllImport(_dllLocation)]
public static extern string foo();

It seems to work on the surface, but I'm getting memory corruption errors during runtime. I think I'm pointing to memory that happens to be correct, but has already been freed.

它似乎在表面上工作,但我在运行时遇到内存损坏错误。我想我指的是恰好是正确的记忆,但已经被释放。

I tried using a PInvoke code gen utility called "P/Invoke Interop Assistant". It gave me the output:

我尝试使用名为“P/Invoke Interop Assistant”的 PInvoke 代码生成实用程序。它给了我输出:

[System.Runtime.InteropServices.DLLImportAttribute(_dllLocation, EntryPoint = "foo")]
public static extern System.IntPtr foo();

Is this correct? If so, how do I convert this IntPtr to a string in C#?

这样对吗?如果是这样,我如何将此 IntPtr 转换为 C# 中的字符串?

采纳答案by JaredPar

You must return this as an IntPtr. Returning a System.String type from a PInvoke function requires great care. The CLR must transfer the memory from the native representation into the managed one. This is an easy and predictable operation.

您必须将其作为 IntPtr 返回。从 PInvoke 函数返回 System.String 类型需要非常小心。CLR 必须将内存从本机表示转移到托管表示中。这是一个简单且可预测的操作。

The problem though comes with what to do with the native memory that was returned from foo(). The CLR assumes the following two items about a PInvoke function which directly returns the string type

但问题在于如何处理从 foo() 返回的本机内存。CLR 假设以下两项关于直接返回字符串类型的 PInvoke 函数

  1. The native memory needs to be freed
  2. The native memory was allocated with CoTaskMemAlloc
  1. 需要释放本机内存
  2. 本机内存是用 CoTaskMemAlloc 分配的

Therefore it will marshal the string and then call CoTaskMemFree on the native memory blob. Unless you actually allocated this memory with CoTaskMemAlloc this will at best cause a crash in your application.

因此,它将封送字符串,然后在本机内存 blob 上调用 CoTaskMemFree。除非您实际使用 CoTaskMemAlloc 分配了此内存,否则这充其量只会导致您的应用程序崩溃。

In order to get the correct semantics here you must return an IntPtr directly. Then use Marshal.PtrToString* in order to get to a managed String value. You may still need to free the native memory but that will dependent upon the implementation of foo.

为了在此处获得正确的语义,您必须直接返回 IntPtr。然后使用 Marshal.PtrToString* 以获得托管字符串值。您可能仍然需要释放本机内存,但这将取决于 foo 的实现。

回答by Strelok

You can use the Marshal.PtrToStringAuto method.

您可以使用 Marshal.PtrToStringAuto 方法。

IntPtr ptr = foo();
string str = Marshal.PtrToStringAuto(ptr);

回答by Shpand

Current answers are not complete so I just wanted to post full solution in one place. Returning IntPtr instead of string doesn't solve any problem at all as you still have to free native memory allocated in C script. The best possible solution is to allocate bytes buffer on managed side and pass the memory to C script that will write string into that buffer not allocating memory at all.

当前的答案并不完整,所以我只想在一个地方发布完整的解决方案。返回 IntPtr 而不是 string 根本不能解决任何问题,因为您仍然必须释放在 C 脚本中分配的本机内存。最好的解决方案是在托管端分配字节缓冲区并将内存传递给 C 脚本,该脚本将字符串写入该缓冲区,根本不分配内存。

Returning a string from C is exactly like this:

从 C 返回一个字符串就像这样:

//extern "C" __declspec(dllexport) uint32_t foo(/*[out]*/ char* lpBuffer, /*[in]*/ uint32_t uSize)
uint32_t __stdcall foo(/*[out]*/ char* lpBuffer, /*[in]*/ uint32_t uSize)
{
  const char szReturnString[] = "Hello World";
  const uint32_t uiStringLength = strlen(szReturnString);

  if (uSize >= (uiStringLength + 1))
  {
    strcpy(lpBuffer, szReturnString);
    // Return the number of characters copied.
    return uiStringLength;
  }
  else
  {
    // Return the required size
    // (including the terminating NULL character).
    return uiStringLength + 1;
  }
}

C# code:

C#代码:

[DllImport(_dllLocation, CallingConvention=CallingConvention.StdCall, CharSet=CharSet.Ansi)]
private static extern uint foo(IntPtr lpBuffer, uint uiSize);

private static string foo()
{
    // First allocate a buffer of 1 byte.
    IntPtr lpBuffer = Marshal.AllocHGlobal(1);
    // Call the API. If the size of the buffer
    // is insufficient, the return value in
    // uiRequiredSize will indicate the required
    // size.
    uint uiRequiredSize = GetString(lpBuffer, 1);

    if (uiRequiredSize > 1)
    {
        // The buffer pointed to by lpBuffer needs to be of a
        // greater size than the current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        lpBuffer = Marshal.ReAllocHGlobal(lpBuffer, (IntPtr)uiRequiredSize);
        // Call the API again.
        GetString(lpBuffer, uiRequiredSize);
    }

    // Convert the characters inside the buffer
    // into a managed string.
    string str = Marshal.PtrToStringAnsi(lpBuffer);

    // Free the buffer.
    Marshal.FreeHGlobal(lpBuffer);
    lpBuffer = IntPtr.Zero;

    // Display the string.
    Console.WriteLine("GetString return string : [" + str + "]");

    return str;
}

There is simpler way to manage memory allocation/deallocation on C# side using StringBuilder:

使用 StringBuilder 在 C# 端管理内存分配/释放有更简单的方法:

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
private static extern uint foo(StringBuilder lpBuffer, UInt32 uiSize);

private static string foo()
{
    StringBuilder sbBuffer = new StringBuilder(1);
    uint uiRequiredSize = TestAPIAnsiUsingStringBuilder(sbBuffer, (uint)sbBuffer.Capacity);

    if (uiRequiredSize > sbBuffer.Capacity)
    {
        // sbBuffer needs to be of a greater size than current capacity.
        // This required size is the returned value in "uiRequiredSize"
        // (including the terminating NULL character).
        sbBuffer.Capacity = (int)uiRequiredSize;
        // Call the API again.
        foo(sbBuffer, (uint)sbBuffer.Capacity);
    }

    return sbBuffer.ToString();
}

There's a good topicexplaining different ways of returning string from C/C++ code.

有一个很好的主题解释了从 C/C++ 代码返回字符串的不同方法。