Learning to call methods (C++ Reflection, part 2)
Ok, last time we got the basics of the system in place. Tonight we'll fix up some of the stuff that we had before (in case you were wondering, I'm figuring this out at the same time as you guys), then get the invocation of some functions.
First, our RuntimeInfo struct from last time is a little lacking and should be implementing a little better.
1: class MapComparer
2: {
3: public:
4: bool operator()( const char* s1, const char* s2 ) const
5: {
6: return strcmp(s1, s2) < 0;
7: }
8: };
9:
10: template< class T >
11: class RuntimeInfo
12: {
13: public:
14: static const unsigned int CLASS_NAME_LENGTH = 50;
15:
16: RuntimeInfo( unsigned int classId, const char* name );
17:
18: unsigned int GetClassId() const;
19: const char* GetClassName() const;
20:
21: void RegisterMethod( Method< T >* method );
22:
23: Method< T >* GetMethod( const char* name );
24: std::map< const char*, Method< T >*, MapComparer >* GetMethods();
25:
26: private:
27: unsigned int _classId;
28: char _className[ CLASS_NAME_LENGTH ];
29: std::map< const char*, Method< T >*, MapComparer > _methods;
30: };
The first changes to note is the change from a struct to a class. This is technically only a semantic change in C++, but we'll be adding functionality via functions so it's better to call it a class. Next, I moved the class ID and name to private member data, which it probably should have been in the first place. You should also notice that the class is now a templated class. The templace type will tell us the type for member methods will be called. The last important thing is the addition of the RegisterMethod function. This allows us to register any member function with the class so that we can later invoke it.
Alright, now that we've updated the RuntimeInfo class to support our Methods, let's go ahead and define those. =)
1: template< class OwnerType >
2: class Method
3: {
4: public:
5: typedef void (OwnerType::*m0)();
6:
7: Method( const char* name, m0 method );
8: virtual ~Method();
9:
10: const char* GetName() const;
11:
12: void Invoke( OwnerType& obj );
13:
14: protected:
15: Method( const char* name );
16:
17: private:
18: char* _methodName;
19: m0 _method;
20: };
Our Method class is pretty straight forward; it will be used to define functions that have no return type, and for now, take no parameters. First, this class is templated to define the type for the function that is being passed in. For instance, if I have a class MyInt and have exposed Double(), the type would be MyInt. Next thing to note is the typedef for the function signature; this is a function that returns void and takes no parameters. The next interesting piece is the Invoke() function; this is where all of the action will actually be done.
You might be wondering why I've created a protected Method constructor... that's a good question. The reason is that when I start extending this class, I want to be able to take advantage of the base functionality for storing the name.
Alright, so this gives us the ability to call functions that don't return anything, let's extend it so that we can call a function and have it return something interesting.
1: template< class OwnerType, class ReturnType >
2: class Function : public Method< OwnerType >
3: {
4: public:
5: typedef ReturnType (OwnerType::*f0)();
6:
7: Function( const char* name, f0 function );
8: virtual ~Function();
9:
10: ReturnType Invoke( OwnerType& obj );
11:
12: private:
13: f0 _method;
14: };
The major difference between this class and the Method class is that this class takes two template arguments: the type for the member function and the return type for the function. Also notice the overloading of the Invoke() function so that it returns the proper type.
And that's all there is to it. I'll post a little sample application that shows it in use.
1: int
2: _tmain( int argc, _TCHAR* argv[] )
3: {
4: Object o;
5: MyInt i;
6:
7: i.SetValue( 30 );
8:
9: Method< MyInt >* imi = i.GetRuntimeInfo()->GetMethod( "Double" );
10: Function< MyInt, int >* imi2 =
11: static_cast< Function< MyInt, int >* >( i.GetRuntimeInfo()->GetMethod( "GetValue" ) );
12:
13: cout << "-- OBJECT --" << endl;
14: cout << "\tClass ID: " << o.GetRuntimeInfo()->GetClassId() << endl;
15: cout << "\tClass Name: " << o.GetRuntimeInfo()->GetClassName() << endl;
16: cout << endl;
17:
18: cout << "-- MYINT --" << endl;
19: cout << "\tClass ID: " << i.GetRuntimeInfo()->GetClassId() << endl;
20: cout << "\tClass Name: " << i.GetRuntimeInfo()->GetClassName() << endl;
21: cout << "\tOriginal Value: " << i.GetValue() << endl;
22: imi->Invoke( i );
23: cout << "\tDouble Value: " << i.GetValue() << endl;
24: cout << "\tGetValue: " << imi2->Invoke( i ) << endl;
25: cout << endl;
26:
27: cout << "Press enter to exit." << endl;
28: cin.get();
29:
30: return 0;
31: }
So there we have it. We've built a reflection system in C++. Is it the most robust and feature rich, no, most definately not. For instance, there is not way to pass in arguments to either the Method or Function class, there is no way to call the constructor, although, arguably that is just registering the constructor with by using the Function class. But overall, I think I've pretty much met the goal that I set out to achieve.
If you have any questions or comments, I'd be happy to answer them, but I think I'm relatively happy with the reflection system I have in C++... for now atleast. =)
Source Code: Reflection.zip