Calling 1 - Managed to Managed

Content:

  1. Introduction
  2. JVM languages
    1. Compiled language to compiled language
    2. Compiled language to interpreted script
    3. Interpreted script to compiled language
    4. Graal polyglot
  3. .NET languages
    1. Compiled language to compiled language
    2. Compiled language to interpreted script
    3. Interpreted script to compiled language
  4. Mixing JVM languages and CLR languages
    1. IKVM
  5. Windows COM
    1. Concept
    2. Server
    3. Client

Introduction:

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:

JVM languages:

Compiled language to compiled language:

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.

Compiled language to interpreted script:

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)

Interpreted script to compiled language

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 polyglot:

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)

.NET languages:

Compiled language to compiled language:

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#

Compiled language to interpreted script:

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:

IronPython model IronRuby model Python.NET model

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>

Interpreted script to compiled language

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}

Mixing JVM languages and CLR languages:

IKVM:

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:

ikvmc
compile Java byte code to CIL code that can run on CLR
ikvmstub
generate a Java byte code stub (not implementation) of CLR code (to allow Java code using CLR code to be compiled by the Java compiler before ikvmc is run)

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.

Windows COM:

Concept:

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.

Server:

It is easy to write a COM components in .NET.

Basivally it is just:

  1. Write an interface and a class
  2. Put some COM attributes on interface and class
  3. Build a signed assembly
  4. Put assembly in GAC and register it in registry

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.

Client:

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:

Dispatch
procedural style
ActiveXCompnent
object orieneted style

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:

Dispatch
procedural style
ActiveXCompnent
object orieneted style

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

Article history:

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

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj