MetaFFI

Multi-Lingual Interoperability System

MetaFFI

MetaFFI stands for Multilingual Indirect Interoperability System. It’s a clever solution to a common problem: how do we make different programming languages play nicely together? Each language has its strengths, and sometimes we need to harness those strengths in harmony. But—here comes the challenge—how do we get these languages to talk to each other seamlessly?

MetaFFI employs a similar concept to loading libraries in C/C++, but it provides a layer that doesn’t restrict to a specific runtime or binary, but any runtime. Once a module is loaded you can load entities from that module, weather it is a function, class, field and more. MetaFFI also allows you to pass callback functions of one language to other languages.

How awesome is that?

There is no virtual machine envolved, and each langauges runs in its own original runtime. The system leverages existing Foregin Function Interface (FFI) and embedding mechanisms to link the runtimes together.

Installation

MetaFFI system and runtime cross-platform Python3 installer: v0.3.0

-s: silent mode, uses default installation directory.

Plugin installers (Python3 installers):

What does the MetaFFI installer do?

Windows

Linux

Pre-Installed Docker Container

You can use a pre-installed MetaFFI container based on Ubuntu 22.04 at metaffi/metaffi-u2204:latest

Distribute Binaries

You can distribute MetaFFI binaries with your application (under the license terms). To use MetaFFI on the target machine, you can either install MetaFFI using the installer, or place the MetaFFI directory (without the executable) within your application directory.

Make sure to set METAFFI_HOME environment variable. You can set it in your application before your code uses MetaFFI. On windows, also add to PATH environment variable.

(Currently) Supported Programming Langauges

Language Supported Tested
Go From v1.22.7 v1.18 → v1.23
JVM Languages JNI supported JVM OpenJDK11/21 x64
Microsoft OpenJDK11/21 Hotspot JVM
Python3.11 v3.11 v3.11.*

(Currently) Supported Operating Systems

Operating System Supported Versions Regularly Tested
Windows From 7 11
Ubuntu From 20.04 22.04

Build From Source

MetaFFI uses Python-based SCons build system and Conan package manager to build MetaFFI.

Visual Studio Code Dev Container

The github.com/MetaFFI/metaffi-root repository provides Ubuntu 22.04 devcontainer.json to develop within a docker container.

MetaFFI Paper

The paper (link) discusses the research and internals of MetaFFI. Sections for academic audiance or technical audiance are explicitly marked, as explained in the end of the introduction section.

Documentation

The following provide documentation and explaination about the following MetaFFI APIs:

Two Usage Examples

log4j from Python3 (link):

# load JVM
runtime = metaffi.metaffi_runtime.MetaFFIRuntime('openjdk')

# load log4j
log4j_api_module = runtime.load_module('log4j-api-2.21.1.jar;log4j-core-2.21.1.jar')

# load getLogger() method to get a new logger
getLogger = log4j_api_module.load_entity('class=org.apache.logging.log4j.LogManager,callable=getLogger', 
		[new_metaffi_type_info(metaffi_string8_type)], 
		[new_metaffi_type_info(metaffi_handle_type, 'org.apache.logging.log4j.Logger')])

# load error() method in logger
perror = log4j_api_module.load_entity('class=org.apache.logging.log4j.Logger,callable=error,instance_required',
	[new_metaffi_type_info(MetaFFITypes.metaffi_handle_type),
	new_metaffi_type_info(MetaFFITypes.metaffi_string8_type)],
	None)

# create logger with getLogger()
logger = getLogger('pylogger')
perror(logger, 'Logging error from python!')

runtime.release_runtime_plugin()

More examples from Python3, Java and Go.

Example using a compiler

Some programming languages, like Go, need some help from MetaFFI to build a module available to other programming languages. For that, MetaFFI also provides a compiler to build a MetaFFI enabled module.

For example, assume TestMap.go:

func NewTestMap() *TestMap{
	return &TestMap{ 
		m: make(map[string]interface{}),
		Name: "TestMap Name",
	}
}

func (this *TestMap) Set(k string, v interface{}){
	this.m[k] = v
}

func (this *TestMap) Get(k string) interface{}{
	v := this.m[k]
	return v
}

