ActiveX Extensions to SWI Prolog

 

David Hovel
mailto:davidhov@microsoft.com


October 1, 1999
Version 0.3


1         Introduction

1.1       SWI Prolog

SWI Prolog is a prolog compiler and interpreter suite developed by Jan Wielemaker of the Department of Social Science Informatics of the University of Amsterdam in the Netherlands.  It is made available free of charge, in both source and binary distributions, for non-commercial applications.

1.2       Extensions

SWI Prolog (SWIP) is a well-crafted product that supports embedding and programmatic extensions.  In other words, the SWI Prolog engine can be used as a component in a larger framework of diverse language modules.  In addition, SWIP is easily extended to support additional capabilities available on Microsoft Windows platforms.

This extensibility is greatly enhanced by the fact that complete Windows-compatible source code is available for SWIP.

This document describes two extensions developed by me as part of our work in the Advanced Systems Integration group in Microsoft Research.  These extensions to SWIP are:

·        A companion DLL called SWIXDLL, which allows direct access to ActiveX automation objects from SWIP source code.  SWIXDLL allows Prolog to interact directly with the ActiveX IDispatch scripting interface of the newer families of ActiveX objects.

·        An additional DLL called PROQUERY contains the entire compiled SWIP code base, making it available as an ActiveX automation object.  This allows Prolog queries, both deterministic and non-deterministic, to be invoked from Visual Basic, C++, J++, Perl, JavaScript or VBScript.

1.3       Versions and Documentation

All software discussed in this document is based upon the 3.2.5 release of SWI Prolog.  The SWIP documentation notes refer to the SWI 3.2 Reference Manual (or SWIPRM).

1.4       A Note About SWIP “Libraries”

As part of its support for modular programming, SWIP can dynamically consult (i.e., load) Prolog modules on an as-needed basis.  The extension software described in this document exploits this capability.  See the SWIP Reference Manual for more details.

1.5       Changes to Primary Sources

Two small changes were made to the primary SWI source files to implement the capabilities described in this document.  Refer to later sections entitled “Changes to SWI Prolog Sources”.

2         SWIACTX: ActiveX Automation Support

2.1       SWIP Interaction

The SWIACTX dynamic-link library provides ActiveX automation support to SWI Prolog programs.  It is built upon the “foreign library” support capability of SWIP, as documented in the SWI-Prolog Reference Manual (SWIPRM), section 5.4.

The SWIACTX DLL is automatically loaded through the following interaction.

1) The SWIACTX.DLL is placed in the Bin directory of the SWIP installation.

2) The Prolog program SWIACTX.PL in the Library directory declares the predicates that exist in SWIACTX.DLL and loads the DLL as a “foreign library”.

3) The make_library_index/1 predicate is used to rebuild the file INDEX.PL in the Library directory of the SWIP installation.  This identifies for SWIP where particular predicates are defined in the modules contained in the Library directory.

4) When a consulted Prolog program or predicate references a predicate defined in SWIACTX.PL, SWIP identifies the predicate, via INDEX.PL, as having been defined in SWIACTX.PL; SWIP then consults SWIACTX.PL and loads SWIACTX.DLL.

In other words, it is entirely automatic.  If the SWIP installation is correctly configured, a Prolog program can simply start using the ActiveX predicates immediately.

2.2       Limitations of SWIACTX

SWIACTX only supports ActiveX objects, not controls.  Only IDispatch invocation is used; early binding is not supported.

Event interfaces are not supported.

2.3       Binary Versions of SWIACTX

The distribution set contains both the “debug” and “release” versions of SWIACTX.DLL.  The debug version is called SWIACTXD.DLL; this is consistent with other extensions DLLs used by SWIP.

The choice of DLL is determined by SWIACTX.PL depending upon the build variant of SWIP currently running (see section 2.4.1 below).

2.4       Changes to SWI Prolog Sources

One change was made to the primary SWI-Prolog sources to implement these capabilities.

2.4.1      Addition of win32debugmode/0

The module pl-ext.c was changed to add a built-in predicate for testing whether the core Prolog engine was built with the Visual C++ _DEBUG flag or not.  This predicate, win32debugmode/0, is used to load the correct version of the SWIACTX extension DLL.

This was necessary due to the fact that SWI Prolog will crash if the build modes between the extension DLLs and the primary module are not compatible.  The win32debugmode predicate is called in the SWIACTX.PL foreign module declaration file.

