Using P / Invoke to Retrieve a String

I have a solution consisting of two projects: a C # console application and a C library. The C library has a function that returns HRESULT. I need to somehow change this function in order to return it to C # code. Here's how it should look:

WITH#:

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

WITH

 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 is no way to know how large the string will be. So I tried to highlight a line in C lib, but this led to access to exceptions and various problems. What would be the best way to handle this?

+5
source share
5 answers

Blimey! almost 3 years and there is no right answer to this question yet!

The right way to pass strings from an unmanaged one is to, at least in my experience, combine the StringBuilder class with an extra parameter representing the size of the "buffer".

Something like that:

 // 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) { //lots of code if ( textSize < requiredSize) { SetLastError(ERROR_INSUFFICIENT_BUFFER) return FALSE; } return TRUE; } 

And use it like this:

 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; } 
+1
source

Have you tried string str = Marshal.PtrToStringAuto((IntPtr)MyFunction(...)) ?

0
source

At first, you cannot select a string in C, because a string is a reference to a class, and it was controlled by GC. Even if you marshal it and create a copy in managed memory, you will need to free up unmanaged memory after it causes a memory leak.

Alternatively, you can create a second subroutine in C that will give you the length of the string. And then you can use, for example, a char array. Select an array in C # and give it a function to set the result.

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

Sort of:

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

As long as the string type C can be defined as the type of the string pointer, this should work. You may need to use [MarshalAs(UnmanagedType.LPWStr)] depending on your implementation. [out] should only be used in situations where unmanaged code allocates memory space.

In any case, you should never allocate memory from your unmanaged library that you plan to return to managed space; as you saw, this is a big no no.

Note that if you use StringBuilder , you must first allocate a certain amount of maximum space in the constructor.

0
source

You need to manually march BSTR data. Try the following:

 [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); 
0
source

Source: https://habr.com/ru/post/1381182/


All Articles