1

I'm trying to open a char* that's being exported from a Dll in my C# project.

Since the codes are too much, I'll write a short code that is similar to the code that I'm working on.

C++:

__declspec(dllexport) typedef struct Kid {
    char* _name;
    int _age;
    int _grade;
} Kid;

Kid get_default_kid()
{
    char name[] = { "Quinn" };

    return Kid{
        name, 2,7
    };
}

extern "C" __declspec(dllexport) Kid get_default_kid();

C#:

public struct Kid
{
    public string _name;
    public int _age;
    public int _grade;
}

private const string Dll2 = @"C:\Users\Me\source\repos\Tom\x64\Release\JerryDll.dll";

[DllImport(Dll2, CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid();

public static void Main(string[] args){
    Kid defaultKid = get_default_kid();

    Console.WriteLine(defaultKid._name);
    Console.WriteLine(defaultKid._age);
    Console.WriteLine(defaultKid._grade);
}

This code writes some random characters like '♠' in the console instead of the name that is being exported from the dll.

What I've tried:
Trying to import the char* as IntPtr then read it using:

Marshal.PtrToStringAnsi()/BSTR/Auto/Uni

and Marshal.ReadIntPtr before trying to read it using the line above.

I tried converting the string to UTF-8 in C# since.

searching a lot in google.

  • In real world it is **very**, **very**, **very** complex. The problem is that "normally" the C-side string name would be allocated in some way (like _malloc_), so you would need a corresponding `FreeKid(Kid*)`... In this example `Quinn` is a C string literal, so you shouldn't ever ever free it... :-) – xanatos Dec 31 '20 at 17:36
  • 3
    `name` in `get_default_kid` is a local variable that will disappear when the function returns. – Manuel Dec 31 '20 at 17:37
  • And there is the problem of memory ownership: see https://limbioliong.wordpress.com/2011/06/16/returning-strings-from-a-c-api/ – xanatos Dec 31 '20 at 17:40
  • @Manuel so it means, that when the function returns the pointer to that array of characters(string), the array will be disappeared. hence when my C# project tries to dereference that pointer, there's nothing in there. I get it, thank you so much. – Amirreza Moeini Yegane Dec 31 '20 at 18:44
  • Manuel helped me to solve my problem. It'd be great if someone adds an answer to this question about how to import char* from dll in c# in general and best practices. So I and anyone that might need help, can read and learn. Thanks. – Amirreza Moeini Yegane Dec 31 '20 at 18:50
  • Does this answer your question? [Passing a return char\* from C++ to C# with DllImport](https://stackoverflow.com/questions/59358169/passing-a-return-char-from-c-to-c-sharp-with-dllimport) – GSerg Dec 31 '20 at 19:28

2 Answers2

1

In general when we speak of marshaling strings (or other allocated memory) between C/C++ and C# the most important and complex question is: how will the memory be freed? The simplest solution, if possible, is to export from C/C++ side one or more deallocator methods. Here I'm giving some examples:

Note that you can't

public struct Kid
{
    public string _name;
    public int _age;
    public int _grade;
}

You'll get an error. The .NET marshaler doesn't want to marshal non-blittable structs (so structs that have complex types like strings) that are return values. For this reason I use a IntPtr and then I marshal manually the char* to a string.

C++:

extern "C"
{
    const char* pstrInternal = "John";

    typedef struct Kid
    {
        char* _name;
        int _age;
        int _grade;
    };

    // WRONG IDEA!!! HOW WILL THE "USER" KNOW IF THEY SHOULD
    // DEALLOCATE OR NOT?
    __declspec(dllexport) Kid get_default_kid_const()
    {
        // MUSTN'T DEALLOCATE!!!
        return Kid { (char*)pstrInternal, 2,7 };
    }

    __declspec(dllexport) Kid get_a_kid()
    {
        // this string must be freed with free()
        char* pstr = strdup(pstrInternal);

        return Kid { pstr, 2,7 };
    }

    __declspec(dllexport) void free_memory(void* ptr)
    {
        free(ptr);
    }
        
    __declspec(dllexport) void free_kid(Kid* ptr)
    {
        if (ptr != NULL)
        {
            free(ptr->_name);
        }
    }
}

C#:

public struct Kid
{
    public IntPtr _namePtr;
    public int _age;
    public int _grade;
    public string _name { get => Marshal.PtrToStringAnsi(_namePtr); }
}

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_default_kid_const();

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern Kid get_a_kid();

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_memory(IntPtr ptr);

[DllImport("CPlusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void free_kid(ref Kid kid);

public static void Main(string[] args)
{
    Kid kid = get_default_kid_const();
    Console.WriteLine($"{kid._name}, {kid._age}, {kid._grade}");
    // MUSTN'T FREE this Kid!!!

    Kid kid2 = get_a_kid();
    Console.WriteLine($"{kid2._name}, {kid2._age}, {kid2._grade}");
    free_memory(kid2._namePtr);

    Kid kid3 = get_a_kid();
    Console.WriteLine($"{kid3._name}, {kid3._age}, {kid3._grade}");
    free_kid(ref kid3);
}

Be aware that there is a world of pain (pain made from random crashes and memory leaks) for those that marshal strings between C/C++ and C# without knowing exactly how it works.

xanatos
  • 109,618
  • 12
  • 197
  • 280
0

The default marshaling behavior for strings is wide strings, e.g. LPWSTR. You're going to have to add a marshaling directive to tell the .NET marshaler that what you really supplied is a narrow string (e.g. LPSTR or char*).

Read more on MSDN.

There's an example on that page that's close to what you want - you can mostly just copy the marshaling directives:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct StringInfoA
{
    [MarshalAs(UnmanagedType.LPStr)] public string f1;
}

So yours would be:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Kid
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string _name;
    public int _age;
    public int _grade;
}
Tumbleweed53
  • 1,491
  • 7
  • 13
  • `The default marshaling behavior for strings is wide strings` - as stated in the very documentation you are linking to, the default marshaling for strings in structs [is `UnmanagedType.LPStr`](https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-for-strings#strings-used-in-structures). In fact, not a single string marshaling type on that page has `LPWStr` as the default option. – GSerg Dec 31 '20 at 18:09
  • I changed the code to this, and it throws an exception: `Unhandled Exception: System.Runtime.InteropServices.MarshalDirectiveException: M ethod's type signature is not PInvoke compatible.` – Amirreza Moeini Yegane Dec 31 '20 at 18:34