2.5       Basics of ActiveX Interaction

This section assumes that the reader is somewhat familiar with ActiveX object creation and usage.

For further information, link to http://msdn.microsoft.com/ and search for “Working with Objects” in the Visual Basic documentation.

In simple terms, ActiveX objects have interfaces; each interface presents a set of methods (functions) for public use.  Each method may take zero or more arguments, and may return a single result argument.   However, only certain data types are allowed as arguments to methods.

Each binary image supporting an ActiveX object also provides a compiled information block describing the object, its interfaces and methods.  

Interpretive or scripting environments only use one such interface: IDispatch.  This interface is special, in that it really has only one key method: Invoke.  The IDispatch::Invoke method takes a token representing the actual method to call, and an array of arguments.

The IDispatch mechanism simplifies the usage of scriptable objects, since the interpreter doesn’t have to know the specifics of using the objects.  It merely has to convert the arguments presented by the script into standard ActiveX data types, invoke the method, and convert the results back to the scripting languages data structures.

The portion of a binary image that describes the object, its interfaces and methods is called the type library.

2.5.1      An ActiveX Glossary

Some ActiveX terms occur often enough to justify redefining here.

Object

A reference to an interface to an ActiveX structure created on-demand.

BSTR

A length-counted string in the UNICODE character set; the string format for all ActiveX strings.

VARIANT

The structure used to store generic variables, including object references.

SAFEARRAY

The structure used to store variable-length, variable-dimensioned arrays

IDispatch

The name used for the primary automation (scripting) interface.

Table 2.1: Basic definitions

2.5.2      The activeXObject Functor

The core of ActiveX interaction is the IDispatch interface reference, or what is known in VB as an “Object”. 

In accordance with the recommendations in the SWIPRM, every object reference is “wrapped” in a functor called activeXObject.  For example:

activeXObject(7).

This is how a typical returned SWIACTX object reference would look if printed from within SWIP. 

These references are returned from a variety of calls; their life cycle is entirely controlled by SWIACTX.  Most commonly, they arise as a result of a request to create a new instance of an object.   Prolog users are responsible for maintaining and releasing these references according to the needs of their programs.

The integer argument of the activeXObject functor is a unique key to an information map maintained by SWIACTX that contains the actual IDispatch interface pointers.  Assignment of this integer is referred to as registration.

2.5.3      Argument Conversion

Since the basic task of SWIACTX is to translate between calls in one programming model (Prolog) to that of another (ActiveX), data must be extensively converted.

Most of these conversions are fairly intuitive.  However, some key points deserve notice.

·        Strings returned from ActiveX functions are converted to Prolog strings.  This means that they will not unify with atoms or standard Edinburgh character lists. 

·        Prolog atoms, strings and string lists are all converted to ActiveX BSTRs as needed.

·        Lists of terms with a common type (e.g., all integers) are converted to SAFEARRAYs and stored in VARIANTs.

·        SWIACTX defines a special functor for control of SAFEARRAYs.  Refer to documentation of the activeXSafearray functor later in this document.

·        SWIACTX defines a special functor for control of certain data types that may appear in a VARIANT, such as VT_DATE.  Refer to documentation of the activeXVariant functor later in this document.

2.6       SWIACTX Functions

These are the predicates defined by SWIACTX.PL and SWIACTX.DLL.  They are document in more detail later in this document.

actxCreateObject/2:  Given a UUID or ProgID string, creates an automation object and returns a reference functor.

actxReleaseObject/1: Releases an automation object.

actxInvokeObject/4: Calls a method on an automation object.

actxQueryInterface/3: Performs a QueryInterface call on the automation object, and returns the IDispatch interface of the result.

actxEnumObject/2: Performs enumeration of an IEnumVARIANT collection, backtracking over the results.

actxCloneObject/2:  Creates a new automation object reference.

actxCollectionList/2:  Returns the results of an IEnumVARIANT enumeration as a list.

actxReleaseAll/0:  Releases all stored automation references maintained by SWIACTX.

actxBreak/0:  (Debugging only) Causes a run-time debugging “break” in SWIACTX.DLL.

actxContext/0:  Sets up a “once”-style backtracking predicate that discards all automation object references upon backtracking.

actxContextGlobal/2:  Promotes a local (context-specific) automation reference to a global reference.

actxListToDate/2: Bi-directionally converts OLE/ActiveX date/time values to Prolog floats.

