Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sharing variables between C# and C++

Tags:

c++

c#

sharing

I'm writing a software in c# which needs to call many times and by many threads a function in a c++ unmanaged dll.

I have a C++ file like that:

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

and a C# class like that

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

}

I wrote "I would like to do ..." because the c++ function needs at each round to have the "variables" updated, too.

My question is:

Is it possible to share memory between C++ and C# in order to avoid passing reference every time? Is it just a waste of time?

I read about memory mapped files. Could they help me? However, do you know more appropriate solutions?
Thank you very much.

like image 400
888 Avatar asked Nov 07 '12 17:11

888


1 Answers

There is no problem sharing memory between C# and C++ using P/Invoke once you know how it works. I would suggest reading about marshaling in MSDN. You might also want to read about using the unsafe keyword and fixing memory.

Here is a sample that assumes that your variables can be described as a simple struct:

In C++ declare your function as follows:

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

In C# do something like this:

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

And in your class:

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

You can use more complex scenarios like allocating and releasing the structure in c++, using other forms of marshaling to get the data back like building your own class to read and write directly to the unmanaged memory. But if you can use a simple struct to hold your variables the method above is the simplest.

EDIT: Pointers on how to handle correctly more complex data

So the sample above is in my opinion the correct way to "share" data between C# and C++ if it is simple data eg. a structure holding primitive types or arrays of fixed size of primitive types.

This being said there are actually very little limitations on the way you can access memory using C#. For more information look into the unsafe keyword, the fixed keyword and the GCHandle struct. And still if you have a very complex data structures that contain arrays of other structures etc. then you have a more complicated job.

In the case above I would recommend moving the logic on how to update the "variables" into C++. Add in C++ a function to look something like this:

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

I would still suggest not to use global variables so I propose the following scheme. In 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
    }
};

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

If you still want to maintain you logic in C# you will have to do something like the following: Create a class to hold the memory returned from C++ and write your own memory access logic. Expose the data to C# using copy semantics. What I mean is as follows, Say you have in C++ a structure like this:

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

in C# you do something like this

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

In the above sample the structure can still be passed as in the first sample. This of course is just an outline on how to handle complex data with araays of structures and arrays of structures within arrays of structures. As you can see you will need a lot of copying to guard yourself from memory corruption. Also if the memory is allocated via C++ it will be very bad if you use FreeHGlobal to free it. If you want to avoid copying memory and still maintain the logic within C# the you can write a native memory wrapper with accessors for what ever you want For instance you will have a method to directly set or get the subInt of Nth array member - This way you will keep your copies to exactly what you access.

Another option would be to write specific C++ functions to do the difficult data handling for you and call them from C# according to your logic.

And last but not least you can always use C++ with CLI interface. However I myself do it only if I must - I don't like the lingo but for very complex data you certainly have to consider it.

EDIT

I added the correct calling convention to the DllImport for completeness. Please note that the default calling convention used by the DllImport attribute is Winapi (which on windows translates to __stdcall) while the default calling convention in C/C++ (unless you change the compiler options) is __cdecl.

like image 116
Sebastian Cabot Avatar answered Sep 29 '22 13:09

Sebastian Cabot