Guides/J CSharp

From J Wiki
Jump to navigation Jump to search

In this guide, we will be building a full C# class library, usable in any .NET application, that will act as a wrapper class for the J.


The following are the step by step guide on how to build the library starting with the base concepts. All the C# files are built with Visual Studio .NET 2005 Professional edition. I believe that the same project described here can also be created using Express edition of Visual Studio ... albeit with some conversion.

Before continuing, I would like to point out this excellent .NET Interop Guide which discusses direct use of the J System into your .NET application. It is a well documented guide which provides ready to use code snippets with brilliant explanation on the why and how of each examples written by, the J Community respected, Oleg Kobchenko.

So on with the show ... to follow this guide, you'll need at least J version 601a and Visual Studio .NET 2005

WHTTD

What were trying to do is build a generic C# class library that will allow us to execute J scripts from any .NET application.

With that statement in mind, we will be creating the class library in two parts.

1. J base code - The minimum J script that will be embedded and pre-loaded by the C# class. We need this to properly boot-up the class library. 2. C# class - This is where the bulk of the coding are located. Handles the interop between .NET and COM J system. Provides features like loading and executing external J scripts.

J base code

As specified in the previous chapter, we need to create a script file with the minimum amount of J script that will be needed by any application. The idea here is that the actual all the code needed by an application should come with the external script that is going to be loaded at run-time.

To create the J base code project, follow these steps

1. First, create a directory in your computer where to store both your J and C# scripts. In my PC, I put all my projects in D:\Projects. So I put my project in D:\Projectc\J.NET\jscripts 2. So fire-up J and load the Project Manager by pressing CTRL+B. 3. In the Project Manager, create a new project by clicking on the menu File->New to display the New Project Dialog Box. Navigate to the project directory, which in my case is D:\Projectc\J.NET\jscripts and enter jscript then click on Save button.
Newijp.jpg 4. Now in the Project Manager window, click on the Library tab and add the following libraries to the project. Note though that these are the minimum from my experience ... you're free to remove or add library scripts as you saw fit.
Cslibraries.jpg 5. We need to set the J Build Options before proceding so click on the Project->Build Options... to open the J Build Options dialog box and fill it up as with the attached image. The setting that you make here will ensure that the source libraries will be included and loaded in the 'z' locale.
Jbuild.jpg 6. Next is settings the target file for your J base code. So go to your directory and create a bin directory like so: D:\Projects\J.NET\bin. This is where the compiled j scripts will be stored and referenced by the C# Class library. 7. So now, go back to the the Project Manager dialog box, click on the Add button to bring up the Add Project Script dialog box and make sure that the Target Option is selected before clicking on the OK button.
Addtarget.jpg 8. In the Target File dialog box, navigate to the D:\Projects\J.NET\bin directory and save the file jscripts.ijs there like in this image:
Targetfile.jpg 9. After step 8, your project library should look something like this.
Projtarget.jpg

You can now actually build the J base code by clicking on the Build button on the project manager. But the current code is not enough. You see, besides being able to load and execute scripts ... we also want to be able to DEBUG the code at runtime. So we need to add a couple of debugging help scripts to square us away.

So you'll need to add these two scripts into your library then Build the project again and were done with the J base code.

1. Runtime J Session Window - facilitates displaying of of the current J Session window for interactive operations. 2. Profile Loader - this loads the user profile for full access to the J Session.

Runtime J Session Window

To be able to debug, we need to be able to display the J Session window at runtime and this is the script to do it.

NB. note: pjijx form name permits tests on "jijx" if
NB. there is any ijx session, and on "pjijx" if there
NB. is a pijx session
NB. $Id: pijx.ijs,v 1.1.1.1 2004/02/09 06:24:48 cburke Exp $

coclass 'pijx'

NB. =========================================================
JIJX=: 0 : 0
pc pjijx nomax nosize;
xywh 0 0 250 150;cc e editijx ws_hscroll ws_vscroll es_nohidesel rightmove bottommove;
pas 0 0;
rem form end;
)

NB. =========================================================
showJ=: 3 : 0
if. 1 e. 'jijx' E. wd 'qpx' do. return. end.
create''
)

NB. =========================================================
create=: 3 : 0
wd JIJX
wd 'pn *',pname''
wd 'setfont e "Lucida Console" 11 oem'
wd 'pshow'
)

NB. =========================================================
closeJ=: 3 : 0
if. 0 = 2 wdquery (pname'');'Do you want to close?' do.
  2!:55 ''
end.
)

NB. =========================================================
pname=: 3 : 0
'Session Server'
)

