Click here to Skip to main content
15,890,670 members
Please Sign up or sign in to vote.
4.67/5 (3 votes)
See more:
Help with passing structure from unmanaged c++ dll to c# ?

I am struggling with passing a structure from an unmanaged third party c++ dll back into my c# application. The dll is for a spectrometer. All of the other commands to the spectrometer work fine except this one. I am pasting more documentation below, but here is the c++ call:

C++
SDK_API MTSSE_GetDeviceSpectrometerFrameData( int DeviceID, int SpectrometerID, int WaitUntilDone, tFrameRecord* &FrameData);


and it is the last argument that is the problem. The documentation says this:


FrameRecordData - the pointer to the spectrum data structure if spectrum is ready or nil if spectrum is not ready. It is defined as:
C++
typedef struct
{
  double* CCDData;
  double* CalibData;
  double* AbsIntenData
}tFrameRecord;

And each item again should points to a double data array which defined as double data[3648].


First I tried this (in the namespace):
C#
public struct MTFrameRecordData
{
    public double[] CCDData;
    public double[] CalibData;
    public double[] AbsIntenData;
}

With this in a class called myUSBCam, that holds the wrappers:
C#
   //MTSSE_GetDeviceSpectrometerFrameData(int DeviceID, int SpectrometerID, int      WaitUntilDone, tFrameRecord* &FrameData );
   public int MTGetDeviceSpectrometerFrameData(int DeviceID, int SpectrometerID, int WaitUntilDone, MTFrameRecordData FrameData)
   {
       int numOut = MT_GetDeviceSpectrometerFrameData(DeviceID, SpectrometerID, WaitUntilDone, FrameData);

       if (numOut < 0)
       {
           MessageBox.Show("Error trying to SetDeviceExposureTime. No cameras found on USB 2.0 bus.", _camError, MessageBoxButtons.OK, MessageBoxIcon.Error);
       }

       return numOut;
   }

//...

   //MTSSE_GetDeviceSpectrometerFrameData(int DeviceID, int SpectrometerID, int WaitUntilDone, tFrameRecord* &FrameData );
   [DllImport("MT_Spectrometer_SDK.dll", EntryPoint = "MTSSE_GetDeviceSpectrometerFrameData", CallingConvention = CallingConvention.StdCall)]
   private static extern int MT_GetDeviceSpectrometerFrameData(int DeviceID, int SpectrometerID, int WaitUntilDone, MTFrameRecordData FrameData);

And then I call this in my main application (nInit is equal to 1):
C#
int numOut5 = 0;
while (numOut5 <= 0)
{
    System.Threading.Thread.Sleep(20);
    numOut5 = myUSBCam.MTGetDeviceSpectrometerFrameData(nInit, 1, 1, frameRecordData);
 }


With this I get the error message:


An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in USBCam.exe


Additional information: Cannot marshal 'parameter #4': Invalid managed/unmanaged type combination.


I also tried this for the declaration of the structure (again in the name space):


C#
[StructLayout(LayoutKind.Explicit)]
public struct MTFrameRecordData
{
    [FieldOffset(0)]
    public double[] CCDData;
    [FieldOffset(29184)]
    public double[] CalibData;
    [FieldOffset(58368)]
    public double[] AbsIntenData;
}


This produces the error messsage

An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in USBCam.exe
Additional information: Internal limitation: method signature is too complex or too large.

Can anyone help me with how to pass the structure from the unmanaged c++ dll into my c# application?


Begin more detailed description from the documentation:

