Ron Teller
Ron Teller

Reputation: 1890

Passing a struct from c++ to c# when the structure is only known during runtime

I have the following problem:

I have a C# app that calls functions from an unmanaged C++ dll. There's an initialization function in the dll which creates an interface between the C# and C++ (basically a list of values and their types) that will be stored in a struct.

After that, there's a callback function that the C# app sends to the dll, which the dll calls every once in a while, and it returns a struct variable (or a bytes array) as it was defined in the interface.

My question: How would you pass and marshal this struct? is it possible to pass the struct itself, or should I pass a byte array?

If you pass a byte array, how would you marshal it when returned to the C# app?

What I have right now:

In the C# app:

Marshaling the callback function:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ProcessOutputDelegate(???); // not sure what should be here.

Importing the dll function:

[DllImport("MyDLL.dll", CallingConvention=CallingConvention.Cdecl)]
public static extern void Test(ProcessOutputDelegate ProcessOutput);

Calling the dll function:

ProcessOutputDelegate process = new ProcessOutputDelegate(ProcessOutput);
new thread(delegate() { Test(process); } ).Start();

Processing the output:

public void ProcessOutput(???)
{
    // Assume we have a list of values that describes the struct/bytes array.
}

In the C++ dll I have the following struct (this is an example, as a different dll may be called in different runtimes):

struct
{
    int x;
    double y;
    double z;
} typedef Interface;

And the function called by the C# app:

__declspec(dllexport) void Test(void (*ProcessOutput)(Interface* output))
{
    int i;
    Interface* output = (Interface*)malloc(sizeof(Interface));

    for (i = 0; i < 100; i++)
    {
        sleep(100);
        output->x = i;
        output->y = i / 2;
        output->z = i / 3;
        ProcessOutput(output); // or generate a bytes array out of the struct
    }
}

EDIT:

The C# application is a generic GUI, that suppose to show some heavy computations performed by some c++ dll. In the initialization process, the dll tells the GUI about the variables (and their types) that should be presented and the GUI is constructed according to these directions (again, the computation and variables may change, and the values may be ints, floats, chars...). After that, the dll runs and in each couple of time steps, it calls the callback function to update the GUI. This should work with any dll that implements this idea: generating an interface, then sending information according to this interface.

Upvotes: 1

Views: 1110

Answers (1)

Spook
Spook

Reputation: 25929

I don't know, whether this is a direct answer to your problem, but I solved it in the following way.

  1. You call GetResource in the DLL and get an IntPtr:

    (...)
    IntPtr res = Native.GetResource();
    
  2. Then, you instantiate a generic wrapper for this IntPtr:

    ResourceWrapper rw = new ResourceWrapper(res);
    
  3. If you want to access specific fields of the structure, you call appropriate methods on the wrapper:

    int field = rw.GetField();
    
  4. Wrapper calls a function in the DLL:

    (...)
    int result = Native.GetField(res);
    
  5. DLL "re-objectizes" the call:

    __declspec(dllexport) int __stdcall GetField(MyStructure * s)
    {
        return s->GetField();
    }
    
  6. Struct gets the data (or throws an exception or sets error flag or returns error etc.)

    int MyStructure::GetField()
    {
        return this->field;
    }
    

This solution requires the following changes:

  • Your data structures should derive from the same base class
  • If it is possible, they should become classes with virtual methods allowing access to their different fields
  • You have to implement some kind of security mechanism, which checks, whether access to specific field is possible or not. The simplest one is the following:

    __declspec(dllexport) BOOL __stdcall GetField(MyStruct * s, int & result)
    {
        result = 0;
        try
        {
            result = s->GetField();
        }
        catch(...)
        {
            return FALSE;
        }
    
        return TRUE;
    }
    

    and:

    protected class Native
    {
        [DllImport("MyDll.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetField(IntPtr res, out int result);
    }
    

    and:

    // Wrapper
    int GetField()
    {
        int result;
        if !(Native.GetField(res, out result))
            throw new InvalidOperationException("Resource does not support field!");
    }
    

Upvotes: 1

Related Questions