Search code examples
c#structpinvokedllimport

C# marshaling C struct


I've this C-C# code that works:

.h

typedef struct {

  float a;
  float b;

} MyStruct;

extern MyStruct mystruct;

__declspec(dllexport) void  GetMyStruct (MyStruct* s);
__declspec(dllexport) void  SetMyStruct (MyStruct* s);

.c

    MyStruct mystruct;

    void GetMyStruct (MyStruct* s)
    {

        *s = AeroLink_IOPkt;
    }

    void SetMyStruct (MyStruct* s)
    {

        AeroLink_IOPkt = *s; 
    }

void test()
{
   // some code that update element in struct
   // mystruct.a = 0.4;
   // mystruct.a = 0.1;
}

.cs

public struct MyStruct
{

  public float a;
  public float b;

} 


[DllImport(DLL_NAME, EntryPoint = "GetMyStruct")]
protected static extern void GetMyStruct(ref MyStruct s);

[DllImport(DLL_NAME, EntryPoint = "SetMyStruct")]
protected static extern void SetMyStruct(ref MyStruct s);

This way, every time I need to set data from C# to C, I must call void SetMyStruct and vice-versa if I want to get data from C (updated from void test) to C# I must call GetMyStruct. I must do this 50 times per seconds.

Is there a way to avoid calling SetMyStruct and GetMyStruct every time? I would like to use SetMyStruct one time and then have all changes be reflected, from and to. I do not know if this is possible.


Solution

  • You can do this with unsafe and pointers.

    You need to compile your C# program with "Unsafe" enabled.

    EDIT: A better way:

    Add a following function to the library:

    __declspec(dllexport) void  GetMyStructRef (MyStruct** s);
    void GetMyStructRef(MyStruct** s)
    {
        *s = &mystruct;
    }
    

    In C#:

    [DllImport(DLL_NAME, EntryPoint = "GetMyStructRef")]
    protected static extern void GetMyStructRef(ref MyStruct* s);
    
    MyStruct* data;
    GetMyStructRef(ref data);
    Console.WriteLine($"{data->a} {data->b}");
    

    Old answer:

    unsafe class MyClass : IDisposable
    {
        [DllImport(DLL_NAME, EntryPoint = "GetMyStruct")]
        protected static extern void GetMyStruct(MyStruct* s);
    
        [DllImport(DLL_NAME, EntryPoint = "SetMyStruct")]
        protected static extern void SetMyStruct(MyStruct* s);
    
        GCHandle handle;
        MyStruct* structRef;
    
        public void MyClass()
        {
            //we need to get a pinned reference to your struct
            handle = GCHandle.Alloc(new MyStruct(), GCHandleType.Pinned);
            structRef = (MyStruct*)handle.AddrOfPinnedObject().ToPointer();
    
            SetMyStruct(structRef);
        }
    
        public void Dispose()
        {
            //We need to free the handle to release memory
            //GC will not collect it without this
            handle.Free();
        }
    }