You can probably start with the following skeleton.
Create a managed class to hold the data:
public class FrameOfData
{
public int iFrame { get; set; }
//...
}
Mark the return value of the p/invoke import with MarshalAs indicating a class that will do the custom marshal (i.e. FrameMarshaler class):
[DllImport("Cortex_SDK.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FrameMarshaler))]
public static extern FrameOfData Cortex_GetCurrentFrame();
The FrameMarshaler class is responsible of doing the custom marshaling, receiving a pointer to the unmanaged struct and returning a managed object, this can be done in the MarshalNativeToManaged method:
public class FrameMarshaler : ICustomMarshaler
{
public void CleanUpManagedData(object ManagedObj)
{
}
public void CleanUpNativeData(IntPtr pNativeData)
{
}
public int GetNativeDataSize()
{
return -1;
}
public IntPtr MarshalManagedToNative(object ManagedObj)
{
throw new NotImplementedException();
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
// Here, we call C++/CLI code
FrameOfData frame = Marshaler.MarshalFrame(pNativeData);
return frame;
}
}
The method Marshaler.MarshalFrame will be on a C++/CLI assembly. The following code is an example of a C++/CLI marshaler for the struct:
#include "Cortex_SDK.h"
#pragma once
using namespace System;
public ref class Marshaler
{
public:
static FrameOfData^ MarshalFrame(IntPtr dispo)
{
// Cast the IntPtr to the unmanaged pointer
sFrameOfData* unmanaged = static_cast<sFrameOfData*>(dispo.ToPointer());
// Transform unmnaged pointer to a managed object
FrameOfData^ managed = gcnew FrameOfData();
managed->iFrame = unmanaged.iFrame;
// ...
}
}