Network communication is important today for integration.
But sometimes a simple call is what is needed.
That can create some challenges when the calling language is different than the called language.
This article will cover managed (Java, .NET) to managed (Java, .NET, JavaScript, Python).
Other articles:
Not surprisingly JVM code interact nicely with JVM code. OO languages compiled to JVM can usually be used completely transparent.
Major compiled JVM languages are:
Here comes a demo of how easy they interact.
Test.java:
package m2m;
public interface Test {
public void test(Tracker t);
}
Tracker.java:
package m2m;
public class Tracker {
private String trace;
public Tracker(String start) {
trace = start;
}
public void addTrace(String me) {
trace = trace + " -> " + me;
}
public String getTrace() {
return trace;
}
}
Main.java:
package m2m;
public class Main {
public static void main(String[] args) {
Tracker t = new Tracker("Java");
System.out.println(t.getTrace());
Test o = new TestGroovy();
o.test(t);
}
}
TestGroovy.groovy:
package m2m
class TestGroovy implements Test {
void test(Tracker t) {
t.addTrace("Groovy")
println(t.getTrace())
def o = new TestScala()
o.test(t)
}
}
TestScala.scala:
package m2m
class TestScala extends Test {
def test(t: Tracker): Unit = {
t.addTrace("Scala")
println(t.getTrace())
var o: Test = new TestKotlin()
o.test(t)
}
}
TestKotlin.kt:
package m2m
class TestKotlin : Test {
override fun test(t: Tracker) {
t.addTrace("Kotlin")
println(t.getTrace())
var o: Test = TestJava()
o.test(t)
}
}
TestJava.java:
package m2m;
public class TestJava implements Test {
@Override
public void test(Tracker t) {
t.addTrace("Java");
System.out.println(t.getTrace());
}
}
Output:
Java Java -> Groovy Java -> Groovy -> Scala Java -> Groovy -> Scala -> Kotlin Java -> Groovy -> Scala -> Kotlin -> Java
For another example showing Kotlin calling Java see here.
For another example showing Scala calling Java see here.
Java has a standard for integrating with script languages: JSR 223. JSR 223 was approved and released with Java 1.6/6 in 2006 (the spec merged into the Java standard itself in 2016 with Java 9).
There are many script languages available supporting JSR 223, including:
(yes - Groovy is suitable both as a compiled language and as script language)
It is really simple to use:
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine se = sem.getEngineByName(languagename);
ScriptContext ctx = new SimpleScriptContext();
se.eval(scriptsourcecode, ctx);
The ScriptContext can be used to preload variables into the script engine so they can be used from the script code.
Here comes an example showing all of above 5 languages.
TestScript.java:
package m2m;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.ScriptContext;
import javax.script.SimpleScriptContext;
public class TestScript {
public static class Data {
public int iv;
public String sv;
public Data(int iv, String sv) {
this.iv = iv;
this.sv = sv;
}
public void m() {
iv++;
sv += "X";
}
@Override
public String toString() {
return String.format("(%d,%s)", iv, sv);
}
}
public static void test(String language, String source) throws ScriptException {
System.out.println(language);
System.out.println(source);
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine se = sem.getEngineByName(language);
ScriptContext ctx = new SimpleScriptContext();
ctx.setAttribute("n", 3, ScriptContext.ENGINE_SCOPE);
ctx.setAttribute("s", "Hi", ScriptContext.ENGINE_SCOPE);
ctx.setAttribute("o", new Data(123, "ABC"), ScriptContext.ENGINE_SCOPE);
se.eval(source, ctx);
Data o = (Data)ctx.getAttribute("o");
System.out.println(o);
}
public static void main(String[] args) throws ScriptException {
test("javascript", "for(i = 0; i < n; i++) {\r\n" +
" print(\"JavaScript says: \" + s)\r\n" +
"}\r\n" +
"o.iv = o.iv + 1;\r\n" +
"o.sv = o.sv + \"X\";\r\n" +
"o.m();");
test("python", "for i in range(n):\r\n" +
" print('Python says: ' + s)\r\n" +
"o.iv = o.iv + 1\r\n" +
"o.sv = o.sv + 'X'\r\n" +
"o.m()");
test("jruby", "for i in 1..n do\r\n" +
" puts 'Ruby says: ' + s\r\n" +
"end\r\n" +
"o.iv = o.iv + 1\r\n" +
"o.sv = o.sv + 'X'\r\n" +
"o.m()");
test("php", "<?php\r\n" +
"for($i = 0; $i < $n; $i++) {\r\n" +
" echo 'PHP says: ' . $s . \"\\r\\n\";\r\n" +
"}\r\n" +
"$o->iv = $o->iv + 1;\r\n" +
"$o->sv = $o->sv . 'X';\r\n" +
"$o->m();\r\n" +
"?>");
test("groovy", "for(i = 0; i < n; i++) {\r\n" +
" println \"Groovy says: \" + s\r\n" +
"}\r\n" +
"o.iv = o.iv + 1\r\n" +
"o.sv = o.sv + \"X\"\r\n" +
"o.m()");
}
}
Output:
javascript for(i = 0; i < n; i++) { print("JavaScript says: " + s) } o.iv = o.iv + 1; o.sv = o.sv + "X"; o.m(); JavaScript says: Hi JavaScript says: Hi JavaScript says: Hi (125,ABCXX) python for i in range(n): print('Python says: ' + s) o.iv = o.iv + 1 o.sv = o.sv + 'X' o.m() Python says: Hi Python says: Hi Python says: Hi (125,ABCXX) jruby for i in 1..n do puts 'Ruby says: ' + s end o.iv = o.iv + 1 o.sv = o.sv + 'X' o.m() Ruby says: Hi Ruby says: Hi Ruby says: Hi (125,ABCXX) php <?php for($i = 0; $i < $n; $i++) { echo 'PHP says: ' . $s . "\r\n"; } $o->iv = $o->iv + 1; $o->sv = $o->sv . 'X'; $o->m(); ?> PHP says: Hi PHP says: Hi PHP says: Hi (125,ABCXX) groovy for(i = 0; i < n; i++) { println "Groovy says: " + s } o.iv = o.iv + 1 o.sv = o.sv + "X" o.m() Groovy says: Hi Groovy says: Hi Groovy says: Hi (125,ABCXX)
Compiled JVM code is typical directly accessible from a JVM script language.
Action.java:
package m2m;
public interface Action {
public void m();
}
J.java:
package m2m;
public class J {
public static void demo(int n, String s, Action o) {
for(int i = 0; i < n; i++) {
System.out.println("Java says: " + s);
}
o.m();
}
public static void simpledemo(int n, String s) {
for(int i = 0; i < n; i++) {
System.out.println("Java says: " + s);
}
}
}
jy.py:
from m2m import Action
from m2m import J
class PyAction(Action):
def __init__(self, _iv, _sv):
self.iv = _iv
self.sv = _sv
def m(self):
self.iv = self.iv + 1
self.sv = self.sv + "X"
return
def __str__(self):
return '{%s,%s}' % (self.iv, self.sv)
o = PyAction(123, "ABC")
J.demo(3, "Hi", o)
print(o)
Run:
java -cp jython-standalone-2.7.1.jar;.. org.python.util.jython jy.py
Output:
Java says: Hi Java says: Hi Java says: Hi {124,ABCX}
gr.grooy:
import m2m.Action
import m2m.J
class GrAction implements Action {
int iv
String sv
GrAction(int iv, String sv) {
this.iv = iv
this.sv = sv
}
void m() {
iv = iv + 1
sv = sv + "X"
}
String toString() {
return String.format("(%d,%s)", iv, sv)
}
}
o = new GrAction(123, "ABC")
J.demo(3, "Hi", o)
println o
Run:
java -cp %GROOVYDIR%\lib\*;.. groovy.ui.GroovyMain gr.groovy
Output:
Java says: Hi Java says: Hi Java says: Hi (124,ABCX)
nas.js:
Packages.m2m.J.simpledemo(3, "Hi");
Run:
jjs -cp .. nas.js
Output:
Java says: Hi Java says: Hi Java says: Hi
Note that implementing the Java interface and calling Java with an instance is not easy.
q.php:
<?php
import m2m.J;
J::simpledemo(3, 'Hi');
?>
Run:
java -cp quercus.jar;.. com.caucho.quercus.CliQuercus q.php
Output:
Java says: Hi Java says: Hi Java says: Hi
Note that implementing the Java interface and calling Java with an instance is not easy.
Graal is a very interesting project. It is a traditional JVM, but it is way more than that.
More specifically it can:
(the JavaScript support includes a fully functional node.js implementation)
These capabilities enable Graal to support new forms of polyglot programming.
Example with Java to script languages:
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
public class TestScript {
public static class Data {
public int iv;
public String sv;
public Data(int iv, String sv) {
this.iv = iv;
this.sv = sv;
}
public void m() {
iv++;
sv += "X";
}
@Override
public String toString() {
return String.format("(%d,%s)", iv, sv);
}
}
public static void test(String language, String source) {
System.out.println(language);
System.out.println(source);
try (Context context = Context.newBuilder().allowAllAccess(true).build()) {
Data o = new Data(123, "ABC");
context.getBindings(language).putMember("n", 3);
context.getBindings(language).putMember("s", "Hi");
context.getBindings(language).putMember("o", o);
context.eval(language, source);
System.out.println(o);
}
}
public static void main(String[] args) {
test("js", "for(i = 0; i < n; i++) {\r\n" +
" print(\"JavaScript says: \" + s)\r\n" +
"}\r\n" +
"o.iv = o.iv + 1;\r\n" +
"o.sv = o.sv + \"X\";\r\n" +
"o.m();");
/*
test("python", "for i in range(n):\r\n" +
" print('Python says: ' + s)\r\n" +
"o.iv = o.iv + 1\r\n" +
"o.sv = o.sv + 'X'\r\n" +
"o.m()");
test("ruby", "for i in 1..n do\r\n" +
" puts 'Ruby says: ' + s\r\n" +
"end\r\n" +
"o.iv = o.iv + 1\r\n" +
"o.sv = o.sv + 'X'\r\n" +
"o.m()");
*/
}
}
Output:
js for(i = 0; i < n; i++) { print("JavaScript says: " + s) } o.iv = o.iv + 1; o.sv = o.sv + "X"; o.m(); JavaScript says: Hi JavaScript says: Hi JavaScript says: Hi (125,ABCXX)
Not surprisingly CLR code interact nicely with CLR code. OO languages compiled to .NET can usually be used completely transparent.
Major compiled .NET languages are:
Here comes a demo of how easy they interact.
Test.cs:
namespace M2M
{
public interface ITest
{
void Test(Tracker t);
}
}
Tracker.cs:
namespace M2M
{
public class Tracker
{
private string trace;
public Tracker(string start)
{
trace = start;
}
public void AddTrace(string me)
{
trace = trace + " -> " + me;
}
public string GetTrace()
{
return trace;
}
}
}
Main.cs:
using System;
namespace M2M
{
public class Program
{
public static void Main(string[] args)
{
Tracker t = new Tracker("C#");
Console.WriteLine(t.GetTrace());
ITest o = new Test.TestFS(); // Note: prefix with module name
o.Test(t);
}
}
}
TestFS.fs
namespace M2M
open System
module Test =
type TestFS() =
interface ITest with
member __.Test(t) =
t.AddTrace("F#")
Console.WriteLine(t.GetTrace())
let o = new TestVB()
o.Test(t)
TestVB.vb:
Imports System
Namespace M2M
Public Class TestVB
Implements ITest
Public Sub Test(t As Tracker) Implements ITest.Test
Call t.AddTrace("VB.NET")
Console.WriteLine(t.GetTrace())
Dim o As ITest = New TestCS()
Call o.Test(t)
End Sub
End Class
End Namespace
TestCS.cs:
using System;
namespace M2M
{
public class TestCS : ITest
{
public void Test(Tracker t)
{
t.AddTrace("C#");
Console.WriteLine(t.GetTrace());
}
}
}
Output:
C# C# -> F# C# -> F# -> VB.NET C# -> F# -> VB.NET -> C#
Scripting in .NET is a bit messy.
.NET 1.0 (2002) came with JScript (JavaScript) and VBScript support. They were both deprecated in .NET 2.0 (2005) and VBScript support was removed in .NET 4.0 (2010).
.NET 4.0 (2010) introduced DLR (Dynamic Language Runtime). Originally Microsoft had big ambitions for DLR. But they were scaled back before release. Currently the two main DLR languages are Python via IronPython and Ruby via IronRuby. Supposedly PowerShell 3.0+ is also DLR compliant.
For Python a library Python.NET exist that allow integrating .NET with a standard CPython.
Note that the IronPython/IronRuby approach and the Python.NET approach are very different:
Old way with JavaScript via JScript.
TestScript.cs:
using System;
using Microsoft.JScript;
// Note: VSA is obsolete since .NET 2.0 - the recommended replacement is to compile using JScript compiler
namespace M2M
{
public class Program
{
public static void TestJS(String source)
{
Console.WriteLine(source);
#pragma warning disable 618
Microsoft.JScript.Vsa.VsaEngine eng = Microsoft.JScript.Vsa.VsaEngine.CreateEngine();
eng.SetOption("print", true);
string prefix = "var n = 3;\r\nvar s = 'Hi';\r\n";
string res = (string)Eval.JScriptEvaluate(prefix + source, eng);
Console.WriteLine(res);
#pragma warning restore 618
}
public static void Main(String[] args)
{
TestJS("for(var i = 0; i < n; i++) {\r\n" +
" print(\"JScript says: \" + s);\r\n" +
"}\r\n" +
"\"OK\"");
}
}
}
Note that preloading of variables are not supported.
Output:
for(var i = 0; i < n; i++) { print("JScript says: " + s); } "OK" JScript says: Hi JScript says: Hi JScript says: Hi OK
And DLR with Python via IronPython and Ruby via IronRuby.
Technically DLR is very similar to JSR 223.
It is really simple to use:
ScriptRuntime srt = ScriptRuntime.CreateFromConfiguration();
ScriptEngine se = srt.GetEngine(languagename);
ScriptScope ss = se.CreateScope();
se.Execute(scriptsourcecode, ss);
The ScriptScope can be used to preload variables into the script engine so they can be used from the script code.
TestScriptDLR.cs:
using System;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
namespace M2M
{
public class Program
{
public class Data
{
public int Iv;
public string Sv;
public Data(int iv, string sv)
{
this.Iv = iv;
this.Sv = sv;
}
public void M()
{
Iv++;
Sv += "X";
}
public override string ToString()
{
return string.Format("({0},{1})", Iv, Sv);
}
}
public static void Test(String language, String source)
{
Console.WriteLine(language);
Console.WriteLine(source);
ScriptRuntime srt = ScriptRuntime.CreateFromConfiguration();
ScriptEngine se = srt.GetEngine(language);
ScriptScope ss = se.CreateScope();
ss.SetVariable("n", 3);
ss.SetVariable("s", "Hi");
ss.SetVariable("o", new Data(123, "ABC"));
se.Execute(source, ss);
Data o = (Data)ss.GetVariable("o");
Console.WriteLine(o);
}
public static void Main(String[] args)
{
Test("Python", "for i in range(n):\r\n" +
" print('Python says: ' + s)\r\n" +
"o.Iv = o.Iv + 1\r\n" +
"o.Sv = o.Sv + 'X'\r\n" +
"o.M()");
Test("Ruby", "for i in 1..n do\r\n" +
" puts 'Ruby says: ' + s\r\n" +
"end\r\n" +
"o.Iv = o.Iv + 1\r\n" +
"o.Sv = o.Sv + 'X'\r\n" +
"o.M()");
}
}
}
TestScriptDLR.exe.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="microsoft.scripting" type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting"/>
</configSections>
<microsoft.scripting>
<languages>
<language names="IronPython;Python;py" extensions=".py" displayName="IronPython" type="IronPython.Runtime.PythonContext, IronPython"/>
<language names="IronRuby;Ruby;rb" extensions=".rb" displayName="IronRuby" type="IronRuby.Runtime.RubyContext, IronRuby"/>
</languages>
</microsoft.scripting>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
Output:
Python for i in range(n): print('Python says: ' + s) o.Iv = o.Iv + 1 o.Sv = o.Sv + 'X' o.M() Python says: Hi Python says: Hi Python says: Hi (125,ABCXX) Ruby for i in 1..n do puts 'Ruby says: ' + s end o.Iv = o.Iv + 1 o.Sv = o.Sv + 'X' o.M() Ruby says: Hi Ruby says: Hi Ruby says: Hi (125,ABCXX)
Python.NET makes it possible to embed a standard CPython in .NET.
Project setup:
dotnet new console
dotnet add package pythonnet
Program:
using System;
using Python.Runtime;
namespace M2M
{
public class Program
{
public class Data
{
public int Iv;
public string Sv;
public Data(int iv, string sv)
{
this.Iv = iv;
this.Sv = sv;
}
public void M()
{
Iv++;
Sv += "X";
}
public override string ToString()
{
return string.Format("({0},{1})", Iv, Sv);
}
}
public static void Main(String[] args)
{
string language = "Python";
string source = "for i in range(n):\r\n" +
" print('Python says: ' + s)\r\n" +
"o.Iv = o.Iv + 1\r\n" +
"o.Sv = o.Sv + 'X'\r\n" +
"o.M()";
Console.WriteLine(language);
Console.WriteLine(source);
PythonEngine.Initialize();
using(Py.GIL())
{
using(PyModule scope = Py.CreateScope())
{
scope.Set("n", 3);
scope.Set("s", "Hi");
scope.Set("o", new Data(123, "ABC"));
scope.Exec(source);
dynamic o = scope.Get("o");
Console.WriteLine("({0},{1})", o.Iv, o.Sv);
}
}
PythonEngine.Shutdown();
}
}
}
Build and run (Windows):
set PYTHONNET_PYDLL=C:\Python\Python311\python311.dll
dotnet build
dotnet run
Note that it is necessary to point to the CPython version to use.
Output:
Python for i in range(n): print('Python says: ' + s) o.Iv = o.Iv + 1 o.Sv = o.Sv + 'X' o.M() Python says: Hi Python says: Hi Python says: Hi (125,ABCXX)
On newer .NET versions (8+) it is necessary to explicit enable unsafe binary serialization in the csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="pythonnet" Version="3.0.3" />
</ItemGroup>
</Project>
Compiled .NET code can be directly accessible from a .NET script language.
DN.cs:
using System;
namespace m2m
{
public interface IAction
{
void M();
}
public class DN
{
public static void Demo(int n, string s, IAction o)
{
for(int i = 0; i < n; i++)
{
Console.WriteLine("C# says: " + s);
}
o.M();
}
}
}
iron.py:
import clr
clr.AddReference("DN.dll")
from m2m import IAction
from m2m import DN
class PyAction(IAction):
def __init__(self, _iv, _sv):
self.iv = _iv
self.sv = _sv
def M(self):
self.iv = self.iv + 1
self.sv = self.sv + "X"
return
def __str__(self):
return '{%s,%s}' % (self.iv, self.sv)
o = PyAction(123, "ABC")
DN.Demo(3, "Hi", o)
print(o)
Run:
ipy iron.py
Output:
C# says: Hi C# says: Hi C# says: Hi {124,ABCX}
iron.rb:
require 'DN.dll'
include M2M
class RbAction
include IAction
def initialize(iv, sv)
@iv = iv
@sv = sv
end
def m
@iv = @iv + 1
@sv = @sv + 'X'
end
def print
printf "(#{@iv},#{@sv})\n";
end
end
o = RbAction.new 123, 'ABC'
DN.Demo 3, 'Hi', o
o.print
Run:
ir iron.rb
Output:
C# says: Hi C# says: Hi C# says: Hi (124,ABCX)
Python.NET makes it possible to embed .NET in a standard CPython.
Install:
pip install pythonnet
DN.cs:
using System;
namespace m2m
{
public interface IAction
{
void M();
}
public class DN
{
public static void Demo(int n, string s, IAction o)
{
for(int i = 0; i < n; i++)
{
Console.WriteLine("C# says: " + s);
}
o.M();
}
}
}
Build:
csc /t:library DN.cs
test.py:
import clr
clr.AddReference("DN")
from m2m import IAction
from m2m import DN
class PyAction(IAction):
__namespace__ = "pym2m" # necessary to avoid a weird error
def __init__(self, _iv, _sv):
self.iv = _iv
self.sv = _sv
def M(self):
self.iv = self.iv + 1
self.sv = self.sv + "X"
return
def __str__(self):
return '{%s,%s}' % (self.iv, self.sv)
o = PyAction(123, "ABC")
DN.Demo(3, "Hi", o)
print(o.__str__()) # not sure why explicit call is needed
Output:
C# says: Hi C# says: Hi C# says: Hi {124,ABCX}
The JVM and the CLR (.NET runtime) are obviously incompatible, so mixing a JVM language and a .NET language create a hard problem.
One solution is the open source product IKVM.
IKVM allows to run JVM code (Java byte code) on CLR by translating it to .NET byte code (CIL/MSIL).
Specifically IKVM comes with two tools:
This allows one to mix JVM languages and .NET languages very freely.
Here comes an example.
Tracker.cs:
namespace IKVM
{
public class Tracker
{
private string trace;
public Tracker(string start)
{
trace = start;
}
public void AddTrace(string me)
{
trace = trace + " -> " + me;
}
public string GetTrace()
{
return trace;
}
}
}
Test.cs:
namespace IKVM
{
public interface ITest
{
void Test(Tracker t);
}
}
TestJava.java:
package ikvm;
import cli.IKVM.ITest;
import cli.IKVM.Tracker;
public class TestJava implements ITest {
@Override
public void Test(Tracker t) {
t.AddTrace("Java");
System.out.println(t.GetTrace());
}
}
TestCS.cs:
using System;
using ikvm;
namespace IKVM
{
public class TestCS : ITest
{
public void Test(Tracker t)
{
t.AddTrace("C#");
Console.WriteLine(t.GetTrace());
ITest o = new TestJava();
o.Test(t);
}
}
}
TestKotlin.kt:
package ikvm
import cli.IKVM.ITest
import cli.IKVM.Tracker
import cli.IKVM.TestCS
class TestKotlin : ITest {
override fun Test(t: Tracker) {
t.AddTrace("Kotlin")
println(t.GetTrace())
var o: ITest = TestCS()
o.Test(t)
}
}
TestVB.vb:
Imports System
Namespace IKVM
Public Class TestVB
Implements ITest
Public Sub Test(t As Tracker) Implements ITest.Test
Call t.AddTrace("VB.NET")
Console.WriteLine(t.GetTrace())
Dim o As ITest = New TestKotlin()
Call o.Test(t)
End Sub
End Class
End Namespace
Main.cs:
using System;
namespace IKVM
{
public class Program
{
public static void Main(string[] args)
{
Tracker t = new Tracker("C#");
Console.WriteLine(t.GetTrace());
ITest o = new TestVB();
o.Test(t);
}
}
}
Build and run:
csc /t:library Test.cs Tracker.cs
ikvmstub Test
ikvmstub mscorlib
javac -cp ..;Test.jar;mscorlib.jar TestJava.java
ikvmc -target:library -r:Test.dll -out:TestJava.dll TestJava.class
csc /t:library /r:Test.dll /r:TestJava.dll /r:IKVM.OpenJDK.Core.dll TestCS.cs
ikvmstub TestCS
call kotlinc -cp ..;Test.jar;mscorlib.jar;TestCS.jar -d .. TestKotlin.kt
ikvmc kotlin-stdlib.jar
ikvmc -target:library -r:Test.dll -r:TestCS.dll -r:kotlin-stdlib.dll -out:TestKotlin.dll TestKotlin.class
vbc /t:library /r:Test.dll /r:TestKotlin.dll /r:IKVM.OpenJDK.Core.dll TestVB.vb
csc /r:Test.dll /r:TestVB.dll Main.cs
Main
Output:
C# C# -> VB.NET C# -> VB.NET -> Kotlin C# -> VB.NET -> Kotlin -> C# C# -> VB.NET -> Kotlin -> C# -> Java
IKVM is such a cool technology. But I would not rely on it for important solutiom - it is somewhat an unsupported technique.
There are also some funny things one can do with libraries only available in the other technology.
Newer versions of Java does not have the JDBC-ODBC bridge so accessing ODBC datasources is a problem.
With IKVM a Java program can use .NET OdbcXxxxxx classes.
import cli.System.Data.*;
import cli.System.Data.Odbc.*;
public class DBJ {
public static void main(String[] args) {
OdbcConnection con = new OdbcConnection("Driver={MySQL ODBC 5.1 Driver};Server=localhost;Database=Test;User=root;Password=;");
con.Open();
OdbcCommand cmd = new OdbcCommand("SELECT f1,f2 FROM t1", con);
OdbcDataReader rdr = cmd.ExecuteReader();
while(rdr.Read()) {
int f1 = rdr.GetInt32(0);
String f2 = rdr.GetString(1);
System.out.println(f1 + " " + f2);
}
con.Close();
}
}
Build:
ikvmstub mscorlib
ikvmstub System
ikvmstub System.Data
javac -cp .;mscorlib.jar;System.jar;System.Data.jar DBJ.java
ikvmc -target:exe -r:System.dll -r:System.Data.dll -out:DBJ.exe DBJ.class
All Oracle clients for .NET require Oracle client to be installed. There is a type 4 JDBC driver for Java that works without Oracle client.
With IKVM that JDBC driver can be compiled to CIL and used by a .NET program.
using System;
using System.Reflection;
using java.lang;
using java.sql;
namespace IKVM
{
public class Program
{
public static void Main(string[] args)
{
ikvm.runtime.Startup.addBootClassPathAssembly(Assembly.Load("ojdbc6")); // necessary for Class.forName to work
Class.forName("oracle.jdbc.OracleDriver");
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "arne", "xxxxxx");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT f1,f2 FROM t1");
while(rs.next())
{
int f1 = rs.getInt(1);
string f2 = rs.getString(2);
Console.WriteLine(f1 + " " + f2);
}
con.close();
}
}
}
Build:
ikvmc -target:library -out:ojdbc6.dll ojdbc6.jar
csc /r:ojdbc6.dll /r:IKVM.OpenJDK.Jdbc.dll /r:IKVM.OpenJDK.Core.dll DBN.cs
Note that it can be very tricky to get IKVM working.
COM/DCOM/COM+/ActiveX is a core Windows technology.
It covers a lot of different functionality:
Here we will focus on the two first aspects.
At the very lowest level a COM class is just a class implementing the interface IUnknown and following some conventions.
COM classes that need to support dynamic API explorartion must also implement the interface IDispatch. This is needed to support script languages without strong type system.
But this article will not focus on the lower levels - instead it will focus on how it practically can be used for cross language calls.
It is easy to write a COM components in .NET.
Basivally it is just:
Here comes an example.
S.cs:
using System;
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyKeyFile("COM.snk")]
namespace COM
{
[Guid("0CA914A7-0B38-4501-AEAE-DE05A6C5A74E")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IS
{
public int Iv { get; set; }
public string Sv { get; set; }
int Add(int a, int b);
string Dup(string s);
void M();
}
[Guid("AB58747E-C79B-430C-871D-D24666B7A42D")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("COM.SDN")]
public class S : IS
{
public int Iv { get; set; }
public string Sv { get; set; }
public int Add(int a, int b)
{
return a + b;
}
public string Dup(string s)
{
return s + s;
}
public void M()
{
Iv = Iv + 1;
Sv = Sv + "X";
}
}
}
Build:
sn -k COM.snk
csc /t:library S.cs /out:SDN.dll
tlbexp SDN.dll
The exported TLB (Type Library) will be used by languages with strong type system.
Register:
regasm SDN.dll
gacutil /i SDN.dll
Be sure to register it correctly as either a 64 bit or 32 bit COM component.
We will see how to call the above COM component from C#, VB.NET, VBScript and JScript.
using System;
namespace COM
{
public class MainClass
{
public static void Main(string[] args)
{
dynamic sdn = Activator.CreateInstance(Type.GetTypeFromProgID("COM.SDN"));
int a = 123;
int b = 456;
int c = sdn.Add(a, b);
Console.WriteLine(c);
string s = "ABC";
string s2 = sdn.Dup(s);
Console.WriteLine(s2);
sdn.Iv = 123;
sdn.Sv = "ABC";
sdn.M();
int iv = sdn.Iv;
string sv = sdn.Sv;
Console.WriteLine(iv + " " + sv);
}
}
}
Build:
csc CDyn.cs
Output:
579 ABCABC 124 ABCX
Option Strict Off
Imports System
Namespace COM
Public Class MainClass
Public Shared Sub Main(args As String())
Dim sdn As Object = Activator.CreateInstance(Type.GetTypeFromProgID("COM.SDN"))
Dim a As Integer = 123
Dim b As Integer = 456
Dim c As Integer = sdn.Add(a, b)
Console.WriteLine(c)
Dim s As String = "ABC"
Dim s2 As String = sdn.Dup(s)
Console.WriteLine(s2)
sdn.Iv = 123
sdn.Sv = "ABC"
sdn.M()
Dim iv As Integer = sdn.Iv
Dim sv As String = sdn.Sv
Console.WriteLine(iv & " " & sv)
End Sub
End Class
End Namespace
Build:
vbc CDyn.vb
Output:
579 ABCABC 124 ABCX
Set sdn = CreateObject("COM.SDN")
a = 123
b = 456
c = sdn.Add(a, b)
WScript.Echo CStr(c)
s = "ABC"
s2 = sdn.Dup(s)
WScript.Echo s2
sdn.Iv = 123
sdn.Sv = "ABC"
call sdn.M
iv = sdn.Iv
sv = sdn.Sv
WScript.Echo CStr(iv) & " " & sv
Set sdn = Nothing
Run:
cscript c.vbs
Output:
579 ABCABC 124 ABCX
var sdn = new ActiveXObject("COM.SDN");
var a = 123;
var b = 456;
var c = sdn.Add(a, b);
WScript.Echo(c);
var s = "ABC";
var s2 = sdn.Dup(s);
WScript.Echo(s2);
sdn.Iv = 123;
sdn.Sv = "ABC";
sdn.M();
var iv = sdn.Iv;
var sv = sdn.Sv;
WScript.Echo(iv + " " + sv);
sdn = null;
Run:
cscript c.js
Output:
579 ABCABC 124 ABCX
The old Microsoft Java came with support for COM.
Other Java implementations and that include all recent does not support COM. But an open source project Jacob adds the capability.
Strictly speaking Jacob does not call COM from Java. Strictly speaking Jacob use JNI to call some C/C++ code in a DLL that then call COM. But from a practical developer perspective Jacob "calls" COM.
Jacob provide two classes for calling COM:
Both as names indicateuse IDispatch and dynamic API.
This is Dispatch:
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
public class C1 {
public static void main(String[] args) {
Dispatch snat = new Dispatch("COM.SDN");
int a = 123;
int b = 456;
int c = Dispatch.callN(snat, "Add", new Variant(a), new Variant(b)).getInt();
System.out.println(c);
String s = "ABC";
String s2 = Dispatch.callN(snat, "Dup", new Variant(s)).getString();
System.out.println(s2);
Dispatch.put(snat, "Iv", new Variant(123));
Dispatch.put(snat, "Sv", new Variant("ABC"));
Dispatch.callN(snat, "M");
int iv = Dispatch.get(snat, "Iv").getInt();
String sv = Dispatch.get(snat, "Sv").getString();
System.out.println(iv + " " + sv);
}
}
Build and run:
javac -cp %JACOBDIR%\jacob.jar C1.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar C1
Output:
579 ABCABC 124 ABCX
The old Microsoft Java came with support for COM.
Other Java implementations and that include all recent does not support COM. But an open source project Jacob adds the capability.
Strictly speaking Jacob does not call COM from Java. Strictly speaking Jacob use JNI to call some C/C++ code in a DLL that then call COM. But from a practical developer perspective Jacob "calls" COM.
Jacob provide two classes for calling COM:
Both as names indicate use IDispatch and dynamic API.
This is ActiveXComponent:
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;
public class C2 {
public static void main(String[] args) {
ActiveXComponent snat = new ActiveXComponent("COM.SDN");
int a = 123;
int b = 456;
int c = snat.invoke("Add", new Variant(a), new Variant(b)).getInt();
System.out.println(c);
String s = "ABC";
String s2 = snat.invoke("Dup", new Variant(s)).getString();
System.out.println(s2);
snat.setProperty("Iv", 123);
snat.setProperty("Sv", "ABC");
snat.invoke("M");
int iv = snat.getPropertyAsInt("Iv");
String sv = snat.getPropertyAsString("Sv");
System.out.println(iv + " " + sv);
}
}
Build and run:
javac -cp %JACOBDIR%\jacob.jar C2.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar C2
Output:
579 ABCABC 124 ABCX
Version | Date | Description |
---|---|---|
1.0 | February 2nd 2020 | Initial version |
1.1 | February 9th 2020 | Add script to compiled and Windows COM |
1.2 | February 22nd 2020 | Add IKVM and Java Jacob |
1.3 | October 12th 22nd 2020 | Add Graal polyglot |
1.4 | May 16th 2024 | Add Python.NET |
See list of all articles here
Please send comments to Arne Vajhøj