From 1a4596cdd6b24337927126c030872ccda82a72ef Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Oct 2019 17:57:34 +0100 Subject: [PATCH 1/2] Start implementing the .NET framework loader --- .gitignore | 7 ++- clr_loader/ffi/__init__.py | 12 +++- clr_loader/ffi/framework.py | 10 ++++ clr_loader/framework.py | 29 ++++++++++ netframework_loader/ClrLoader.cs | 84 ++++++++++++++++++++++++++++ netframework_loader/ClrLoader.csproj | 17 ++++++ 6 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 clr_loader/ffi/framework.py create mode 100644 netframework_loader/ClrLoader.cs create mode 100644 netframework_loader/ClrLoader.csproj diff --git a/.gitignore b/.gitignore index 3995605..44ba70b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # .NET Core build folders -example/bin -example/obj -example/out +bin/ +out/ +obj/ +.vs/ ### Python ### # Byte-compiled / optimized / DLL files diff --git a/clr_loader/ffi/__init__.py b/clr_loader/ffi/__init__.py index 3cbd164..67e63d8 100644 --- a/clr_loader/ffi/__init__.py +++ b/clr_loader/ffi/__init__.py @@ -3,13 +3,13 @@ import cffi -from . import coreclr, hostfxr, mono +from . import coreclr, hostfxr, mono, framework -__all__ = ["ffi", "load_coreclr", "load_hostfxr", "load_mono"] +__all__ = ["ffi", "load_coreclr", "load_hostfxr", "load_mono", "load_framework"] ffi = cffi.FFI() -for cdef in coreclr.cdef + hostfxr.cdef + mono.cdef: +for cdef in coreclr.cdef + hostfxr.cdef + mono.cdef + framework.cdef: ffi.cdef(cdef) @@ -40,6 +40,12 @@ def load_mono(path=None, gc=None): return ffi.dlopen(path) +def load_framework(): + path = "netframework_loader/bin/x64/Debug/net472/ClrLoader.dll" + + return ffi.dlopen(path) + + def _get_dll_name(name): import sys diff --git a/clr_loader/ffi/framework.py b/clr_loader/ffi/framework.py new file mode 100644 index 0000000..150a45b --- /dev/null +++ b/clr_loader/ffi/framework.py @@ -0,0 +1,10 @@ +cdef = [ + """ +typedef void* pyclr_domain; +typedef int (*entry_point)(void* buffer, int size); + +void* pyclr_create_appdomain(const char* name, const char* config_file); +entry_point pyclr_get_function(pyclr_domain domain, const char* assembly_path, const char* class_name, const char* function); +void pyclr_close_appdomain(pyclr_domain domain); + """ +] diff --git a/clr_loader/framework.py b/clr_loader/framework.py index e69de29..598b1e8 100644 --- a/clr_loader/framework.py +++ b/clr_loader/framework.py @@ -0,0 +1,29 @@ +from .ffi import ffi, load_framework + + +_FW = None + + +class Framework: + def __init__(self, name=None, config_file=None): + global _FW + if _FW is None: + _FW = load_framework() + + self._domain = _FW.pyclr_create_appdomain( + name or ffi.NULL, config_file or ffi.NULL + ) + + def get_callable(self, assembly_path, typename, function): + func = _FW.pyclr_get_function( + self._domain, + assembly_path.encode("utf8"), + typename.encode("utf8"), + function.encode("utf8"), + ) + + return ffi.cast("entry_point*", func) + + def __del__(self): + if self._domain and _FW: + _FW.pyclr_close_appdomain(self._domain) diff --git a/netframework_loader/ClrLoader.cs b/netframework_loader/ClrLoader.cs new file mode 100644 index 0000000..4009f95 --- /dev/null +++ b/netframework_loader/ClrLoader.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using RGiesecke.DllExport; + +namespace ClrLoader +{ + public static class ClrLoader + { + delegate int EntryPoint(IntPtr buffer, int size); + + [DllExport("pyclr_create_appdomain", CallingConvention.Cdecl)] + public static IntPtr CreateAppDomain( + [MarshalAs(UnmanagedType.LPUTF8Str)] string name, + [MarshalAs(UnmanagedType.LPUTF8Str)] string configFile + ) + { + Print($"Creating AppDomain {name} with {configFile}"); + if (!string.IsNullOrEmpty(name)) + { + var setup = new AppDomainSetup + { + ConfigurationFile = configFile + }; + var domain = AppDomain.CreateDomain(name, null, setup); + + Print($"Located domain {domain}"); + + var handle = GCHandle.Alloc(domain, GCHandleType.Pinned); + + Print($"Created handle {handle}"); + + return handle.AddrOfPinnedObject(); + } + else + { + return IntPtr.Zero; + } + } + + [DllExport("pyclr_get_function", CallingConvention.Cdecl)] + public static IntPtr GetFunction( + IntPtr domain, + [MarshalAs(UnmanagedType.LPUTF8Str)] string assemblyPath, + [MarshalAs(UnmanagedType.LPUTF8Str)] string typeName, + [MarshalAs(UnmanagedType.LPUTF8Str)] string function + ) + { + var domainObj = AppDomain.CurrentDomain; + if (domain != IntPtr.Zero) + { + var handle = GCHandle.FromIntPtr(domain); + domainObj = (AppDomain)handle.Target; + } + + var assembly = domainObj.Load(AssemblyName.GetAssemblyName(assemblyPath)); + var type = assembly.GetType(typeName); + var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); + + return Marshal.GetFunctionPointerForDelegate(deleg); + } + + [DllExport("pyclr_close_appdomain", CallingConvention.Cdecl)] + public static void CloseAppDomain(IntPtr domain) + { + if (domain != IntPtr.Zero) + { + var handle = GCHandle.FromIntPtr(domain); + var domainObj = (AppDomain)handle.Target; + AppDomain.Unload(domainObj); + handle.Free(); + } + } + + static void Print(string s) + { + Console.WriteLine(s); + } + } + +} \ No newline at end of file diff --git a/netframework_loader/ClrLoader.csproj b/netframework_loader/ClrLoader.csproj new file mode 100644 index 0000000..65e3496 --- /dev/null +++ b/netframework_loader/ClrLoader.csproj @@ -0,0 +1,17 @@ + + + net472 + x64;x86 + + + + + + + + x86 + + + x64 + + From 8c086fbec75a09c0ebbd538e97465909a38c6fed Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Tue, 29 Oct 2019 23:47:17 +0100 Subject: [PATCH 2/2] Catch exceptions and return correct function type --- clr_loader/framework.py | 2 +- example/example.csproj | 4 ++-- netframework_loader/ClrLoader.cs | 27 ++++++++++++++++++--------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/clr_loader/framework.py b/clr_loader/framework.py index 598b1e8..f960469 100644 --- a/clr_loader/framework.py +++ b/clr_loader/framework.py @@ -22,7 +22,7 @@ def get_callable(self, assembly_path, typename, function): function.encode("utf8"), ) - return ffi.cast("entry_point*", func) + return func def __del__(self): if self._domain and _FW: diff --git a/example/example.csproj b/example/example.csproj index 2003109..f8edf98 100644 --- a/example/example.csproj +++ b/example/example.csproj @@ -1,6 +1,6 @@ - + - netcoreapp3.0 + netcoreapp30;netstandard20 true diff --git a/netframework_loader/ClrLoader.cs b/netframework_loader/ClrLoader.cs index 4009f95..4635910 100644 --- a/netframework_loader/ClrLoader.cs +++ b/netframework_loader/ClrLoader.cs @@ -49,18 +49,27 @@ public static IntPtr GetFunction( [MarshalAs(UnmanagedType.LPUTF8Str)] string function ) { - var domainObj = AppDomain.CurrentDomain; - if (domain != IntPtr.Zero) + try { - var handle = GCHandle.FromIntPtr(domain); - domainObj = (AppDomain)handle.Target; - } + var domainObj = AppDomain.CurrentDomain; + if (domain != IntPtr.Zero) + { + var handle = GCHandle.FromIntPtr(domain); + domainObj = (AppDomain)handle.Target; + } - var assembly = domainObj.Load(AssemblyName.GetAssemblyName(assemblyPath)); - var type = assembly.GetType(typeName); - var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); + var assembly = domainObj.Load(AssemblyName.GetAssemblyName(assemblyPath)); + var type = assembly.GetType(typeName, throwOnError: true); + Print($"Loaded type {type}"); + var deleg = Delegate.CreateDelegate(typeof(EntryPoint), type, function); - return Marshal.GetFunctionPointerForDelegate(deleg); + return Marshal.GetFunctionPointerForDelegate(deleg); + } + catch (Exception exc) + { + Print($"Exception in {nameof(GetFunction)}: {exc.GetType().Name} {exc.Message}\n{exc.StackTrace}"); + return IntPtr.Zero; + } } [DllExport("pyclr_close_appdomain", CallingConvention.Cdecl)]