Any way to create a pointer to a method?

BlitzMax Forums/BlitzMax Programming/Any way to create a pointer to a method?

TomToad(Posted 2013) [#1]
In BMax, I can do this
Function AFunction:Int(B:Int)
	Return B+7
End Function

Local BFunction:Int(B:Int) = AFunction

Print BFunction(6)

I can even reference functions inside a Type.
Type MyType
	Global C:Int = 7
	
	Function AFunction:Int(B:Int)
		Return B+C
	End Function
End Type

Local BFunction:Int(B:Int) = MyType.AFunction

Print BFunction(6)

However, I can't seem to reference a Method, even when creating an instance of one.
Type MyType
	Global C:Int = 7
	
	Method AFunction:Int(B:Int)
		Return B+C
	End Method
End Type

Local BFunction:Int(B:Int) = MyType.AFunction

Print BFunction(6)
Type MyType
	Global C:Int = 7
	
	Method AFunction:Int(B:Int)
		Return B+C
	End Method
End Type

Local AType:MyType = New MyType

Local BFunction:Int(B:Int) = AType.AFunction

Print BFunction(6)

So, how do I create a pointer to that Method?


grable(Posted 2013) [#2]
You cant. At least not directly.

You can use BRL.Reflection though, to get at the pointer.
but the default implementation is a bit lacking, so youd need some extras.

And the resulting function prototype must include the type as an argument, since its a method.

Import BRL.Reflection

Type MyType
	Global C:Int = 7
	
	Method AMethod:Int(B:Int)
		Return B+C
	EndMethod
End Type

Local AType:MyType = New MyType

Local AMethod:Int( this:MyType, B:Int) = GetMethodPointer( AType, "AMethod")

Print AMethod( AType, 6)


Function GetMethodPointer:Byte Ptr( obj:Object, name:String)
	Extern "C"
		Function bbRefMethodPtr:Byte Ptr( obj:Object, index:Int )
	EndExtern
	
	Local t:TTypeId = TTypeId.ForObject(obj)
	If Not t Then Return Null
	
	Local m:TMethod = t.FindMethod(name)
	If Not m Then Return Null
	
	Return bbRefMethodPtr( obj, m._index)
EndFunction




TomToad(Posted 2013) [#3]
Thanks a lot. I knew reflection had to be the answer, but just couldn't figure it out. This also answers my second question, how to access more than one instance through the reference function.
SuperStrict
Import BRL.Reflection

Type MyType
	Field C:Int
	
	Method AMethod:Int(B:Int)
		Return B+C
	EndMethod
End Type

Local AType:MyType[4]
For Local i:Int = 0 To 3
	AType[i] = New MyType
	AType[i].C = i*2
Next

Local AMethod:Int( this:MyType, B:Int) = GetMethodPointer(AType[0],"AMethod")

For Local i:Int = 0 To 3
	Print AMethod(AType[i],6)
Next




Function GetMethodPointer:Byte Ptr( obj:Object, name:String)
	Extern "C"
		Function bbRefMethodPtr:Byte Ptr( obj:Object, index:Int )
	EndExtern
	
	Local t:TTypeId = TTypeId.ForObject(obj)
	If Not t Then Return Null
	
	Local m:TMethod = t.FindMethod(name)
	If Not m Then Return Null
	
	Return bbRefMethodPtr( obj, m._index)
EndFunction



Yasha(Posted 2013) [#4]
The "correct" way to do this in a language like BlitzMax is to make your "methods" explicit as simple functions that take an explicit self object as the first parameter.

To go into this further, consider: what are you actually hoping to achieve by doing this?

The difference between a method and a function in BlitzMax is that a method has implicit user data associated with it, while a function is just a code vector. When you call a method, the first thing it gets access to - even before its parameters - is its host object. So presumably what you want is to be able to access that function+implicit data after separating it from its containing object, as though it were simply contained in a data field and you were extracting it.

Unfortunately this won't work because the internal implementation of the method is just a function with a hidden first parameter. The method data doesn't actually contain a built-in link to the host object; when you call a method on an object, the dot syntax doesn't just mean "extract this from the structure", it's actually a completely different operation that includes passing the structure it was looked up in.

In other words, the entire object-dot-method construct is required for the "implicit data" to work; that is how it is made available. If you take the machine code vector out of the object and use it on its own, it will no longer have magical hidden data associated with it, and become just a dumb function (as you can see above in the reflective code, it needs obj to be passed explicitly again even thought was written as a method).

Unfortunately the entire concept of a "method pointer" is logically unsound. A method isn't an entity that has any kind of independent existence. It can logically exist only in the context of a parent scope object. Without that parent, it's exactly the same as a function with explicit self.


"But Yasha, I can do this in JavaScript!" No, you can't. In JavaScript, all functions are methods of at least one hidden scope, and the function call operation is therefore always used to access that parent scope (this can be in addition to the "this" object because JavaScript is multi-paradigm). In other words, in a language where it looks like you can do this, function calls are more than just jumps to a code vector the way they are in BlitzMax or C: they're a more involved operation (this also makes them slower) that also has to look up the self object the way a BlitzMax method does explicitly with the dot. In effect JavaScript (and Scheme, and Clang, and {edit} for that matter the code in the post below ...) give you this power at the cost of "real" fast procedure calls.

The correct object-oriented way to associate data+action is to bundle them together into an object with a method that does that thing, and call the method of the object when you want to use them together. The idea of functions that carry data with them is from "functional programming", and FP and OOP are orthogonal, i.e. anything you can do with one, you can do with the other: in this case, an FP "closure" function is directly equivalent to an OOP object with a single "call" method, except that you can invoke the "call" method using bracket syntax.


To summarise:
-- method pointers are not a logical concept, as methods have no independent existence
-- if you cheat using reflection, they stop being methods (implementation detail!)
-- other languages that look like they can do this are actually hiding more stuff under the function call syntax that BlitzMax doesn't have
-- using a method on an object is the idiomatic way to associate an action with data in this language

So. If you want to associate data with action... you should design your code to expect an action delegate object. If you want to extract and potentially swap functionality within an object, write it with functions as members, not methods (there are a whole load of other reasons why using methods for this one would be logically incoherent), and give the functions an explicit self parameter.

If you need to take apart someone else's code, then reflection is probably the right way to do it, and also none of what I said above is relevant to your situation anyway. (But please consider a rewrite if you're using reflection on your own code: pretty much without exception it means you're doing something in a very inefficient and roundabout way compared to how it "should" be done.)


grable(Posted 2013) [#5]
If you need real method references (function pointers linked to instances) or just cant stand passing the this argument explicitly,
you can use the functions below:
Function GetMethodReference:Byte Ptr( obj:Object, name:String)
	Extern "C"
		Function bbGCRetain( obj:Object)
	EndExtern
	
	Function get_displacement:Int( where:Byte Ptr, with:Byte Ptr, size:Int)
		Return (with - where) - size
	EndFunction

	Local fn:Byte Ptr = GetMethodPointer( obj, name)
	If Not fn Then Return Null

	Local p:Int Ptr = Int Ptr MemAlloc(32)
	
	' store a reference to the object instance
	bbGCRetain obj	
	p[0] = Int Ptr(Varptr obj)[0]		
	p :+ 1
		
	' create method trampoline
	Local o:Byte Ptr = Byte Ptr(p)

	' pop EAX
	o[0] = $58; o :+ 1	
	' push <obj>
	o[0] = $68; o :+ 1
	Int Ptr(o)[0] = p[-1]; o :+ 4
	' push EAX
	o[0] = $50; o :+ 1	
	' jmp <fn>
	o[0] = $E9; o :+ 1	
	Int Ptr(o)[0] = get_displacement( o, fn, 4)

	Return p
EndFunction

Function FreeMethodReference( ref:Byte Ptr)
	Extern "C"
		Function bbGCRelease( obj:Int)
	EndExtern
	
	If Not ref Then Return	
	Local p:Int Ptr = Int Ptr(ref) - 1
	bbGCRelease p[0]
	MemFree p
EndFunction



TomToad(Posted 2013) [#6]
I am actually trying to create a VM in which I have a type CPU. The function pointers are stored in an array with the index corresponding to an opcode. Figured this would be a faster and more elegant way of doing things rather than a list of Select..Case commands. Here is a tiny version of what I did using type functions. This version uses just 3 opcodes, 0 = Print contents of buffer, 1 = stuff the next byte onto the buffer as an ASCII character, 3 = end program. Doesn't do much, but like I said, it is a simplified version for an example.

I thought I might try create multiple CPUs using threading, but that would seem to require more than one instance of the CPU type, hence the need for pointers to Methods rather than Functions.


Yasha(Posted 2013) [#7]
Here's a "more OO" way to do that using delegates (probably full of errors but you should get the general idea):



It also illutrates the point that a delegate/closure/first-class-function in other languages is isomorphic to an object with an application method.

In this case though, it would have been easy to just make all of the opcode function pointers accept a single CPU object as their sole parameter: depending on how similar this is to the real thing I would have thought that would still be the best way to do this.


Not that I wish to suggest grable's mini-JIT is anything other than sheer unbridled awesome, but it's not exactly the "cleanest" way to go about doing something as (comparatively) simple as this.


grable(Posted 2013) [#8]
Not that I wish to suggest grable's mini-JIT is anything other than sheer unbridled awesome, but it's not exactly the "cleanest" way to go about doing something as (comparatively) simple as this.
It is indeed a hack ;)

There was even a bug in the trampoline it generated (pushing wrong this pointer), but the sample was so simple so it worked anyway hehe


In this case though, it would have been easy to just make all of the opcode function pointers accept a single CPU object as their sole parameter: depending on how similar this is to the real thing I would have thought that would still be the best way to do this.
Thats what i would do too, and maybe even forgo the bytecode dispatch and have the code stream be function pointers directly (if speed is your thing, or just feeling adventures)


ziggy(Posted 2013) [#9]
I had the same problem when designing Jungle Gui with Monkey and finally did this workaround to "emulate" delegates:
http://code.google.com/p/junglegui/source/browse/eventhandler.monkey

Also implemented an eventhandler class. It's Monkey but it is very similar to BlitzMax


TomToad(Posted 2013) [#10]
In this case though, it would have been easy to just make all of the opcode function pointers accept a single CPU object as their sole parameter: depending on how similar this is to the real thing I would have thought that would still be the best way to do this.
This seems to be the best way to accomplish what I want to do. Here is a threaded version of my previous demo with multiple CPUs



ImaginaryHuman(Posted 2013) [#11]
Instead of pointers to methods, to handle multiple virtual cpu's, you could just always pass an object reference to every function?