Обмен переменными между С# и С++ - программирование
Подтвердить что ты не робот

Обмен переменными между С# и С++

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

У меня есть файл С++:

// "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"
    }
}

и класс С#, подобный этому

public class class1
{
    //  "variables" <--- the "same" as the C++ file 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" 
        }
    }

}

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

Мой вопрос:

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

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

4b9b3361

Ответ 1

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

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

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

#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"
    }
}

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

[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" 
}

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

EDIT: указатели на то, как правильно обрабатывать более сложные данные

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

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

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

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

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

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

В С#:

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

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

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

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

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

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

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

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

ИЗМЕНИТЬ

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

Ответ 2

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

Ответ 3

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

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