C# 使用P/Invoke获取字符串

C# 使用P/Invoke获取字符串,c#,c,pinvoke,C#,C,Pinvoke,我有一个由两个项目组成的解决方案:一个C#控制台应用程序和一个C库。C库有一个返回HRESULT的函数。我需要以某种方式更改此函数,使其向我的C#代码返回字符串。它应该是这样的: C#: C: 我可以改变这两个项目。然而,没有办法知道绳子会有多大。因此,我尝试在C库中分配字符串,但这会导致访问冲突异常和各种问题。解决这个问题的最佳方法是什么?您是否尝试过string str=marshall.PtrToStringAuto((IntPtr)MyFunction(…)?首先,您不能在C中分配字符串

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

C#:

C:


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

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

首先,您不能在C中分配字符串,因为string-是对类的引用,它过去是由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字符串类型可以被封送为某种字符串指针类型,这就应该可以工作。根据您的实现,您可能需要使用
[marshallas(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);

哇!差不多三年了,这个问题没有一个正确的答案

至少根据我的经验,从非托管类传输字符串的正确方法是将
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;
}

实际上我不确定,但是“System.Runtime.InteropServices.Marshal”类应该包含您需要的内容。我尝试过类似的方法,其中有一个IntPtr而不是out字符串,但它不起作用。我可以更改函数的参数,但不能更改其返回类型。问题不在于C#,而在于C库。为什么库要求写入缓冲区而不知道其长度。。。在这种情况下,返回字符串的传统方法是使用
StringBuilder
,但事先不知道要分配的字节数,并且由于函数不要求缓冲区的原始长度,这将导致缓冲区溢出或访问冲突。调用约定不匹配:cdecl与stdcall。在我看来,这是正确的,除了SetLastError应该为false,字符集应该为Unicode(但应该没有区别)。我怀疑问题在别处。尝试创建一个不带参数的最小示例。注意:可能需要指定
ref
,而不是
out
out
不会将指针
*text
初始化为NULL,因此如果本机函数调用
SysFreeString
释放内容,则会导致访问冲突。不过,正确的答案是在调试器中运行它,并找出访问冲突发生的位置,在这一点上,所有可能都会被照亮……我自己也想到了这一点,但我只会将其作为最后的手段。这可能行得通,但这不是最好的办法。但这就是本机WinAPI中内存分配的设计方式。你几乎找不到比这更好的东西(当然,我假设你不想在你的C项目中添加一些令人毛骨悚然的COM内容)。此外,我经常在我的c#项目中使用本机调用,我对此一无所知。它不是很好,但是非常稳定。是的,但是我事先不知道绳子的大小。在我确定之前,必须先执行该函数。正如其他人所说,在使用StringBuilder时存在缓冲区溢出的风险。
[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);
// 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;
}