Я дам вам два ответа.Первый метод довольно простой.Второй довольно продвинутый.
Дано:
Сторона 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();
}