Как вернуть массив структуры из C ++ DLL в C # - PullRequest
0 голосов
/ 28 мая 2018

Мне нужно вызвать функцию в 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;
};

1 Ответ

0 голосов
/ 28 мая 2018

Я дам вам два ответа.Первый метод довольно простой.Второй довольно продвинутый.

Дано:

Сторона 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, как я написал, довольно продвинут, и он пытается обойти этот маршал за копией и сделать так, чтобы 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 #:

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 я использовал ::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 # (указатель на функцию).Этот распределитель будет выделять length элементов и должен затем запомнить адрес выделенной памяти.Хитрость в том, что на стороне C # мы выделяем ResultOfStrategy[] размера, требуемого для C, который затем используется непосредственно на стороне C.Это плохо сломается, если ResultOfStrategy не является блитабельным (термин, означающий, что вы можете использовать только некоторые типы внутри ResultOfStrategy, в основном числовые типы, нет string, нет char, нет bool, см. здесь ).Код довольно продвинутый, потому что помимо всего этого, он должен использовать GCHandle для закрепления массива .NET, чтобы он не перемещался.Обработка этого GCHandle довольно сложна, поэтому мне пришлось создать ResultOfStrategyContainer, то есть IDisposable.В этом классе я даже сохраняю ссылку на созданный массив (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();
}
...