actxErrorsAsExceptions/1: Controls the reporting of interfaces errors as exceptions.

The terms automation and scripting are interchangeable.

2.7       Example of ActiveX Interaction

This extended example accesses a database through the ADO (Active Data Objects) system.  It is intended to give the flavor of the interaction, not to provide specific details, which are driven by the object model of ADO.

For simplicity, the global Prolog database is used to store functors returned from the SWIACTX predicates.

Note that predicates whose names begin with ‘actx’ are provided by SWIACTX.

adoConnect :-
       actxCreateObject('ADODB.Connection',IP),
       assertz(adoobj(connection,IP)).

This predicate uses the actxCreateObject predicate to create a connection object.  If successful, it asserts the result functor into the Prolog database.

adoOpenSource :-
       adoobj(connection,IPConn),
       actxInvokeObject(IPConn,'Open',
            ['evlog',optional,optional,optional],
            IPSource),
       assertz(adoobj(source,IPSource)).

This predicate uses the functor asserted above and calls the Open method on the created ADODB.Connection object.   The argument list to the Open method requests that the ODBC database source evlog be opened for access.

adoOpenRecordsetRuns :-
       actxCreateObject('ADODB.Recordset',IPRset),
       assertz(adoobj(recordset,IPRset)),
       adoobj(connection,IPConn),
       actxInvokeObject(IPRset,'Open',
                   ['Runs',IPConn,optional,optional,optional],_).

This predicate creates an ADODB.Recordset object and calls its Open method using the ADODB.Connection object to open the database table called “Runs”.

adoNotEof :-

            adoobj(recordset,IPRset),

            actxInvokeObject(IPRset,['EOF','propget'],[],false).

This predicate fails if the recordset is at end-of-file.

adoRead :-
       adoobj(recordset,IPRset),
       actxInvokeObject(IPRset,'MoveFirst',[],_),
       adoReadNext.

This predicate reads the recordset by invoking the MoveFirst method and the invoking the adoReadNext predicate.

adoReadNext :-
      adoNotEof,
      adoobj(recordset,IPRset),
      actxInvokeObject(IPRset,'MoveNext',[],_),
      adoReadNext.

This predicate invokes the MoveNext method for the recordset and repeats using tail recursion.

adoTest :-
      adoConnect,
      adoOpenSource,
      adoOpenRecordsetRuns,
      not(adoRead).

This predicate performs all database connection operations and reads the recordset.

The details of accessing individual fields have been omitted for brevity.

2.8       SWIACTX Exceptions

The SWIACTX foreign predicates use the built-in SWIP “exception” mechanism to report serious internal errors.  User-level Prolog code should use the catch/3 predicate to “catch” exceptions “thrown” by the SWIACTX predicates. 

The error functor “thrown” by SWIP is defined as

activeXError(StrFunc,StrDesc,TermInError)

The first term is unified with the string name of the SWIACTX foreign predicate that failed.   The second term is unified with a string description of the cause of the failure.  The third term is unified with the term that caused the error or ‘[]’.

For example

doRewrite :-
    catch(rewritePass,
          activeXError(StrFunc,StrDesc,TermInError),
          printException(StrFunc,StrDesc,TermInError)).

In this example, the doRewrite predicate acts as a “wrapper” for the real worker predicate, rewritePass.  If an SWIACTX exception is thrown during rewritePass, the predicate printException is invoked, and its arguments are unified with the terms from the SWIACTX exception function, activeXError.

All Prolog programs using SWIACTX should use this mechanism, since there is no other means for determining the cause of an exception generated in SWIACTX.

2.9       Types of Arguments

2.9.1      Argument Conversion

Since Prolog and ActiveX automation are not directly compatible, conversions between intrinsic data types is necessary.  In other words, SWIACTX automatically converts from Prolog data types to automation data types when an object’s method is called, and then converts the resulting automation data (if any) back to Prolog data types.

This conversion is driven by two factors:

·        The intuitive mapping between Prolog data and automation data, and

·        The type library information associated with the specific method of the target IDispatch interface.

2.9.2      Standard SWI Data Types

The following data types are supported by SWIP internally:

PL_VARIABLE: This is an unbound Prolog variable.

PL_ATOM:  This is a string of character that has been permanently associated with a unique token for rapid access and comparison.

PL_INTEGER:  This is a 30-bit integer.

PL_FLOAT:  This is a double-precision floating-point number.

