Совместное использование переменных между C # и переполнением стека

Я пишу программное обеспечение на C #, которое должно вызывать много раз и многими потоками функцию в неуправляемой DLL C ++.

У меня есть файл C ++, как это:

// "variables" which consist in some simple variables (int, double)
//  and in some complex variables (structs containing arrays of structs)extern "C"{
__declspec(dllexport) int function1()
{
// some work depending on random and on the "variables"}
}

и такой класс C #

public class class1
{
//  "variables" <--- the "same" as the C++ file's ones
//  Dll import <--- ok

public void method1()
{
int [] result;

for(int i=0; i<many_times; i++)
{
result = new int[number_of_parallel_tasks];

Parallel.For(0, number_of_parallel_tasks, delegate(int j)
{
// I would like to do  result[j] = function1()
});

//  choose best result
//  then update "variables"}
}

}

Я написал «Я хотел бы сделать …», потому что функция c ++ должна в каждом раунде обновлять «переменные».

Мой вопрос:

Можно ли разделить память между C ++ и C #, чтобы избежать передачи ссылок каждый раз? Это просто пустая трата времени?

Я читал о файлах, отображенных в память. Могут ли они помочь мне? Однако знаете ли вы более подходящие решения?
Большое спасибо.

16

Решение

Нет проблем с разделением памяти между C # и C ++ с помощью P / Invoke, если вы знаете, как это работает. Я бы предложил почитать про маршалинг в MSDN. Вы также можете прочитать об использовании небезопасного ключевого слова и об исправлении памяти.

Вот пример, который предполагает, что ваши переменные могут быть описаны как простая структура:

В C ++ объявите вашу функцию следующим образом:

#pragma pack(1)
typedef struct VARIABLES
{
/*
Use simple variables, avoid pointers
If you need to use arrays use fixed size ones
*/
}variables_t;
#pragma pack()
extern "C"{
__declspec(dllexport) int function1(void * variables)
{
// some work depending on random and on the "variables"}
}

В C # сделайте что-то вроде этого:

[StructLayout(LayoutKind.Sequential, Pack=1)]
struct variables_t
{
/*
Place the exact same definition as in C++
remember that long long in c++ is long in c#
use MarshalAs for fixed size arrays
*/
};

[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int function(ref variables_t variables);

И в вашем классе:

variables_t variables = new variables_t();
//Initialize variables here
for(int i=0; i<many_times; i++)
{
int[] result = new int[number_of_parallel_tasks];
Parallel.For(0, number_of_parallel_tasks, delegate(int j)
{
result[j] = function1(ref variables)
});

//  choose best result
//  then update "variables"}

Вы можете использовать более сложные сценарии, такие как выделение и освобождение структуры в c ++, использование других форм маршалинга для возврата данных, например создание собственного класса для чтения и записи непосредственно в неуправляемую память. Но если вы можете использовать простую структуру для хранения переменных, описанный выше метод является самым простым.

РЕДАКТИРОВАТЬ: Указатели о том, как правильно обрабатывать более сложные данные

Таким образом, приведенный выше пример является, на мой взгляд, правильным способом «обмена» данными между C # и C ++, если это простые данные, например. структура, содержащая примитивные типы или массивы фиксированного размера примитивных типов.

Это говорит о том, что на самом деле очень мало ограничений на то, как вы можете получить доступ к памяти с помощью C #. Для получения дополнительной информации посмотрите ключевое слово unsafe, ключевое слово fixed и структуру GCHandle. И все же, если у вас есть очень сложные структуры данных, которые содержат массивы других структур и т. Д., Тогда у вас есть более сложная работа.

В приведенном выше случае я бы рекомендовал перенести логику обновления «переменных» в C ++.
Добавьте в C ++ функцию, которая будет выглядеть примерно так:

extern "C"{
__declspec(dllexport) void updateVariables(int bestResult)
{
// update the variables
}
}

Я бы по-прежнему предлагал не использовать глобальные переменные, поэтому я предлагаю следующую схему.
В C ++:

typedef struct MYVERYCOMPLEXDATA
{
/*
Some very complex data structure
*/
}variables_t;
extern "C"{
__declspec(dllexport) variables_t * AllocVariables()
{
// Alloc the variables;
}
__declspec(dllexport) void ReleaseVariables(variables_t * variables)
{
// Free the variables;
}
__declspec(dllexport) int function1(variables_t const * variables)
{
// Do some work depending on variables;
}
__declspec(dllexport) void updateVariables(variables_t * variables, int bestResult)
{
// update the variables
}
};

В C #:

[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern IntPtr AllocVariables();
[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void ReleaseVariables(IntPtr variables);
[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int function1(IntPtr variables);
[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern void updateVariables(IntPtr variables, int bestResult);

Если вы все еще хотите сохранить свою логику в C #, вам придется сделать что-то вроде следующего:
Создайте класс для хранения памяти, возвращенной из C ++, и напишите свою собственную логику доступа к памяти. Предоставьте данные в C #, используя семантику копирования. Я имею в виду следующее:
Скажем, у вас в C ++ есть такая структура:

#pragma pack(1)
typedef struct SUBSTRUCT
{
int subInt;
double subDouble;
}subvar_t;
typedef struct COMPLEXDATA
{
int int0;
double double0;
int subdata_length;
subvar_t * subdata;
}variables_t;
#pragma pack()

в C # вы делаете что-то вроде этого

[DllImport("kernel32.dll")]
static extern void CopyMemory(IntPtr dst, IntPtr src, uint size);

[StructLayout((LayoutKind.Sequential, Pack=1)]
struct variable_t
{
public int int0;
public double double0;
public int subdata_length;
private IntPtr subdata;
public SubData[] subdata
{
get
{
SubData[] ret = new SubData[subdata_length];
GCHandle gcH = GCHandle.Alloc(ret, GCHandleType.Pinned);
CopyMemory(gcH.AddrOfPinnedObject(), subdata, (uint)Marshal.SizeOf(typeof(SubData))*subdata_length);
gcH.Free();
return ret;
}
set
{
if(value == null || value.Length == 0)
{
subdata_length = 0;
subdata = IntPtr.Zero;
}else
{
GCHandle gcH = GCHandle.Alloc(value, GCHandleType.Pinned);
subdata_length = value.Length;
if(subdata != IntPtr.Zero)
Marshal.FreeHGlobal(subdata);
subdata = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SubData))*subdata_length);
CopyMemory(subdata, gcH.AddrOfPinnedObject(),(uint)Marshal.SizeOf(typeof(SubData))*subdata_length);
gcH.Free();
}
}
}
};
[StructLayout((LayoutKind.Sequential, Pack=1)]
sturct SubData
{
public int subInt;
public double subDouble;
};

В приведенном выше примере структура все еще может быть передана, как в первом примере. Это, конечно, просто схема того, как обрабатывать сложные данные с массивами структур и массивами структур в массивах структур. Как видите, вам нужно много копировать, чтобы уберечь себя от повреждения памяти. Также, если память выделяется через C ++, будет очень плохо, если вы используете FreeHGlobal для ее освобождения.
Если вы хотите избежать копирования памяти и по-прежнему поддерживать логику в C #, вы можете написать собственную оболочку памяти с аксессорами для чего угодно. Например, у вас будет метод для прямой установки или получения дочернего элемента N-го массива — таким образом вы будете хранить свои копии именно к тому, что вы получаете доступ.

Другой вариант — написать конкретные функции на C ++, которые будут выполнять сложную обработку данных, и вызывать их из C # в соответствии с вашей логикой.

И последнее, но не менее важное: вы всегда можете использовать C ++ с интерфейсом CLI. Однако я сам делаю это только в том случае, если я должен — мне не нравится жаргон, но для очень сложных данных вы, безусловно, должны это учитывать.

РЕДАКТИРОВАТЬ

Я добавил правильное соглашение о вызовах в DllImport для полноты. Обратите внимание, что соглашение о вызовах по умолчанию, используемое атрибутом DllImport, — это Winapi (который в Windows переводится как __stdcall), тогда как соглашение о вызовах по умолчанию в C / C ++ (если вы не изменили параметры компилятора) — __cdecl.

23

Другие решения

Лучшее, что вы можете сделать, это определить свой код C ++ (который, я полагаю, будет неуправляемым).
Затем напишите обертку для него в C ++ / CLI. Оболочка предоставит вам интерфейс к C # и станет местом, где вы можете позаботиться о маршаллинге (перемещении данных из unmanagerd в управляемую область).

3

Один из способов избежать передачи ссылки на переменные / данные, которые требуются как в коде C #, так и в C ++, — экспортировать две функции из собственной DLL. В дополнение к вашей функции, которая выполняет эту работу, предоставьте еще одну функцию, которая позволяет передавать ссылку и сохранять ее в статическом указателе области файла, который определен в том же файле .cpp, что и обе функции, так что он доступен для обеих.

Как вы упомянули, вы также можете использовать отображенный в памяти файл (в этом случае он может не сохраняться, поскольку его никогда не нужно записывать на диск).

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector