Calling 3 - Native to Managed

Content:

  1. Introduction
  2. Java
    1. JNI
  3. .NET
    1. COM host
    2. C++/CLI mixed mode
  4. Python
    1. Embedded
  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 native (C) to managed (Java, .NET, Python).

Mixing managed and native create some special problems as a variable is either managed or native, so data need to be converted when moving from one to another.

Other articles:

Java:

JNI:

JNI (Java Native Interface) is mostly used to call native code from JVM code. See details here. But it can also be used to call from native code to JVM code.

A typical usage for that is to create a custom executable to start JVM.

TestClass.java:

package n2m;

public class TestClass {
    public static int testMethod(String s) {
        System.out.printf("Java got %s\n", s);
        return 123;
    }
}

TestJ.c:

#include <stdio.h>

#include <jni.h>

int main()
{
    JavaVMOption options[2];
    JavaVMInitArgs vm_args;
    JNIEnv *env;
    JavaVM *jvm;
    jint res;
    jclass clz;
    jmethodID method;
    jstring arg;
    options[0].optionString = "-Xmx256m";
    options[1].optionString = "-Djava.class.path=..";
    vm_args.version = JNI_VERSION_1_8;
    vm_args.nOptions = 2;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = JNI_FALSE;
    res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
    if(res < 0)
    {
        printf("Error creating JVM\n");
        return 1;
    }
    clz = (*env)->FindClass(env, "n2m/TestClass");
    if(clz == NULL)
    {
        printf("Error finding class\n");
        return 1;
    }
    method = (*env)->GetStaticMethodID(env, clz, "testMethod", "(Ljava/lang/String;)I");
    if (method ==NULL)
    {
        printf("Error getting method in class\n");
        return 1;
    }
    arg = (*env)->NewStringUTF(env, "ABC");
    res = (*env)->CallStaticIntMethod(env, clz, method, arg);
    printf("C got %ld back\n", res);
    (*jvm)->DestroyJavaVM(jvm);
    return 0;
}

Output:

Java got ABC
C got 123 back

.NET:

COM host:

The .NET runtime can actually be loaded as a COM component. The API has been enhanced over time.

TestClass.cs:

using System;

namespace N2M
{
    public class TestClass
    {
        public static int TestMethod(string s)
        {
            Console.WriteLine("C# got {0}", s);
            return 123;
        }
    }
}

TestDN.cpp:

// standard
#include <iostream>
#include <cstdio>
using namespace std;

// Windows
#include <windows.h>
#include <tchar.h>

// .NET
#include <MSCorEE.h>
#include <MetaHost.h>
#pragma comment(lib, "mscoree.lib")                                    

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %d = %08X = %s\n", func, res, res, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT hr;
    DWORD dwRet;
    ICLRRuntimeHost *pClrHost;
    ICLRMetaHost *lpMetaHost;
    ICLRRuntimeInfo *lpRuntimeInfo;
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&lpMetaHost));
    ReturnCheck("CLRCreateInstance", hr);
    hr = lpMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&lpRuntimeInfo));
    ReturnCheck("GetRuntime", hr);
    hr = lpRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID *)&pClrHost);
    ReturnCheck("GetInterface", hr);
    hr = pClrHost->Start();
    ReturnCheck("Start", hr);
    hr = pClrHost->ExecuteInDefaultAppDomain(L"Test.dll", L"N2M.TestClass", L"TestMethod", L"ABC", &dwRet);
    ReturnCheck("ExecuteInDefaultAppDomain", hr);
    cout << "C++ got " << dwRet << " back" << endl;
    hr = pClrHost->Stop();
    ReturnCheck("Stop", hr);
    hr = pClrHost->Release();
    ReturnCheck("Stop", hr);
    return 0;
}

Output:

C# got ABC
C++ got 123 back

C++/CLI mixed mode:

MSVC++ is a very advanced C++ compiler. It supports 3 different modes:

  1. Pure native code (standard C++)
  2. Pure managed code (in the language C++/CLI that are defined in ECMA 372 standard)
  3. Mixed with both native and managed code

C++/CLI mixed mode is an convenient way to get from native code to managed code.

TestClass.cs:

using System;

namespace N2M
{
    public class TestClass
    {
        public static int TestMethod(string s)
        {
            Console.WriteLine("C# got {0}", s);
            return 123;
        }
    }
}

TestMix.cpp:

// standard
#include <iostream>
using namespace std;

// .NET
#using <mscorlib.dll>
using namespace System;

#using "test.dll"

int main()
{
    int ret = N2M::TestClass::TestMethod(gcnew String("ABC"));
    cout << "C++ got " << ret << " back" << endl;
    return 0;
}

Output:

C# got ABC
C++ got 123 back

Python:

Embedded:

Python ships with header file and library to easily embed Python in a C application.

TestPy.c:

#include <stdio.h>

#include <Python.h>