NB. =========================================================
pjijx_close=: pjijx_cancel=: closeJ

NB. =========================================================
showJ_z_=: showJ_pijx_

You need to add this to your J project. You can just copy and paste it and your project should look something like this:
Pjix.jpg

Profile Loader

This code loads the user profile of the current user. Please NOTE that this code presupposes that a complete J installation have been done in the computer where the J Class Library will be running and j scripts debugged.

NB. =========================================================
NB.*loadprofile v Code to allow developers to properly debug
NB.
NB. NOTE: This codes assumes that the current session has a
NB. complete J system installed.
NB. $Id: pijx.ijs,v 1.1.1.1 2004/02/09 08:20:16 cburke Exp $
loadprofile=: 3 : 0
ifp=. 1 e. 'pjijx' E. wd 'qpx'
if. ifp do.
  wd 'psel pjijx'
  fx=. wd 'qformx'
  wd 'pclose'
end.
0!:0 < 1!:45''
wd 'psel ',qsmact_jijs_''
if. ifp do.
  wd 'pmovex ',fx
end.
wd 'pn *',pname_pijx_''
9!:3 [ 5 NB. linear representation
load 'debug coutil'
)

After adding this to your project, I named mine profile.ijs, your Project dialog box should look something like this:
Profile.jpg

C# Class Library

This is where we build the C# Class Library that will make use of the J base code. In general, it does the following:

  • Create a J session
  • Load the base code into that J session
  • Allow the calling application to run scripts
  • Allow the calling application to load new external scripts and execute them
  • Provides facilities to debug and test loaded scripts at runtime

I'll be breaking up the steps into smaller pieces to make the tutorial more manageable ... so here we go.

Create the Project and Add the J Reference

Here are the steps required to create the project and add the reference to the J COM object.

1. Create the project by selecting New->Project and fill up the dialog box as with this image:
Newproject.jpg 2. We now add the J.EXE as a COM Interop reference to the project. You can do this by right clicking on the Reference node and clicking on the Add Reference contect menu item. The Add Reference dialog box will be displayed and click on the COM tab and look for JEXESERVER entry.
Jexeserverlib.jpg

Add the J base code as Resource

Here, we will show you how to add the J base code as a resource into the class library. This will mean that the scripts will be stored INSIDE the DLL since were sure that the base script will NEVER change.

1. Right+Click on the properties window and click on the Open context menu to open the Properties Window in Visual Studio.
Propertiesopen.jpg 2. Click on the Resource tab and follow the instruction on the screen so that Visual Studio can initialize the resources for our class library.
Newresource.jpg 3. After initializing the resource file, you need to click on the Add Existing file drop down list to add the J base script located in D:\Projects\J.NET\bin\jscript.ijs.
Addfile.jpg 4. Verify that the file jscript.ijs is added as a Resource in the Solution Explorer.
Jscriptresource.jpg

Since we have the J base code as an embedded resource, we still need a piece of code to be embedded for to complete the resource. It's the script loader that's going to be called in later on by the class library and it looks like this:

18!:4 <'z'
0!:100 baseScript
18!:4 <'base'
erase <'loadScript'

So to add it to the Class Library, you'll need to do the following:

1. In the properties window, click on new resource and select the Add New String item.
Addnewstring.jpg 2. Fill up the grid with following information:
Scriptloader.jpg

Later on, we will be using these defined Resources in the class library.

JSoftware.Session

This is the class that will be instantiated by the .NET application. It provides all the functionality that you need to execute your J Scripts from your .NET application.

using System;
using System.Collections.Generic;
using System.Text;
using JEXEServerLib;
using JSoftware.Properties;
using System.ComponentModel;

namespace JSoftware
{
    /// <summary>
    /// Created by: Alexander M. Rufon
    /// Year: 2006
    /// EMail: amrufon@gmail.com
    /// </summary>
    /// <remarks>A class to use J in .NET Framework</remarks>
    public class Session : IDisposable
    {
        /// <summary>
        /// Holds the instance of the J Object
        /// </summary>
        private JEXEServerClass jObject;
        /// <summary>
        /// Holds the debugging flag value
        /// </summary>
        private bool debug = false;
        /// <summary>
        /// Holds the flag for the IDisposed state
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Create an J session with the debugging state specified
        /// </summary>
        /// <param name="debug">Set to true if you want debugging turned on</param>
        public Session(bool debug)
        {
            this.debug = debug;
            this.initialize();
        }

        /// <summary>
        /// Creates an instance of J with default settings
        /// </summary>
        public Session() { this.initialize(); }

