Search code examples
c#c++pointersdlldllimport

Passing char * parameter to C++ dll function from C# GUI


I have a cpp function as DLL file to read a file from certain file path and gives back "0" if success and other number if something failed:

short __stdcall ReadPx(char *filePath, MAP *map, int *num);

This function is defined in my C# as:

[DllImport("lib.dll", EntryPoint = "ReadPx", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern System.Int16 ReadPx([MarshalAs(UnmanagedType.LPStr)] string filePath, ref MAP Map, ref Int32 numE);

And it is called in the main function as:

var pix = new MAP();
int num = 1;
string path = "C:/Users/Visual Studio 2015/Projects/testWrapper2/Map\0";
System.Int16 Output = ReadPx(path, ref pix, ref num);
Console.WriteLine(Output);

The function runs fine but gives an invalid file path error. I think the problem might be that in the C# code defines “String filePath” as Unicode (2 bytes per character), whereas the ReadPx expects a pointer to a simple ASCII string. That is why I tried some modifications shown below, but the file path error still there.

[DllImport("lib.dll", EntryPoint = "ReadPx", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern System.Int16 ScReadPixMap(IntPtr filePath, ref PIXMAPENTRY pixMap, ref Int32 numEntries);

IntPtr ptrCString = (IntPtr)Marshal.StringToHGlobalAnsi(path);
System.Int16 output = ReadPx(ptrCString, ref pix, ref num);
Marshal.FreeHGlobal(ptrCString);

Some thoughts and suggestions are appreciated. Thank you.


Solution

  • The C++ declaration is:

    short __stdcall ReadPx(char *filePath, MAP *map, int *num);
    

    Your C# declaration is:

    [DllImport("lib.dll", EntryPoint = "ReadPx", 
        CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public static extern System.Int16 ReadPx(
        [MarshalAs(UnmanagedType.LPStr)] string filePath, 
        ref MAP Map, 
        ref Int32 numE
    );
    

    Given the information that we have available, there are no mistakes in this. Note however that we do not know how MAP is declared and it is possible that there are mistakes there. And it is, I suppose, possible that the second and third arguments could be arrays. Only you can know those details.

    The p/invoke declaration is more verbose than necessary. Were it me I would write it like so:

    [DllImport("lib.dll")]
    public static extern short ReadPx(string filePath, ref MAP Map, ref int numE);
    

    In your question you go on to state that you call the function like this:

    string path = "C:/Users/Visual Studio 2015/Projects/testWrapper2/Map\0";
    System.Int16 Output = ReadPx(ref path, ref pix, ref num);
    

    The explicit null-terminator that you added is pointless. The framework ensures that the marshalled string has a null-terminator. It doesn't hurt that you add one explicitly, but it serves no purpose. Remove it.

    A bigger problem is how you pass the string parameter. The code says:

    ref path
    

    Well, that will not compile because your p/invoke declares the first parameter as a value parameter, as indeed it should.

    Clearly then the information presented in the question is erroneous. Perhaps your p/invoke declaration mistakenly declares the first parameter as a ref parameter, in contradiction to what is stated in your question.

    In summary, given the information that we have, the p/invoke declaration that you provide in the question is accurate. Your actual code is not using that declaration, and instead is using an erroneous declaration.