Swi-cs-pl - A CSharp class library to connect .NET languages with SWI-Prolog
SbsSW.SwiPlCs
Abstract
This document describes a CSharp interface to SWI-Prolog. The described interface provides a layer around the C-interface for natural programming from C#. The interface deals with automatic type-conversion to and from Prolog, mapping of exceptions and making queries to Prolog in an easy way. There is a call-back from Prolog to C#.
Introduction
The first version of this Interface was more or less a port of the C++ interface. Now the naming is more '.Net' like and the interface provides a number of features that make queries to SWI-Prolog very easy and powerful. Using programmable type-conversion (casting), native data-types can be translated automatically into appropriate Prolog types. Automatic destruction deals with most of the cleanup required.
Acknowledgements
I would like to thank Jan Wielemaker for answering many questions and for his comments.
Also to Arne Skjærholt for the 64-Bit version (SwiPlCs64.dll) and Batu Akan for the Mono code. Foutelet Joel provide the F# sample.
Download binaries

Here is the link to download the latest binaries or older versions. Download page
There is also a copy on SWI-Prolog Twiki page and SWI-Prolog contrib page. The latter might be outdated.
At present I only publish the binaries including the documentation. The sources, which are now under LGPL 2, might be published later. If you like to see them or work on them don't hesitate to contact me via mail.

Versions

The latest version work with SWI-Prolog 6.3.1 and higher.

The AssemblyVersion number, e.g. 1.1.60601.0, can be interpreted as follows:
  • 1 - major version
  • 1 - minor version
  • 60601 - SWI-Prolog version 6.6.1 (test cases run against this prolog version)
  • 0 - patch level version
Getting started

Copy SwiPlCs.dll or SwiPlCs64.dll and SwiPlCs.XML where ever you want and add a project reference to SwiPlCs.dll.

After that IntelliSense and tool tips should be available.

screen shot

Make sure that libswipl.dll and its dependencies could be found by the system. For the sample below it is: libswipl.dll, pthreadGC2.dll, libgmp-10.dll, files.dll For a big application it could be a lot more.

TIP: For development add the SWI-prolog bin directory to the PATH environment variable.
NOTE: Don't forget to restart Visual Studio after that. VS must recognize the new environment for debugging.

Basically windows search first in the folder where the executable resist than in the windows system directory and at least in the directories that are listed in the PATH environment variable. For details see "Dynamic-Link Library Search Order"

If libswipl.dll or one of its dependencies could not found you will recive an error like
System.IO.FileNotFoundException: Das angegebene Modul wurde nicht gefunden. (Ausnahme von HRESULT: 0x8007007E)

An other common error is:
SWI-Prolog: [FATAL ERROR:
     Could not find system resources]
Failed to release stacks

To fix this add the SWI_HOME_DIR environment variable as described in SWI-Prolog FAQ FindResources with a statment like this befor calling PlEngine.Initialize.

Environment.SetEnvironmentVariable("SWI_HOME_DIR", @"the_PATH_to_boot32.prc");
First program

A sample says more then I want to write here.

CopyC#
using System;
using SbsSW.SwiPlCs;

namespace HelloWorldDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //Environment.SetEnvironmentVariable("SWI_HOME_DIR", @"the_PATH_to_boot32.prc");
            if (!PlEngine.IsInitialized)
            {
                String[] param = { "-q" };  // suppressing informational and banner messages
                PlEngine.Initialize(param);
                PlQuery.PlCall("assert(father(martin, inka))");
                PlQuery.PlCall("assert(father(uwe, gloria))");
                PlQuery.PlCall("assert(father(uwe, melanie))");
                PlQuery.PlCall("assert(father(uwe, ayala))");
                using (PlQuery q = new PlQuery("father(P, C), atomic_list_concat([P,' is_father_of ',C], L)"))
                {
                    foreach (PlQueryVariables v in q.SolutionVariables)
                        Console.WriteLine(v["L"].ToString());

                    Console.WriteLine("all children from uwe:");
                    q.Variables["P"].Unify("uwe");
                    foreach (PlQueryVariables v in q.SolutionVariables)
                        Console.WriteLine(v["C"].ToString());
                }
                PlEngine.PlCleanup();
                Console.WriteLine("finshed!");
            }
        }
    }
}

Here is how to use the library in F#. Manny thanks to Foutelet Joel for this sample.

CopyF#
// Learn more about F# at http://fsharp.net
open System
open SbsSW.SwiPlCs;

let ple = PlEngine.IsInitialized in
       if ple then printfn "Echec initialisation" else
         begin
             PlEngine.Initialize([|"-q"|])
             PlQuery.PlCall("assert(father(martin, inka))") |> ignore
             PlQuery.PlCall("assert(father(uwe, gloria))") |> ignore
             PlQuery.PlCall("assert(father(uwe, melanie))") |> ignore
             PlQuery.PlCall("assert(father(uwe, ayala))") |> ignore

             let q = new PlQuery "father(P, C), atomic_list_concat([P,'is_father_of ',C], L)" in
             begin
                 Seq.iter (fun (x : PlQueryVariables)  -> printfn "%A" (x.Item("L").Name)) q.SolutionVariables

                 printfn "all children from uwe:"
                 let r : PlQueryVariables = q.Variables in r.Item("P").Unify("uwe") |> ignore
                 Seq.iter (fun (x : PlQueryVariables)  -> printfn "%A" (x.Item("C").Name)) q.SolutionVariables
             end

             PlEngine.PlCleanup()
             printfn "%A" "finished!"
         end

For further samples see the examples in SbsSW.SwiPlCs and SbsSW.SwiPlCs.PlEngine.

The class SbsSW.SwiPlCs.PlQuery is the key to ask SWI-Prolog for proofs or solutions.

The SbsSW.SwiPlCs.PlTerm plays a central role in conversion and operating on Prolog data.

Programming tips

I strongly recomanate to use SbsSW.SwiPlCs.PlQuery in a using statment using statment like in the sample program above.
An alternitive is to call Dispose explicitly like in the sample below.

CopyC#
const string strRef = "a;e;";
PlQuery.PlCall("assert(n('" + strRef + "'))");
var q = new PlQuery("n(X)");
Assert.IsTrue(q.NextSolution());
Assert.AreEqual(strRef, q.Variables["X"].ToString());
var q2 = new PlQuery("n('" + strRef + "')");
Assert.IsTrue(q2.NextSolution());
Assert.AreEqual(strRef, q.Variables["X"].ToString());
q2.Dispose();
q.Dispose();

Note that access to the query Variables is impossible after Dispose().

Known Bugs

SbsSW.SwiPlCs.PlEngine.Initialize work *not* as expected if there are e.g. German umlauts in the parameters e.g. in the path or filename for a qlf file ( switch -x )
See marshalling in the source NativeMethods.cs