5

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?

4
  • The problem lies not with C#, but with the C library. Why would the library ask for a buffer to write to without knowing its length... The traditional way of returning a string in this context is to use a StringBuilder, but without knowing the number of bytes to allocate in advance and since the function does not ask for the buffer's original length, this will lead to buffer overflows or access violations. Commented Nov 14, 2011 at 19:12
  • Calling conventions don't match: cdecl vs stdcall. Commented Nov 14, 2011 at 21:58
  • That looks right to me, except SetLastError should be false and CharSet should be Unicode (should make no difference though). I suspect the problem lies elsewhere. Try creating a minimal example which takes no parameters. Commented Jan 23, 2012 at 15:01
  • NOTE: It may be that you need to specify ref instead of out. out does not initialise the pointer *text to NULL, so if the native function is calling SysFreeString to free the contents, it will cause an access violation. The proper answer though is to run it up in the debugger and find out where the access violation occurs, at which point all will probably be illuminated... Commented Jan 23, 2012 at 15:13

5 Answers 5

2

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; } 
Sign up to request clarification or add additional context in comments.

Comments

0

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

2 Comments

I'm actually not sure, but 'System.Runtime.InteropServices.Marshal' class should contain what you need
I've tried something similar to this where instead of an out string I had an IntPtr but it didn't work. I can change the function's params but not its return type.
0

At first you can't allocate string in C, because string - is reference to the class and it used to be managed by GC. Even if you marshal it and create copy in managed memory you will need to free unmanaged memory after or it will leads to memory leaks.

Alternatively you can create second routine in C, which will give you a string length. And then you may use for example a char array. Allocate array in c# and give it to function for setting result.

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

2 Comments

I thought of this myself but I would only use it as a last resort. It would probably work but it's not the nicest way.
Sure. But that is how memory allocation designed in native WinAPI. And you hardly find anything significantly better (Of course I assume that you don't want to add some creepy COM stuff in your C project). Also I frequently use native calls in my c# projects and I don't know nothing better. It's not very nice, but it extremely stable.
0

Something like:

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

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

At any rate, you should never allocate memory from your unmanaged library that you plan to return into managed space; as you have seen, that is a big no-no.

Note that if you use StringBuilder, you must preallocate some amount of maximum space in the constructor.

1 Comment

Yeah, however I don't know the size of the string in advance. The function must execute first before I can determine that. Like the others said, there's a risk of buffer overflows in using StringBuilder.
0

You need to manually marshal the BSTR data across. Try something like 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); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.