Passing a C# class object in and out of a C++ DLL class

c#, c++, dllimport, marshalling, pinvoke

Solution

Bond was correct, I can't pass an object between managed and unmanaged code and still have it retain its stored information.

I ended up just calling C++ functions to create an object and pass the pointer back into C#'s IntPtr type. I can then pass the pointer around to any C++ function I need (provided it's extern) from C#. This wasn't excatly what we wanted to do, but it will serve its purpose to the extent we need it.

Here's C# the wrapper I'm using for example/reference. (Note: I'm using StringBuilder instead of the 'int intTest' from my example above. This is what we wanted for our prototype. I just used an integer in the class object for simplicity.):

class LibWrapper
{
    [DllImport("CPPDLL.dll")]
    public static extern IntPtr CreateObject();
    [DllImport("CPPDLL.dll")]
    public static extern void SetObjectData(IntPtr ptrObj, StringBuilder strInput);
    [DllImport("CPPDLL.dll")]
    public static extern StringBuilder GetObjectData(IntPtr ptrObj);
    [DllImport("CPPDLL.dll")]
    public static extern void DisposeObject(IntPtr ptrObj);
}

public static void CallDLL()
{
    try
    {
        IntPtr ptrObj = Marshal.AllocHGlobal(4);
        ptrObj = LibWrapper.CreateObject();
        StringBuilder strInput = new StringBuilder();
        strInput.Append("DLL Test");
        MessageBox.Show("Before DLL Call: " + strInput.ToString());
        LibWrapper.SetObjectData(ptrObj, strInput);
        StringBuilder strOutput = new StringBuilder();
        strOutput = LibWrapper.GetObjectData(ptrObj);
        MessageBox.Show("After DLL Call: " + strOutput.ToString());
        LibWrapper.DisposeObject(ptrObj);
    }
    ...
}

Of course the C++ performs all the needed modifications and the only way for C# to access the contents is, more or less, by requesting the desired contents through C++. The C# code does not have access to the unmanged class contents in this way, making it a little longer to code on both ends. But, it works for me.

This is the references I used to come up with the basis of my solution: http://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class

Hopefully this can help some others save more time than I did trying to figure it out!

Problem

I've been working on a prototype code application that runs in C# and uses classes and functions from older C++ code (in the form of an imported DLL). The code requirement is to pass in a class object to the unmanaged C++ DLL (from C#) and have it be stored/modified for retrieval later by the C# application. Here's the code I have so far... Simple C++ DLL Class: ``` class CClass : public CObject { public: int intTest1 }; ``` C++ DLL Functions: ``` CClass *Holder = new CClass; extern "C" { // obj always comes in with a 0 value. __declspec(dllexport) void SetDLLObj(CClass* obj) { Holder = obj; } // obj should leave with value of Holder (from SetDLLObj). __declspec(dllexport) void GetDLLObj(__out CClass* &obj) { obj = Holder; } } ``` C# Class and Wrapper: ``` [StructureLayout(LayoutKind.Sequential)] public class CSObject { public int intTest2; } class LibWrapper { [DLLImport("CPPDLL.dll")] public static extern void SetDLLObj([MarshalAs(UnmanagedType.LPStruct)] CSObject csObj); public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] ref CSObject csObj); } ``` C# Function Call to DLL: ``` class TestCall { public static void CallDLL() { ... CSObject objIn = new CSObject(); objIn.intTest2 = 1234; // Just so it contains something. LibWrapper.SetDLLObj(objIn); CSObject objOut = new CSObject(); LibWrapper.GetDLLObj(ref objOut); MessageBox.Show(objOut.intTest2.ToString()); // This only outputs "0". ... } } ``` Nothing but junk values appear to be available within the DLL (coming from the passed in C# object). I believe I am missing something with the class marshalling or a memory/pointer issue. What am I missing? Edit: I changed the above code to reflect changes to the method/function definitions, in C#/C++, suggested by Bond. The value (1234) being passed in is retrieved by the C# code correctly now. This has exposed another issue in the C++ DLL. The 1234 value is not available to the C++ code. Instead the object has a value of 0 inside the DLL. I would like to use predefined C++ functions to edit the object from within the DLL. Any more help is greatly appreciated. Thanks!

Original source