170 likes | 279 Vues
This piece explores the experimental RDNZL project by Dr. Edmund Weitz, enabling access to .NET libraries directly from Lisp, fostering a more integrated programming experience. It covers features such as reflection, the creation of native GUIs, and transparent handling of various Lisp types. The article delves into the technical aspects, providing examples of how to load assemblies, invoke methods, and manage .NET objects in Lisp. Designed for Lisp enthusiasts and developers curious about C++ and Windows API, it highlights the intersection of these languages and the potential for creative programming solutions.
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