【问题标题】:How to import an exported char* from C++ dll in C# project如何在 C# 项目中从 C++ dll 导入导出的 char*
【发布时间】:2021-04-07 23:01:57
【问题描述】:

我正在尝试打开从我的 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。

在谷歌中搜索了很多。

【问题讨论】:

  • 在现实世界中,它是 veryveryvery 复杂的。问题是“通常” C 端字符串名称会以某种方式分配(如 malloc),因此您需要相应的 FreeKid(Kid*)... 在此示例中 Quinn 是一个 C 字符串文字,所以你永远不应该释放它...... :-)
  • name in get_default_kid 是一个局部变量,当函数返回时会消失。
  • 还有内存所有权的问题:见limbioliong.wordpress.com/2011/06/16/…
  • @Manuel 所以这意味着,当函数返回指向该字符(字符串)数组的指针时,该数组将消失。因此,当我的 C# 项目试图取消引用该指针时,那里什么都没有。我明白了,非常感谢。
  • Manuel 帮我解决了我的问题。如果有人在一般情况下和最佳实践中添加一个关于如何在 c# 中从 dll 导入 char* 的问题的答案,那就太好了。所以我和任何可能需要帮助的人都可以阅读和学习。谢谢。

标签: c# c++ marshalling c-strings char-pointer


【解决方案1】:

字符串的默认编组行为是宽字符串,例如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;
}

【讨论】:

  • The default marshaling behavior for strings is wide strings - 如您链接到的文档中所述,结构 is UnmanagedType.LPStr 中字符串的默认编组。事实上,该页面上没有一个字符串封送类型将 LPWStr 作为默认选项。
  • 我把代码改成了这个,它抛出了一个异常:Unhandled Exception: System.Runtime.InteropServices.MarshalDirectiveException: M ethod's type signature is not PInvoke compatible.
【解决方案2】:

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

请注意,您不能

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

你会得到一个错误。 .NET 封送拆收器不想封送作为返回值的非 blittable 结构(因此 structs 具有像 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# 之间编组字符串却不知道其工作原理的人来说,这是一个痛苦的世界(由随机崩溃和内存泄漏造成的痛苦)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多