Dynamically Create Class

BlitzMax Forums/BlitzMax Programming/Dynamically Create Class

BLaBZ(Posted 2013) [#1]
Is something like this possible?

local t:TTypeId = TTypeId.CreateClass("TClass")
t.AddMethod(FuncPtr:byte Ptr)


I know these functions don't exists, but is something like this possible?


Yasha(Posted 2013) [#2]
Not easily. It can be done; fundamentally all you need to do is allocate the object using malloc and fill it out in a pattern that matches any other class; you'd then be able to instantiate it using the reflection commands as you would with a hardcoded class. You can find the definitions of class structures in the BRL.Blitz module if I recall correctly.

However, if you want to do things like this, you're seriously using the wrong language. You should look into JavaScript, Lua, Python... something that actually supports dynamic programming. BlitzMax isn't designed to work this way; other languages are.

Anyway it's quite rare even in highly dynamic languages to actually create new classes at runtime, because usually that means you wouldn't have any statically known way to use them (if you had to create it you don't know what methods it supports). 99% of dynamic class use cases can be served equally well or better by static interfaces instead (not a native BlitzMax feature, but you can find a hack-around here - still not how Max was designed but it's much closer in spirit; or use Monkey which supports them natively).

You might also find you can factor your dynamic behaviour down to a set of function pointers or delegate objects that can be put into statically structured wrapper objects, for a much simpler and more streamlined approach (creating completely new things inside reflection is a bit pointless because it introduces a lot of bumph you don't need on top of the dispatch system).


BLaBZ(Posted 2013) [#3]
What I would like to do is dynamically add pre and post functions to already existing methods.

Ideally, I would use method pointers to overwrite the existing methods.

But, I don't want to change my code base. I want to "intercept" method calls simple by importing this file\module.


Yasha(Posted 2013) [#4]
Ah. "Advice" in aspect-oriented programming ring any bells? I meant to develop a framework for that. Never got around to it...

You could use a similar technique to that linked Interfaces framework: extend the JIT stubs so that instead of just forwarding Self by calling the selected method's function pointer, they forward Self and the selected method's function pointer by calling the stored advice function.


BLaBZ(Posted 2013) [#5]
I just wiki'd "advice" in aspect oriented programming and it's pretty much exactly what I'm looking for.

Hmm. I don't entirely understand the c++ aspect of the interface.

I'll have to look more into this.


TomToad(Posted 2013) [#6]
Not completely sure what you are trying to do. Maybe something like this?



BLaBZ(Posted 2013) [#7]
Nope! This requires modification to your code base.

Ideally I would use method ptr's but it's not possible in blitzmax.


Yasha(Posted 2013) [#8]
To clarify -

Do you want to permanently advise whole classes, so this operation is basically just for convenience and maintentance's sake and could also be achieved with a hand-rewrite; or do you want be able to apply advice to individual objects while leaving others of the same type untouched?

The former is a lot easier than the latter. First obvious thing is that it doesn't require you to create new classes, only change existing ones.


I don't entirely understand the c++ aspect of the interface.


Which bit of which interface?


BLaBZ(Posted 2013) [#9]
I want to temporarily advice whole classes, but I don't always want this functionality so it couldn't be achieved by a hand rewrite.

Lets say I want to profile a method and figure out how much time is spent collectively executing this method for each game loop.

So my code looks something like this -


Type MyClass

	Method AMethod()
	
		'Do stuff
	
	End Method

End Type



By simply "importing" the following code I would then be able to profile the method.

Global ExecuteTime:Int

Type Profile

	Field MethodPtr()
	
	Method Execute()
		Local s:Int = MilliSecs()
		MethodPtr()
		Local e:Int = MilliSecs()
		ExecuteTime:+e-s
	End Method

End Type

Profile.MethodPtr = MyClass.AMethod
MyClass.AMethod = Profile.Execute


When I no longer want to profile, I would just remove the "import"

Does that make sense?


Hardcoal(Posted 2013) [#10]
Maybe what you want is what Ive done.
Add behavior to an object?


Type classExtender
   Play() Asbtract
End Type

Type Rotate extends classExtender

  method Play()
     '... do stuff
  end method()

End Type

Type AnObject
    Field ObjectClasses:Tlist      'How the Object will Behave.
End Type


listaddlast (AnObject.ObjectClasses, New Rotate)



now, a play process play threw all the object behaviours

not sure if thats what your looking for but this allow you to add and remove behaviour from an object.

ive done much more the this simple exmaple


Yasha(Posted 2013) [#11]
Does that make sense?


Indeed it does.

What you need to be able to do is either to copy a new set of function pointers over the class's vtable (storing the original somewhere safe), or to replace the class pointer within individual objects. You can view the structures of everything in BRL/Blitz/object.h if I recall correctly. Class pointer is at the start of the object header (eight bytes before the beginning of an object); vtable begins from class[4]. You could also try just replacing the constructor of the old class with that of the subclass, if all objects will belong to one group or the other and their lifetimes won't cross the advised/non-advised boundary.

It should be possible to do this without any JIT (dynamic machine code generation) as long as you want it to be on a per-class basis; you can probably do something like extend the class with a subclass that wraps around the methods, and then swap the vtable to the subclass at runtime.

Definitely going to build a proper AOP framework next time I'm able to use BlitzMax. (Don't hold your breath.) Thinking about this is fun, wish I could give better suggestions from memory.


BLaBZ(Posted 2013) [#12]
is it the vfns[32] variable?

struct BBClass{
	//extends BBGCPool
	BBClass*	super;
	void		(*free)( BBObject *o );
	
	BBDebugScope*debug_scope;

	int		instance_size;

	void		(*ctor)( BBObject *o );
	void		(*dtor)( BBObject *o );
	
	BBString*	(*ToString)( BBObject *x );
	int		(*Compare)( BBObject *x,BBObject *y );
	BBObject*	(*SendMessage)( BBObject *m,BBObject *s );
	void		(*_reserved1_)();
	void		(*_reserved2_)();
	void		(*_reserved3_)();
	
	void*	vfns[32];
};


sorry I'm not very familiar with c\c++ but if I'm heading in the right direction I'll read up on it :)


Yasha(Posted 2013) [#13]
Yeah, although due to quirks of the way C works (there's no ++ in this code), that doesn't mean what it would mean if it were expressed in BlitzMax.

1) Everything in C is a value; there are no references (pointers have to be taken explicitly, and are themselves first-class values; you can never get "invisible" references the way you handle objects in Blitz). So that array vfns isn't an object referenced in the last field of the struct - it is the last 32 fields of the struct, accessible by number. The same block of memory starts at super and carries seamlessly on right to the end of the vfns array.

2) Because of this, the actual function vtable begins before vfns - the New, Delete, ToString, Compare and SendMessage methods - you can see them there by name - appear before the numbered fields because, since they're part of the root Object type's signature, we know they'll always be there so may as well not mess around. They're really to be considered part of the array (the _reserved_ functions are in case Object ever got more methods - it has room for three extensions without breaking the offsets of existing compiled Max code).

3) C doesn't check array accesses are within bounds. That means the 32 there is likely unconnected to the actual size of a class object - create a bigger class and you can go on accessing elements of vfns as long as there's space to index into.

( 3.5) The actual accesses are of course being done by Blitz-generated machine code, but C is defining the protocol to use. )

4) On x86, every field of that struct and element of vfns has size 4, which is the same as a Blitz Int. So if you get the class pointer, convert it to an Int Ptr, you can get each element by its simple field position! So to get ctor, use class[4], to get ToString, use class[6], and to get vfns[3], use class[15].

5) Because all classes are allocated statically, all normal method offsets are known and there is no need for the class object's size to be recorded anywhere. This means to allocate new classes or to read all fields from an existing class, you'll need to compute its size. You can probably use reflection to get its method count and go from there.


BLaBZ(Posted 2013) [#14]
This is fun! Thanks Yasha for helping me explore this. I really enjoy discovering the backend of blitzMAX.

I've been reading up on some good c references and have some real noob questions.

The following references a BBString pointer, the "variable" is (*ToString)( BBObject *x ). I've never seen anything similar to this so I'm not sure what to google to find out what this means lol.
BBString*	(*ToString)( BBObject *x );


I understand the concept of virtual tables from a high level, though this specific implementation has me a little fuzzy.

Thanks!


Yasha(Posted 2013) [#15]
That syntax declares ToString as a pointer to a function, which accepts one BBObject* as its argument and returns one BBString*. C function pointer syntax is weird (putting the name in the middle of the declaration...), and for any declaration more complicated than that most people just give up and use typedefs. There's a handy utility to translate C declarations to plain English (and English to C, actually) at http://cdecl.org/

Although C proper doesn't define this, note also that on x86 all pointers are the same size and don't carry any metadata - this means that you can freely convert between different function pointer types and also void* (in fact, this is exactly what you've already been doing in BlitzMax with Byte Ptr). So the fact that vfns is declared as an array of void* doesn't affect the machine code at all and the method pointers placed there - of many different Max types - will work normally, because they're just code addresses. Similarly the reserved method slots could end up taking any arguments later on. I expect Mark declared vfns as holding void* simply because it's shorter and none of those methods will ever be called from C anyway so it doesn't need to care.

The named methods on Object are declared in full (proper names and types) so that they can work correctly with the C code in the runtime which manipulates Object, not because the generated Max code needs to see that stuff.


Vlad(Posted 2013) [#16]
Some advices:
1. Use event system (Observer pattern) with support of addition handlers before and after or with priority.
Something like:


2. Use simplified event system - callbacks. But you have to add additional code to each method of type.


3. Brute Hack :) Overwrite VMT item of desired method to the new method/function, which can call previous and any of additional methods. Use for these reflections and bbRefMethodPtr extern function.