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

19,092

Solution 1

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!

Solution 2

I believe you should declare your returning method like this

__declspec(dllexport) void getDLLObj(__out CClass* &obj)

and respectively the C# prototype

public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
      ref CSObject csObj);

the inbound method should take a pointer to CClass too, the C# prototype is ok.

Share:
19,092

Related videos on Youtube

notsodev
Author by

notsodev

Updated on October 22, 2022

Comments

  • notsodev
    notsodev over 1 year

    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!

  • notsodev
    notsodev almost 12 years
    I get a "pointer to reference illegal" error when trying to compile the DLL. Also defining the C++ function as a reference gives a "connot access private member in class" error for the public class. If I define the returning function as a pointer to the class it does change the output in the C# app, however the class junk I mentioned passed into the DL changes to 0 values instead. Could this be related to another issue?
  • notsodev
    notsodev almost 12 years
    This is interesting because I had a feeling it was something related to memory access between the two and I haven't ran accross GCHandle. But using this I would need to use an IntPtr type in place of passing my CSObject. Is there some way to convert the CSObject class to an IntPtr? Otherwise I cannot pass in the class contents. Thanks!
  • user629926
    user629926 almost 12 years
    You just replace CClass with CClass* or void* and you can use it as IntPtr on C# side. Do you have do change its values on C++ side? Also C# class CClass marshals automatically as CClass*. You coud try declaring your class as struct and use
  • user629926
    user629926 almost 12 years
    public static extern void GetDLLObj( out CSObject csObj);
  • Ventsyslav Raikov
    Ventsyslav Raikov almost 12 years
    My mistake, it has to be either a reference to pointer or pointer to pointer - I fixed my sample code. You need to use reference to pointer in your getDLLObj and just a pointer in the SetDLLObj and fix your C# prototype for getDLLObj by adding the ref keyword on the parameter.
  • notsodev
    notsodev almost 12 years
    Thank you Bond, your suggestions helped with removing the junk values being stored inside the DLL. However, I still have my issue of a 0 value being passed in, where as it should be 1234. Believe there is still an issue between the unmanged and managed memory as @user629926 brought up. I appreciate your help and any other thoughts would be great!
  • Ventsyslav Raikov
    Ventsyslav Raikov almost 12 years
    what is junk exactly? the obj's intTest1 field inside SetDLLObj(CClass obj)? could you please update your question with your current code?
  • notsodev
    notsodev almost 12 years
    Yes, I do have to change the value on the C++ side. I am able to get the 1234 value passed in on the C# side (thanks to @Bond) when it comes back out, but any changes I attempt in the code do not alter the out/return object value. I am also still showing the objects value as 0 in the C++ code.
  • notsodev
    notsodev almost 12 years
    I did try changing the C# class to a struct but it produced some marshalling problems. For some reason int intTest2 could not be marshalled to the C++ counterpart.
  • notsodev
    notsodev almost 12 years
    The junk I was refering to was random int values from the object pointer I'm assuming. Changing the code got rid of the junk values but replaced them with 0 for the intTest1 value within the C++ code. If I'm right it just means it's a null CClass object being received? The pointer is no longer pointing to a random value. I edited the code above to reflect changes. Thanks!
  • Ventsyslav Raikov
    Ventsyslav Raikov almost 12 years
    uh, I just saw this but... you cant just marshal a managed object(CSObject) to an unmanaged object(CClass) like this. It's just not gonna work, the layout is different - your CClass inherits from CObject... ah... I don't think that's possible, I guess you should use COM for this, or managed C++(C++/CLI)
  • Jamie Nicholl-Shelley
    Jamie Nicholl-Shelley almost 6 years
    Exactly what I was looking for thanks, no need for object, but object ref certainly. Good to keep the pattern functional