Click here to Skip to main content
15,867,488 members
Please Sign up or sign in to vote.
5.00/5 (3 votes)
See more:
I have C# application that needs to interface with a native (unmanaged) DLL. The native DLL exposes its library through a C++ Class, so I decided to wrap the class with a C++/CLI DLL to be able to use it in the C# app. The problem centers on the implementation of a wrapper method within C++/CLI and how it is called from the managed C# app. The method is called with 2 buffers for the native code to write to even after this method returns. The native library will use a callback function to notify the managed application of new data. So I can't use pin_ptr<> which would be perfect if it had global scope. The C++/CLI DLL and the C# application compiles fine and runs without exceptions. I can see the data from the managed side on the native side. I can write to the data on the native side without issue. However, I cannot see what was natively written on the managed side. I think a copy is made somewhere possibly through the GCHandle.Alloc() call.

The native method has a structure called sHSLPCalibProfileData defined as
C#
typedef struct _sHSLPDataPt
{
    float fX;
    float fZ;
    int   iI;
    float fJ;
}sHSLPDataPt;

typedef struct _sHSLPCalibProfileData
{
    unsigned int uiHeader;
    unsigned int uiIO;
    unsigned int uiProfileID;
    unsigned int uiNbValidPts;
    sHSLPDataPt  sData[1280];
}sHSLPCalibProfileData;


I created analogous managed structures as follows:
C#
[StructLayoutAttribute(LayoutKind::Explicit, Size = 16)]
public value struct S_HSLPDataPt
{
    [FieldOffsetAttribute(0)] float     fX;
    [FieldOffsetAttribute(4)] float     fZ;
    [FieldOffsetAttribute(8)] int       iI;
    [FieldOffsetAttribute(12)] float    fJ;
};

[StructLayoutAttribute(LayoutKind::Explicit, Size = 16 + 16 * 1280)]
public value struct S_HSLPCalibProfileData
{
    [FieldOffsetAttribute(0)] unsigned int      uiHeader;
    [FieldOffsetAttribute(4)] unsigned int      uiIO;
    [FieldOffsetAttribute(8)] unsigned int      uiProfileID;
    [FieldOffsetAttribute(12)] unsigned int     uiNbValidPts;
    [FieldOffsetAttribute(16)] array<S_HSLPDataPt> ^sData;
};

The intent with the above was to make the footprint of the native and managed structures the same so that I could use a native pointer without issue.

Here is the implementation of the wrapper method:
VB
int SetDumpingBuffer(unsigned int uiSensorIndex, System::IntPtr ipBuf1, System::IntPtr ipBuf2)
{
    int ret = 0;

    // get native pointer to managed structure
    sHSLPCalibProfileData** p1 = static_cast<sHSLPCalibProfileData**>(ipBuf1.ToPointer());
    sHSLPCalibProfileData** p2 = static_cast<sHSLPCalibProfileData**>(ipBuf2.ToPointer());

    // native call
    ret = pHSLP->SetDumpingBuffer(uiSensorIndex, *p1, *p2, sizeof(sHSLPCalibProfileData));

    return ret;
}


Here is a contrived snippet of the implementation of the managed app calling the wrapper method:
private void cbDumping_Click(object sender, EventArgs e)
{
    int ret;
    // allocate gc memory for managed structure
    HSLPLibWrapper.S_HSLPCalibProfileData Buf1 = new HSLPLibWrapper.S_HSLPCalibProfileData();
    Buf1.sData = new S_HSLPDataPt[1280];
    HSLPLibWrapper.S_HSLPCalibProfileData Buf2 = new HSLPLibWrapper.S_HSLPCalibProfileData();
    Buf2.sData = new S_HSLPDataPt[1280];
    // tell gc to not collect Buf1 and Buf2 because native code has reference
    GCHandle gch1 = GCHandle.Alloc(Buf1, GCHandleType.Weak);
    GCHandle gch2 = GCHandle.Alloc(Buf2, GCHandleType.Weak);
    // get pointer to handle
    IntPtr ip1 = GCHandle.ToIntPtr(gch1);
    IntPtr ip2 = GCHandle.ToIntPtr(gch2);

    // make call to wrapper (hslp is managed wrapper)
    ret = hslp.SetDumpingBuffer(0, ip1, ip2);
    // free handle so gc can collect Buf1 and Buf2
    gch1.Free();
    gch2.Free();
}

I am fairly new to C++/CLI so if there is a better way to accomplish this I am all ears.
Posted

1 solution

It looks like Buf1 and Buf2 become eligible for garbage collection as soon as cbDumping_Click completes. This may be problematic if the callback runs after GC.

Since you know the sizes and the layouts of your structures, you could potentially use Marshal.AllocHGlobal/FreeHGlobal to prepare your buffers, and then use unsafe code with pointers to read your data C-style:
C#
unsafe {
    void* p = Marshal.AllocHGlobal(N); // Allocate N bytes
    ...
    float v = *((float*)(((byte*)p)+4)); // Read a float value at the offset of 4
    ...
    Marshal.FreeHGlobal(p); // Free the buffer
    ...
}
Of course, in your case, the allocation, the access, and the deallocation would happen in different methods. Deallocation should be done only when you are certain that the unmanaged code is not going to perform callbacks any longer.
 
Share this answer
 
Comments
David J Perez 8-Apr-11 12:47pm    
You're right about Buf1 and Buf2, i wanted to minimize my already too large question. I think what you propose with AllocHGlobal()will be my fallback position but I was hoping there was a more elegant solution. Thanks
dasblinkenlight 8-Apr-11 12:57pm    
To me, "native code" and "elegant solution" sound kind of awkward next to each other :)

You could also try GCHandleType.Pinned to avoid letting GC to move your buffer, but I am not sure if it's "kosher" in callback situations: MS recommends unpinning as soon as possible.
David J Perez 8-Apr-11 13:04pm    
I tried GCHandleType.Pinned but got an exception because my structure is not "blittable". There does appear to be ways to make unblittable structures blittable but I have not run that down yet. I think GCHandleType.Weak gets me the same effect but I have not tested it.

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