Совместное использование переменных между C # и 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" 
 Sebastian Cabot09 нояб. 2012 г., 10:35
Я проголосовал за твой вопрос, но ты должен действительно указать природупеременные» чтобы получить хороший ответ
 88809 нояб. 2012 г., 10:57
Я только что отредактировал. Спасибо.

Ответы на вопрос(3)

Решение Вопроса

если вы знаете, как это работает. Я бы предложил почитать про маршалинг в 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="" then="" update="" "variables"="" }="" <="" code=""></many_times;>

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

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

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

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

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

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

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

<code>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
    }
};
</code>

В C #:

<code>[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); 
</code>

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

<code>#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()
</code>

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

<code>[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;
};
</code>

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

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

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

<strong>РЕДАКТИРОВАТЬ</strong>

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

 88809 нояб. 2012 г., 11:10
Спасибо за этот четкий ответ. Я проголосовал за это, но яя еще не воодушевлен. Может быть, в моем вопросе я пропустил, чтобы указать, что я уже пытался с P / invoke, но таким образом (если яя не ошибаюсь) Я должен передать все переменные (хорошо, ссылка) на каждой итерации. Мне было интересно, есть ли способ избежать этого.
 Sebastian Cabot09 нояб. 2012 г., 15:55
@mcdg Нет проблем с передачей ссылки на структуру. При этом нет значительных накладных расходов. Во многих отношениях гораздо лучше использовать какую-то структуру или класс для хранения состояния. Таким образом, вы можете стать многопоточным и сделать вашу функцию повторно входящей, тогда как при использовании глобальных переменных вы ограничиваете свои возможности. Однако, так как вы обновили свой вопрос со спецификацией "переменные» Я добавлю к ответу несколько советов о том, как обрабатывать более сложные случаи

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

 88808 нояб. 2012 г., 18:32
Я немного изменил вопрос, чтобы лучше объяснить себя.
 PapaAtHome07 нояб. 2012 г., 22:07
C ++ / CLI немного более гибок в обработке всех деталей, особенно если вы хотите переключаться между управляемым и неуправляемым кодом. C ++ / CLI, объединяющий .NET и C ++, не являетсяс легким характером' если ты'по крайней мере, не привыкли к C ++ (но это только мое мнение). Тогда придерживаться C # может быть более разумным выбором.
 PapaAtHome08 нояб. 2012 г., 21:53
Ваш код C будет использоватьнеуправляемый» код и память (нативная), в то время как ваш код C # будет использоватьудалось' код и память (система .NET CLR) Эти два нельзя обменять или ссылаться в смешанном поместье. Вам нужен конверсионный код для этого.
 PapaAtHome08 нояб. 2012 г., 21:55
Если возможно перекомпилировать ваш код C как код C ++ как 'удалось' тогда вам не нужен конверсионный код. Это еще одна вещь, которую вы можете попытаться сделать.
 88814 нояб. 2012 г., 15:14
Спасибо за ваши подсказки, но в этом случае яя буду держать dll неуправляемым для выступлений ради.
 Security Hound07 нояб. 2012 г., 19:47
Почему они используют C ++ / CLI для создания оболочки? Вы можете написать оболочку на C # достаточно легко.

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

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

Ваш ответ на вопрос