This is an overview of an interface which allows SWI-Prolog programs to dynamically create and manipulate .NET objects.
Here are some significant features of the interface and its implementation:
All .NET values and object references which are passed between Prolog engines and .NET VMs via SWICLI's Prolog API are seen as instances of types within this simplified SWICLI type system:
a datum (this term is introduced, out of necessity, to refer jointly to values and refs)is a value (values are copied between Prolog and the .NET)
is a boolean
or a char
or a long, int, short or byte
or a double or float
or a string (an instance of System.String)
or a void (an artificial value returned by calls to .NET void methods)
or a ref
is null
or an object (held within the .NET, and represented in Prolog by a canonical reference)
is an array
or a class instance (other than of System.String)
Instances of SWICLI types are represented within Prolog as follows:
boolean has two values, represented by @(true) and @(false)
char values are represented by corresponding Prolog integers
int, short and byte values are represented by corresponding Prolog integers
long values are represented as Prolog integers if possible (32-bit in current SWI-Prolog), else as jlong(Hi,Lo) where Hi is an integer corresponding to the top32 bits of the long, and Lo similarly represents the lower 32 bits
double and float values are represented as Prolog floats (which are equivalent to .NET doubles) (there may be minor rounding, normalisation or loss-of-precision issues when a .NET float is widened to a Prolog float then narrowed back again, but what the heck)
string values (immutable instances of System.String) are represented as Prolog atoms (in UTF-8 encoding)
null has only one value, represented as @(null)
void has only one value, represented as @(void)
array and class instance references are currently represented as @(Tag), where Tag ia an atom whose name encodes a P/INVOKE global reference value; this may change, but won't affect Prolog programs which respect the opacity of references
The SWICLI Prolog API allows Prolog applications to inspect, manipulate, and reason about the types of .NET values, references, methods etc., and this section describes how these types themselves (as opposed to instances thereof) are represented. Predicates which pass these type representations include cli_type_to_type/2, cli_typename_to_type/2, cli_datum_to_type/2, cli_is_object_type/1, cli_is_type/1, cli_object_to_type/2, cli_primitive_type/1, cli_ref_to_type/2, cli_type_to_type/2. cli_type_to_typename/2.
void is represented as void
null is represented as null
the primitive types are represented as boolean, char, byte, short, int, long, float, double
classes are represented as class(package_parts,classname_parts)
e.g. class(['System','Forms'],['TextBox'])
array types are represented as array(type)
e.g. array(boolean)
e.g. array(class([System],['String'])
This structured notation for .NET types is designed to be convenient for composition and decomposition by matching (unification).
The descriptor notation for .NET types is one of two textual notations employed by the .NET and the .NET class libraries; SWICLI (necessarily) supports both (and supports conversion between all three representations).
Examples:
'bool' denotes boolean
'byte(4)' denotes System.Byte
'char' denotes System.Character
'short' denotes short
'int' denotes int32
'long' denotes long
'single' denotes float
'double' denotes double
'System.DateTime' (for example) denotes the System.Datetime
'type[]' denotes an array of type
'(argument_types)return_type' denotes the type of a method
The classname notation for .NET types is the other textual notation employed by the .NET and the .NET class libraries. It is a (seemingly unnecessary) variation on the descriptor notation, used by a few P/INVOKE routines. It has the slight advantage that, in the case of simple class types only, it resembles the .NET source text notation for classes. This representation is supported only because certain P/INVOKE functions use it; it is used within SWICLI's implementation of cli_call/4 etc. You may encounter this notation when tracing SWICLI activity, but otherwise you need not know about it.
Examples:
'System.Array' denotes the .NET class System.Array
'array(bool)' denotes an array of boolean
'System.String[]' denotes an array of string
To create an instance of a .NET class from within Prolog, call cli_new(+Class,+Params,-Ref) with a classname, a list of actual parameters for the constructor, and a variable to be bound to the new reference, e.g.
cli_new( 'System.Forms.Frame', ['frame with dialog'], F)
which binds F to a new object reference, e.g.
@('C#0008272420')
(not that
the details of this structure are of any necessary concern to the Prolog
programmer or to the applications she writes).
NB for convenience, this predicate is overloaded: Class can also be a
class type in structured notation, e.g. array(boolean)
.
The object reference generated by the cli_new/3 call (above) can be passed to other SWICLI API predicates such as
cli_call( +Ref, +Method, +Params, -Result)
e.g.
cli_call( F, setVisible, [@(true)], _)
which calls the setVisible method of the object to which F refers, effectively passing it the .NET value true.
(This call should display the new JFrame in the top left corner of the desktop.)
Note the anonymous variable passed as the fourth argument to cli_call/4. A variable in this position receives the result of the method call: either a value or a reference. Since SetVisible() is a void method, the call returns the (artificial) reference @(void).
Some may prefer to code this call thus:
cli_call( F, setVisible, [@true], @void)
which
documents the programmer's understanding that this is a void method (and
fails if it isn't :-).
If the +Ref argument represents a class, then
the named static method of that class is called.
The cli_get/3 API predicate can retrieve the value of an instance field or a static field, e.g.
cli_get( 'System.Color', pink, Pink)
which binds the Prolog variable Pink to a reference to the predefined System.Drawing.Color "constant" which is held in the static final .pink field of the System.Drawing.Color class.
More generally, cli_get/3 has the following interface:
cli_get( +Class_or_Object, +Field, -Datum)
If the first argument represents a class, then a static field of that class with FieldName is accessed.
Object and class fields can be set (i.e. have values or references assigned to them) by the cli_set/3 API procedure, which has the following interface:
cli_set( +Class_or_Object, +Field, +Datum)
where Datum must be a value or reference of a type suitable for assignment to the named field of the class or object.
This code fragment
findall(
Ar,
( current_prolog_flag( N, V),
term_to_atom( V, Va),
cli_new( '[LSystem.String;', [N,Va], Ar)
),
Ars
),
cli_new( '[[LSystem.String;', Ars, Ac),
cli_datums_to_array( [name,value], Ah),
cli_new( 'System.Forms.Frame', ['current_prolog_flag'], F),
cli_call( F, getContentPane, [], CP),
cli_new( 'System.Forms.Table', [Ac,Ah], T),
cli_new( 'System.Forms.ScrollPane', [T], SP),
cli_call( CP, add, [SP,'Center'], _),
cli_call( F, setSize, [600,400], _),!.
builds an array of arrays of strings containing the names and values of the current SWI-Prolog "flags", and displays it in a JTable within a ScrollPane within a JFrame:
In addition to SWICLI API calls, this code calls cli_datums_to_array/2, a utility which converts any list of valid representations of .NET values (or objects) into a new .NET array, whose base type is the most specialised type of which all list members are instances, and which is defined thus:
cli_datums_to_array( Ds, A) :-
ground( Ds),
cli_datums_to_most_specific_common_ancestor_type( Ds, T),
cli_new( array(T), Ds, A).
Having found the "most specific common ancestor type" (my phrase :-), a new array of this type is created, whose elements are initialised to the successive members of the list of datums.
This illustrates another mode of operation of cli_new/3:
cli_new( +ArrayType, +InitialValues, -ArrayRef)
See the relevant Appendix for fuller details of the API procedures.
Don't forget the possibility of writing and manipulating new .NET classes to serve your Prolog applications: this interface is not designed to make .NET programming redundant :-)
X can be:
if X denotes a primitive type and Argz is castable to a value of that type, then V is that value (a pointless mode of operation, but somehow complete...)
if X denotes an array type and Argz is a non-negative integer, then V is a new array of that many elements, initialised to the appropriate default value
if X denotes an array type and Argz is a list of datums, each of which is (independently) castable to the array element type, then V is a new array of as many elements as Argz has members, initialised to the results of casting the respective members of Argz
if X denotes a non-array object type and Argz is a list of datums, then V is the result of an invocation of that type's most specifically-typed constructor to whose respective parameters the members of Argz are assignable
cli_call( +X, +Method, +Args, -R) :-
X can be:
· a type, class object or classname (for static methods of the denoted class, or for static or instance methods of System.Class)
· a class instance or array (for static or instance methods)
Method can be:
· an atomic method name (if this name is ambiguous, as a result of method overloading, then it will be resolved by considering the types of Args, as far as they can be inferred)
· an integral method index (untested: for static overload resolution)
· a methodID/1 structure (ditto)
Args must be
- a proper list (possibly empty) of ground arguments
Finally, an attempt will be made to unify R with the returned result.
basically, sets the Fspec-th field of object X to value V
X can be:
- a class instance (for non-static fields)
- an array (for indexed element or subrange assignment)
- but not a string (no fields to retrieve)
Field can be:
- an atomic field name (overloading will be resolved dynamically, by considering the inferred type of V)
- an integral field index (static resolution: not tried yet)
- a fieldID/1 (static resolution: not tried yet)
- a variable (field names, or array indices, are generated)(?!)
- an array index I (X must be an array object: X[I] is assigned V)
- a pair I-J of integers (J can be a variable) (X must be an array object, V must be a list of values: X[I-J] will be assigned V)
V must be ground (although one day we may pass variables to SWICLI?!)
X can be:
- a class instance (for non-static fields)
- an array (for the 'length' pseudo field, or for indexed element retrieval)
- but not a String (clashes with classname; anyway, System.String has no fields to retrieve)
Field can be
- an atomic field name
- or an integral field index (these are a secret :-)
- or a fieldID/1 (not for general consumption :-)
- or an integral array index (high-bound checking is done by .NET, maybe throwing an exception)
- or a variable (field names, or array indices, are generated)
- or a pair I-J of integers or variables (array subranges are generated) (relational or what?!)
Immediately before cli_get/4 returns, an attempt will be made to unify V with the internally computed result.
Uncaught exceptions thrown by the .NET in the course of handling a SWICLI 3.x Prolog API call are mapped onto Standard Prolog exceptions, e.g.
cli_new( 'Systen.DateTime', [yesterday], D)
raises the Prolog exception
cli_exception('System.IllegalArgumentException', @'C#0008408972')
because, as
the exception suggests, yesterday is not a valid constructor argument.
.NET exceptions are
always returned as Prolog exceptions with this structure:
cli_exception( classname, reference_to_exception_object)
cli_add_event_handler( +Class_or_Object, +EventName, +PredicateIndicator) :-
ADDING A NEW EVENT HOOK
We already at least know that the object we want to hook is found via our call to
?- botget(['Self'],AM).
So we ask for the e/7 (event handlers of the members)
?- botget(['Self'],AM),cli_memb(AM,e(A,B,C,D,E,F,G)).
Press ;;;; a few times until you find the event Name you need (in the B var)
A = 6, % index number
B = 'IM', % event name
C = 'System.EventHandler'('InstantMessageEventArgs'), % the delegation type
D = ['Object', 'InstantMessageEventArgs'], % the parameter types (2)
E = [], % the generic paramters
F = decl(static(false), 'AgentManager'), % the static/non static-ness.. the declaring class
G = access_pafv(true, false, false, false) % the PAFV bits
So reading the parameter types "['Object', 'InstantMessageEventArgs']" lets you know the predicate needs at least two arguments
And "F = decl(static(false), 'AgentManager')" says add on extra argument at start for Origin
handle_im(Origin,Obj,IM)
So registering the event is done:
?- botget(['Self'],AM), cli_add_event_handler(AM,'IM',handle_im(_Origin,_Object,_InstantMessageEventArgs))
To target a predicate such as: handle_im(Origin,Obj,IM):-writeq(handle_im(Origin,Obj,IM)),nl.
char,
byte
, class(['System'],['String'])
,
boolean
, array(boolean)cli_typeref _to_typename( +Class, -Classname)
Class must be a SWICLI
reference to a .NET class object (i.e. an instance of System.Type); Classname
is its canonical dotted name, e.g. 'System.Collections.Date'
.
cli_typeref_to_type( +Class, -Type)
Class must be a SWICLI
reference to a .NET class object (i.e. an instance of System.Type); Type
is its SWICLI type, e.g. class(['System'],['DateTime'])
or array(double)
.
cli_typename_to_typeref( +Classname, -Class)
Classname must be a
canonical dotted name (an atom) of a .NET class, e.g. 'System.Date'
; Class is a SWICLI
reference to a corresponding .NET class object (i.e. an instance of System.Type).
cli_typename_to_type( +Classname, -Type)
Classname must be a
canonical dotted name (an atom) of a .NET class, e.g. 'System.Collections.Date'
; Type
is its SWICLI type, e.g. class(['System'],['DateTime'])
.
cli_type_to_typeref( +Type, -Class)
Type is a SWICLI
class (or array) type, e.g. class(['System','Data','Sql'],['Timestamp'])
or array(boolean)
; Class
is a SWICLI reference to a .NET class object (an instance of System.Type)
which corresponds to Type.
cli_type_to_typename( +Type, -Classname)
Type is a SWICLI class (or array)
type, e.g. class(['System','Data','Sql'],['Timestamp'])
or array(boolean)
; Classname
is its canonical dotted name (an atom).
cli_object_is_typeref( +Object, ?Class) AKA: cli_get_type/2
Object is a SWICLI
reference to a .NET object; Class is a SWICLI
reference to a .NET class object (an instance of System.Type) which
represents Object's class.
cli_object_is_type( +Object, ?Type) AKA: cli_is_type/2
Object is a SWICLI
reference to a .NET object; Type is its SWICLI type,
e.g. array(boolean)
, class(['System','Data','Sql'],['Timestamp'])
.
cli_object_is_typename( +Object, ?Classname) AKA: cli_get_typename/2
Object is a SWICLI reference to a .NET object; Classname is its canonical dotted name (an atom)..
cli_datum_to_type( +Datum, ?Type)
Datum must be a valid SWICLI
representation of some .NET object or value e.g. 3
, fred
,
@(false)
; Type is
its SWICLI type, e.g. char_byte
,
class(['System'],['String'])
,
boolean
.
cli_ref_to_type( +Ref, ?Type)
Ref is a SWICLI
reference to a .NET object; Type is the SWICLI type of
Object, e.g. array(boolean)
,
class(['System','Data','Sql'],['Timestamp'])
.
cli_primitive_type( -Type)
Type is one of the SWICLI
primitive types boolean
, char
, byte
,
short
, int
, long
,
float
, double
.
Term is a SWICLI reference to a .NET class object, i.e. to an instance of System.Type. No further instantiation of Term will take place; if it is not ground, this predicate fails.
cli_is_object( ?Term)
Term is a SWICLI
reference to a .NET object. No further instantiation of Term will
take place; if it is not ground, this predicate fails.
Term is a SWICLI class or array type
(but not null
, void
, or one of the primitive
types). No further instantiation of Term will take place; if
it is not ground, this predicate fails.
Term is a SWICLI class or array
type, or is null
(i.e. the SWICLI
type of .NET's null reference) (but not void
or one of the primitive types). No further instantiation of Term
will take place; if it is not ground, this predicate fails.
cli_is_type( ?Term)
Term is a SWICLI
type, e.g. char_byte
, float
, array(int)
. No further instantiation of Term
will take place; if it not ground, this predicate fails.
Datum is the SWICLI
representation of the (notional but convenient) .NET value void, i.e. @(void)
.
cli_false( -Datum)
Datum is the SWICLI
representation of the .NET boolean value false, i.e. @(false)
.
Datum is the SWICLI
representation of the .NET boolean value true.
Term is the SWICLI
representation of the .NET boolean value false. No further instantiation
of Term will take place; if it is not ground, this predicate fails.
Term is a SWICLI
representation of the .NET boolean value null. No further instantiation
of Term will take place; if it is not ground, this predicate fails.
Term is the SWICLI
representation of the .NET boolean value true. No further
instantiation of Term will take place; if it is not ground, this
predicate fails.
Term is the SWICLI
representation of the (notional but convenient) .NET value void, i.e. @(void)
. No further instantiation
of Term will take place; if it not ground, this predicate fails.
Datum is the SWICLI
representation of the .NET null reference null.
cli_array_to_length( +Array, -Length)
Array is a SWICLI reference to a .NET array; Length is its length (an integer).
cli_array_to_list( +Array, -ListOfDatums)
Array is a SWICLI
reference to a .NET array (of any base type); ListOfDatums is a
(Prolog) list of SWICLI references to, or values of, its
respective elements.
cli_datums_to_array( +ListOfDatums, -Array)
ListOfDatums is a (Prolog) list of SWICLI references or values; Array is a SWICLI reference to a .NET array of corresponding objects or values. The base type of Array is the most specific .NET type of which each member of ListOfDatums is (directly or indirectly) an instance. If there is no such type, this predicate fails. Values of .NET primitive types are not automatically "boxed". Lists which are mixtures of numbers, booleans and object references cannot be converted to .NET arrays with this predicate.
cli_enumeration_element( +Enumeration, -Element)
Enumeration is a SWICLI reference to a .NET object whose class implements the System.Collections.Enumeration interface; Element is an element of Enumeration. This predicate can generate each element of an enumeration.
cli_enumeration_to_list( +Enumeration, -ListOfElement)
Enumeration is a SWICLI reference to a .NET object whose class implements the System.Collections.Enumeration interface; ListOfElement is a list of SWICLI references to each element of Enumeration.
cli_hashtable_pair( +Hashtable, -KeyValuePair)
Hashtable is a SWICLI
reference to a .NET hashtable object (an instance of System.Collections.Hashtable);
KeyValuePair is a -/2
compound term whose first arg is a key (atom or ref) from Hashtable,
and whose second arg is its corresponding value (atom or ref), e.g.fred-@'J#0008127852'
.
cli_iterator_element( +Iterator, -Element)
Iterator is a SWICLI
reference to a .NET object whose class implements the System.Collections.Iterator
interface; Element is a SWICLI reference to one of
its elements. This predicate can generate all elements.
cli_list_to_array( +ListOfDatum, -Array)
This is a synonym for cli_datums_to_array/2,
in case you forget that SWICLI values and references are called
"datums".
cli_map_element( +Map, -KeyValuePair)
Map is a SWICLI
reference to a .NET object whose class implements the System.Collections.Map
interface; KeyValuePair is a -/2
compound term whose first arg is a key (atom or ref) from Map, and
whose second arg is its corresponding value (atom or ref), e.g. -(fred,@'J#0008127852'
), or fred-@'J#0008127852'
using conventional
operator definitions.
cli_set_element( +Set, -Element)
Set is a SWICLI reference to a .NET object whose class implements the System.Collections.Set interface; Element is a SWICLI reference to an object (or null) within Set. This predicate can generate all elements of Set
Apart from any bugs I don't know about, this interface is usable and useful as it stands. Nevertheless there are some things "to do" at some stage in the future, e.g.