繁体   English   中英

使用 P/Invoke 获取字符串

[英]Using P/Invoke to fetch a string

我有一个包含两个项目的解决方案:一个 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 库中分配字符串,但这会导致访问冲突异常和各种问题。 解决这个问题的最佳方法是什么?

哇! 快3年了,这个问题没有正确的答案!

至少根据我的经验,从非托管传输字符串的正确方法是将StringBuilder类与表示“缓冲区”大小的附加参数相结合。

像这样的东西:

// C#

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

// C:

extern "C" __declspec(dllexport) BOOL MyFunction(bunch of params, LPTSTR* text, unsigned int textSize)
{
    char *mySourceString = doSomethingAndReturnString(bunch of params);

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

    strncpy(text, mySourceString, strlen(mySourceString))

    return TRUE;
}

并以这种方式使用它:

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

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

你试过string str = Marshal.PtrToStringAuto((IntPtr)MyFunction(...))吗?

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

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

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

就像是:

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

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

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

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

您需要手动编组 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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM