使用P / Invoke获取字符串

时间:2011-11-14 18:59:24

标签: c# c pinvoke

我有一个由两个项目组成的解决方案:一个C#控制台应用程序和一个C库。 C库有一个返回HRESULT的函数。我需要以某种方式更改此函数,以使其返回一个字符串到我的C#代码。这应该是它的样子:

C#:

[DllImport("MyLib.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern long MyFunction(bunch of params, [MarshalAs(UnmanagedType.BStr)] out string text);

C:

extern "C" HRESULT __declspec(dllexport) MyFunction(bunch of params, BSTR* text)
{
PWSTR finalResult;
//lots of code

(*text) = SysAllocString(finalResult);
//cleanup code
}

我可以改变这两个项目。但是,没有办法知道字符串有多大。因此,我尝试在C lib中分配字符串,但这会导致访问冲突异常和各种问题。解决这个问题的最佳方法是什么?

5 个答案:

答案 0 :(得分:0)

您是否尝试过string str = Marshal.PtrToStringAuto((IntPtr)MyFunction(...))

答案 1 :(得分:0)

首先,你不能在C中分配字符串,因为string - 是对类的引用,它曾经由GC管理。 即使你编组它并在托管内存中创建副本,你也需要在之后释放非托管内存,否则会导致内存泄漏。

或者,您可以在C中创建第二个例程,它将为您提供字符串长度。然后你可以使用例如char数组。在c#中分配数组并赋予其设置结果的功能。

public static extern long MyFunctionCalcLength(bunch of params, [OUT]int textLength);
public static extern long MyFunction(bunch of params, [OUT]Char[] text);

答案 2 :(得分:0)

类似的东西:

public static extern long MyFunction(bunch of params, StringBuilder text); 

只要C字符串类型可以作为某种字符串指针类型进行编组,这应该可行。您可能需要使用[MarshalAs(UnmanagedType.LPWStr)],具体取决于您的实施。 [out]只应在非托管代码分配内存空间的情况下使用。

无论如何,您永远不应该从您计划返回托管空间的非托管库中分配内存;如你所见,这是一个很大的禁忌。

请注意,如果使用StringBuilder,则必须在构造函数中预先分配一定量的最大空间。

答案 3 :(得分:0)

您需要手动封送BSTR数据。尝试以下内容:

[DllImport("MyLib.dll", SetLastError = true]
public static extern long MyFunction(bunch of params, out IntPtr text);

//Create location for BSTR to be placed (BSTR is a pointer not a buffer).
IntPtr pBSTR;

//Call function and check for error.
if(MyFunction(bunch of params, out pBSTR) != S_OK)
{
    //Handle error.
}

//Retrieve BSTR data.
string data = Marshal.PtrToStringBSTR(pBSTR);

//Free the memory allocated in the unmanaged function.
Marshal.FreeBSTR(pBSTR);

答案 4 :(得分:0)

哇!差不多3年了,这个问题还没有得到正确答案!

从非托管中传输字符串的正确方法应该是,恕我直言,将StringBuilder类与表示“缓冲区”大小的附加参数结合起来。

这样的事情:

// C#

[DllImport("MyLib.dll",
    SetLastError = true,
    CharSet = CharSet.Auto,
    CallingConvention = CallingConvention.Cdecl)]
public static extern bool MyFunction(
    bunch of params,
    StringBuilder buffer,
    [MarshalAs(UnmanagedType.U4)] int bufferSize
);

// C:

extern "C" __declspec(dllexport) BOOL MyFunction(bunch of params, LPTSTR* text, unsigned int textSize)
{
    //lots of code

    if ( textSize < requiredSize) {
        SetLastError(ERROR_INSUFFICIENT_BUFFER)
        return FALSE;
    }

    return TRUE;
}

并以这种方式使用它:

StringBuilder sb = new StringBuilder(128);
while (!NativeMethods.MyFunction(bunch of params, sb, sb.Capacity))
{
    if (Marshal.GetLastWin32Error() != 0x7A) {
        // throw 
    }

    // Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER
    sb.Capacity *= 2;
}