Guides/OLE Server for CSharp

From J Wiki
Jump to navigation Jump to search

These examples have been created for Visual Studio 2013 and J804 on Windows 10.

Introduction

In C# you can load J and then send it expressions for execution. The results can be obtained as objects.

First of all, C# needs a reference of typelib info of J servers. The usual way of doing this is to add a reference of J server in Visual Studio IDE, but this example shows another way without using Visual Studio IDE.

These 2 files File:Jdllserverlib.cs and File:Jexeserverlib.cs are wrappers using .Net interop, one for JDLLServer, the other for JEXEServer.

Building Executable

Download the above 2 wrappers and also File:Jole csharp test dll.cs and File:Jole csharp test exe.cs to the same folder.

At the command prompt, change directory to the source folder and set up Windows SDK for 64bit

CALL "c:\program files (x86)\microsoft visual studio 12.0\vc\bin\x86_amd64\vcvarsx86_amd64.bat"

See Norman Drinkwater's Guides/Compiling Jqt for how to download and setup Visual Studio 2013.

to build using command line csc.exe, type

csc jole_csharp_test_dll.cs jdllserverlib.cs
csc jole_csharp_test_exe.cs jexeserverlib.cs

Executable files jole_csharp_test_dll.exe and jole_csharp_test_exe.exe will be built.

How to use

The JEXEServer is preferred for development and debugging so we shall take JEXEServer as an example for discussion. The few essential steps are explained.

using JEXEServerLib;

the source file jexeserverlib.cs should be in the same folder or included in the project (if you are using Visual Studio IDE)

    // Create instance of JEXEServer
    try {
      JEXEServerLib.JEXEServer jobject =
        new JEXEServerLib.JEXEServer();

      // QueryInterface for the IJEXEServer interface:
      JEXEServerLib.IJEXEServer js =
        (JEXEServerLib.IJEXEServer)jobject;

This will create the j server object. Note that it is the IJEXEServer object (js) that we will actually use.

      int rc;  // return code, 0 = success

rc will hold return code of COM methods. This is important because it will raise strange errors for methods involving objects if you do not assign the result to a variable.

// the following 3 line have no effect on jdll ole server
//      rc = js.Quit();       // uncomment to close IDE automatically
      rc = js.Show(1);       // show Term
      rc = js.Log(1);        // log input

Quit will not shutdown j server immediately, but it will terminate J server after C# has finished using it. Quit is not called here, otherwise J server will shutdown before you have a chance to see it.

Up to this point, Term is a bare terminal without menu bar, the standard J profile has not been loaded, so that usual utility verbs such as load or smoutput will not work. Most likely your scripts will not run without a profile. See Guides/OLE Automation for how to load profile.

      rc = js.Do("i.3 4");   // simple test

You should see the result as follows inside Term (not applicable for JDLLServer).

0 1  2  3
4 5  6  7
8 9 10 11
      rc = js.Do("1+'a'");   // examine error code
      int err=rc;
      object errmsg;
      rc = js.ErrorTextB(err, out errmsg);
      Console.WriteLine("type : "+errmsg.GetType().ToString());
      Console.WriteLine("value: "+errmsg.ToString());

This demonstrate how to use the ErrorTextB method. Note the out keyword, you will not miss it because the program will otherwise failed to compile.

// c# wide char to/from J utf8
      rc = js.Do("a=:'"+Convert.ToChar(1056)+Convert.ToChar(1086)+
                 Convert.ToChar(1089)+Convert.ToChar(1089)+
                 Convert.ToChar(1080)+Convert.ToChar(1103)+"'");
      rc = js.Do("(3!:0 ; # ; ]) a");
      object a;
      rc = js.GetB("a",out a);
      Console.WriteLine("type : "+a.GetType().ToString());
      Console.WriteLine("value: "+a.ToString());
      Console.WriteLine("length: "+a.ToString().Length);
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[0]));
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[1]));
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[2]));
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[3]));
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[4]));
      Console.WriteLine("value: "+Convert.ToInt32(a.ToString()[5]));

J server interface can now handle unicode text. You should see some Cyrillic text in J Term.

In GetB("a",out a), the first refers to a name in J, while the second refers to an object variable in C#.

// boxed array
      object mat = new object[3, 2] { {1, 2}, {3, 4}, {5, 6} };
      rc = js.SetB("b",ref mat);
      rc = js.Do("b");
      object b;
      rc = js.GetB("b",out b);
      Console.WriteLine("type : "+b.GetType().ToString());
      Console.WriteLine("value: "+b.ToString());

// integer array
      rc = js.Do("[b=: ($b)$;b");  // unbox on J server
      rc = js.GetB("b",out b);
      Console.WriteLine("type : "+b.GetType().ToString());
      Console.WriteLine("value: "+b.ToString());

Note the ref keyword in SetB, it is required when passing objects from C# to J. The input to Do does not need ref because it is passed as a string.

C# does not understand J array and J does not support most of data types/structures in C#. Marshaling of data between C# and J only works for some special cases. It should be sufficient for normal use, if not, write your own code to handle it.