PL_STRING: This is a list of characters (i.e., Edinburgh Prolog) that has been converted to a string using the string_to_list/2 predicate.

PL_TERM: This is a generic Prolog variable.

PL_LIST: This is a functor of the form ./2; that is, a standard Prolog list.

PL_CHARS: This data type is never used by SWIACTX.

PL_POINTER: This data type is never used by SWIACTX.

For more detailed information, refer to the SWIPRM.

2.9.3      Conversion from Prolog to Automation

The arguments to an automation method are delivered to SWIACTX as a Prolog list.  The elements of this list are examined and converted one at a time into a VARIANTARG array for IDispatch::Invoke (DispInvoke).

The ITypeInfo information for each dispatch method defines the number and type of each argument to the function.  Named arguments are not supported. Optional arguments may be specified by use of the atom ‘optional in the argument list.

Argument conversion takes place as follows.  First, the automation data type for the argument is extracted from the type information.  If it is simply VT_VARIANT (i.e., non-specific), then the VARIANT type is established by using the Prolog data type.  If the VARIANT type is specific, then conversion is correctly coerced insofar as possible.

The following table describes the conversion that generic VT_VARIANT arguments undergo in SWIACTX.

 

SWIP Data Type

Resulting VARTYPE

Notes

PL_ATOM “optional”

VT_ERROR, scode = DISP_E_PARAMNOTFOUND

standard handling of optional parameters

PL_ATOM “true

VT_BOOL, VARIANT_TRUE

 

PL_ATOM “false

VT_BOOL, VARIANT_FALSE

 

PL_INTEGER

VT_I4

 

PL_FLOAT

VT_R8

 

PL_TERM (if list)

if term is a list, a SAFEARRAY is generated

see later description of SAFEARRAY handling

PL_STRING

PL_ATOM

PL_TERM

VT_BSTR, using SWIP’s PL_get_char() function.

will fail on variables

Table 2.2: Default Prolog to Automation Conversions

The following table describes the conversion that arguments with specific VARIANT data types undergo in SWIACTX.  This conversion occurs after the conversions above in the case of a VT_VARIANT; in other words, a VT_VARIANT is assigned a data type through analysis of its Prolog data type, then the standard conversion below is performed.

 

VARTYPE from ITypeInfo

Conversion Routine

Notes

VT_UI2

VT_I2

PL_get_integer()

stored as a short

VT_INT

VT_UI4

VT_UINT

VT_I4

PL_get_integer()

stored as a long

VT_R4

PL_get_float()

stored as a single

VT_R8

PL_get_float()

stored as a double

VT_BSTR

PL_get_chars()

 

VT_BOOL

PL_get_chars(), followed by comparison for “true”.

VARIANT_TRUE if successful, VARIANT_FALSE otherwise

VT_USERDEFINED

VT_I4

User defined types must resolve to TKIND_ENUM.

VT_DATE

 

 

VT_DISPATCH

argument is check for a valid activeXObject functor

IDispatch pointer given if successful

Table 2.3: Default Prolog VARTYPE Conversions

If any SWIP “foreign interface” function fails to perform the necessary conversion, the invocation predicate fails.

2.9.4      Conversion from Automation to Prolog

When an automation method returns, its result VARIANT is converted from an automation data to a Prolog data type; this operation is basically the reverse of the conversion documented in the last section.

Typically, an unbound term (PL_VARIABLE) is given for the result value.  No conversion is performed if the method fails; the variable remains unbound.  Since unification routines are used, results which are fully known or anticipated (such as the atom representing Boolean true) can be used in the result term.

In the table below, the appropriate member variable of the VARIANT union is used in each case.

 

VARTYPE from result VARIANT

Conversion Routine

Notes

VT_EMPTY

VT_NULL

PL_unify_nil()

unifies with ‘[]’, the empty list

VT_UI2

VT_I2

PL_unify_integer()

 

VT_INT

VT_UI4

VT_UINT

VT_I4

VT_USERDEFINED

PL_unify_integer()

 

VT_R4

PL_unify_float()

 

VT_R8

PL_unify_float()

 

VT_BSTR

PL_unify_string_chars()

 

VT_BOOL

PL_unify_atom()

“true” if VARIANT_TRUE, else “false” if VARIANT_FALSE

VT_DATE

special functor

see documentation on activeXVariant

VT_DISPATCH

internal

IDispatch pointer is registered if successful

Table 2.4: Default Automation to Prolog Conversions

2.9.5      Conversion Errors

SWIACTX is unable to resolve some ambiguous cases.  Typically, any errors arising from conversion of arguments, either before or after method invocation, results in failure of the predicate. 

It is important to note the distinction between failure of the method and failure to convert input or output arguments.    To determine the exact nature of the failure, refer to the sections on exception handling and the actxErrorsAsExceptions predicate.  These capabilities allow the Prolog programmer to get detailed information on interface errors.

2.9.6      Special Cases

If a Prolog term that is a list is sent to an automation routine, it is first converted to a SAFEARRAY.  In this case, the list must be one-dimensional (i.e., none of its elements may be lists).  The list is scanned before conversion, and the data type of the first element is used as the data type for all.  Element conversions are limited to the following table:

 

Verification Routine

VARTYPE

PL_is_string

VT_BSTR

PL_is_atom

VT_BSTR

PL_is_integer

VT_I4

PL_is_float

VT_R8

Table 2.5: Special conversion in SAFEARRAYs

I hope that in the future I will be able to make this conversion more sophisticated, but it should suffice for most automation uses.

2.10  SAFEARRAY Handling

The automation data type SAFEARRAY presents a special problem.  While it is straightforward to convert lists of common terms to SAFEARRAYs, many interfaces require particular VARTYPEs.   When using an ADO 1.5 “Bookmark”, for example, it is necessary to pass a SAFEARRAY of unsigned characters (8-bit values); no other value type will work.  The only way SWIACTX can do this correctly is to be explicitly given the required VARTYPE value.

Likewise, when a SAFEARRAY value is returned from a call, there must be a means for the Prolog client to know exactly what type of data is present.

For these reasons, a special functor is used to “wrap” SAFEARRAY values passing into and out of the SWIACTX interface.  This functor, activeXSafearray/2, is defined as:

activeXSafearray(VarTypeInt,ListOfElements).

Every SAFEARRAY returned to the caller as the result of an actxInvoke call is converted to this form. The Prolog author can examine both the elements of the list and the specific automation data type.

Similarly, when an ActiveX function requires or allows a SAFEARRAY, the Prolog programmer must create this functor in order to specify exactly how the data is stored and passed to the automation layer.

As stated above, if naked Prolog lists are presented where SAFEARRAYs are required, SWIACTX attempts to convert the underlying data as best it can.  This set of conversions is similar to those in Table 2.1.  The area of greatest uncertainty occurs with integer values; many automation interfaces accept several alternatives; some are much more restrictive.  The best means of determining the requirements of a particular interface’s SAFEARRAY property is to call the corresponding “get” property and see what VARTYPE it uses.  Then use that same type for SAFEARRAY “put” operations.

2.11  Other Special VARIANT Data Types

As stated above, the VT_DISPATCH variant type is handled through the use of the activeXObject functor.

Additionally, there is a means by which unusual data VARIANT types can be read from and reported back to Prolog.  This is done with the activeXVariant/2 functor, which is similar to the activeXSafearray.

activeXVariant(VarTypeInt,DataTerm).

When such a functor is passed as a function argument to SWIACTX, the variant type integer (first argument) is use to guide the conversion of the data term (second argument).

These are the variant types supported using this method.

 

Variant Type

Data Term Type

Notes

VT_DATE

float (double)

other conversion routines apply to the float

Table 2.6: Special conversions using activeXVariant

2.12  Types of Function Invocation

Function invocation requires several arguments:

·        the IDispatch functor

·        the invocation method name or list

·        the list (possibly empty) of arguments to the method

·        the result argument

The most common case is a simple method invocation. In this case, the second argument is only a single atom—the name of the method.  However, there are four basic types of ActiveX automation invocation, and using the others types requires that a list be presented.  The four types, listed by the required atom, are:

func: This is a simple method invocation, and is the default when only a method name is present.

propget:  This is a call to return a standard property from an object.

propput: This is a call to alter a standard property of an object.

propputref:  This is a call to alter an object’s property that is an COM interface reference.

In these cases, the second argument might appear as:

[‘Size’,propput]

The method (or property name) is Size.

2.13  Context Management

One important requirement that ActiveX automation imposes on its clients is the careful management of reference counts on returned object references.  SWIACTX supports two kinds of reference management.

Global reference management is the default.  In this case, the Prolog programmer is responsible for recording (i.e., asserting) returned ActiveX functor information and releasing it when no longer necessary.  There is also a “release all” function which discards all known references maintained by SWIACTX.

Context-sensitive reference management is also available.  This means that a “placeholder” is put on to the Prolog backtrack stack, and when evaluation rolls back to that point, all ActiveX information accumulated since the placeholder was recorded is discarded.  This is very similar to standard C++ or VB stack-based variable garbage collection.

The predicate actxContext/0, is used to perform context-based reference management.  This predicate succeeds exactly once, but is marked as non-deterministic, meaning that Prolog will backtrack to it.  Upon backtracking, it will fail, and, in failing, remove all ActiveX information accumulated by SWIACTX since the predicate was first evaluated.

There is one other interesting case in context management.  It sometimes happens that a complex object model will require that the programmer obtain several intermediate object references before getting an object reference that is important.  In this case, the context handler will mark the both the temporary and desired object references the same way—as belonging to the local context.  The programmer must have a way to “promote” the useful reference into the “global” or outermost context, thereby preserving it for later use.   This is accomplished using the actxContextGlobal/2 predicate.

2.14  SWIACTX Predicates

This section documents the individual SWIACTX predicates and their usage.  The predicates are referred to using the standard predicate/arity notation.  The argument direction (input, output or both) is document as in the SWIPRM.

In the current version of SWIACTX, these predicates will fail if either the method fails (HRESULT != S_OK) or argument conversion fails.

Predicates are deterministic unless otherwise noted.

2.14.1                       actxCreateObject/2

actxCreateObject( StringProgId+, FunctorObject-)

Given a UUID or ProgID string, creates an automation object and returns a reference functor.  If a GUID/UUID is given, it must be enclosed in curly braces, such as “{24345413-F98C-11D2-A93B-00C04F72E076}”.

If successful, the output value is a functor of the form

activeXObject(n)

where n is a unique integer maintained by the SWIACTX DLL.

2.14.2                       actxReleaseObject/1

actxReleaseObject( FunctorObject+ )

Releases an automation object.

2.14.3                       actxInvokeObject/4

actxInvokeObject( FunctorObject+,
                  InvocationAtomOrList+,
                  ArgumentList+,
                  Result- )

Calls a method on an automation object.  The FunctorObject  must be a in the standard form and must contain a currently registered automation reference index.

The InvocationAtomOrList is either an atom (n.b., not a string or character list) containing the method name or a list containing the method name atom and the method invocation type atom.  See the section Types of Function Invocation  for more information.

The ArgumentList is a Prolog list of arguments to be passed to the method.  These are converted as documented elsewhere.  If there are no arguments, pass the empty list (‘[]’).

The Result is usually an unbound variable, which is unified with the results of the method invocation.  If the function doesn’t return a value, it is unified with the empty list.

2.14.4                       actxQueryInterface/3

actxQueryInterface( FunctorObject+,
                    StringGuid+,
                    NewFunctorObject- )

Performs a QueryInterface call on the automation object, and returns the IDispatch interface of the result.

The FunctorObject is as per usual.

The StringGuid is a GUID in standard text form (i.e., with curly braces).

The NewFunctorObject term is unified with the resulting registered automation reference.

2.14.5                       actxEnumObject/2 (non-deterministic)

actxEnumObject( FunctorObject+, EnumerationResult- )

Performs enumeration of an IEnumVARIANT collection, backtracking over the results.

The FunctorObject is as per usual.

The EnumerationResult term is unified with results of the enumeration. 

2.14.6                       actxCloneObject/2

actxCloneObject( FunctorObject+, NewFunctorObject- )

Creates a new automation object reference which is a copy of an existing reference.

The FunctorObject is as per usual.

The NewFunctorObject term is unified with the resulting registered automation reference.

2.14.7                       actxCollectionList/2

actxCollectionList( FunctorObject+, CollectionAsList- )

Returns the results of an IEnumVARIANT enumeration as a list.

The FunctorObject is as per usual.

The CollectionAsList term is unified with a list containing all the elements successfully enumerated from the collection.

2.14.8                       actxReleaseAll/0

Releases all stored automation references maintained by SWIACTX.

2.14.9                       actxBreak/0

(Debugging only) Causes a run-time debugging “break” in SWIACTX.DLL.

2.14.10                  actxContext/0  (non-deterministic)

Sets up a “once”-style backtracking predicate that discards all automation object references upon backtracking.

