We have extended the introspective capabilities of .Net CLI adding new namespace to the BCL: System.Reflection.Structural
. We have also extended the reflective facilities of the virtual machine, enhancing the semantics of some IL instructions. The programmer could combine these facilities with the introspective services already offered by the .Net platform, making the CLI an appropriate platform to develop language-neutral adaptive software.
A new NativeStructural
utility class that centralizes all reflection primitives has been created in the System.Reflection.Structural
namespace. In this class the implemented functionality can be grouped into:
Primitives to add/remove/modify methods to single objects or classes (System.Type
). Any existing method can be added to any object or class. If we want to create a new method to add, we could use the already existing System.Reflection.Emit
namespace to create the specific method we want, adding it later. These methods receive an object or class (System.Type
) as a first parameter, indicating whether we want to modify (or inspect) a single object or a shared behavior. The second parameter is a MethodInfo
object of the System.Reflection namespace
. This object uniquely describes the identifier, parameters, return type, attributes and modifiers of a method. The IsStatic
property of this object is used to select the schema evolution behavior (prototype-based language) or class (static) member adaptation (class-based language). Any existing or newly created method can be added to any object or class using its associated MethodInfo
instance. The detailed signature of these primitives at virtual machine level is:
void
addMethod(object owner
, MethodWrap miP,bool
isStatic);bool
removeMethod(object
owner,string
name,bool
overrideDeleted);void
alterMethod(object
owner,string
sig,object
val,bool
isStatic);bool
existMethod(object
owner,string
sig);object
internalGetMethod(object
owner,string
signature);
The invoke
primitive, that executes the method of an object or class specifying its name, return type and parameters. Our goal is to support duck typing, as called in the dynamic language community. If no reflection has been used (we are running "static" programs) a fast concatenation strategy is used. However, in the execution of reflective dynamic languages, method invocation is based on delegation. A MissingMethodException
is thrown if the message has not been implemented in the hierarchy. The detailed signature of this primitive is:
object
invoke(object
target,string
ret,string
mName,object
param);
Primitives to add/remove/modify the runtime structure of single objects (prototype-based model) or their common schema (classes or trait objects. In case of we want to add fields, new ones can be created on-the-fly thanks to our new functionality. The {add
, remove
, alter
, get
, exist
}Field
methods modify the runtime structure of single objects (prototype-based model) or their common schema (classes or trait objects) passed as the first parameter. The second parameter is an instance of a new RuntimeStructuralFieldInfo
class (derived from the .Net FieldInfo
class) that describes the field's type, visibility, and its attributes. We use this new class to create new fields at will. Once again, the Static
attribute of the second parameter selects the schema evolution behavior (class-based and Python models) or class (static) member adaptation (class-based and Ruby semantics).
object
getField (object
owner,string
name);object
getFieldValue (object
owner,string
name);void
addField (object
owner, RuntimeStructuralFieldInfo rsfi);void
removeField (object
owner,string
name);void
alterField (object
owner, RuntimeStructuralFieldInfo rsfi);void
setFieldValue (RuntimeStructuralFieldInfo field,object
val);bool
existField (object
owner,string
name);
When using the new reflective BCL services, the customization of running programs should be reflected in its execution. However, legacy non-reflective code does not make explicit calls to the BCL Reflection.Structural
namespace, and, thus, reflective changes will not be taken into account within the original program. This is the reason why the reflective model requires extending the semantics of some specific IL statements: to make existing applications adaptable.
An example to clarify this idea is the method invocation mechanism. The old behavior is based on a statically typed class model. The new platform offers a dynamic message passing mechanism that can be obtained by calling the invoke method of the NativeStructural
class. Although the explicit execution of this method is possible, the idea is to obtain this new behavior with the original program (i.e. using the call
and callvirt
IL statements) when any class or object is reflectively modified. What we achieve with this approach is making existing .Net components or applications adaptable without needing to recompile them.
Following our motivating example, we present an example of how to invoke a method following the static and dynamic typing approach. We first send a bark
message of the statically inferred type Dog
. In case the compiler does not know the type of the called object, a dynamic typing message call is needed (bark
); then, the Object
(or any other) type should be used instead. What we have done to produce this behavior is extend the semantics of the callvirt IL statement with the computational model previously described.
// * Static typing message passing
ldloc
dogcallvirt instance void
Dog::bark()// * Dynamic typing message passing
ldloc
dogcallvirt instance void
System.Object::bark()
In order to achieve this goal, we have modified the native code generated by the JIT compiler when executing the following IL statements:
ldfld
, ldsfld
and ldflda
: Loads into the stack the (instance or class) field value (or address) following the computational model previously described.
stfld
and stsfld
: Stores a value into a (instance or class) field, deciding its appropriate memory location at runtime.
call
and callvirt
: Executes a method following both the concatenation and delegation inheritance strategies. The extension of these IL statements must enable any program to access its reflective information added at runtime, not available when the code is compiled.