In this section we will look at some studies
(executed within the hush framework)
that exemplify a multi-paradigm and multi-lingual approach.
We will first look at the issues that arise when embedding
a logic (that is Prolog) interpreter.
Then we will extend the embedded logic with objects
that may correspond to (native) objects in the host language,
that is C++.
These sections may safely be skipped by readers
not interested in logic programming.
Finally, we will look at how to realize corresponding
collections of objects in (native) C++ and Java.
Embedded logic -- crossing the paradigm boundary
Knowledge is a substantial ingredient in many applications.
By knowledge we mean information and rules
operating on that information, to obtain derived information.
As in any (software) engineering effort, maintenance,
that is knowledge maintenance, is of crucial importance.
When we do not avoid the dispersion of knowledge and information
in the actual code of the system, maintenance will
be difficult.
Put differently, for reasons of flexibility and maintenance
we need to factor out the (volatile) knowledge and information
components.
Traditionally, the information components are often taken care of by a database that allows for the formulation of views to obtain (possibly aggregate) information. Logic or logic programming is a strictly more powerful mechanism to deal with information and knowledge. In our group, we have been studying the use of logic programming in knowledge-intensive software engineering applications.
The query tag is an element of one of the
text processing filters to provide
hypermedia
support for software engineering described
in
The query example was motivated by the need to maintain
Web pages for the administration of a colloquium
within our group.
The actual knowledge base consists of a list of people
and some rules to determine their affiliations
and email addresses.
The knowledge base is made available by
consulting the file
As concerns the implementation, the Java fragment below indicates how to access the logic programming interpreter from a (Java) program.
pl.eval("X:assistant(X)");
String res = null;
while ( (res = pl.result()) != null ) {
System.out.println(" Distributed knowledge servers
:- source('www.cs.vu.nl/~eliens/db/se/people.pl').
:- source('www.cs.vu.nl/~eliens/db/se/institute.pl').
:- source('www.cs.vu.nl/~eliens/db/se/property.pl').
:- source('www.cs.vu.nl/~eliens/db/se/query.pl').
However, processing the information accessed by url is still done locally. So, the next step that may be suggested is to distribute the knowledge processing itself, for example by using CORBA.
Native bindings for these languages are available
only on the level of functions.
Even for Java, native methods of an object are defined as
functions that receive a handle to the invoking object.
Given a language with objects, possibly by
adopting an object extension for the languages
without objects,
the problem is to find a proper correspondence
between objects defined in the high-level (script)
language and the native objects defined in C/C++.
In this (sub)section we will first study an extension of Prolog
with objects, and then indicate
a solution to establish a close correspondence
between the (Prolog) objects and their native counterparts.
In the next (sub)section, we will apply this
approach to establish a correspondence between
Java and C++ objects.
Objects (or classes of objects, if you prefer)
are defined by a collection of clauses with a head predicate
of the form class_method(This,...),
specifying the class, method
and object identity parameter.
The actual invocation of the method takes
the form self(This):method(...), where
the colon acts as the familiar dot object access parameter.
Note that the identity parameter (This) does not
occur among the method parameters, but is instead contained
in the object specifier.
Instead of the keyword self,
we may also use a class name to enforce a cast to specific
object type when invoking the method.
In the actual object extension, we also support
object state instance variables, which are however not relevant
for our discussion here.
Object methods may be defined as native by including
a goal of the form native(Handler, Method, Result),
where Handler specifies the (native) handler
to be invoked, Method the actual request,
and Result a variable to store the possible
outcome of the request.
When the Handler parameter is left unspecified,
the handler defined for the object will be taken to
effect the native call.
Let's look at some examples first, to augment this
admittedly concise description.
As outlined in section Reactor, in the hush framework we use an event-based
mechanism to effect foreign language bindings.
This means that the information concerning the native call
is stored in an event object that is passed to a handler,
which invokes the operator function
on the occurrence of an event.
In the code fragment below it is shown how native method
dispatching is taken care of in the operator
function of a C++ kit_object, for which
a corresponding object in Prolog is assumed to exist.
The solution to establishing corresponding object class hierarchies
in Java and C++ that we have adopted relies on
storing a reference to the native C++ object in the Java object
and the conversion of this reference to a smart pointer
encapsulating access to the native C++ object.
Upcalls, which occur for example when Java handlers are invoked
in response to an event, require some additional machinery,
as will be explained shortly.
Each Java class in hush is derived from the obscure
class, which contains an instance variable _self
that may store a C++ object reference, encoded as an integer.
As an example, look at the (partial) Java class description for
kit below.
Each native method must be implemented as a function,
of which the name and signature are fixed by
the JNI conventions, as illustrated below.
In somewhat more detail,
the Java handler object is invoked through the
C++ handler object created in the bind method
of the kit.
The C++ handler is activated when an event occurs, or a Tcl
or Prolog command is given.
Activating the handler amounts to calling the dispatch
method with an appropriate event.
To decide whether the activation must be passed through
to the Java handler object,
the handler::dispatch
method checks for the availability of a smart pointer,
as illustrated below.
The Java smart pointer template class for the Java/C++
binding is derived from the smart pointer
template class introduced in the previous (sub)section.
(C) Æliens
04/09/2009
void source(in string file);
long eval(in string cmd);
string result(in long id);
oneway void halt();
};
Native objects -- crossing the language boundary
Embedding (script) language interpreters is becoming
standard practice, as testified by the existence
of embeddable interpreters for Tcl,
Perl, Python, Javascript, Java, and Prolog.
Each of these languages also supports calling native
code, that is code written in C or C++,
to allow for accessing system resources or simply
for reasons of efficiency.
Objects in Prolog
In our solution, objects are represented by dynamic
fact clauses, containing a Handler,
indicating how native calls are to be dealt with,
a Class, and object identity ID,
possibly a reference REF to a native C/C++ object,
and a list of Ancestors.
:- use(library(midi:[midi,lily,music,process])).
:- declare(midi:object,class(midi),[handler]).
midi_midi(This) :- // constructor
midi(This):handler(H), // gets Handler from class
declare(H,new(midi(This)),[],[],_).
native methods
midi_read(This,F) :- native(_,This,read(F),_).
midi_analyse(This,I,O) :- native(_,This,analyse(I,O),_).
midi_open(This,F) :- native(_,This,open(F),_).
midi_header(This,M) :- native(_,This,header(M,0,480),_).
midi_track(This,X) :- native(_,This,track(X),_).
midi_tempo(This,X) :- native(_,This,tempo(X),_).
midi_event(This,D,C,M,T,V) :-
native(_,This,event(D,C,M,T,V),_).
C++ bindings
public:
vm(event* e) {
int p = 0;
char* id = e->option("ref");
if (id) {
p = atoi(id);
}
_self = (T*) p;
}
virtual inline T* operator->() { return _self; }
private:
T* _self;
};
Combining Java and C++
The designers of the Java language have created an elegant
facility for incorporating native C/C++ code
in Java applications, the Java Native Interface (JNI).
Elegant, since native methods can be mixed freely
with ordinary methods.
When qualifying methods as native,
the implementer must provide a dynamically loadable library
that contains functions, of which the names and signatures
must comply with the JNI standard,
defining the functionality of the methods.
Nevertheless, the JNI does not provide for
generic means to establish a direct correspondence
between an object class hierarchy in C++
that (partially) implements a corresponding object class
hierarchy in Java.
In this section, we will study how
such a correspondence is realized in the hush framework,
using the Java Native Interface.
public int _self; // peer object pointer
...
};
public kit() { _self = init(); }
protected kit(int x) { }
private native int init();
public native void source(String cmd);
public native void eval(String cmd);
public String result() {
String _result = getresult();
if (_result.equals("-")) return null;
else return _result;
}
private native String getresult();
public native void bind(String cmd, handler h);
...
};
include @lt;hush/hush.h>
include @lt;hush/java.h>
include @lt;native/hush_dv_api_kit.h>
#define method(X) Java_hush_dv_api_kit_##X
JNIEXPORT jint JNICALL method(init)(JNIEnv *env, jobject obj)
{
jint result = (jint) kit::_default; // (jint) new kit();
if (!result) {
kit* x = new kit("tk");
session::_default->_register(x);
result = (jint) x;
}
return result;
}
public:
java_vm(JNIEnv* env_, jobject obj_) {
_env = env_;
_obj = obj_;
_self = self();
}
...
event* dispatch(event* e) { java dispatch
call("dispatch",(int)e);
return e;
}
T* operator->() { return _self; }
T* self() {
jfieldID fid = fieldID("_self","I");
return (T*) _env->GetIntField( _obj, fid);
}
void call(const char* md, int i) { // void (*)(int)
jmethodID mid = methodID(md,"(I)V");
_env->CallVoidMethod(_obj, mid, i);
}
private:
JNIEnv* _env;
jobject _obj;
T* _self;
};
Discussion