In other words, all ActiveX object references obtained during deeper searches are marked with a context “horizon”.   This predicate always fails upon backtracking, and, along with failing, discards any ActiveX object references accumulated during goal searching descent.

2.14.11                  actxContextGlobal/2

actxContextGlobal( FunctorObject+,
                   BooleanAtomOrUnboundVar? )

Promotes a local (context-specific) automation reference to a global reference.

If the Boolean (second) term is bound to the atom true, the object reference represented by the given ActiveX functor is promoted to the global context.  Hence, it will never automatically be released due to backtracking.

If the Boolean term is bound to the atom false, the object reference represented by the given ActiveX functor is marked as belonging to the local context.  Hence, it will be released when the actxContext predicate that created the current context is backtracked over.

If the Boolean term is unbound, it is unified with true if the ActiveX object is in the global context or false if it is not.

2.14.12                  actxListToDate/2

actxListToDate(DateAsIntegerList?, DateAsFloat?)

This predicate performs bi-directional conversion between standard OLE dates (such as used by ADO) and lists of integers.  For example,

actxListToDate([1998,11,2,0,0,0],36101.0).

In this case, both terms are instantiated, so only a unification check is performed.

If the list term is a variable, the date term is converted to an integer list and unified with it.

If the date term is a variable, the list term is converted to an OLE date and unified with it.

2.14.13                  actxErrorsAsExceptions/1

actxErrorsAsExceptions( Bool? )

This predicate controls whether SWIACTX reports an IDispatch interface error as an exception (using the activeXError functor) or simply fails the predicate. 

The default is to fail the predicate.

If the Bool term is unbound, it will be bound to the current setting of the treat-errors-as-exceptions flag, either true or false.

If the Bool term is bound to either true or false, this value will be applied to the internal flag.

Note that this is a persistent “side effect”, and is not undone during backtracking. 

This reporting mechanism currently only applies to actxInvokeObject; that is, calls to IDispatch::Invoke (via DispInvoke).

It is recommended that setting actxErrorsAsExceptions(true) should only be done during testing to track down the source of an interface failure.  Since many ActiveX methods routinely report failure during normal operations (such as end of file or non-present properties), throwing exceptions during such benign operations can be seriously misleading.

3         PROQUERY: Prolog Automation

3.1       Overview

Programs developed in Prolog will usually be invoked in a larger framework of scripting language or higher-level control.   SWIP supports this capability by allowing its top-level “read-eval-print” loop to be supplanted by a dynamic-link library interface.

This capability allows the entire SWIP engine to be embedded into an in-process ActiveX DLL and invoked, when necessary, to perform operations be suited to Prolog.

These operations are usually limited to goal queries for well-established goals.  The development cycle, then, would be similar to the following.

1) Use the “command line” or “windowed” version of the interpreter and you favorite text editor to construct the Prolog programs representing the goals to be answered.

2) Save the resulting Prolog programs into a “library” directory and build an index for them.

3) Build a higher-level application using Visual Basic or some other scripting language for ActiveX.  Add an instance of the PrologQuery object the application.

4) Set your private “library” directory as a reference library directory for automatic loading by SWIP by executing a simple query using PrologQuery.  See the SWIPRM for documentation about the library_directory/1 predicate.

5) Pass Prolog queries to the PrologQuery object as your application requires.

This procedure allows programs in VB, C++ or a scripting language to directly invoke and obtain results from any amount of previously tested Prolog code.

3.2       Limitations

Only one instance of the PROQUERY object may be created during the execution of a single process.  This is due to the non-reentrant nature of the underlying SWI-Prolog code.

3.3       Caveat

This interface is still under development, so not all of the arguments and routines are fully documented or are listed in their final forms.

3.4       Changes to SWI Prolog Sources

Only one change was made to the SWI Prolog sources to implement the object described in this section.

The routine initPaths() in module pl-main.c was changed to use the zeroth command line argument to locate the directory of the executable from which the application is running.  Without this change, any binary that wanted to use IPrologQuery would have to be located in SWIP’s “Bin” directory.

3.5       Interface IPrologQuery

The program identification string (ProgID) for IPrologQuery is “ProQuery.PrologQuery.1”.

This section documents its methods.

3.5.1    SetPredicate

This method is used to bind the IPrologQuery object to a particular predicate, giving its name, arity and module.

