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:
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
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
MSVC++ is a very advanced C++ compiler. It supports 3 different modes:
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 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
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++ 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
Version | Date | Description |
---|---|---|
1.0 | February 2nd 2020 | Initial version |
1.1 | February 9th 2020 | Add script to compiled and Windows COM |
See list of all articles here
Please send comments to Arne Vajhøj