Code archives/Algorithms/Ad-Hoc Interfaces
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| how its done: It abuses a Reflection feuture (not a bug!) to create Abstract types. Even though these instances are in an invalid state when created, each method can be patched to point to a method of any other type. Each method points to a trampoline that sets up the call so as to swap the instance of the Interface with the instance of the actual type. After this, the interface behaves like any other object, completly transparent to BlitzMax and the GC. note: the interface-types dont have to be abstract, they can have method bodies and fields, but they will not be available to the methods that are patched in or visa verca. note: Ive tried to make creation of interfaces as fast as possible at runtime by caching which types adhere to which interfaces. The interface-classes are also cached in a separate map. So at most there is 3 map lookups per instantiation: 1 lookup for interface-class (if a string is used) 1 lookup through reflection to get type of implementation object 1 lookup for membership of implementation-type in interface-class !!!! Also, i use some non-standard modifications to BRL.Reflection, notably Object->Object[] casting (search for HACK in the source if you dont have this) And a simple function that needs to be added to BRL.Reflection/reflection.cpp.
void *bbSetMethodPtr( BBObject *obj, int index, void *ptr){
void *old = *( (void**) ((char*)obj->clas+index) );
*( (void**) ((char*)obj->clas+index) ) = ptr;
return old;
}
see top of source-file for more info. Heres an example: SuperStrict Import "adhocintf.bmx" InitializeInterfaces() Type TTheType Method TheMethod() Print "The Method!" EndMethod EndType Type TTheOtherType Method TheMethod() Print "The Other Method!" EndMethod EndType Type ITheInterface Extends Interface Method TheMethod() Abstract EndType Local obj:TTheType = New TTheType Local intf:ITheInterface If QueryInterface( intf, "ITheInterface", obj) Then intf.TheMethod() EndIf Local list:TList = New TList list.AddLast New TTheType list.AddLast "this isnt an interface" list.AddLast New TTheOtherType For Local intf:ITheInterface = EachIn EnumInterface( "ITheInterface", list) intf.TheMethod() Next | |||||
Rem
Type Interfaces
this unit adds ad-hoc Interfaces via Abstract Types.
!!!!!!!
note that i use a modified BRL.Reflection supporting Object->Object[] Type casting.
If this doesnt work For you, search For HACK And enable the line after it
(and disable the one right after that) there are 3 such places.
you will allso have To add this Function To "BRL.Reflection/reflection.cpp" Or make en equivalent in blitzmax.
void *bbSetMethodPtr( BBObject *obj, int index, void *ptr){
void *old = *( (void**) ((char*)obj->clas+index) );
*( (void**) ((char*)obj->clas+index) ) = ptr;
return old;
}
!!!!!!!
public interface:
Type TInterfaceClass
Type Interface
.. these two interfaces are wrappers for blitzmaxs internal enumeration "interfaces"
Type IEnumerable ' ObjectEnumerator()
Type IEnumerator ' HasNext() / NextObject()
Type TInterfaceEnum
Type TInterfaceEnumArray
Function InitializeInterfaces( verify:Int)
.. initializes all Interface classes and caches all interface->type lookups for faster runtime queries
.. [verify]: If True, verifies ReturnType and ArgumentTypes. Default is True in debug mode, False in Release mode.
Function LookupInterfaceClass:TInterfaceClass( name:String)
.. lookup an interface class by name
Function QueryInterface:Int( out:Interface Var, interfacename:Object, implementation:Object, verify:Int)
.. checks if the methods in [interfacename] are present in [implementation]
.. on Success: returns True and [out] contains new interface
.. on Failure: returns False And [out] contains Null
.. [interfacename]: either a String representing an interface Or a TInterfaceClass instance
Function UpdateInterface:Int( out:Interface Var, implementation:Object, verify:Int)
.. updates an interface instance to a new [implementation]
.. on Success: returns True and [out] is patched To the New implementation
.. on Failure: returns False and [out] is left unchanged
Function EnumInterface:TInterfaceEnum( interfacename:Object, interfaces:Object, verify:Int)
.. enumerates over objects in [interfaces], either Arrays Or IEnumerable compatible
.. and queries each element for [interfacename], skips non-compliant objects
interface defenition:
Type TTheType
Method TheMethod()
Print "The Method!"
EndMethod
EndType
Type TTheOtherType
Method TheMethod()
Print "The Other Method!"
EndMethod
EndType
Type ITheInterface Extends Interface
Method TheMethod() Abstract
EndType
interface instantiation:
Local obj:TTheType = New TTheType
Local intf:ITheInterface
If QueryInterface( intf, "ITheInterface", obj) Then
intf.TheMethod()
EndIf
interface enumeration:
Local list:TList = New TList
list.AddLast New TTheType
list.AddLast "this isnt an interface"
list.AddLast New TTheOtherType
For Local intf:ITheInterface = EachIn EnumInterface( "ITheInterface", list)
intf.TheMethod()
Next
EndRem
SuperStrict
Import BRL.Map
Import BRL.LinkedList
Import BRL.Reflection
Private
?debug
Const DEFAULT_VERIFY_METHOD:Int = True
' logging modes
' 0 = no logging
' 1 = basic 1 line log item
' 2 = same as 1 with extra sub items
Const LOG_VERBOSE:Int = 0
?Not debug
Const DEFAULT_VERIFY_METHOD:Int = False
?
Const INTERFACE_TYPENAME:String = "Interface"
' from BRL.Reflection
Extern
Function bbRefMethodPtr:Byte Ptr( obj:Object, index:Int)
Function bbSetMethodPtr:Byte Ptr( obj:Object, index:Int, p:Byte Ptr) ' reflection.cpp addition
'HACK: enable this and the other 2 HACK spots if object->array casting doesnt work
'Function bbRefAssignObject( p:Byte Ptr,obj:Object)
EndExtern
'
' method trampoline
'
Const TRAMPOLINE_SIZE:Int = 16 ' size of each block, not the same as method_trampoline.Length
Const METHOD_OFFSET:Int = 7 ' offset of method pointer in trampoline
Const IMPLREF_OFFSET:Byte = 8 ' must be byte
Global method_trampoline:Byte[] = [ ..
$59:Byte, .. ' pop <ecx> ; store return-addr
$58:Byte, .. ' pop <eax> ; get interface-ptr
$FF:Byte, $70:Byte, IMPLREF_OFFSET, .. ' push dword [eax + offs] ; push implementation-ptr
$51:Byte, .. ' push <ecx> ; restore return-addr
$B8:Byte, $00:Byte, $00:Byte, $00:Byte, $00:Byte, .. ' mov eax, $method-ptr ; load and jump to method
$FF:Byte, $E0:Byte .. ' jmp eax
]
Type TInterfaceTypeCache
Field Methods:TMethod[]
Field Trampolines:Byte[]
EndType
'used by EnumInterface() as null value
Global NullInterfaceEnum:TInterfaceEnum = New TNullInterfaceEnum
Global InterfaceMap:TMap = New TMap
Public
'
' initializes all Interface classes, must be called by user
'
Function InitializeInterfaces( verify:Int = DEFAULT_VERIFY_METHOD)
Function RemoveSpecialMethods:TList( list:TList) ' remove New() and Delete()
Local n:TLink = list.FirstLink()
While n
Select TMethod(n.Value()).Name()
Case "New", "Delete"
Local t:TLink = n.NextLink()
n.Remove()
n = t
Continue
EndSelect
n = n.NextLink()
Wend
Return list
EndFunction
Function AddInterfaces( list:TList)
If Not list Then Return
For Local intf:TTypeId = EachIn list
Local inst:TInterfaceClass = New TInterfaceClass
inst.TypeId = intf
'HACK: if Object->TMethod[] casting doesnt work, this might
'bbRefAssignObject( Varptr inst.Methods, RemoveSpecialMethods( intf.EnumMethods()).ToArray())
inst.Methods = TMethod[] RemoveSpecialMethods( intf.EnumMethods()).ToArray()
inst.NumMethods = inst.Methods.Length
InterfaceMap.Insert( intf.Name().ToLower(), inst)
AddInterfaces( intf.DerivedTypes())
Next
EndFunction
' find and register all interfaces classes
Local itype:TTypeId = TTypeId.ForName(INTERFACE_TYPENAME)
AddInterfaces( itype.DerivedTypes())
' cache INTERFACE -> TYPE relationships for faster runtime instantiation
For Local intfc:TInterfaceClass = EachIn InterfaceMap.Values()
Local mfuncs:TMethod[]
For Local tid:TTypeId = EachIn TTypeId.EnumTypes()
If tid.ExtendsType(itype) Then Continue
' validate methods
If Not mfuncs Then mfuncs = New TMethod[intfc.NumMethods]
Local midx:Int = 0, ok:Int = True
For Local m:TMethod = EachIn intfc.Methods
Local impl:TMethod = tid.FindMethod( m.Name())
If Not impl Then
ok = False
Exit
EndIf
If verify Then
' verify method return-type
' special hack for ObjectEnumerator() which needs different result types
If m.Name() <> "ObjectEnumerator" Then
Local mret:TTypeId = m.ReturnType()
Local iret:TTypeId = impl.ReturnType()
If (mret.Name() <> iret.Name()) And (Not mret.ExtendsType(iret)) Then
ok = False
Exit
EndIf
EndIf
' verify argument types
Local args1:TTypeId[] = m.ArgTypes()
Local args2:TTypeId[] = impl.ArgTypes()
If args1.Length <> args2.Length Then
ok = False
Exit
EndIf
For Local i:Int = 0 Until args1.Length
If args1[i].Name() <> args2[i].Name() Then
ok = False
Exit
EndIf
Next
EndIf
' create trampoline for this method
mfuncs[midx] = impl
midx :+ 1
Next
If ok Then
' cache the methods
Local cache:TInterfaceTypeCache = New TInterfaceTypeCache
cache.Methods = mfuncs
' build the trampoline buffer
cache.Trampolines = New Byte[ intfc.NumMethods * TRAMPOLINE_SIZE]
Local tr:Byte Ptr = cache.Trampolines
For Local i:Int = 0 Until mfuncs.Length
MemCopy tr, method_trampoline, method_trampoline.Length
tr :+ TRAMPOLINE_SIZE
Next
intfc.TypeImpls.Insert( tid, cache)
mfuncs = Null
EndIf
Next
?debug
If LOG_VERBOSE Then
If intfc.TypeImpls.IsEmpty() Then
DebugLog intfc.TypeId.Name() + " is not implemented by any types"
Else
Local count:Int = 0
For Local n:TNode = EachIn intfc.TypeImpls
count :+ 1
Next
DebugLog intfc.TypeId.Name() + " is implemented by " + count + " types"
If LOG_VERBOSE >= 2 Then
' list all implementation types
For Local tid:TTypeId = EachIn intfc.TypeImpls.Keys()
DebugLog "~t" + tid.Name()
Next
EndIf
EndIf
EndIf
?
Next
' internal interface identifiers
IEnumerable.IID = LookupInterfaceClass("IEnumerable")
IEnumerator.IID = LookupInterfaceClass("IEnumerator")
EndFunction
' InitializeInterfaces() must be called first
Function LookupInterfaceClass:TInterfaceClass( name:String)
Return TInterfaceClass( InterfaceMap.ValueForKey( name.ToLower()))
EndFunction
'
' interface classes
'
Type TInterfaceClass
Field TypeId:TTypeId
Field Methods:TMethod[]
Field NumMethods:Int
Field TypeImpls:TMap = New TMap
EndType
'
' interfaces
'
Type Interface Abstract
Field Ref:Object ' reference to the implementation object (must match position of IMPLREF_OFFSET)
Field Mem:Byte[] ' trampoline buffer
Field Class:TInterfaceClass
EndType
Type IEnumerable Extends Interface
Global IID:TInterfaceClass ' used internally to skip class lookup (set by InitializeInterfaces())
Method ObjectEnumerator:IEnumerator() Abstract
EndType
Type IEnumerator Extends Interface
Global IID:TInterfaceClass ' used internally to skip class lookup (set by InitializeInterfaces())
Method HasNext:Int() Abstract
Method NextObject:Object() Abstract
EndType
'
' enumerations
'
Type TInterfaceEnum
Field intfclass:TInterfaceClass
Field enum:IEnumerator
Field intf:Interface
Method HasNext:Int()
If Not enum.HasNext() Then Return False
Local val:Object = enum.NextObject()
If Not intf Then
If QueryInterface( intf, intfclass, val) Then Return True
Else
If UpdateInterface( intf, val) Then Return True
EndIf
Return HasNext()
EndMethod
Method NextObject:Object()
Return intf
EndMethod
Method ObjectEnumerator:TInterfaceEnum()
Return Self
EndMethod
EndType
Type TInterfaceEnumArray Extends TInterfaceEnum
'Field ref:Object
Field array:Object[]
Field index:Int
Method HasNext:Int()
If index >= array.Length Then Return False
Local val:Object = array[index]
index :+ 1
If Not intf Then
If QueryInterface( intf, intfclass, val) Then Return True
Else
If UpdateInterface( intf, val) Then Return True
EndIf
Return HasNext()
EndMethod
EndType
Type TNullInterfaceEnum Extends TInterfaceEnum
Method HasNext:Int()
Return False
EndMethod
Method NextObject:Object()
Return Null
EndMethod
Method ObjectEnumerator:TInterfaceEnum()
Return Self
EndMethod
EndType
'
' public interface functions
'
Function QueryInterface:Int( out:Interface Var, interfacename:Object, obj:Object)
If (Not obj) Then Return False
' get interface class
Local intf:TInterfaceClass = TInterfaceClass(interfacename)
If Not intf Then intf = TInterfaceClass( InterfaceMap.ValueForKey( String(interfacename).ToLower()))
If Not intf Then Return False
' create interface object
out = Interface( intf.TypeId.NewObject())
out.Ref = obj
out.Class = intf
' search for interface methods
Local ot:TTypeId = TTypeId.ForObject(obj)
Local cache:TInterfaceTypeCache = TInterfaceTypeCache(intf.TypeImpls.ValueForKey(ot))
If cache Then
' use cached result to create the trampoline
out.Mem = cache.Trampolines[..]
Local tr:Byte Ptr = out.Mem
For Local i:Int = 0 Until cache.Methods.Length
' update method pointer
Local methptr:Byte Ptr
If cache.Methods[i]._index < 65536 Then
methptr = bbRefMethodPtr( obj, cache.Methods[i]._index)
Else
methptr = Byte Ptr(cache.Methods[i]._index)
EndIf
Int Ptr(tr + METHOD_OFFSET)[0] = Int methptr
bbSetMethodPtr( out, intf.Methods[i]._index, tr)
tr :+ TRAMPOLINE_SIZE
Next
Return True
EndIf
Return False
EndFunction
Function UpdateInterface:Int( out:Interface Var, obj:Object)
If (Not out) Or (Not obj) Then Return False
Local ot:TTypeId = TTypeId.ForObject(obj)
Local cache:TInterfaceTypeCache = TInterfaceTypeCache(out.Class.TypeImpls.ValueForKey(ot))
If cache Then
' use cached result to update the trampoline
out.Ref = obj
Local tr:Byte Ptr = out.Mem
For Local i:Int = 0 Until cache.Methods.Length
' update method pointer
Local methptr:Byte Ptr
If cache.Methods[i]._index < 65536 Then
methptr = bbRefMethodPtr( obj, cache.Methods[i]._index)
Else
methptr = Byte Ptr(cache.Methods[i]._index)
EndIf
Int Ptr(tr + METHOD_OFFSET)[0] = Int methptr
bbSetMethodPtr( out, out.Class.Methods[i]._index, tr)
tr :+ TRAMPOLINE_SIZE
Next
Return True
EndIf
Return False
EndFunction
Function EnumInterface:TInterfaceEnum( interfacename:Object, obj:Object)
If interfacename And obj Then
' get interface class
Local intf:TInterfaceClass = TInterfaceClass(interfacename)
If Not intf Then intf = TInterfaceClass( InterfaceMap.ValueForKey( String(interfacename).ToLower()))
If Not intf Then Return Null
' check if it supports IEnumerable / IEnumerator
Local e:IEnumerable
If QueryInterface( e, IEnumerable.IID, obj) Then
Local enum:TInterfaceEnum = New TInterfaceEnum
enum.intfclass = intf
If QueryInterface( enum.enum, IEnumerator.IID, e.ObjectEnumerator()) Then Return enum
Else
' check if its an array
Local t:TTypeId = TTypeId.ForObject(obj)
If t.ExtendsType(ArrayTypeId) Then
Local enum:TInterfaceEnumArray = New TInterfaceEnumArray
'HACK: if Object->Object[] casting doesnt work, this might.
'bbRefAssignObject( Varptr enum.array, obj)
enum.array = Object[] obj
enum.index = 0
enum.intfclass = intf
Return enum
EndIf
EndIf
EndIf
Return NullInterfaceEnum
EndFunction |
Comments
None.
Code Archives Forum