簡體   English   中英

如何將結構數組從C ++ dll返回到C#

[英]How to return array of struct from C++ dll to C#

我需要在dll中調用一個函數並返回一個結構數組。 我事先並不知道陣列的大小。 如何才能做到這一點? 該錯誤can not marshal 'returns value' invalid managed / unmanaged

C#中的代碼:

[DllImport("CppDll"]
public static extern ResultOfStrategy[] MyCppFunc(int countO, Data[] dataO, int countF, Data[] dataF);

在C ++中:

extern "C" _declspec(dllexport) ResultOfStrategy* WINAPI MyCppFunc(int countO, MYDATA * dataO, int countF, MYDATA * dataF)
{
    return Optimization(countO, dataO, countF, dataF);
}

返回結構數組:

struct ResultOfStrategy
{
bool isGood;
double allProfit;
double CAGR;
double DD;
int countDeals;
double allProfitF;
double CAGRF;
double DDF;
int countDealsF;
Param Fast;
Param Slow;
Param Stop;
Param Tp;
newStop stloss;
};

我會給你兩個回復。 第一個是非常基本的方法。 第二個是非常先進的。

鑒於:

C面:

struct ResultOfStrategy
{
    //bool isGood;
    double allProfit;
    double CAGR;
    double DD;
    int countDeals;
    double allProfitF;
    double CAGRF;
    double DDF;
    int countDealsF;
    ResultOfStrategy *ptr;
};

C# - 側:

public struct ResultOfStrategy
{
    //[MarshalAs(UnmanagedType.I1)]
    //public bool isGood;
    public double allProfit;
    public double CAGR;
    public double DD;
    public int countDeals;
    public double allProfitF;
    public double CAGRF;
    public double DDF;
    public int countDealsF;
    public IntPtr ptr;
}

請注意,我刪除了bool ,因為它在案例2中存在一些問題(但它適用於案例1)......現在......

案例1非常基礎,它將導致.NET封送程序將C語言中的數組復制到C#數組。

我編寫的案例2非常先進,它試圖繞過這個marshal-by-copy並使得C和.NET可以共享相同的內存。

為了檢查差異,我寫了一個方法:

static void CheckIfMarshaled(ResultOfStrategy[] ros)
{
    GCHandle h = default(GCHandle);

    try
    {
        try
        {
        }
        finally
        {
            h = GCHandle.Alloc(ros, GCHandleType.Pinned);
        }

        Console.WriteLine("ros was {0}", ros[0].ptr == h.AddrOfPinnedObject() ? "marshaled in place" : "marshaled by copy");
    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }
}

我已經添加了一個ptr場的struct ,其中包含了原來的地址struct (C面),看它是否已被復制,或者如果它是原來的struct

情況1:

C面:

__declspec(dllexport) void MyCppFunc(ResultOfStrategy** ros, int* length)
{
    *ros = (ResultOfStrategy*)::CoTaskMemAlloc(sizeof(ResultOfStrategy) * 2);
    ::memset(*ros, 0, sizeof(ResultOfStrategy) * 2);
    (*ros)[0].ptr = *ros;
    (*ros)[0].allProfit = 100;
    (*ros)[1].ptr = *ros + 1;
    (*ros)[1].allProfit = 200;
    *length = 2;
}

和C#-side:

public static extern void MyCppFunc(
    [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 1)] out ResultOfStrategy[] ros, 
    out int length
);

然后:

ResultOfStrategy[] ros;
int length;
MyCppFunc(out ros, out length);

Console.Write("Case 1: ");
CheckIfMarshaled(ros);

ResultOfStrategy[] ros2;

.NET編組器知道(因為我們給它的信息)第二個參數是out ResultOfStrategy[] ros的長度(參見SizeParamIndex ?),因此它可以創建一個.NET數組並從C分配的數組中復制數據。 請注意,在C代碼中,我使用了::CoTaskMemAlloc來分配內存。 .NET 希望使用該分配器分配內存,因為它隨后釋放它。 如果你使用malloc / new / ??? 分配ResultOfStrategy[]內存,會發生不好的事情。

案例2:

C面:

__declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
{
    ResultOfStrategy *ros = allocator(2);
    ros[0].ptr = ros;
    ros[1].ptr = ros + 1;
    ros[0].allProfit = 100;
    ros[1].allProfit = 200;
}

C# - 側:

// Allocator of T[] that pins the memory (and handles unpinning)
public sealed class PinnedArray<T> : IDisposable where T : struct
{
    private GCHandle handle;

    public T[] Array { get; private set; }

    public IntPtr CreateArray(int length)
    {
        FreeHandle();

        Array = new T[length];

        // try... finally trick to be sure that the code isn't interrupted by asynchronous exceptions
        try
        {
        }
        finally
        {
            handle = GCHandle.Alloc(Array, GCHandleType.Pinned);
        }

        return handle.AddrOfPinnedObject();
    }

