繁体   English   中英

如何在 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 。

在谷歌搜索了很多。

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

请注意,您不能

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

你会得到一个错误。 .NET 封送拆收器不想封送作为返回值的非 blittable 结构(因此具有复杂类型的struct ,如string s)。 出于这个原因,我使用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# 之间编组字符串却不知道它是如何工作的人来说,有一个痛苦的世界(随机崩溃和 memory 泄漏造成的痛苦)。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM