SWICLI 1.x Prolog API overview



Introduction

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:


SWICLI types (.NET types, as seen by Prolog)

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)


representation of .NET values and references within Prolog

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


Representation of .NET types within Prolog (1): structured notation

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).


representation of .NET types within Prolog (2): descriptor notation

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


representation of .NET types within Prolog (3): classname notation

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


Using the SWICLI 1.x Prolog API

creating instances of .NET classes

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).


calling methods of .NET objects or classes

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.


Fetching field and property values of .NET objects or classes

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.


Setting field or properties values of .NET objects or classes

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.


a slightly longer example

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 :-)



cli_new( +X, +Argz, -V) :-

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

Finally, an attempt will be made to unify R with the returned result.



cli_set( +X, +Field, +V) :-

basically, sets the Fspec-th field of object X to value V

X can be:

Field can be:

V must be ground (although one day we may pass variables to SWICLI?!)



cli_get( +X, +Field, -V) :-

X can be:

Field can be

Immediately before cli_get/4 returns, an attempt will be made to unify V with the internally computed result.

 


exceptions thrown by .NET

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.

 


We have 3 different ways to denote Types in the system:

Type:SWICLI type, e.g. char, byte, class(['System'],['String']), boolean, array(boolean)

Typeref: �The @('C#345345345')� that points to an instance of System.Type

Typename: �The 'System.Char', 'System.Boolean','System.Boolean[]'


Conversions between the three

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).


Object Type Checking

 

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']).


Inspection of Terms denoting Objects

 

cli_primitive_type( -Type)

Type is one of the SWICLI primitive types boolean, char, byte, short, int, long, float, double.
 

cli_is_typeref( ?Term)

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.
 

cli_is_object_type( ?Term) ��

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.

cli_is_ref( ?Term)

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. 


Inspection of Terms denoting Values

 cli_void( -Datum)

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).
 

cli_true( -Datum)

Datum is the SWICLI representation of the .NET boolean value true.
 

cli_is_false( ?Term)

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.
 

cli_is_null( ?Term)

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.
 ����������

cli_is_true( ?Term)

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.
 

 

cli_is_void( ?Term)

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.
 

cli_null( -Datum)

Datum is the SWICLI representation of the .NET null reference null.
 

Utilities

 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

 

to do

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.


Douglas R. Miles
drafted 10th April 2012