C++ Scripting: Part 23 – Base Type APIs
Now that we have complete support overriding everything—methods, properties, indexers, events—that can be overridden in a base class or interface, there’s a bit of tidying up to do. In today’s article, we’ll take steps to make base types much more useful by inserting them into their proper place in the type hierarchy.
Table of Contents
- Part 1: C#/C++ Communication
- Part 2: Update C++ Without Restarting the Editor
- Part 3: Object-Oriented Bindings
- Part 4: Performance Validation
- Part 5: Bindings Code Generator
- Part 6: Building the C++ Plugin
- Part 7: MonoBehaviour Messages
- Part 8: Platform-Dependent Compilation
- Part 9: Out and Ref Parameters
- Part 10: Full Generics Support
- Part 11: Collaborators, Structs, and Enums
- Part 12: Exceptions
- Part 13: Operator Overloading, Indexers, and Type Conversion
- Part 14: Arrays
- Part 15: Delegates
- Part 16: Events
- Part 17: Boxing and Unboxing
- Part 18: Array Index Operator
- Part 19: Implement C# Interfaces with C++ Classes
- Part 20: Performance Improvements
- Part 21: Implement C# Properties and Indexers in C++
- Part 22: Full Base Type Support
- Part 23: Base Type APIs
- Part 24: Default Parameters
- Part 25: Full Type Hierarchy
- Part 26: Hot Reloading
- Part 27: Foreach Loops
- Part 28: Value Types Overhaul
- Part 29: Factory Functions and New MonoBehaviours
- Part 30: Overloaded Types and Decimal
Last week we completed support for overriding everything possible to override in a base type, but some of their structure still isn’t right. There are two issues we’ll tackle today.
First, all generated C++ classes for base types extended System::Object
instead of their proper base type. This is fine for interfaces, but doesn’t match the C# side when used with base classes. For example, here’s Queue
:
namespace System { namespace Collections { struct Queue : System::Object { // ... generated contents }; } }
This causes a problem: we can’t use this Queue
type like a normal Queue
. For example, we can’t call Clear
on it:
void Foo(Queue& queue) { queue.Clear(); // compiler error, Queue doesn't have Clear() }
This sounds like an easy adjustment. We just need to make Queue
extend Queue
:
namespace System { namespace Collections { struct Queue : System::Collections::Queue { // ... generated contents }; } }
Of course this won’t work because we can’t have the same class extending itself. We need to disambiguate the real class—Queue
—from this base class that game code is supposed to derive from. We can do that easily enough by changing its name to include “Base”:
namespace System { namespace Collections { struct BaseQueue : System::Collections::Queue { // ... generated contents }; } }
Now we have a class that’s properly inserted into the type hierarchy. Polymorphism now works with this type and allows the Clear
call to work as expected:
void Foo(BaseQueue& queue) { queue.Clear(); // OK, BaseQueue derives from Queue which has Clear() }
Game code using base types just needs to derive from the “Base” version now:
struct MyQueue : System::Collections::BaseQueue { // ... override methods, properties, indexers, and events };
This also has implications on the code generator’s JSON config file. In this example, we’d need two blocks:
{ "Types": [ { "Name": "System.Collections.Queue", "Methods": [ { "Name": "Clear", "ParamTypes": [] } ] } ], "BaseTypes": [ { "Name": "System.Collections.Queue", "OverrideProperties": [ { "Name": "Count", "Get": {}, "Set": {} } ] } ] }
We can’t add to the “BaseTypes” section without also adding to the “Types” section. This can be a little confusing and redundant, so let’s combine the two so it’s easier to configure the code generator:
{ "Types": [ { "Name": "System.Collections.Queue", "Methods": [ { "Name": "Clear", "ParamTypes": [] } ], "BaseTypes": [ { "OverrideProperties": [ { "Name": "Count", "Get": {}, "Set": {} } ] } ] } ] }
“BaseTypes” is now inside the individual types of the “Types” section. It’s just one more thing to generate for a given type. If we want the ability to derive from the type, we specify a base type there. Because they’re combined, we no longer need a “Name” field. That’s one less string to type and one fewer annoying typo that’ll cause the code generator to error.
With these changes we now have much more useful support for base types. They participate in the type hierarchy now, so we can use them just like regular types. We can use polymorphism to pass them in place of the types they extend and use their full public API. All of this is up on the GitHub project if you want to try it out or see how it was implemented.
Got questions about the project or article series? Post a comment or a GitHub Issue!