How is inner class in C++ wrapped in C# by xInterop NGen++?
In C++, an inner class or nested class is a class declared entirely within the body of another class, nesting class. Inner classes are basically a form of scope. C# supports inner class or nested class as well, so mapping a nested C++ class in a nesting C++ class to a nested C# wrapper class in a nesting C# wrapper class is doable in theory. But how?
The following native C++ code is a very simple example of declaring a nested class inside another class, a nesting C++ class. In the following example, the nested native C++ class is called NestedObject which is declared and implemented inside of the nesting C++ native class named SimpleObject.
class SimpleObject { public: SimpleObject() { } ~SimpleObject() { } class NestedObject { private: char szName[100]; public: NestedObject() { strcpy(szName, "NestedObject"); }; ~NestedObject() { } const char * GetName() { return szName; }; }; ... }
xInterop NGen++ (A C# wrapper generator for the native C++ DLL) fully supports nested class, nested struct/structure, nested enum, even nested anonymous struct/structure and nested anonymous enum via pure Explicit P/Invoke. Calling into a native nested C++ class from C# .NET and accessing its method and fields has no difference of calling a regular native C++ class.
We always look into the issues which other code generators such as like SWIG (Simplified Wrapper and Interface Generator) and CXXI are facing while working on implementing and improving xInterop NGen++. CXXI is not very active as of today. SWIG does not really support nested classes or its support is limited from reading its document, but it does provide some workarounds with defining the nested class inside the nesting class where it is forward declared, which means the actual declaration and implementation should be defined outside of the class where the nested class is forward declared in order for SWIG to process it. On SWIG’s to-do-list of 2013 Google Summer of Code, the difficulty is marked as medium to hard. I am not an expert of SWIG implementation or using SWIG to create C# wrapper semi-automatically, but I think its difficulty may have something to do with the fact that SWIG’s underlying mechanism of wrapping C++ is another layer of C style DLL, which means SWIG must wrap the native C++ DLL in a C style DLL which then gets wrapped in C# by using Explicit P/Invoke.
When wrapping the native un-managed native C++ class residing in a native C++ DLL, xInterop NGen++ takes a different approach than SWIG. xInterop NGen++ does not try to wrap the original C++ native un-managed DLL in another C style DLL except that it needs to generate a supplement C++ DLL when any instantiated template class is not exported in the original C++ DLL in order to export the instantiated template class, xInterop NGen++ uses the native C++ DLL as it is. The C# .NET code generated by xInterop NGen++ operates on the pointer of C++ object/instance exactly like a traditional C++ application linking with that native C++ DLL based on the information of the memory layout and the method signatures of a C++ object, including both exported methods and virtual methods. The resulting C# wrapper class has exact same interface of the corresponding native C++ class does, which enables developers to easily convert a C++ application to a C# application when using the native C++ DLL via the generated C# wrapper library.
To xInterop NGen++, any native un-managed C++ object is basically just a pointer pointing to the beginning of a specific block of memory in the un-managed native world. The layout, virtual function table and the exported methods of the C++ object are all known to xInterop NGen++. An instance of a nested class is just another native pointer. So it does not matter if the class of the object is nested or not. Basically speaking, being nested or not is just a name scope of the class definition in the original C++ source code.
The way which xInterop NGen++ chose to handle the native nested C++ class is same as the way of handling other regular native C++ classes, a corresponding C# .NET wrapper class gets generated for each native nested C++ class. As for the name scope or namespace, the C# .NET wrapper class of the native nested C++ class will be put inside of the C# .NET wrapper class of the C++ nesting class where the native C++ nested class resides so that the original C++ class structure can be maintained.
I have listed the meta data and class definition of the C# .NET wrapper class of the native nseting C++ class along with nested class below.
public class SimpleObject : SimpleLibraryBaseClass { public class NestedObject : SimpleLibraryBaseClass { public NestedObject(); public NestedObject(NestedObjectStruct objectStruct); public NestedObject(IntPtr thisObject, IntPtr thatObject, bool initialize, bool allocate = false); public static implicit operator NestedObjectStruct(SimpleObject.NestedObject o); public override string NativeClassName { get; } protected override int StorageSize { get; } public char szName { get; set; } public SimpleObject.NestedObject assign(SimpleObject.NestedObject thatObject); protected override void CleanupNativeResource(); public string GetName(); } public SimpleObject(); public SimpleObject(SimpleObjectStruct objectStruct); public SimpleObject(IntPtr thisObject, IntPtr thatObject, bool initialize, bool allocate = false); public static implicit operator SimpleObjectStruct(SimpleObject o); public override string NativeClassName { get; } protected override int StorageSize { get; } public NestedStruct tagNestedStruct { get; set; } public int Add(int x, int y); public SimpleObject assign(SimpleObject thatObject); protected override void CleanupNativeResource(); public void SetEnum(ref NestedEnum e); public void SetStruct(NestedStruct s); }
If you look into the preceding code carefully, you would find that the structure of the interface of the C# nested inner class has no much difference than a regular top-level C# wrapper class, they are both derived from the same base C# class, the only difference is that the nested inner class is declared and implemented in another C# wrapper class like it does in C++. It is a difference on the name scope in C++ native world, it is also just the same difference on the name scope in the C# managed world, so they are perfectly matched in both worlds.