func (this *TestMap) Contains(k string) bool{
	_, found := this.m[k]
	return found
}

To use it from other languages using MetaFFI, execute the MetaFFI compiler:

metaffi -c --idl TestMap.go -g

This creates a dynamic library for TestMap (i.e. .so or .dll). To use it, simply load the dynamic library using MetaFFI. Here’s an example in Java using JVM MetaFFI API:


// Load Go runtime
MetaFFIRuntime runtime = new MetaFFIRuntime("go");
runtime.loadRuntimePlugin();

// Load the compiled module (in this case, .dll in windows)
MetaFFIModule module = runtime.loadModule("TestMap_MetaFFIGuest.dll");

// Load a function that creates an instance of TestMap
metaffi.Caller newTestMap = module.load("callable=NewTestMap",
		null,
		new MetaFFITypeInfo[]{
			// returns MetaFFI Handle (i.e. handle to the object)
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIHandle)
		});

// Load Set() method
metaffi.Caller testMapSet = module.load("callable=TestMap.Set,instance_required",
		new MetaFFITypeInfo[]{
			// 1st parameter is an instance of the object
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIHandle),
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIString8), // key
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIAny) // // value
		},
		null); // no return values

// Load Contains() method
metaffi.Caller testMapContains = module.load("callable=TestMap.Contains,instance_required",
		new MetaFFITypeInfo[]{ 
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIHandle),
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIString8) // key
		},
		new MetaFFITypeInfo[]{ 
			// boolean return value
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIBool)
		});

// Load Get() method
metaffi.Caller testMapGet = module.load("callable=TestMap.Get,instance_required",
		new MetaFFITypeInfo[]{
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIHandle),
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIString8) // key
		},
		new MetaFFITypeInfo[]{
			new MetaFFITypeInfo(MetaFFITypes.MetaFFIAny) // returned value
		});

// Create new TestMap
// call() method in the API returns Object[], as other languages
// can return multiple return values. We will update the API for a more convenient usage.
var testMap = ((Object[])newTestMap.call())[0];

// set into the map an array of strings:
testMapSet.call(testMap, "key", new ArrayList<>(Arrays.asList("one", "two", "three")));

// get the array list from the map:
var arr = testMapGet.call(testMap, "key");
ArrayList<String> list = (ArrayList<String>)arr[0];

Finding a foreign entity - Entity Path

Entity path refers to a string that represents the foreign entity within the loaded module.

For instance, in C, a module corresponds to a .so/.dll file, and the function path corresponds to the name of the exported function.

However, in other languages or for other entities besides functions, a single name is insufficient. Hence, the function path consists of a list of key-value pairs or tags separated by commas: key1=val1,tag1,...,...,tagN,keyN=valN.

Each plugin requires different keys and tags. Although the keys and tags are similar across plugins, they are not identical.

The following links provide the list of each runtime plugin:

Python3, Java Virtual Machine, Go

Bridging Language Boundaries - MetaFFI XCall and Capabilities-based calling convension

The XCall is the mechanism MetaFFI uses to facilitate cross-language calls. MetaFFI’s agnostic approach ensures that each language remains unaware of the others, allowing for independent plugin development.

XCall is a runtime-independent calling convention that uses Common Data Types (inspired by Microsoft’s Variant and GTK gObject) to enable languages to call and use entities in other languages, even if they lack certain features. While the generic calling convention supports a wide range of cross-language calls, it can affect the performance. </br>Therefore, XCall determines the calling convention used at runtime, based on the required capabilities. This allows MetaFFI to use the full-featured calling convention when necessary, a subset of the capabilities to improve performance, or revert completely to a direct function call when possible (like x64 calling convension), resulting in efficient cross-language interactions.

For more details, please refer to the MetaFFI paper.

Note: The current version of MetaFFI always chooses the generic calling convention due to the differences between the initial languages implemented.

Report a bug

Found a bug? You can report it here.

GitHub Projects

The MetaFFI Project on GitHub.com contains several repositories:

Environment variable

METAFFI_HOME environment variable is set to MetaFFI installation directory. On Windows, also set METAFFI_HOME to the PATH environment variable.