SetPredicate ( BSTR bstrName,
               long iArity,
               [optional] BSTR bstrModule )

This preparatory method establishes the goal predicate used in the actual query methods.

This method only needs to be called once for any number of invocations of the same goal predicate.

3.5.2    OpenQuery

This method starts a new goal.

OpenQuery([in] long iFlags,
          [in] SAFEARRAY(VARIANT)* rgArgs )

The second argument is an array of VARIANTs to be converted and passed to Prolog when the query is invoked.  This array must have a dimension of one, although individual elements may themselves be SAFEARRAYs.

The elements of the SAFEARRAY are converted to an array of Prolog terms in a manner very similar to that document in the previous section about the SWIACTX interface.  These are the important differences.

·        Variables that are to receive data from Prolog (i.e., that will become unified as a result of the query) must be represented in the SAFEARRAY by VARIANTs of type VT_EMPTY.

·        There is no way to pass an object reference directly to Prolog, or to return one.

If a VARIANT of type VT_VARIANT is encountered, it is converted in the same manner.   This means that “by ref” values are dereferenced.

3.5.3    NextSolution

This function is called to obtain the first and all subsequent results of the query. 

NextSolution( [out] SAFEARRAY(VARIANT)* rgArgs,
              [out,retval] VARIANT_BOOL * pbSucceed )

The second argument indicates whether the predicate succeeded.

If the predicate succeeds, a SAFEARRAY argument will be returned which is the result of converting the terms of the query back to VARIANTs.

This returned array is always a one-dimensional array of VARIANTs.  The expected conversions are performed, in a similar spirit to the SWIACTX conversion documented in the earlier section.  The significant differences are:

·        Any Prolog term (PL_TERM) that is a list (PL_is_list()) is converted to yet another SAFEARRAY of VARIANTs.

·        Any unbound variables are returned as VT_EMPTY.

3.5.4    CutQuery

This operation performs a logical “cut” on the query.

Note: in this release, this is identical to CloseQuery.

3.5.5    CloseQuery

This operation closes the query and discards all Prolog information associated with it.  It must be called before another OpenQuery will operate successfully.

3.5.6    ModulePath

This function returns the complete path to the installation location of the model ProQuery.DLL.   Normally, this is the SWI-Prolog installation’s bin directory.

ModulePath ( [out,retval] BSTR * pbstrPath );

3.6       Usage Example

This example performs a simple “consultation” of a Prolog program as a query.

Dim ipQuery As new PrologQuery

This declares the interface reference to the PrologQuery object.

The following subroutine is used to enumerate all the possible solutions.  Note that failure of the query causes and error that must be handled in Visual Basic using the on error syntax.

Function CountQueries() As Long
    On Error GoTo CQ_exit
    Dim rgArgs() As Variant
    Dim cQuery As Integer
    cQuery = 0
    While ipQuery.NextSolution(rgArgs)
        cQuery = cQuery + 1
    Wend
CQ_exit:
    CountQueries = cQuery
    Exit Function
End Function

The following routine performs the consult/1 predicate on a Prolog source file.  (Note that the PrologQuery engine requires the full path.)  Clearly, this predicate will succeed only once, but the code is written as though multiple solutions were possible.

Sub TestQuery()
    Dim rgArgs() As Variant  
  Arguments to the query
    Dim cQuery As Integer     Number of solutions
    Dim sQueryPath As String    Path to .PL file
    Dim sCurDir As String      Current directory
       
    On Error GoTo tq_end   
  Set up error handling
    sCurDir = CurDir       Get the current directory
      Create the full path to the Prolog program
    sQueryPath = sCurDir & "\vb\proqtest.pl"
      Prepare the argument array; just one arg—the filename
    ReDim rgArgs(0)
    rgArgs(0) = sQueryPath
      Set the predicate: consult/1, part of the system module
    ipQuery.SetPredicate "consult", 1, "system"
      Open the query, setting the initial arguments.
    ipQuery.OpenQuery 0, rgArgs
      Get the count of solutions (should be 1)
    cQuery = CountQueries
      Pop up a message box
    MsgBox "Query succeeded " & cQuery & " times", vbOKOnly, _
                          "Query Results"
      Close the query (clean up memory, etc.)
    ipQuery.CloseQuery

  Exit Sub

tq_end:
      If we get here, we probably couldn’t find the predicate
    MsgBox "Query processing failed", vbOKOnly, "Test Query"
    Exit Sub
End Sub