    // Some overloads to handle various possible length types
    // Note that normally size_t is IntPtr
    public IntPtr CreateArray(IntPtr length)
    {
        return CreateArray((int)length);
    }

    public IntPtr CreateArray(long length)
    {
        return CreateArray((int)length);
    }

    public void Dispose()
    {
        FreeHandle();
    }

    ~PinnedArray()
    {
        FreeHandle();
    }

    private void FreeHandle()
    {
        if (handle.IsAllocated)
        {
            handle.Free();
        }
    }
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr AllocateResultOfStrategyArray(IntPtr length);

[DllImport("CplusPlusSide.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void MyCppFunc2(
    AllocateResultOfStrategyArray allocator
);

然后

ResultOfStrategy[] ros;

using (var pa = new PinnedArray<ResultOfStrategy>())
{
    MyCppFunc2(pa.CreateArray);
    ros = pa.Array;

    // Don't do anything inside of here! We have a
    // pinned object here, the .NET GC doesn't like
    // to have pinned objects around!

    Console.Write("Case 2: ");
    CheckIfMarshaled(ros);
}

// Do the work with ros here!

現在這個很有趣...... C函數從C#-side(函數指針)接收一個分配器。 此分配器將分配length元素,然后必須記住分配的內存的地址。 這里的訣竅是C# ResultOfStrategy[]我們分配一個C所需大小的ResultOfStrategy[] ,然后直接用於C-side。 如果ResultOfStrategy不是blittable(這個術語意味着你只能使用ResultOfStrategy一些類型,主要是數字類型,沒有string ,沒有char ,沒有bool ,請參見此處 ),這將會破壞。 代碼非常先進,因為除此之外,它必須使用GCHandle來固定.NET數組,以便它不會被移動。 處理這個GCHandle非常復雜,所以我必須創建一個IDisposableResultOfStrategyContainer 在這個類中,我甚至保存對創建的數組的引用( ResultOfStrategy[] ResultOfStrategy )。 注意using 這是使用該類的正確方法。

bool和案例2

正如我所說,當bool與案例1合作時,它們不適用於案例2 ......但我們可以作弊:

C面:

struct ResultOfStrategy
{
    bool isGood;

C# - 側:

public struct ResultOfStrategy
{
    private byte isGoodInternal;
    public bool isGood
    {
        get => isGoodInternal != 0;
        set => isGoodInternal = value ? (byte)1 : (byte)0;
    }

這工作:

C面:

extern "C"
{
    struct ResultOfStrategy
    {
        bool isGood;
        double allProfit;
        double CAGR;
        double DD;
        int countDeals;
        double allProfitF;
        double CAGRF;
        double DDF;
        int countDealsF;
        ResultOfStrategy *ptr;
    };

    int num = 0;
    int size = 10;

    __declspec(dllexport) void MyCppFunc2(ResultOfStrategy* (*allocator)(size_t length))
    {
        ResultOfStrategy *ros = allocator(size);

        for (int i = 0; i < size; i++)
        {
            ros[i].isGood = i & 1;
            ros[i].allProfit = num++;
            ros[i].CAGR = num++;
            ros[i].DD = num++;
            ros[i].countDeals = num++;
            ros[i].allProfitF = num++;
            ros[i].CAGRF = num++;
            ros[i].DDF = num++;
            ros[i].countDealsF = num++;
            ros[i].ptr = ros + i;
        }

        size--;
    }
}

C# - 側:

[StructLayout(LayoutKind.Sequential)]
public struct ResultOfStrategy
{
    private byte isGoodInternal;
    public bool isGood
    {
        get => isGoodInternal != 0;
        set => isGoodInternal = value ? (byte)1 : (byte)0;
    }
    public double allProfit;
    public double CAGR;
    public double DD;
    public int countDeals;
    public double allProfitF;
    public double CAGRF;
    public double DDF;
    public int countDealsF;
    public IntPtr ptr;
}

然后

ResultOfStrategy[] ros;

for (int i = 0; i < 10; i++)
{
    using (var pa = new PinnedArray<ResultOfStrategy>())
    {
        MyCppFunc2(pa.CreateArray);
        ros = pa.Array;

        // Don't do anything inside of here! We have a
        // pinned object here, the .NET GC doesn't like
        // to have pinned objects around!
    }

    for (int j = 0; j < ros.Length; j++)
    {
        Console.WriteLine($"row {j}: isGood: {ros[j].isGood}, allProfit: {ros[j].allProfit}, CAGR: {ros[j].CAGR}, DD: {ros[j].DD}, countDeals: {ros[j].countDeals}, allProfitF: {ros[j].allProfitF}, CAGRF: {ros[j].CAGRF}, DDF: {ros[j].DDF}, countDealsF: {ros[j].countDealsF}");
    }

    Console.WriteLine();
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM