简体   繁体   中英

Trouble marshaling array of struct data from C++ to C#

I have searched for days and have tried everything I could find, but still cannot get this to work.

Details: I have a 3rd party stock trading app that is calling into my unmanaged dll. It is supplying data that the dll processes/filters and then saves into a global ring buffer. The ring buffer is an array of structures, 100 long. All of this runs in the stock trading apps process.

I also have a managed C# app calling into the same dll in a different process that needs to get the info in the global ring buffer as quickly and efficiently as possible. Everything works except that I can only get data for the first structure in the array. Also after the call to the dll from C# the C# code no longer knows that arrayMD is an array of structs, it shows up in the debugger as a simple structure. Could it be the memcpy in the dll causing the problem? I've tried all kinds of combinations with [In, Out], IntPtr, and Marchal.PtrToStructure combinations. I am greatly fubar. Any help would be greatly appreciated.

Thanks

Here is what I am attempting. On the dll side:

struct stMD
{
  float Price;
  unsigned int  PriceDir;
  unsigned int  PriceDirCnt;
};

// Global memory
#pragma data_seg (".IPC")
    bool NewPoint = false;      // Flag used to signal a new point.
    static stMD aryMD [100] = {{0}};
#pragma data_seg()

void __stdcall RetrieveMD (stMD *LatestMD [])
{
    memcpy(*LatestMD, aryMD, sizeof(aryMD));
}

On the C# side:

[StructLayout(LayoutKind.Sequential)]
public struct stMD
{
    public float Price;
    public uint PriceDir;
    public uint PriceDirCnt;
};

public static stMD[] arrayMD = new stMD[100];

[DllImport(@"Market.dll")]
public static extern void RetrieveMD(ref stMD[] arrayMD);

RetrieveMD(ref arrayMD);

The problem is the definition of your DLL entry point:

void __stdcall RetrieveMD (stMDP *LatestMD []) 

You don't specify the size of the array, so how is C# supposed to know how many elements were copied into it? This is a problem in other languages too. Your implementation simply assumes that the provided memory is large enough to contain aryMD. But what if it's not? You've just created a buffer overrun.

If you want the caller to allocate the array, then the caller must also pass in the number of elements that the array contains.

Edit

The C++ declaration should look something like this:

// On input, length should be the number of elements in the LatestMD array.
// On output, length should be the number of valid records copied into the array.
void __stdcall RetrieveMD( stMDP * LatestMD, int * length );

The C# declaration would then look something like this:

[DllImport(@"Market.dll")]
public static extern void RetrieveMD(
    [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] ref stMD[] arrayMD,
    [In, Out] ref int length);

Your problem, I think, is that you are trying to pass an array by reference from C# into C, and you almost never need to do that. In your case, all you want to happen is for C# to allocate memory for 100 of your structures, then pass that memory to C to be filled in. In your case, you're passing a pointer to an array of structures, which is an extra level of indirection that you don't really need.

You rarely see C functions that take a fixed sized array; instead, your function would typically be defined in C to accept a pointer and a length, eg something like:

void __stdcall RetrieveMD (stMDP *LatestMD, int size) 
{
   int count = 0;
   if (size < sizeof(aryMD))
     count = size;
   else
     count = sizeof(aryMD);

   memcpy(LatestMD, aryMD, count);      
}

To call this method from C#, you need to do two things. First, you need to allocate an array of the appropriate size to pass in. Second, you need to tell the marshaling code (that does the C# -> C copying) how to figure out how much data to be marshaled, via the [MarshalAs] attribute:

[DllImport(@"Market.dll")]
public static extern void RetrieveMD (
  [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] stMDP[] arrayMD,
  int length
);

var x = new stMDP[100];
RetrieveMD(x, 100);

I got it working. Was I ever tring to make this harder than it is.

I re-read chapter 2 of ".NET 2.0 Interoperability Recipes: A Problem-Solution Approach".

Here's what works.

On C++ side I removed the pointers

struct stMD 
{ 
float Price; 
unsigned int PriceDir; 
unsigned int PriceDirCnt; 
}; 

// Global memory 
#pragma data_seg (".IPC") 
bool NewPoint = false; // Flag used to signal a new point. 
static stMD aryMD [100] = {{0}}; 
#pragma data_seg() 

void __stdcall RetrieveMD (stMD LatestMD [100]) 
{ 
memcpy(LatestMD, aryMD, sizeof(aryMD)); 
}

On the C# side I removed both (ref)s and added [In, Out]

[StructLayout(LayoutKind.Sequential)] 
public struct stMD 
{ 
    public float Price; 
    public uint PriceDir; 
    public uint PriceDirCnt; 
}; 

public static stMD[] arrayMD = new stMD[100]; 

[DllImport(@"Market.dll")] 
public static extern void RetrieveMD([In, Out] stMD[] arrayMD); 

RetrieveMD(arrayMD); 

Thanks to everyone who offered help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM