Guides/.NET Interop

From J Wiki
Jump to navigation Jump to search

In this guide we will explore the ways to communicate between J and Microsoft .NET platform. By using the best of both worlds, this approach keeps clear separation between J code and .NET code thus allowing to effectively apply existing skills without reinventing the wheel.

The .NET code will be responsible for what it's best at, providing object-oriented component architecture, and J will be used as a compact and efficient platform for data manipulation. The array processing power of J will be complemented with generic ways of treating datatypes in .NET.


The examples are separate C# projects within one Visual Studio 2005 solution.
Wm yes check.png

So we start by creating a blank solution in any folder, for example d:\JayInterop\JayInterop.sln

Simple COM

Wm yes check.png

COM is the most successful component architecture and the early choice of J system to expose itself with COM interface among other advantages yields very easy .NET interoperability.

We create a project T01_SimpelCOM as C# Console Application. We remove all unused references and imports: Data, XML, etc.

Here's how simple it is to connect J. First we add a reference to the project, by selecting JDLLServer from the COM tab.

Then we open the C# file and use the referenced J namespace by including

using JDLLServerLib;
<!> Both this directive and entering other J code is supported by intellisense.

J component represents a single session. To start the session, we create an instance of the J component

    JDLLServerClass j = new JDLLServerClass();

For most types of use, as long as you release the reference, the automatic mechanism of disposing COM objects is sufficient, at which point the J session will be closed.

That's it. Now we are ready to make a simple call. Most J component functions return an integer status code and provide the result in the last out argument.

<!> COM types are automatically converted (marshalled) to .NET types. For example, COM BSTR is mapped to .NET String class, COM Variant is mapped to Object.

Method DoR runs a J script and returns text result.

int DoR(string input, out object v) Execute string and return formatted output in BSTR

Here is the rest our program

        object result;
        int status = j.DoR("2| !/~i.8", out result);

        Console.WriteLine(
            string.Format("J DoR ended with status {0} and result\n{1}",
                status, result));

Now we are ready to compile and run the program, which will show

J DoR ended with status 0 and result
1 1 1 1
0 1 0 1
0 0 1 1
0 0 0 1

Error Handling

Wm yes check.png

The status code returned by J component methods indocates an error if its value is other than 0. The next project T02_Errors will show how errors can be handled.

<!> Use Object Browser both for the COM module and the .NET reference to get an idea about what methods are available and how they can be used.

To obtain the error message, the ErrorTextB method is used

int ErrorTextB(int error, out object v) Get variant BSTR with error code text.

Here is one way to handle an error

    int status = j.DoR("2| !/~i.4a", out result);    // deliberate error

    if (status == 0) {
        Console.WriteLine(
            string.Format("J DoR ended with status {0} and result\n{1}",
                status, result));
    } else {
        j.ErrorTextB(status, out result);
        Console.WriteLine(
            string.Format("|error {0}\n|{1}", status, result));
    }

And we obtain the following output

|error 5
|ill-formed number

Direct Call

Wm yes check.png

It is possible to make the calls to J component more like familiar .NET and COM calls, so that the result is returned as the result of the method and errors are raised as exceptions.

We will need to create two classes: a custom exception and a new J interface class. We encapsulate the previous call boilerplate inside the wrapper methods.

<!> It is possible to further improve J COM interface by following the same call pattern, with direct results and errors provided through IErrorInfo COM interface, which would automatically be converted to .NET exception.

Errors become exceptions and are factored out to reuse in other methods.

    class JayException : Exception {
        public JayException(string message) : base(message) { }
    }

    class Jay {
        JDLLServerClass j = new JDLLServerClass();

        public string DoR(string script) {
            object result;
            assert(j.DoR(script, out result));
            return result as string;
        }

        protected void assert(int status) {
            if (status == 0) return;
            object result;
            j.ErrorTextB(status, out result);
            throw new JayException(result as string);
        }
    }

Since COM return types are Variants rather than BSTR we follow the convention by passing Object type, which is then unboxed to String. Indeed, in debugger immediate window we get
    result.GetType().Name
    "String"

Now the interface looks much more familiar and compact.

            Jay j = new Jay();

            string scr = "2| !/~i.4";
            Console.WriteLine("   {0}\n{1}", scr, j.DoR(scr));

            scr = "2| !/~i.4a";
            Console.WriteLine("   {0}\n{1}", scr, j.DoR(scr));

With the result

   2| !/~i.4
1 1 1 1
0 1 0 1
0 0 1 1
0 0 0 1

Unhandled Exception: JayInterop.JayException: ill-formed number
   at JayInterop.Jay.assert(Int32 status) in JayInterop\T03_StrArg\Program.cs:line 23
   at JayInterop.Jay.DoR(String script) in JayInterop\T03_StrArg\Program.cs:line 15
   at JayInterop.Program.Main(String[] args) in JayInterop\T03_StrArg\Program.cs:line 37

The convenience of stack trace tells exactly where the exception happened.

Arguments

Wm yes check.png

Another amazing feature of COM is the generic type Variant, but still more amazing is that J component supports two-way conversion of almost all Variant types into J scallars and vectors. This is naturally translated into .NET Object type and also converted between strong types. As a result we have an easy mapping between J types and .NET equivalents.

To exploit this feature, we add two more wrappers for setting J variables with .NET values and getting result from J variables using J names.

int SetB(string jname, ref object v) Set variant value in J variable (BSTR)
int GetB(string jname, out object v) Get variant value from J variable (BSTR)
        public void SetB(string name, object val) {
            assert(j.SetB(name, ref val));
        }

        public object GetB(string name) {
            object result;
            assert(j.GetB(name, out result));
            return result;
        }
See Strings for mode details on handling strings.

Since we are going to use strong types we need a new execution method that takes J script and only throws an exception on error.

int Do(string input) Execute string
        public void Do(string script) {
            assert(j.Do(script));
        }

Now, ready for some .NET array processing?

    double[] arr = new double[] { 1.2, 3, 4.5 };   // define .NET array

    string scr = "res=: (+/ % #) arg";               // construct J script
    j.SetB("arg", arr);                              // set argument
    j.Do(scr);                                       // apply
    object res = j.GetB("res");                      // fetch result
    Console.WriteLine("   {0}\n{1}\n\nres.GetType(): {2}\n",
                scr, res, res.GetType());

The result is also a .NET scalar, unboxed from generic Object.

   res=: (+/ % #) arg
2.9
res.GetType(): System.Double

Now, we will pass a scalar argument and get an array result

    scr = string.Format("res=: %:1+i.{0}", 4);          // argument as part of script
    j.Do(scr);
    arr = j.GetB("res") as double[];                    // unbox result
    Console.WriteLine("   {0}\n   res", scr);

    new List<double>(arr).ForEach(delegate(double v) {  // output each value
        Console.Write("{0} ", v);
    });

If we passed around strings, we would have lost precision.

   res=: %:1+i.4
   res
1 1.4142135623731 1.73205080756888 2

Functions

Wm yes check.png

In order to encapsulate the business logic implemented with J code, the operations can be exposed as regular .NET interface functions.

As an example we will create a class JayProcessor with a few methods. It will inherit from the Jay wrapper to reuse the management of the J session and the wrapper methods.

First, we will define a convenience method AsString to format .NET arrays for output.

   class JayProcessor : Jay {

        public string AsString(object arg) {
            SetB("arg", arg);
            Do("res=: \": arg");
            return GetB("res") as string;
        }
        ...
   }

Then we will add two functional methods to handle array arguments in a generic way.

        public T[] Sort<T>(T[] arg) {
            SetB("arg", (object)arg);
            Do("res=: /:~ arg");
            return GetB("res") as T[];
        }

        public T Insert<T>(string verb, T[] arg) {
            SetB("arg", arg);
            Do(string.Format("res=: {0}/ arg", verb));
            return (T)GetB("res");
        }

Here is how these methods can be applied to .NET types.

    double[] arr = new double[] { 3, 1.2, 4.5 };         // define .NET array

    Console.WriteLine("   \":arr\n{0}", p.AsString(arr));          // display
    Console.WriteLine("   /:~arr\n{0}", p.AsString(p.Sort(arr)));  // sort
    Console.WriteLine("   +/arr\n{0}",  p.Insert("+", arr));       // sum
    Console.WriteLine("   */arr\n{0}",  p.Insert("*", arr));       // product

With the following results. Note the use of AsString method and compare with equivalent .NET array output loops.

   ":arr
3 1.2 4.5
   /:~arr
1.2 3 4.5
   +/arr
8.7
   */arr
16.2

Next we will explore the possibility of greater code separation, such that J functions are defined and tested in J system, and exposed in .NET as a thin invokation layer.

We will start with loading the library in our class constructor. But we will might need other standard J functionality, so we will also load the profile.

        public JayProcessor() {
            Do("0!:0<1!:45''");
            Do("load '~system/packages/finance/actfns.ijs'");
        }
<!> For a large number of libraries, it may be feasible to load the scripts on as needed basis.

Then we will expose a function for calculating an amortization table. There are a number of ways to pass arguments, such as a .NET array as a single argument. But here for better function signature, we will use separate typed arguments.

        public double[,] Amort(int freq, double rate, int years) {
            SetB("freq", freq);
            SetB("rate", rate);
            SetB("years", years);
            Do("res=: amort freq,rate,years");
            return GetB("res") as double[,];
        }

This is boilerplate code, but because it is so simple, automated interface generation is trivial.

Calling the method: note the ratio between the amount of code for the actual call and handling of data in the rest of the .NET world.

    arr = new double[] { 1, 0.08, 6 };
    double[,] amort = p.Amort((int)arr[0], arr[1], (int)arr[2]);  // actual call here

    Console.WriteLine("   amort {0}", p.AsString(arr));           // a little help from J here

    for (int i = 0; i < amort.GetLength(0); i++) {
        for (int j = 0; j < amort.GetLength(1); j++) {
            Console.Write("{0,9:G6} ", amort[i, j]);
        }
        Console.WriteLine();
    }

And finally the result

   amort 1 0.08 6
 0.216315         1      0.08  0.136315
 0.216315  0.863685 0.0690948  0.147221
 0.216315  0.716464 0.0573171  0.158998
 0.216315  0.557466 0.0445973  0.171718
 0.216315  0.385748 0.0308598  0.185456
 0.216315  0.200292 0.0160234  0.200292

Strings

Wm yes check.png

To handle simple ASCII strings, the previously discussed SetB and GetB methods can be used. In order to handle Unicode, the convertion between .NET strings and UTF-8 byte arrays are necessary.

<!> J component could be improved to support Unicode wide character arrays (WCHAR) as counterpart of literal Unicode arrays. GetB and SetB which map to J literal arrays should make UTF-8 two-way conversion instead of discarding upper bytes. And arrays of BSTRs could be mapped to arrays of boxed strings. See full listing and result for details.

We will need a utility function to display Unicode strings.

        static string ustr(string str) {
            StringBuilder sb = new StringBuilder();
            foreach (int i in str) sb.AppendFormat("\\u{0:x4}", i);
            return sb.ToString();
        }

As an example, we will use a Unicode string. This is all .NET.

        string test = "пример";
        Console.WriteLine("   test\n{0}", ustr(test));

So we the output will look like this. This is what we expect.

   test
\u043f\u0440\u0438\u043c\u0435\u0440

We will use two other get/set methods that will handle J literal arrays as arrays of bytes and not BSTR.

int Set(string jname, ref object v) Set variant value in J variable
int Get(string jname, out object v) Get variant value from J variable
        public void Set(string name, object val) {
            assert(j.Set(name, ref val));
        }

        public object Get(string name) {
            object result;
            assert(j.Get(name, out result));
            return result;
        }

In order to pass a UTF-8 string to J we first encode it as a byte array.

        j.Set("arg", Encoding.UTF8.GetBytes(test));

        Console.WriteLine("   $arg\n{0}", j.DoR("$arg"));
        Console.WriteLine("   3!:0 arg\n{0}", j.DoR("3!:0 arg"));
        Console.WriteLine("   a.i.arg\n{0}", j.DoR("a.i.arg"));

The output shows that we get an array of UTF-8 bytes

   $arg
12
   3!:0 arg
2
   a.i.arg
208 191 209 128 208 184 208 188 208 181 209 128

Now we will convert UTF-8 to Unicode in J in order to operate on charecters atomically.

        j.Do("arg=: 7 u:arg");                               // convert to Wide Char
        Console.WriteLine("   $arg\n{0}", j.DoR("$arg"));
        Console.WriteLine("   3!:0 arg\n{0}", j.DoR("3!:0 arg"));
        Console.WriteLine("   3 u:arg\n{0}", j.DoR("3 u:arg"));

With output

   $arg
6
   3!:0 arg
131072
   3 u:arg
1087 1088 1080 1084 1077 1088

And get the result of strings reverse, converting to byte array and back to Unicode in .NET.

        j.Do("res=: 8 u: |.arg");
        res = j.Get("res");                                  // byte[]
        Console.WriteLine("   res\n{0}\n   res.GetType()\n{1}",
            ustr(Encoding.UTF8.GetString(res as byte[])), res.GetType());

With reversed output, as expected.

   res
\u0440\u0435\u043c\u0438\u0440\u043f
   res.GetType()
System.Byte[]


Contributed by Oleg Kobchenko