        /// <summary>
        /// Use C# destructor syntax for finalization code.
        /// This destructor will run only if the Dispose method
        /// does not get called.
        /// It gives your base class the opportunity to finalize.
        /// Do not provide destructors in types derived from this class.
        /// </summary>
        ~Session()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        /// <summary>
        /// Read-Only property which returns true if object was created with debugging code turned on.
        /// </summary>
        public bool debugging { get { return this.debug; } }

        /// <summary>
        /// Actual method that initializes the session
        /// </summary>
        private void initialize()
        {
            string jScript = "";

            // Create a new copy of the J Object and make sure were in the Z locale
            jObject = new JEXEServerClass();
            jObject.Quit();
            jObject.Log(1);
            jObject.Show(1);
            this.Eval("18!:4 <'z'");

            // Now get the base J Script
            jScript = UnicodeEncoding.ASCII.GetString(Resources.jscript);
            // If were debugging, show the J Session Window
            if (this.debug)
            {
                // Display the session and load the profile
                jScript += "\nshowJ ''";
                jScript += "\nloadprofile ''";
            }
            // load this script to the current session
            this.Variable("baseScript", jScript);

            // We execute the script using the code stored in the resource file
            jScript = Resources.ScriptLoader;
            // Load the new script to the current session
            this.Variable("loadScript", jScript);

            // Now we execute the values in the loadScript variable
            this.Eval("0!:100 loadScript");

            if (this.debug)
            {
                // Just display a fun message
                string script = "NB. Have fun! - bathala <amrufon@gmail.com>";
                this.Eval(script);
            }
        }

        /// <summary>
        /// Returns the contents of an J variable
        /// </summary>
        /// <param name="name">Variable name to retrieve</param>
        public object Variable(string name)
        {
            object retVal;

            // Retrieve the data from the J Session
            jObject.GetB(name, out retVal);

            // Return the object
            return retVal;
        }

        /// <summary>
        /// Sets an J variable to a value
        /// </summary>
        /// <param name="name">Variable name to set</param>
        /// <param name="value">The value to set the J variable to</param>
        public void Variable(string name, object value) { jObject.SetB(name, ref value); }

        /// <summary>
        /// Sets a J variable to a string value
        /// </summary>
        /// <param name="name">Variable name to set</param>
        /// <param name="value">String value to fill the variable name</param>
        public void Variable(string name, string value)
        {
            object objTemp = value;
            this.Variable(name, objTemp);
        }

        /// <summary>
        /// Set a variable to an int value
        /// </summary>
        public void Variable(string name, int value)
        {
            object objTemp = value;
            this.Variable(name, objTemp);
        }

        /// <summary>
        /// Set a variable to a DateTime value
        /// </summary>
        public void Variable(string name, DateTime value)
        {
            string objTemp = value.ToString("MM/dd/yyyy HH:mm:ss");
            this.Variable(name, objTemp);
        }

        /// <summary>
        /// Set a variable to a float value
        /// </summary>
        public void Variable(string name, double value)
        {
            object objTemp = value;
            this.Variable(name, objTemp);
        }

        /// <summary>
        /// Set a variable to a bool value
        /// </summary>
        public void Variable(string name, bool value)
        {
            object objTemp;
            if (value)
            {
                objTemp = 1;
            }
            else
            {
                objTemp = 0;
            }
            this.Variable(name, objTemp);
        }

        /// <summary>
        /// Evaluates J scripts
        /// </summary>
        /// <param name="command">J script to evaluate</param>
        public void Eval(string command)
        {
            try
            {
                int result;

                // Execute the command
                result = jObject.Do(command);

                if (result > 0)
                {
                    // Throw the correct error message
                    object errorMessage;
                    jObject.ErrorTextB(result, out errorMessage);
                    Exception eoe = new Exception(Convert.ToString(errorMessage));
                    throw eoe;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Loads an external script into the current J session
        /// </summary>
        /// <param name="fileName">Complete path and filename to the script to be loaded</param>
        public void Load(string fileName)
        {
            string script;

            // Assign the filename to a J variable
            this.Variable("script2load",fileName);

            // Check if were in debug mode first.
            if (this.debug)
            {
                // Were debugging so we show what were loading and stop on error
                script = "0!:001 < script2load";
            }
            else
            {
                // Not debugging, dont need to show script
                script = "0!:0 < script2load";
            }

            // Now evaluate the script.
            this.Eval(script);
        }

        /// <summary>
        /// Implement IDisposable.
        /// Do not make this method virutal.
        /// A derived class should not be able to overrride this method.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing the second time.
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose(bool disposing) executes in two distinct scenarios.
        /// If disposing equals true, the method has been called directly
        /// or indirectly by a user's code. Managed and unmanaged resources
        /// can be disposed.
        /// If disposing equals false, the method has been called by the
        /// runtime from inside the finalizer and you should not reference
        /// other objects. Only unmanaged resources can be disposed.
        /// </summary>
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    // Component.Dispose();
                }

                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed
                // We basically call it quits and set the JObject to nothing
                if (jObject != null) {
                    jObject.Quit();
                    jObject = null;
                }

                // Force garbage collection
                GC.Collect();
            }
            disposed = true;
        }
    }
}

To use this class, just rename the file Class1.cs to Session.cs and copy and paste the preceding code into it.
Jsession2.jpg

As with all classes, the JSoftware.Session class is composed of Fields, Properties and Methods which as a whole make this class library work.
Session2.jpg

Fields

  • debug - private boolean variable to hold the debug state
  • disposed - private boolean variable that is used for cleanly unloading the class by .NET framework
  • jObject - a private variable that holds the current instance of JEXEServer object

Properties

  • debugging - public read-only property signifying the debugging state

Methods

  • ~Session() - Use C# destructor syntax for finalization code. This destructor will run only if the Dispose method does not get called. It gives your base class the opportunity to finalize. Do not provide destructors in types derived from this class.
  • Dispose() - a public function that implements IDisposable. This is automatically called by .NET Framework during garbage collection
  • Dispose(bool disposing) - the private function that actually does the work for the public function and executes in two distinct scenarios. If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
  • Eval(string command) - executes the J code in the command paramater
  • initialize() - the actual function that initializes the session
  • Load(string fileName) - Loads an external file that contains valid J scripts into the current session
  • Session() - The constructor that creates an instance of J with the debug mode set to FALSE
  • Session(bool debug) - The constructor that creates an instance of J where the caller can set the debugging state
  • Variable(string name,bool value) - sets name into true (1) or false (0)
  • Variable(string name, double value) - sets name into a double value
  • Variable(string name, object value) - sets name into an object value. This is a catch all function where the datatype of value dictates what format it will be assigned to name
  • object Variable(string name) - this is the method you use to get values from the current J session. The return value is an object and you have to cast it to the correct format before using it.
  • Variable(string name, string value) - sets a name into a string value
  • Variable(string name, datetime value) - sets a name into a string value from a datetime type in the format of MM/dd/yyyy HH:mm:ss
  • Variable(string name, int value) - sets a name into an integer value

Real World Example

For those who have not worked on an automated payroll system before, one of the most resource intensive operation in terms of resources and time is the import of the Time Device file.

The Time Device file is a text file which is the record of all the swipes for Time-In and Time-Out of an employee. It usually have the Device id, card no, year, month, day, hours, minutes of every swipe of an employee. In a normal factory, an employee will swipe at least 4 times a day; 1 for time-in, 2 for lunch break and 1 for time-out. So you can imagine the size and number of records of this file if you leave the time device to capture for a month in a city size factory.

This is one of the strength of J. You see, if you write the parser using Visual Basic or C# or any other procedural programming language ... most of the time spent will be looping through the records trying to find the swipes of a certain employee for a certain range of date.

You can download the whole Visual Studio 2005 Project here -->> J_NET.zip

Here are 2 screenshots of the example application where you can see the J Session running on the background. Of course in your production environment ... J won't show and your users won't even find out that you're using it.
Example1.jpg

To make your test as simple as possible, just unzip the file to D:\Projects\J.NET and run the binary file in D:\Projects\J.NET\timedeviceviewer\bin\Debug\timedeviceviewer.exe

Example2.jpg

NOTE: There is a known bug in file D:\Projects\J.NET\timedeviceviewer.ijs if your J system doesnt support the old x. and y. notations ...

|spelling error
|   10000 100 100 base y.
|                      ^
|   dateencode=:    3 :0
|[-343] D:\Projects\J.NET\timedeviceviewer.ijs

To get it to run, you can manually edit the file do one of the following:

  • Put this code on the top of the file: 9!:49[1
  • Search and replace all y. with y and x. with x
  • Run the code in http://www.jsoftware.com/jwiki/Scripts/Fixargs against the file
  • Enable support for it in Edit->Configure dialog box like so
    Configure.jpg

What's next?

There are still a lot of things needed to be done to use J with .NET. What we have done here is the first step. I suggest you look into the following topics.

  • Creation of installers for distributing your application with J. You only need J.EXE and J.DLL!
  • Windows Services using J
  • An alternative IDE/JFE taking advantage of the Visual Studio 2005 IDE SDK

and much-much more!

Contributed by AlexRufon


Guides