Skip to content

Commit 09f258d

Browse files
committed
Implement simple Mono support
1 parent 83321c4 commit 09f258d

File tree

4 files changed

+209
-4
lines changed

4 files changed

+209
-4
lines changed

Example.ipynb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"import os\n",
10+
"# os.environ[\"COREHOST_TRACE\"] = \"1\"\n",
11+
"# os.environ[\"COREHOST_TRACE_VERBOSITY\"] = \"4\"\n",
12+
"# os.environ[\"MONO_LOG_LEVEL\"] = \"debug\"\n",
13+
"\n",
14+
"from clr_loader import hostfxr, mono\n",
15+
"from clr_loader.ffi import ffi\n",
16+
"\n",
17+
"core = hostfxr.HostFxr(\"example/out/example.runtimeconfig.json\")\n",
18+
"mono = mono.Mono()\n",
19+
"\n",
20+
"func_core = core.get_callable(\"example/out/example.dll\", \"example.Class1, example\", \"Test\")\n",
21+
"func_mono = mono.get_callable(\"example/out/example.dll\", \"example.Class1\", \"Test\")"
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": 2,
27+
"metadata": {},
28+
"outputs": [
29+
{
30+
"data": {
31+
"text/plain": [
32+
"0"
33+
]
34+
},
35+
"execution_count": 2,
36+
"metadata": {},
37+
"output_type": "execute_result"
38+
}
39+
],
40+
"source": [
41+
"func_core(ffi.NULL, 123)\n",
42+
"func_mono(ffi.NULL, 124)"
43+
]
44+
},
45+
{
46+
"cell_type": "code",
47+
"execution_count": null,
48+
"metadata": {},
49+
"outputs": [],
50+
"source": []
51+
}
52+
],
53+
"metadata": {
54+
"kernelspec": {
55+
"display_name": "Python 3",
56+
"language": "python",
57+
"name": "python3"
58+
},
59+
"language_info": {
60+
"codemirror_mode": {
61+
"name": "ipython",
62+
"version": 3
63+
},
64+
"file_extension": ".py",
65+
"mimetype": "text/x-python",
66+
"name": "python",
67+
"nbconvert_exporter": "python",
68+
"pygments_lexer": "ipython3",
69+
"version": "3.7.4"
70+
}
71+
},
72+
"nbformat": 4,
73+
"nbformat_minor": 2
74+
}

clr_loader/ffi/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,42 @@
55

66
from . import coreclr, hostfxr, mono
77

8+
__all__ = ["ffi", "load_coreclr", "load_hostfxr", "load_mono"]
9+
810
ffi = cffi.FFI()
911

1012
for cdef in coreclr.cdef + hostfxr.cdef + mono.cdef:
1113
ffi.cdef(cdef)
1214

1315

1416
def load_coreclr(runtime):
15-
dll_name = get_dll_name("coreclr")
17+
dll_name = _get_dll_name("coreclr")
1618
dll_path = os.path.join(runtime.path, dll_name)
1719
return ffi.dlopen(dll_path)
1820

1921

2022
def load_hostfxr(dotnet_root):
21-
2223
hostfxr_version = "3.0.0"
23-
hostfxr_name = get_dll_name("hostfxr")
24+
hostfxr_name = _get_dll_name("hostfxr")
2425
hostfxr_path = os.path.join(
2526
dotnet_root, "host", "fxr", hostfxr_version, hostfxr_name
2627
)
2728

2829
return ffi.dlopen(hostfxr_path)
2930

3031

31-
def get_dll_name(name):
32+
def load_mono(path=None, gc=None):
33+
if path is None:
34+
from ctypes.util import find_library
35+
36+
path = find_library(f"mono{gc or ''}-2.0")
37+
if path is None:
38+
raise RuntimeError("Could not find libmono")
39+
40+
return ffi.dlopen(path)
41+
42+
43+
def _get_dll_name(name):
3244
import sys
3345

3446
if sys.platform == "win32":

clr_loader/ffi/mono.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,24 @@
11
cdef = []
22

3+
cdef.append(
4+
"""
5+
typedef struct _MonoDomain MonoDomain;
6+
typedef struct _MonoAssembly MonoAssembly;
7+
typedef struct _MonoImage MonoImage;
8+
typedef struct _MonoMethodDesc MonoMethodDesc;
9+
typedef struct _MonoMethod MonoMethod;
10+
typedef struct _MonoObject MonoObject;
11+
12+
MonoDomain* mono_jit_init(const char *root_domain_name);
13+
MonoAssembly* mono_domain_assembly_open(MonoDomain *domain, const char *name);
14+
MonoImage* mono_assembly_get_image(MonoAssembly *assembly);
15+
16+
MonoMethodDesc* mono_method_desc_new(const char* name, bool include_namespace);
17+
MonoMethod* mono_method_desc_search_in_image(MonoMethodDesc *method_desc, MonoImage *image);
18+
void mono_method_desc_free(MonoMethodDesc *method_desc);
19+
20+
MonoObject* mono_runtime_invoke(MonoMethod *method, void *obj, void **params, MonoObject **exc);
21+
22+
void* mono_object_unbox(MonoObject *object);
23+
"""
24+
)

clr_loader/mono.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import atexit
2+
3+
from .ffi import load_mono, ffi
4+
5+
6+
__all__ = ["Mono"]
7+
8+
9+
_MONO = None
10+
_ROOT_DOMAIN = None
11+
12+
13+
class Mono:
14+
def __init__(self, domain=None):
15+
self._assemblies = {}
16+
initialize()
17+
18+
if domain is None:
19+
self._domain = _ROOT_DOMAIN
20+
else:
21+
raise NotImplementedError
22+
23+
def get_callable(self, assembly_path, typename, function):
24+
assembly = self._assemblies.get(assembly_path)
25+
if not assembly:
26+
assembly = _MONO.mono_domain_assembly_open(
27+
self._domain, assembly_path.encode("utf8")
28+
)
29+
_check_result(assembly, f"Unable to load assembly {assembly_path}")
30+
self._assemblies[assembly_path] = assembly
31+
32+
image = _MONO.mono_assembly_get_image(assembly)
33+
_check_result(image, "Unable to load image from assembly")
34+
35+
desc = MethodDesc(typename, function)
36+
method = desc.search(image)
37+
_check_result(
38+
method, f"Could not find method {typename}.{function} in assembly"
39+
)
40+
41+
return MonoMethod(self._domain, method)
42+
43+
44+
class MethodDesc:
45+
def __init__(self, typename, function):
46+
self._desc = f"{typename}:{function}"
47+
self._ptr = _MONO.mono_method_desc_new(
48+
self._desc.encode("utf8"),
49+
1 # include_namespace
50+
)
51+
52+
def search(self, image):
53+
return _MONO.mono_method_desc_search_in_image(self._ptr, image)
54+
55+
def __del__(self):
56+
if _MONO:
57+
_MONO.mono_method_desc_free(self._ptr)
58+
59+
class MonoMethod:
60+
def __init__(self, domain, ptr):
61+
self._ptr = ptr
62+
63+
def __call__(self, ptr, size):
64+
exception = ffi.new("MonoObject**")
65+
params = ffi.new("void*[2]")
66+
67+
params[0] = ffi.new("void**", ptr)
68+
params[1] = ffi.new("int*", size)
69+
70+
res = _MONO.mono_runtime_invoke(self._ptr, ffi.NULL, params, exception)
71+
_check_result(res, "Failed to call method")
72+
73+
unboxed = ffi.cast("int32_t*", _MONO.mono_object_unbox(res))
74+
_check_result(unboxed, "Failed to convert result to int")
75+
76+
return unboxed[0]
77+
78+
79+
def initialize(path=None, gc=None):
80+
global _MONO, _ROOT_DOMAIN
81+
if _MONO is None:
82+
_MONO = load_mono(path=path, gc=gc)
83+
_ROOT_DOMAIN = _MONO.mono_jit_init(b"clr_loader")
84+
_check_result(_ROOT_DOMAIN, "Failed to initialize Mono")
85+
atexit.register(_release)
86+
87+
88+
def _release():
89+
if _ROOT_DOMAIN is not None and _MONO is not None:
90+
_MONO.mono_jit_cleanup(_ROOT_DOMAIN)
91+
_MONO = None
92+
_ROOT_DOMAIN = None
93+
94+
95+
def _check_result(res, msg):
96+
if res == ffi.NULL or not res:
97+
raise RuntimeError(msg)

0 commit comments

Comments
 (0)