Passing a C# class object in and out of a C++ DLL class
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.
Related videos on Youtube
notsodev
Updated on October 22, 2022Comments
-
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 almost 12 yearsI 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 almost 12 yearsThis 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 almost 12 yearsYou 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 almost 12 yearspublic static extern void GetDLLObj( out CSObject csObj);
-
Ventsyslav Raikov almost 12 yearsMy 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 almost 12 yearsThank 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 almost 12 yearswhat is junk exactly? the obj's intTest1 field inside SetDLLObj(CClass obj)? could you please update your question with your current code?
-
notsodev almost 12 yearsYes, 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 almost 12 yearsI 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 almost 12 yearsThe 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 almost 12 yearsuh, 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 almost 6 yearsExactly what I was looking for thanks, no need for object, but object ref certainly. Good to keep the pattern functional