Guides/J VB.NET
This guide is based on .NET Class Library which was created with J601a and Visual Studio .NET 2005 Professional Edition. Unfortunately, with the release of J602a, there were significant changes between versions, particularly with handling of user profiles, that that guide won't work anymore without modification. This, coupled with the release of the relatively free Visual Studio .NET 2008 Express Edition, I've decided to create a new guide using VB.NET and J602a.
WHTTD
Create a generic class library that can be used by any .NET application to execute J scripts.
To facilitate the objective stated above, we need to do the following:
1. Create the VB.NET class library using Visual Basic 2008 Express Edition 2. Create the base J script that will part of the VB.NET class 3. Create a sample application that will use the library
Create the VB.NET class library in Microsoft Visual Basic 2008 Express Edition
So our objective here is to create a VB.NET wrapper class to J.
Let's start by firing up Microsoft Visual Basic 2008 Express Edition. This should open to the Start Page allowing you to select an existing project or create a new one.
Create a new project and in the New Project dialog box, make sure to use the Class Library template. In this dialog box, it would give you a default project name, you can use any name that you desire but I'm using "J.NET" in mine.
If you would notice, this dialog box does not prompt you for a location where to save your project. The Express edition automatically creates a "Visual Studio 2008" directory in your Documents folder and would normally save your files under its Projects directory.
After the IDE created the project, it would present you with and edit form for its default Class1 library.
Before we do anything else, we need to add a reference to J in our project. In the menu, select Projects->Add Reference to display the Add Reference Dialog Box. Ignore the other tabs and instead select the Browse tab and navigate to the location of J.EXE. Clicking on the OK button would close the dialog box.
Note that there is an instance that clicking would fail saying that it cannot add J to your reference. This meant that J was not properly registered to your system and all you need to do is go to the same location of J.EXE and run jreg.bat. Unless your not the Administrator of your PC, this would fix the registration entry for J and just do the previous step again.
Since we have added the required references, we can go back to the Class1 edit window. To make things easier, just copy and paste the following code to the edit window, overwriting any previous values:
Public Class Session Private jObject As JEXEServerLib.JEXEServer Private debug As Boolean = False Public Sub New(ByVal debug As Boolean) Me.debug = debug Me.initialize() End Sub Public Sub New() Me.initialize() End Sub Public ReadOnly Property Debugging() As Boolean Get Return Me.debug End Get End Property Private Sub initialize() Dim jScript As String = "BINPATH_z_=: 1!:46''" & vbCrLf & _ "ARGV_z_=: ,<'Session Server'" jObject = New JEXEServerLib.JEXEServer jObject.Quit() If Me.debug Then jScript += vbCrLf & _ "0!:0 <BINPATH,'\\profile.ijs'" & vbCrLf & _ "wd 'pn *Session Server'" jObject.Log(1) jObject.Show(1) Else jObject.Do("11!:0'pc Session;cc e editijx;'") jObject.Log(0) jObject.Show(0) End If Me.Variable("jScript", jScript) Me.Eval("0!:0 jScript") Me.Eval("NB. Have fun! - amrufon@gmail.com 2008 8 30 16 41 3.4009999999999998") End Sub Public Function Variable(ByVal name As String) As Object Dim retVal As Object = Nothing jObject.GetB(name, retVal) Return retVal End Function Public Sub Variable(ByVal name As String, ByVal value As Object) jObject.SetB(name, value) End Sub Public Sub Eval(ByVal command As String) Try Dim result As Integer result = jObject.Do(command) If result > 0 Then Dim errorMessage As Object = Nothing jObject.ErrorTextB(result, errorMessage) Dim jError As New Exception(Convert.ToString(errorMessage)) Throw jError End If Catch ex As Exception Throw ex End Try End Sub End Class
You should now rename Class1.vb to Session.vb or any file name that is relevant to you. Save your project and it would prompt for a save location defaulting to the Documents\Visual Studio 2008\Projects directory.
J.NET.Session Class Dissection
First off, J.NET is essentially a wrapper class to the J OLE Server. If you do further research in the J Wiki, it is most likely that you would stumble on the .NET Interopt guide by Oleg Kobchenko. This is an excellent concise guide on how to call the J in-process server within any .NET application. Most likely, Oleg's guide will suffice your requirement.
Unfortunately, over the years, I realized that most problems encountered with any J implementation is not with the code but with the data. I needed a way to be able to see/debug the input and output data at runtime but the client machines should only install the minimum files on it. With the J.NET.Session class, you'll get the following features:
1. Minimum of 4 files to deploy to the production environment (J.exe, J.dll, J.NET.dll, Interopt.JEXEServerLib.dll). 2. The option to debug at run-time. 3. Full access to wd commands. 4. Provides option to deploy external J scripts to file, database or embedded inside a DLL and loading this scripts at runtime. 5. For J scripts deployed to file or databases, you can update your J scripts at runtime without recompiling the whole application. 6. Coupled with encryption or the J built-in script locking facilty, you can protect your code from modification or decompilation by embedding them inside the DLL.
This class provides 3 types of operations which are initialization, data marshaling and script execution. There are of course a lot of other features that can be supported but I leave that to the user to expand this class to their specific requirements.
Initialization
The proper initialization of the J session is critical. First off, you need to decide if you need to see the J session or not. The J session should never be seen by your users but on your development or support machines is a the reverse.
Public Sub New(ByVal debug As Boolean) Me.debug = debug Me.initialize() End Sub Public Sub New() Me.initialize() End Sub
So the code above provides you with two options, the first one is you can explicitly flag the debugging status of that run by specifying a boolean parameter. The other one is the implicit creation of the class without a parameter, implying that the J session would never be shown.
You would also notice that both constructor functions listed above just executes a the private procedure initialize().
Private Sub initialize() Dim jScript As String = "BINPATH_z_=: 1!:46''" & vbCrLf & _ "ARGV_z_=: ,<'Session Server'" jObject = New JEXEServerLib.JEXEServer jObject.Quit() If Me.debug Then jScript += vbCrLf & _ "0!:0 <BINPATH,'\\profile.ijs'" & vbCrLf & _ "wd 'pn *Session Server'" jObject.Log(1) jObject.Show(1) Else jObject.Do("11!:0'pc Session;cc e editijx;'") jObject.Log(0) jObject.Show(0) End If Me.Variable("jScript", jScript) Me.Eval("0!:0 jScript") Me.Eval("NB. Have fun! - amrufon@gmail.com 2008 8 30 16 41 3.4009999999999998") End Sub
The first thing that this procedure does is initialize the local jScript variable with a code that resolves the current location of the J executable and sets the command line argument to a list. It then proceeds to creating a new instance of J flagging that instance to quit when the current J.NET.Session class is processed by the .NET garbage collector.
Dim jScript As String = "BINPATH_z_=: 1!:46''" & vbCrLf & _ "ARGV_z_=: ,<'Session Server'" jObject = New JEXEServerLib.JEXEServer jObject.Quit()
After this the code decides if were debugging or not. If the private debug variable is true, we append to the jScript variable a script that loads the user profile, sets the created J session window to the words "Session Server" and make sure that all command executed by the J session is displayed and the J session window is shown. On the event that the private debug variable is false, what we do is create a dummy J window that is never shown. We need to do this, otherwise J will crash spectacularly since MS-Windows will try to close a window where none was created.
If Me.debug Then jScript += vbCrLf & _ "0!:0 <BINPATH,'\\profile.ijs'" & vbCrLf & _ "wd 'pn *Session Server'" jObject.Log(1) jObject.Show(1) Else jObject.Do("11!:0'pc Session;cc e editijx;'") jObject.Log(0) jObject.Show(0) End If
The last steps are just sending the created scripts to the current J session and executing it.
Me.Variable("jScript", jScript) Me.Eval("0!:0 jScript")
Data Marshaling
Data can be sent and retrieve from the J session by using the overloaded public function Variable().
Public Function Variable(ByVal name As String) As Object Dim retVal As Object = Nothing jObject.GetB(name, retVal) Return retVal End Function Public Sub Variable(ByVal name As String, ByVal value As Object) jObject.SetB(name, value) End Sub
To send data to the J session, you have to use the public procedure Variable(). It has two parameters which are the variable name in the J session where the data is to be stored and the actual value.
Public Sub Variable(ByVal name As String, ByVal value As Object) jObject.SetB(name, value) End Sub
First off, you can still create more overrides for this function to support sending standard .NET data types. For example, the .NET String data type can be supported by adding the following code to the class:
Public Sub Variable(ByVal name As String, ByVal value As String) Dim temp as Object = value jObject.SetB(name, temp) End Sub
To retrieve data from the J session, make sure that the variable is defined globally then use the public function Variable(). You only need to pass as parameter the J variable name and it will return the data as an object as shown by this function:
Public Function Variable(ByVal name As String) As Object Dim retVal As Object = Nothing jObject.GetB(name, retVal) Return retVal End Function
Please note that as of this writing, J does NOT support passing/retrieving UTF8 data through this method.
Script Execution
Finally, we can execute J commands through the use of the public procedure Eval(). You just need to send you J script as parameter and if an error occurred during execution of your command, an exception will be thrown, letting you handle the problem.
Public Sub Eval(ByVal command As String) Try Dim result As Integer result = jObject.Do(command) If result > 0 Then Dim errorMessage As Object = Nothing jObject.ErrorTextB(result, errorMessage) Dim jError As New Exception(Convert.ToString(errorMessage)) Throw jError End If Catch ex As Exception Throw ex End Try End Sub
Create the base J script that will part of the VB.NET class
Actually, our .NET class library is not yet complete. What's missing is the J script that is going to be part of the DLL. You can of course put all of the J scripts needed by your project into the DLL but I won't be doing that. Instead, to make the class generic, I'll only put in a base library of J scripts as part of the library and create an external file for the application specific J scripts.
To create the base class, the first thing we need to do is add new directory call source to our J.NET project directory.
With the directory created, fire up J and open up the Project Manager by pressing Ctrl+B. In the Project Manager dialog box, select the menu item File->New to create a new J Project. This will open a New Project Dialog box prompting you for the location and filename of the project file. You can put jbase in the filename and press the OK button.
The previous action will create an empty project in the project manager. The first thing you need to do is setup the project build options by clicking on the menu item Project->Build Options... This will display the Build Options dialog box. In this dialog, we make sure that the project source, library and standard libraries are included in the target file. We also need to make sure that we load the base scripts in the 'z' locale. Please use the following image as a guide on how to configure this window then click on the Close button to return to the Project Manager.
Back in the Project Manager dialog, we need to add the standard library files that we would include with the base script. It is up to you if you what you want to add, you can even add all the scripts. What I would advice is just to add the minimum library that your project needs. As a starter, you can use the libraries suggested in this screenshot:
The last thing that we need to configure is the build target file. This is configured in the Project tab of the Project Manager. Now were going to be a bit clever here, you see the build target file is going to be imported by J.NET.Session as a file resource. For some reason, the express edition of VB.NET will make a copy of the imported file into its Resource directory. So what we have to do is create the Resources directory first in the J.NET directory.
Then, when we create the target file in J's Project Manager, we will save it in this directory. To do so, go to the Project tab of the Project Manager dialog box then click on the add button.
This will display the Add Project Script dialog, just make sure that you select the Target radio box and click on the OK button which will prompt you for the target file location. Just save it in the resource directory that we created earlier. For this exercise, just use 'jbase' as the filename. Of course you can use whatever you want but the filename is significant for the next steps.
You can now build the J Project and it will save the consolidated/compiled scripts into the Resources directory. We can now proceed to importing our J script into the VB.NET dll.
With the J base script built, we can not import the file into our DLL. One of the ways to do this is by selecting the Project->J.NET Properties... menu item. This will open the J.NET Project Properties window and just click on the Resources tab which is where we will add the file.
As shown in the image above, the window will default into the String resource. Unfortunately, were adding a file resource so just click on the inverted triangle right beside the Add Resource and select the Add Existing File... sub-menu. This will display the "Add existing file to reesource" dialog box, just select the file jbase.ijs which is in the Resources directory as we have specified above.
Clicking on the OK button in the image above will add the jbase.ijs file into our project. This will also automatically create a property in our projects Resource class with a byte array data type.
To review, we now have a J script embedded into our VB.NET DLL. So the question now is how do we access and load it to our J session? Its actually quite simple and just require a one line addition to our Session class. Basically, VB.NET is helpful enough to automatically make available the My.Resources class and also automatically create a byte array property from our imported J script. Since My.Resources.jbase property is a byte[] we just use the built-in .NET byte[] to string conversion tool as you can see in this script.
jScript += System.Text.UnicodeEncoding.ASCII.GetString(My.Resources.jbase)
This script will go into the initialize() procedure to automatically load the base J script into the current J session like so:
Private Sub initialize() Dim jScript As String = "BINPATH_z_=: 1!:46''" & vbCrLf & _ "ARGV_z_=: ,<'Session Server'" jObject = New JEXEServerLib.JEXEServer jObject.Quit() If Me.debug Then jScript += vbCrLf & _ "0!:0 <BINPATH,'\\profile.ijs'" & vbCrLf & _ "wd 'pn *Session Server'" jObject.Log(1) jObject.Show(1) Else jObject.Do("11!:0'pc Session;cc e editijx;'") jObject.Log(0) jObject.Show(0) End If jScript += System.Text.UnicodeEncoding.ASCII.GetString(My.Resources.jbase) Me.Variable("jScript", jScript) Me.Eval("0!:0 jScript") Me.Eval("NB. Have fun! - amrufon@gmail.com 2008 8 30 16 41 3.4009999999999998") End Sub
With the changes above, here is the new complete Session class.
Public Class Session Private jObject As JEXEServerLib.JEXEServer Private debug As Boolean = False Public Sub New(ByVal debug As Boolean) Me.debug = debug Me.initialize() End Sub Public Sub New() Me.initialize() End Sub Public ReadOnly Property Debugging() As Boolean Get Return Me.debug End Get End Property Private Sub initialize() Dim jScript As String = "BINPATH_z_=: 1!:46''" & vbCrLf & _ "ARGV_z_=: ,<'Session Server'" jObject = New JEXEServerLib.JEXEServer jObject.Quit() If Me.debug Then jScript += vbCrLf & _ "0!:0 <BINPATH,'\\profile.ijs'" & vbCrLf & _ "wd 'pn *Session Server'" jObject.Log(1) jObject.Show(1) Else jObject.Do("11!:0'pc Session;cc e editijx;'") jObject.Log(0) jObject.Show(0) End If jScript += System.Text.UnicodeEncoding.ASCII.GetString(My.Resources.jbase) Me.Variable("jScript", jScript) Me.Eval("0!:0 jScript") Me.Eval("NB. Have fun! - amrufon@gmail.com 2008 8 30 16 41 3.4009999999999998") End Sub Public Function Variable(ByVal name As String) As Object Dim retVal As Object = Nothing Try Dim result As Integer result = jObject.GetB(name, retVal) If result > 0 Then Dim errorMessage As Object = Nothing jObject.ErrorTextB(result, errorMessage) Dim jError As New Exception(Convert.ToString(errorMessage)) Throw jError End If Catch ex As Exception Throw ex End Try Return retVal End Function Public Sub Variable(ByVal name As String, ByVal value As Object) Try Dim result As Integer result = jObject.SetB(name, value) If result > 0 Then Dim errorMessage As Object = Nothing jObject.ErrorTextB(result, errorMessage) Dim jError As New Exception(Convert.ToString(errorMessage)) Throw jError End If Catch ex As Exception Throw ex End Try End Sub Public Sub Eval(ByVal command As String) Try Dim result As Integer result = jObject.Do(command) If result > 0 Then Dim errorMessage As Object = Nothing jObject.ErrorTextB(result, errorMessage) Dim jError As New Exception(Convert.ToString(errorMessage)) Throw jError End If Catch ex As Exception Throw ex End Try End Sub End Class
NOTE: In the Comment section of this page, Raul Miller attached a C# version with error handlers for the Variable() procedures. He's right and I was smacking my head for overlooking that; so the final code above now includes an error handler for these procedures.
Create a sample application that will use the library
Contributed by AlexRufon
Comments
Here's a C# version of this code (with some minor changes): File:Session.cs.txt
(Note that this version of the code was based on this page as of 2008-09-09, except that it had added exception trapping for issues which arise when reading and setting variables. I did not execute System.Text.UnicodeEncoding.ASCII.GetString(My.Resources.jbase) but instead set the working directory to match my executable, and I load .ijs files -- I believe that this makes development easier for me. Also, I use copy /y "$(ProjectDir)\*.ijs" "$(TargetDir)\" in my post build event command line, to make managing my .ijs files easier in the context of microsoft's treatment of configurations (Debug vs. Release) -- this requires some care when determining which .ijs file to edit -- typically, during development, you will be using scripts from your bin\Debug\ directory, so when opening them to edit you need to prefix them with ../../ so that you edit the master copy of the .ijs. And I made a few other minor changes, which should hopefully be obvious and not a problem for anyone using this script.)
Note that these implementations only load profile.ijs if a "debug flag" is set. This means that you will not normally be able to use 'require', 'each', 'LF', 'assert' and so on, unless you provide definitions for them yourself. (Alex's VB script might deal with this, but I am happy enough without.) This minimal profile means that J can be deployed on a new machine with only three files (which you will find in your j602a\bin\ directory): j.exe, jreg.bat and j.dll. You need to run jreg.bat once on the new machine. You might also provide a minimal profile.js that issues a warning if it is used (I am using one that writes a JERROR.LOG that says to install J and references http://www.jsoftware.com/stable.htm). Note also that you can deploy J anywhere on the machine -- jreg.bat arranges the registry so that your program can find j.dll.