PInvoke for C函数返回char *

时间:2008-12-15 23:42:07

标签: c# pinvoke

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

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

在C#中,我首先使用:

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

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

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

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

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

3 个答案:

答案 0 :(得分:73)

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

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

  1. 需要释放本机内存
  2. 使用CoTaskMemAlloc
  3. 分配本机内存

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

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

答案 1 :(得分:23)

您可以使用Marshal.PtrToStringAuto方法。

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

答案 2 :(得分:1)

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

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

$data1      = "W2020";
$data2      = "AB2020";
$data3      = "ZXCV2020";

print_r(splitString($data3));

function splitString($string){
    $alpha_string = '';
    $num_string = '';
    $array2 = str_split( $string );

    foreach($array2 as $value){
        if(is_numeric($value))
            $num_string .= $value;
         else
            $alpha_string .= $value;
    }
    $data_array = array($alpha_string, $num_string);

    return $data_array;
}

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

使用StringBuilder在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 = foo(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.
        foo(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;
}

good topic解释了从C / C ++代码返回字符串的不同方法。