Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

marshal an unsigned char * returning function from a dll, in C#

I have the following function header in a native DLL:

unsigned char* Version_String()

I'm trying to call it from a C# project, I've tried the following call (as found on other similar questions here):

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
public extern static UIntPtr Version_String();

And I keep getting the following exception:

Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

The next try was the following and I get the same exception:

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
[return : MarshalAs(UnmanagedType.LPStr)]
public extern static string Version_String();

I can't seem to get around this issue. Any help would be greatly appreciated!

Edit:

I can't give the DLL code here, as it falls under an NDA, but the function I'm calling looks like this:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    if(check_hardware_stuff())
    {
      strcpy((char *) versionString, "version_string_bla_bla");
      versionString[5] = stuff;
    }
    else if (other_check())
    {
      //will return empty string, that should be filled with '\0'
    }
    else
    {
      strcpy( (char *) versionString, "ERROR" );
    }
  return versionString;
}

I'm not particularly fond of the DLL implementation, but I need to use it "as it is". I get the exception thrown whenever I try to call VersionString(), regardless of what I do with the return value.

like image 457
andreiscurei Avatar asked Jan 19 '12 12:01

andreiscurei


1 Answers

Update

Having seen the updated question, the various comments, and the code of your native function, it seems likely that the exception is raised when you call check_hardware_stuff(). It's simple enough to debug. I would replace your function with one like this:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    strcpy(versionString, "testing");
    return versionString;
}

If that still fails then my guess is that the error is raised in the DllMain of your DLL. Debug that by putting the above function into a plain vanilla DLL that does nothing else.

Original answer

Calling convention is the most obvious problem. Your native code most likely uses cdecl but the p/invoke default is stdcall. Change your p/invoke signature to be like this:

[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static IntPtr Version_String();

You can safely omit the CharSet parameter since none of the parameters have text because you are treating the return value as a pointer.

Edit: Hans correctly points out in the comments that since there are no parameters, the calling convention mis-match does not matter. So this isn't the problem.

Call Marshal.PtrToStringAnsi to convert to a .net string.

string version = Marshal.PtrToStringAnsi(Version_String());

Since PtrToStringAnsi is expecting an IntPtr parameter I would recommend that you use IntPtr as the return type of you p/invoke signature.

This all assumes that the memory returned from your native function is allocated and freed in the native DLL. If it is heap allocated and you expect the caller to deallocate it then you have a small problem. How do you deallocate the memory from C# since you don't have access to the native DLL's heap?

The simple solution is to use the shared COM heap to allocate the memory. Call CoTaskMemAlloc to allocate the buffer for the string. Then declare the return value to be of type string and the p/invoke marshaller will deallocate with the COM allocator.

[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static string Version_String();
...
string version = Version_String();

Of course, this only applies if you are returning heap allocated memory that you expect the caller to deallocate.

like image 52
David Heffernan Avatar answered Sep 28 '22 00:09

David Heffernan