ActiveX Extensions to SWI Prolog

Download Libraries


David Hovel
March 17, 2000
Version 0.7

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 Adaptive Systems and Interaction group of 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.3.2 release of SWI Prolog.  The SWIP documentation notes refer to the SWI 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       A Note about Databases

A primary goal of this effort was to allow Prolog to access standard databases directly, including explicit SQL usage and support for different vendor formats.

Microsoft Corporation provides the fundamental component in its ActiveX Data Access Objects system, or ADO.   ADO provides automation-compliant ActiveX interfaces to ODBC technology, which in turn provides support for a wide variety of database technologies by many software vendors. 

A demonstration Prolog program and its companion database are provided as educational materials with the release of this software.  Please note that running the demonstration requires Microsoft Access 2000, since the database itself was generated by that product.

2         A Brief Introduction to ActiveX

2.1       What is ActiveX?

ActiveX is the name given by Microsoft for its family of component technologies.  In brief, it is a family of software interfaces that allows components, both graphical and non-graphical, to be registered on a user’s computer.   Such components can then be located and dynamically activated on program demand.

2.2       Instances and Interfaces

Each ActiveX load operation (known as CreateInstance) creates an instance of an ActiveX object that supports one or more interfaces.  An interface is a programmatic contract between an object and its clients.

2.3       Types of Interfaces

An ActiveX interface can be constructed to provide virtually and type of data in almost any manner.  This level of flexibility is only available directly from the C or C++ language level. 

More commonly, objects that must be available to Visual Basic or scripting environments support a special interface called IDispatch.   Objects that support IDispatch are commonly known as automation objects. 

Scripting engines can use the type and function information stored along with an automation object to determine:

2.4       SWIACTX Support and Limitations

The features described in this document are only available for non-graphical ActiveX automation objects.  In other words, purely algorithmic components that support the IDispatch interface.

Event interfaces (“connection points”) are not supported.

Named arguments are not supported.

2.5       Documentation

Microsoft Corporation maintains an extensive collection of documentation about ActiveX technologies available for free at http://www.msdn.microsoft.com. 

More to the point, specific documentation is available for such widely distributed ActiveX components as the Active Data Objects interfaces, or ADO, which is the current Microsoft standard for heterogeneous database access on Windows platforms.

3         SWIACTX: ActiveX Automation Support

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

If correctly configured, SWIACTX DLL is automatically loaded when its associated predicates are invoked.   To guarantee such behavior, follow these steps.

1) Place the SWIACTX.DLL is the Bin directory of the SWIP installation.

2) Place the prolog program SWIACTX.PL in the Library directory.  It declares the predicates that exist in SWIACTX.DLL.

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

Then, whenever 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.

3.2       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 executable name extensions used by SWIP.

The choice of DLL is determined by SWIACTX.PL depending upon the build variant of SWIP currently running.   Normal binary distributions for Windows will utilized only the “release” version.

3.3       Basics of ActiveX Interaction Using SWIACTX

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

For further information, link to http://www.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.  The portion of a binary image that describes the object, its interfaces and methods is called the type library.

Interpretive or scripting environments only use one 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.   IDispatch also provides direct access to its type library so that an interpreter can discover the available methods, properties and arguments supported and required by a specific object.  This capability is sometimes referred to as introspection.

The IDispatch mechanism simplifies the use of scriptable objects, since the interpreter doesn’t have to know the specifics of using an object.  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 language’s data structures.

3.3.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-dimension arrays

IDispatch

The name of the primary automation (scripting) interface.

ProgID

A name string used to create an ActiveX object

GUID

Globally Unique Identifier.  In ActiveX, these are used to identify classes of objects and their specific interfaces.

IEnumVariant

A standard interface provided by collection objects that allows enumeration of the contents of the collection.

Table 2.1: Basic definitions

3.3.2      The activex_object Functor

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

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

activex_object(7).

This is how a typical 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 explicitly maintaining and releasing these references according to the needs of their programs.  SWIACTX also provides implicit support for garbage collection using Prolog’s standard backtracking operations.

The integer argument of the activex_object 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.

3.3.3      Argument Conversion

Since the basic task of SWIACTX is to translate between calls in the Prolog programming model to calls in the ActiveX model, data conversion is essential.

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

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

·        On input to ActiveX functions, Prolog atoms, strings and string lists are all converted to ActiveX BSTRs.

·        On input to ActiveX functions, lists of terms with a common type (e.g., all integers) are converted to SAFEARRAYs and stored in VARIANTs.

Most conversions are driven by type library information; they are therefore “automatic”.  In those cases where type information is insufficient, more direct control is available.

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

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

3.4       SWIACTX Functions

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

Name

Arity

Purpose

actx_create_object: 

2

Given a ProgID string or a GUID formatted as a string, this predicate creates an automation object and returns a reference functor.

actx_release_object

1

Releases an automation object or interface.

actx_invoke_object

4

Calls a method on an automation object.

actx_query_interface

3

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

actx_enum_object

2

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

actx_clone_object 

2

Creates a new automation object reference identical to the original.

actx_collection_list 

2

Returns the results of an IEnumVARIANT enumeration as a list.

actx_release_all

0

Releases all stored automation references maintained by SWIACTX.

actx_context

0

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

actx_context_global 

2

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

actx_list_to_date

2

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

actx_errors_as_exceptions

1

Controls the reporting of interfaces errors as exceptions.

The terms automation and scripting are interchangeable.

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

Predicates whose names begin with ‘actx’ are provided by SWIACTX. 

Prolog variable terms whose names begin with ‘IP’ are activex_object functions, which, in turn, represent ActiveX interface pointers.

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

The predicate above uses the actx_create_object predicate to create a connection object.  If successful, it asserts the result functor into the Prolog database nested into another functor.

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

The predicate above uses the functor previously asserted 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 :-
       actx_create_object('ADODB.Recordset',IPRset),
       assertz(adoobj(recordset,IPRset)),
       adoobj(connection,IPConn),
       actx_invoke_object(IPRset,'Open',
                   ['Runs',IPConn],_),
       write('recordset opened'),nl.

The predicate above 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),

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

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

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

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

adoReadNext :-
      adoNotEof,
      adoobj(recordset,IPRset),
      actx_invoke_object(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.

It should be clear from this brief code example that the bulk of the complexities of using ActiveX from SWI Prolog arise from the nature of the ActiveX interfaces.  ADO is a particularly thorny example due to the number of basic object types and the range of their interactions.

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

3.6       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

activex_error(-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,
          activex_error(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, activex_error.

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.

3.7       Types of Arguments

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

3.7.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 32-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.

For more detailed information, refer to the SWIPRM.

3.7.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 an array for passage to IDispatch::Invoke (or 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.

If the supplied argument list has fewer members than the number of parameters expected by the automation method, the conversion routine automatically supplies the correct number of optional arguments.

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 activex_object 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 predicate fails.

3.7.4      Conversion from Automation to Prolog

When an automation method returns, its result VARIANT is converted from an automation data type 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 that 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 activex_variant

VT_DISPATCH

internal

activex_object functor is registered and returned if successful

Table 2.4: Default Automation to Prolog Conversions

3.7.5      Conversion Errors

SWIACTX is unable to resolve some ambiguous cases.  Typically, any error 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 actx_errors_as_exceptions predicate.  These capabilities allow the Prolog programmer to get detailed information on interface errors.

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

3.8       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, activex_safearray/2, is defined as:

activex_safearray(+VarTypeInt,+ListOfElements).

Every SAFEARRAY returned to the caller as the result of an actx_invoke_object 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.

3.9       Other Special VARIANT Data Types

As stated above, the VT_DISPATCH variant type is handled through the use of the activex_object 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 activex_variant/2 functor, which is similar to the activex_safearray.

activex_variant(+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 activex_variant

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

3.11  Default Method Invocation

Any automation object can declare a default method to be invoked by VB or scripting languages when no method name is present.  This default or “value” method (identified by the dispatch ID DISPID_VALUE) is actually a property, and in most cases supports both propget and propput (or propputref, as appropriate).

SWIACTX supports use of the default method in the following way.  If the function invocation list is empty (‘[]’), the default or value method is invoked in its propget form.  If the list contains only an invocation type token, that token is used to call the default method.

3.12  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 that 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 actx_context/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 actx_context_global/2 predicate.

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

3.13.1                       actx_create_object/2

actx_create_object( +StringProgId, -Object)

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

activex_object(n)

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

3.13.2                       actx_release_object/1

actx_release_object( +Object )

Releases an automation object.

3.13.3                       actx_invoke_object/4

actx_invoke_object( +Object,
                  +InvocationAtomOrList,
                  +ArgumentList,
                  -Result)

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

The InvocationAtomOrList is either an atom (note, not a string or character list) containing the method name or a list containing the method name atom and, as required, a 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.

3.13.4                       actx_query_interface/3

actx_query_interface( +Object,
                    +StringGuid,
                    -NewFunctorObject)

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

The Object 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.

3.13.5                       actx_enum_object/2 (non-deterministic)

actx_enum_object( +Object, -EnumerationResult )

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

The Object is as per usual.

The EnumerationResult term is unified with results of the enumeration.  

3.13.6                       actx_clone_object/2

actx_clone_object( +Object, -NewObject )

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

The Object is as per usual.

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

3.13.7                       actx_collection_list/2

actx_collection_list( +Object, -CollectionAsList )

Returns the results of an IEnumVARIANT enumeration as a list.

The Object is as per usual.

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

3.13.8                       actx_release_all/0

Releases all stored automation references maintained by SWIACTX.

3.13.9                       actx_context/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.

3.13.10                  actx_context_global/2

actx_context_global( +Object,
                     ?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 actx_context 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.

3.13.11                  actx_list_to_date/2

actx_list_to_date( ?DateAsIntegerList, ?DateAsFloat )

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

actx_list_to_date([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.

The order of terms in the list is [year,month,day,hour,minute,second].

3.13.12                  actx_errors_as_exception/1

actx_errors_as_exception( ?Bool )

This predicate controls whether SWIACTX reports an IDispatch interface error as an exception (using the activex_error 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 actx_invoke_object; that is, calls to IDispatch::Invoke (via DispInvoke).

It is recommended that setting actx_errors_as_exception(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.

4         PROQUERY: Prolog Automation

4.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 your 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.

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

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

4.4       Interface IPrologQuery

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

This section documents its methods.

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

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

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

4.4.4    CutQuery

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

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

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

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

4.5       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