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