代码之家  ›  专栏  ›  技术社区  ›  Brandon

返回char的C函数的PInvoke*

  •  54
  • Brandon  · 技术社区  · 17 年前

    我正在尝试编写一些从非托管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 回复  |  直到 17 年前
        1
  •  83
  •   Top-Master OMG Ponies    5 年前

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

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

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

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

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

        2
  •  30
  •   Strelok    17 年前

    你可以使用元帅。PtrToStringAuto方法。

    IntPtr ptr = foo();
    string str = Marshal.PtrToStringAuto(ptr);
    
        3
  •  1
  •   Shpand    5 年前

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

    从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#代码:

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

    使用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 = foo(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();
    }
    

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