如何从 C# 项目中的 C++ dll 导入导出的 char*

How to import an exported char* from C++ dll in C# project

我正在尝试打开从我的 C# 项目中的 Dll 导出的 char*。

由于代码太多,我将编写一个与我正在处理的代码类似的简短代码。

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

此代码在控制台中写入了一些随机字符,如“♠”,而不是从 dll 中导出的名称。

我尝试过的:
尝试将 char* 导入为 IntPtr 然后使用以下方式读取它:

Marshal.PtrToStringAnsi()/BSTR/Auto/Uni

Marshal.ReadIntPtr 在尝试使用上面的行读取它之前。

从那以后,我尝试在 C# 中将字符串转换为 UTF-8。

在 google 中搜索了很多。

字符串的默认编组行为是宽字符串,例如LPWSTR。您将不得不添加一个封送处理指令来告诉 .NET 封送处理程序您真正提供的是一个窄字符串(例如 LPSTRchar*)。

阅读更多 on MSDN

该页面上有一个与您想要的很接近的示例 - 您几乎可以只复制编组指令:

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

所以你的是:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Kid
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string _name;
    public int _age;
    public int _grade;
}

一般来说,当我们谈到 C/C++ 和 C# 之间的封送处理字符串(或其他分配的内存)时,最重要和最复杂的问题是:如何释放内存?如果可能的话,最简单的解决方案是从 C/C++ 端导出一个或多个释放器方法。我在这里举一些例子:

请注意,您不能

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

你会得到一个错误。 .NET 封送拆收器不想封送具有 return 值的非 blittable 结构(因此 struct 具有复杂类型,如 strings)。出于这个原因,我使用 IntPtr 然后手动将 char* 编组为 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);
}

请注意,对于那些在 C/C++ 和 C# 之间编组字符串但不知道其工作原理的人来说,这是一个痛苦的世界(随机崩溃和内存泄漏造成的痛苦)。