Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using P/Invoke to fetch a string

Tags:

c

c#

pinvoke

I have a solution which consists of two projects: a C# console app and a C library. The C library has a function which returns a HRESULT. I need to somehow change this function to get it to return a string to my C# code. This is how it should look:

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
}

I can change both projects. However, there's no way of knowing how big the string will be. Therefore, I've tried allocating the string in the C lib but this lead to access violation exceptions and all sorts of problems. What would be the best way to tackle this?

like image 696
Alex G. Avatar asked Nov 05 '22 11:11

Alex G.


1 Answers

Wow! almost 3 years and this question doesn't have a proper answer!

The correct way of transfer strings form the unmanaged is, at least on my experience, to combine the StringBuilder class with an additional parameter representing the size of the "buffer".

Something like this:

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

And use it this way:

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;
}
like image 114
JuanR Avatar answered Nov 14 '22 23:11

JuanR