SDK_API MTSSE_GetDeviceSpectrometerFrameData( int DeviceID, int SpectrometerID, int WaitUntilDone, tFrameRecord* &FrameData);
After sending the above MTSSE_StartGrabSpectrum() command, user can invoke this function to check whether the spectrum is ready and to get the spectrum.
Arguments: DeviceID -- the index of the device, Please refer to the notes of MTSSE_InitDevice() function.
SpectrometerID – the index of the spectrometer, should always be set to 1.
FrameRecordData - the pointer to the spectrum data structure if spectrum is ready or nil if spectrum is not ready. It is defined as:
typedef struct
{
double* CCDData;
double* CalibData;
double* AbsIntenData
}tFrameRecord;
And each item again should points to a double data array which defined as double data[3648].
CCDData is the raw pixel data,
CalibData is the CCDDataa with ETC calibration (if etc flag was turned on), if ETC is off, it should equal the CCDData.
AbsIntenData returns the absolute intensity data if AIC coefficients exists
WaitUntilDone – the frame grabbing process may need considerable time (depends on the exposure time setting and average frame number setting). User might set:
1: The API will be blocked until the spectrum is grabbed, the returned spectrum is pointed by the FrameRecordData, If the returned pointer is nil, “Time out” error occurs.
0: The API will not be blocked, if the spectrum is ready, the spectrum will be returned in FrameRecordData), otherwise, it will return nil.
Return: -1, if the grabbing command is not finished (when WaitUntilDone is “0”) or “Time out” error occurs (when WaitUntilDone is “1”).
1, if function succeeds.
Posted
Comments
[no name] 8-Aug-12 12:08pm    
Not an answer but thanks for posting a question that contains enough information to be solved. Quite a refreshing change from the usual drek we get.
AS01234 8-Aug-12 12:17pm    
I couldn't tell from the listing: Did it properly go to the c# discussion board?

[no name] 8-Aug-12 12:18pm    
Looks fine to me.

The problem is that in the C++ structure there are pointers, you are trying to convert those to arrays and that is definitely a no go.
You will have to use an intptr or something like that.
I found something that should get you in the right direction here :
http://blogs.msdn.com/b/jaredpar/archive/2008/11/05/dereference-a-double-intptr.aspx[^]
 
Share this answer
 
The reference from Philip Stuyck was very helpful, but I want to also acknowledge receiving help from the company that makes the spectrometers (mightexsystems.com).

I still don't like the solution, because now it uses unsafe code in the wrapper, essentially c++, to de-reference the double-pointer. Can anyone suggest pure safe c# code for this?

Here is the solution in all of it's glory. This parallels the description of my initial attempt above, but now it works:

In the namespace:

C#
public struct MTFrameRecordData
{
    public IntPtr CCDData;
    public IntPtr CalibData;
    public IntPtr AbsIntenData;
}


In the spectrometer class:

C#
public double[] RawDataArray;
public double[] CaliDataArray;
public double[] AbsIntenArray;


Also in the spectrometer class, the wrapper:

C#
// Note that the wrapper does not have the MTFrameRecordData
// structure as an argument. The return is passed directly into the
// public spectrometer class double[] arrays: RawDataArray,
// CaliDataArray, AbsIntenArray.
public int MTGetDeviceSpectrometerFrameData(int DeviceID,
     int SpectrometerID, int WaitUntilDone)
{
    IntPtr ptrFrameData = IntPtr.Zero;
    int numOut = MT_GetDeviceSpectrometerFrameData(DeviceID,
      SpectrometerID, WaitUntilDone, ref ptrFrameData);

    if (numOut < 0)
    {
       MessageBox.Show("Error in GetFrameData.", "Spectrometer error ",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        unsafe
        {
            MTFrameRecordData* FrameRecordPtr;
            double* RawDataPtr;
            double* CaliDataPtr;
            double* AbsIntenPtr;
            int i;

            FrameRecordPtr = (MTFrameRecordData*)ptrFrameData;
            RawDataPtr = (double*)FrameRecordPtr->CCDData;
            CaliDataPtr = (double*)FrameRecordPtr->CalibData;
            AbsIntenPtr = (double*)FrameRecordPtr->AbsIntenData;
            for (i = 0;
              i < Properties.Settings.Default.pixelArrayLength; i++)
            {
                RawDataArray[i] = *RawDataPtr++;
                CaliDataArray[i] = *CaliDataPtr++;
                AbsIntenArray[i] = *AbsIntenPtr++;
            }
        }
    }

    return numOut;
}


The p-invoke is still the same I think. Excuse the bad wrapping. The format here is narrow.

C#
[DllImport("MT_Spectrometer_SDK.dll", EntryPoint = "MTSSE_GetDeviceSpectrometerFrameData", CallingConvention = CallingConvention.StdCall)]
private static extern int MT_GetDeviceSpectrometerFrameData(int DeviceID, int SpectrometerID, int WaitUntilDone, ref IntPtr ptrFrameData);


Now the call in the main form:

C#
while (numOut5 <= 0)
{
    System.Threading.Thread.Sleep(100);
    numOut5 = mightexSpectrometer1.MTGetDeviceSpectrometerFrameData(nInit, 1, 1);
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900