簡體   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