void test(char *source)
{
    PyObject *main;
    PyObject *global;
    PyObject *local;
    long iv;
    char *sv;
    printf("%s\n", source);
    Py_Initialize();
    main = PyImport_AddModule("__main__");
    global = PyModule_GetDict(main);
    local = PyDict_New();
#if PY_MAJOR_VERSION == 2
    PyDict_SetItemString(local, "n", PyInt_FromLong(3));    
    PyDict_SetItemString(local, "s", PyString_FromString("Hi"));    
    PyDict_SetItemString(local, "iv", PyInt_FromLong(123));    
    PyDict_SetItemString(local, "sv", PyString_FromString("ABC"));    
#endif
#if PY_MAJOR_VERSION == 3
    PyDict_SetItemString(local, "n", PyLong_FromLong(3));    
    PyDict_SetItemString(local, "s", PyUnicode_FromString("Hi"));    
    PyDict_SetItemString(local, "iv", PyLong_FromLong(123));    
    PyDict_SetItemString(local, "sv", PyUnicode_FromString("ABC"));    
#endif
    PyRun_String(source, Py_file_input, global, local);
#if PY_MAJOR_VERSION == 2
    iv = PyInt_AsLong(PyDict_GetItemString(local, "iv"));
    sv = PyString_AsString(PyDict_GetItemString(local, "sv"));
#endif
#if PY_MAJOR_VERSION == 3
    iv = PyLong_AsLong(PyDict_GetItemString(local, "iv"));
    sv = PyUnicode_AsUTF8(PyDict_GetItemString(local, "sv"));
#endif
    printf("%d %s\n", iv, sv);
    Py_Finalize();
}

int main()
{
    test("for i in range(n):\n"
         "    print('Python say: ' + s)\n"
         "iv = iv + 1\n"
         "sv = sv + 'X'");
    return 0;
}

We note that the API was changed between Python 2 and 3.

Output:

for i in range(n):
    print('Python say: ' + s)
iv = iv + 1
sv = sv + 'X'
Python say: Hi
Python say: Hi
Python say: Hi
124 ABCX

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++ and Delphi/Lazarus.

// C++
#include <iostream>
using namespace std;

// Windows
#include <windows.h>
#include <initguid.h>
#include <tchar.h>

// our stuff
#import "SDN.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    try
    {
        ISPtr spS;
        res = spS.CreateInstance(CLSID_S);
        ReturnCheck(_T("CreateInstance"), res);
        long a = 123;
        long b = 456;
        long c = spS->Add(a, b);
        wcout << c << endl;
        BSTR s = SysAllocString(L"ABC");
        BSTR s2 = spS->Dup(s);
        wcout << s2 << endl;
        SysFreeString(s);
        SysFreeString(s2);
        BSTR temp = SysAllocString(L"ABC");
        spS->PutIv(123);
        spS->PutSv(temp);
        SysFreeString(temp);
        spS->M();
        long iv = spS->GetIv();
        BSTR sv = spS->GetSv();
        wcout << iv << " " << sv << endl;
        SysFreeString(sv);
        spS = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    //
    CoUninitialize();
    return 0;
}

Build:

cl /EHsc C1.cpp ole32.lib oleaut32.lib

Output:

579
ABCABC
124 ABCX
// C++
#include <iostream>
using namespace std;

// Windows
#include <windows.h>
#include <initguid.h>
#include <tchar.h>

// our stuff
#import "SDN.tlb" no_namespace named_guids

int main()
{
    CoInitialize(NULL);
    try
    {
        ISPtr spS(__uuidof(S));
        long a = 123;
        long b = 456;
        long c = spS->Add(a, b);
        wcout << c << endl;
        BSTR s = SysAllocString(L"ABC");
        BSTR s2 = spS->Dup(s);
        wcout << s2 << endl;
        SysFreeString(s);
        SysFreeString(s2);
        BSTR temp = SysAllocString(L"ABC");
        spS->PutIv(123);
        spS->PutSv(temp);
        SysFreeString(temp);
        spS->M();
        long iv = spS->GetIv();
        BSTR sv = spS->GetSv();
        wcout << iv << " " << sv << endl;
        SysFreeString(sv);
        spS = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    //
    CoUninitialize();
    return 0;
}

Build:

cl /EHsc C2.cpp ole32.lib oleaut32.lib

Output:

579
ABCABC
124 ABCX
program CProgram;

uses
  ActiveX,ComObj;

var
  a, b, c : integer;
  s, s2 : widestring;
  sdn : Variant;

begin
  CoInitialize(nil);
  sdn := CreateOLEObject('COM.SDN');
  a := 123;
  b := 456;
  c := sdn.Add(a, b);
  writeln(c);
  s := 'ABC';
  s2 := sdn.Dup(s);
  writeln(s2);
  sdn.Iv := 123;
  sdn.Sv := 'ABC';
  sdn.M;
  writeln(sdn.Iv:1,' ',sdn.Sv);
  CoUninitialize;
end.

Note that Delphi/Lazarus in this example use IDispatch and the dynamic API.

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

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj