170 likes | 275 Views
RDNZL. R aw D ot N et Z upport for L isp (?). But why?. access to .NET libraries (XML, SOAP, ...) fully "native" GUIs reflection, i.e. no header parsing learn C++ learn Windows API maybe it‘s fun?. Current Status. experimental... basic stuff works: load assemblies import types
E N D
RDNZL Raw DotNet Zupport for Lisp (?)
But why? • access to .NET libraries (XML, SOAP, ...) • fully "native" GUIs • reflection, i.e. no header parsing • learn C++ • learn Windows API • maybe it‘s fun? Dr. Edmund Weitz
Current Status • experimental... • basic stuff works: • load assemblies • import types • invoke methods • get and set fields and properties • transparent handling of (some) Lisp types • uses LispWorks FLI and MOP • first public release: maybe in November Dr. Edmund Weitz
Prior Art • Dot-Scheme (Pedro Pinto, PLT Scheme) • C++ code partly reusable for RDNZL • FLI very different (more like ECL) • JFLI (Rich Hickey, LispWorks) • uses JNI • good ideas for CLOS integration Dr. Edmund Weitz
Managed C++ • has full access to managed code • managed code can be mixed with unmanaged code • can export functions with C linkage (necessary for all CL FLIs) • not possible with C#, J#, VB, etc. Dr. Edmund Weitz
Representation of .NET objects - 1 class DotNetReference { public: DotNetReference(); DotNetReference(Object *o); ~DotNetReference(); Object *getObject(); private: void *ptr; }; Dr. Edmund Weitz
Representation of .NET objects - 2 DotNetReference::DotNetReference() : ptr(0) {} DotNetReference::DotNetReference(Object *o) { ptr = static_cast<IntPtr>(GCHandle::Alloc(o)).ToPointer(); } DotNetReference::~DotNetReference() { if (ptr) { GCHandle::op_Explicit(ptr).Free(); } } Object *DotNetReference::getObject() { return ptr ? dynamic_cast<Object*>(GCHandle::op_Explicit(ptr).Target) : 0; } Dr. Edmund Weitz
Representation of .NET objects - 3 // only excerpts class DotNetContainer { public: DotNetContainer(Object *o, Type *t); DotNetContainer(__int32 n); DotNetContainer(__wchar_t *s); Object *getContainerObject(); Type *getContainerType(); private: DotNetReference* object; DotNetReference* type; }; extern "C" { __declspec(dllexport) void *makeDotNetContainerFromInt(int n); __declspec(dllexport) void *makeDotNetContainerFromString(__wchar_t *s); __declspec(dllexport) int getDotNetContainerTypeStringLength(void *ptr); __declspec(dllexport) void getDotNetContainerTypeAsString(void *ptr, __wchar_t *s); __declspec(dllexport) int getDotNetContainerObjectStringLength(void *ptr); __declspec(dllexport) void getDotNetContainerObjectAsString(void *ptr, __wchar_t *s); __declspec(dllexport) int getDotNetContainerIntValue(void *ptr); __declspec(dllexport) void freeDotNetContainer(void *ptr); } Dr. Edmund Weitz
Representation of .NET objects - 4 void DotNetContainer::init(Object *o, Type *t) { object = o ? new DotNetReference(o) : new DotNetReference(); type = t ? new DotNetReference(t) : new DotNetReference(); } DotNetContainer::DotNetContainer(Object *o, Type *t) { init(o, t); } DotNetContainer::DotNetContainer(__int32 n) { init(__box(n)); } __declspec(dllexport) void *makeDotNetContainerFromInt(int n) { return new DotNetContainer(n); } __declspec(dllexport) void getDotNetContainerObjectAsString(void *ptr, __wchar_t *s) { const __wchar_t __pin *temp = PtrToStringChars(static_cast<DotNetContainer *>(ptr)->getContainerObject()->ToString()); wcscpy(s, temp); } __declspec(dllexport) void freeDotNetContainer(void *ptr) { delete static_cast<DotNetContainer *>(ptr); } Dr. Edmund Weitz
On the Lisp Side... (define-foreign-function (%free-dotnet-container "freeDotNetContainer") ((ptr :pointer)) :result-type :void) (defstruct dotnet-container pointer) (defun maybe-free-ptr (object) (when (dotnet-container-p object) (%free-dotnet-container (dotnet-container-pointer object)))) (add-special-free-action 'maybe-free-ptr) ... (flag-special-free-action dotnet-container) Dr. Edmund Weitz
Example: Invoking a Method __declspec(dllexport) void* invokeInstanceMember(void *target, __wchar_t *name, int nargs, void *args[]) { try { Object *realArgs[] = new Object*[nargs]; Type *realTypes[] = new Type*[nargs]; for (int i = 0; i < nargs; i++) { DotNetContainer *c = static_cast<DotNetContainer *>(args[i]); realArgs[i] = c->getContainerObject(); realTypes[i] = c->getContainerType(); } DotNetContainer *container = static_cast<DotNetContainer *>(target); Type *t = container->getContainerType(); MethodInfo *mi = t->GetMethod(name, realTypes); // todo: throw exception if mi == 0 Object *newInstance = mi->Invoke(container->getContainerObject(), realArgs); // InvocationResult is a wrapper which can handle void results and exceptions if (mi->ReturnType->Equals(__typeof(System::Void))) { return new InvocationResult(); } else { return new InvocationResult(newInstance); } } catch (Exception *e) { return new InvocationResult(e, true); } } Dr. Edmund Weitz
On the Lisp Side... (defun invoke-member (object method &rest args) (with-dynamic-foreign-objects () (let ((arg-pointers (allocate-dynamic-foreign-object :type :pointer :nelems (length args)))) (loop for arg in args for i from 0 do (setf (dereference arg-pointers :index i) (dotnet-container-pointer (box arg)))) (with-foreign-string (method-name element-count byte-count :external-format :unicode) method (declare (ignore element-count byte-count)) (get-invocation-result ;; handles GC, exceptions, "unboxing" (%invoke-instance-member ;; FLI call (dotnet-container-pointer object) method-name (length args) arg-pointers)))))) Dr. Edmund Weitz
Name Mangling • .NET type System.String • Lisp class .SYSTEM:STRING • .NET type System.Runtime.InteropServices • Lisp class .SYSTEM.RUNTIME:INTEROP-SERVICES • not bijective, but see CLS • not good, will be replaced by reader macros Dr. Edmund Weitz
CLOS integration • load assembly • import public types, map to CLOS classes • preserve class hierarchy • create constructor(s) (new '.system:string #\a 3) • create (CLOS) methods (.get-type "foo") • create field/property accessors (setf (.name (new '.system.reflection::assembly-name)) "MyAssembly") Dr. Edmund Weitz
Delegates • clever trick (by Pedro Pinto) • at runtime create CLR code for new delegate type Foo and method InvokeClosure with correct signature • Foo inherits from managed C++ class DelegateAdapterBase • InvokeClosure calls DelegateAdapterBase::invoke(Object* args []) • DelegateAdapterBase constructor accepts (long) integer which is index into Lisp hash of closures • invoke calls Lisp foreign callable with index and DotNetContainer array • invoke knows about InvokeClosure's signature via run-time reflection Dr. Edmund Weitz
To Do • better name mangling, reader macros • (partly) integrate .NET exceptions into CL condition system • convenience functions/macros for enumerations and arrays • performance improvements • porting to other Lisps (any volunteers?) Dr. Edmund Weitz