- Basic Concepts
- Basic API
- Arrays and Enumerable
- Type Resolution
- Reflection API
- Exception Handling
- Namespace Management
- Type Aliases
- Assembly and Type Cache Management
- Direct Memory Access
- Printer Facility
- Apropos Facility
- .NET Callable Classes
- Custom Syntax
- Conditions
- Limitations
A dotnet object is a concept used by the .NET runtime. In .NET, not unlike in Lisp, everything is an object, everything has a class, etc.
.NET distinguishes between two categories of objects. The first one includes objects of reference types. Like System.String
instances.
The second category includes objects of value types. Like System.Int32
instances.
In .NET, objects of value types are passed around by value (i.e. by copying), and so do not hold referential identity.
Since Lisp does not have a concept of such partitioning, and most lisp objects, including defstruct instances, are passed by copying a reference to the actual data, the library does also treat most objects as objects of reference types. And performs boxing of value type instances.
To be able to be usable from Lisp, a boxed .NET object is then converted to a GCHandle, and then to an IntPtr value, which
is then put into an instance of DOTNET-OBJECT
structure class(or its subclasses) on the Lisp side.
There are some exceptions to this rule, however. The most primitive object types are passed by value without boxing, whenever possible.
These types include:
System.Char
System.Boolean
System.Byte
System.SByte
System.Int16
System.UInt16
System.Int32
System.UInt32
System.Int64
System.UInt64
System.Single
System.Double
System.IntPtr
System.UIntPtr
Note that large integers and floats could still be boxed(in the generic sense, not in the sense of the DOTNET-OBJECT
structure)
on the Lisp side by the Lisp implementation, that depends on the implementation and the processor architecture
Given all this, the library is unable, and probably will never be able, to handle ref struct
s.
Should an object of such type be spotted by the library, the interop layer throws a .NET exception of type BikeInterop.RefStructParameterException
.
One of the most important concepts used by the library is the type specifier or typespec for short.
A type specifier is a form with a specific syntax. A type specifier argument can either be evaluated or not, that depends on the context.
The most basic type specifier is a string object, which designates a type by its name. For example:
"System.DateTime"
Any string designator
can be used instead of a string. That means symbols and characters could also be used as type specifiers.
Type specifiers are case-insensitive.
System.DateTime ; designates the same type as the above string, regardless of the (readtable-case *readtable*) value
String designators can actually include anything accepted by Type.GetType
method from .NET.
Those complex string type definitions are parsed into Lisp type specifiers in most cases, however.
String designators can also point to type aliases, as defined by USE-TYPE-ALIAS
function.
Some aliases are pre-defined for several primitive .NET types, for ex. :int
actually points to System.Int32
.
The second most simple type specifier is an object of type DOTNET-TYPE
, which wraps a System.Type
object from .NET itself.
There are also complex type specifiers.
An array type specifier is a list of form
(ARRAY element-type &optional (rank 1))
The ARRAY
name here is any string designator, which could be converted to the string "ARRAY"
.
Element type can be any type specifier except for the ref typespec (see below).
Array rank can designate the number of array dimensions. It could be any integer between 1 and 32 inclusively.
Array rank can also be the *
symbol. In this case, an MZ array type is assumed.
MZ arrays can have variable dimensions and variable lower bounds.
A pointer type specifier is a list of form
(* inner-type)
It designates a pointer type from dotnet. Note that inner type cannot be a by-ref type.
A reference type is a list of form
(REF inner-type)
A by-ref value represents a managed strongly-typed pointer to some .NET object.
It is used for out
and ref
(i.e. in/out) parameters, for example.
Speaking of type specifiers, REF
can only be a top-level typespec.
I.e. no inner complex types can include inner REF
types.
(QUALIFIED full-name assembly-string)
Qualified type specifiers designates a specific type from a specific assembly.
An example:
(:qualified
"System.Int32"
"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e")
(generic-type-definition-name arg1-typespec &rest other-arg-type-specifiers)
A generic typespec designates an instantiated generic type. For example:
(System.Collections.Generic.List :int)
equals to the following type designator from C#
System.Collections.Generic.List<int>
typespec ::= simple-typespec | complex-typespec
simple-typespec ::= string-designator
complex-typespec ::= array-typespec | pointer-typespec | by-ref-typespec | qualified-typespec | generic-typespec
array-typespec ::= (ARRAY typespec [rank])
pointer-typespec ::= (* typespec)
by-ref-typespec ::= (REF typespec)
qualified-typespec ::= (QUALIFIED full-name assembly-string)
generic-typespec ::= (generic-type-definition-name typespec typespec*)
A method designator is either a string designator
representing method name
or a list of form:
(method-name generic-argument1 &rest generic-arguments)
Where METHOD-NAME
is, again, a string designator, and each of the generic arguments
must be a type specifier that is used for the generic method instantiation.
Method designators are case-insensitive.
An assembly designator is either a string, which represents assembly name, or a .NET object
of type System.Reflection.Assembly
.
Assembly designators are case-insensitive.
Actually, most of the things described below are case-insensitive.
Creates an instance of the specified TYPE
.
In case of the TYPE
being a delegate type, first,
and only, argument, must be a lisp function-like
object.
(new type &rest constructor-args) ; => an object
Invokes a method designated by METHOD
on a TARGET
which can
either be a type specifier, in which case a static method is
invoked, or an instance, which would lead to instance method
invocation.
(invoke target method &rest args) ; => value
Retrieves a value of property named NAME
from a TARGET
, which
can either be a type specifier, in which case a static property
is accessed, or an instance, which would lead to instance property
access.
(property target name) ; => property value
Changes a value of property named NAME
of a TARGET
, which
can either be a type specifier, in which case a static property
is accessed, or an instance, which would lead to instance property
access.
(setf (property target name) new-value) ; => new-value
Retrieves a value of an indexer from a TARGET
, which must be an instance.
(ref target index &rest indices) ; => indexer value
Changes a value of an indexer from a TARGET
, which must be an instance.
(setf (ref target index &rest indices) new-value) ; => new-value
Retrieves a value of field named NAME
from TARGET
, which
can either be a type specifier, in which case a static field
is accessed, or an instance, which would lead to instance field
access.
(field target name) ; => field value
Changes a value of field named NAME
of a TARGET
, which
can either be a type specifier, in which case a static field
is accessed, or an instance, which would lead to instance field
access.
(setf (field target name) new-value) ; => new-value
Subscribes to an event named NAME
on a TARGET
, which
can either be a type specifier, in which case a static event
is accessed, or an instance, which would lead to an instance event
subscription.
HANDLER
can either be a .NET delegate or lisp function-like object, in
which case a delegate is allocated for it behind the scenes.
Note that in the latter case, you won't be able to unsubscribe from an event.
(event-add target name handler)
Converts LOGIOR
'ed VALUE
and VALUES
to enum object of specified ENUM-TYPE
(enum enum-type value &rest values)
Unsubscribes from an event named NAME
on a TARGET
, which
can either be a type specifier, in which case a static event
is accessed, or an instance, which would lead to an instance event
reference.
HANDLER
must be a .NET delegate object.
(event-remove target name handler)
Makes a boxed representation of an OBJECT
, optionally converting it into a TYPE
.
(box object &optional type) ; => boxed object
Attempts to unbox an OBJECT
into lisp object.
(unbox object) ; => unboxed object
Tests for .Net object equality, i.e. it is a wrapper for Object.Equals
method.
(bike-equals object1 object2) ; => T or NIL
Retrieves .Net type of an OBJECT
.
(bike-type-of object) ; => an instance of DOTNET-TYPE
Tests whether an OBJECT
(or its boxed representation) belongs to a dotnet TYPE
.
(bike-type-p object type) ; => T or NIL
Tests whether an OBJECT
is a DOTNET-OBJECT
, which is a wrapper structure for .Net objects.
(dotnet-object-p object) ; => T or NIL
Tests whether an OBJECT
is a DOTNET-TYPE
, which is a wrapper structure for .Net types.
(dotnet-type-p object) ; => T or NIL
Tests whether an OBJECT
is a DOTNET-DELEGATE
, which is a wrapper structure for .Net delegates.
(dotnet-delegate-p object) ; => T or NIL
Tests whether an OBJECT
is a DOTNET-EXCEPTION
, which is a wrapper structure for .Net exceptions.
(dotnet-exception-p object) ; => T or NIL
Binds VAR
to an IDisposable OBJECT
during the execution of BODY
forms.
OBJECT
is disposed right before BODY
forms exit.
(with-disposable (var object) &body body) ; => results of the BODY forms execution
Binds variables to IDisposable objects during the execution of BODY
forms.
Variables are bound by means of LET
form.
Objects are disposed right before BODY
forms exit.
(with-disposables (&rest specs) &body body) ; => results of the BODY forms execution
Binds variables to IDisposable objects during the execution of BODY
forms.
Variables are bound in a sequence, as in LET*
form.
Objects are disposed right before BODY
forms exit.
(with-disposables* (&rest specs) &body body) ; => results of the BODY forms execution
Accesses a .Net VECTOR
(an array of rank 1) at INDEX
.
(dnvref vector index) ; => object at the specified index
Accesses a .Net VECTOR
(an array of rank 1) at INDEX
.
(setf (dnvref vector index) new-value) ; => new-value
Accesses a .Net ARRAY
at INDICES
.
(dnaref array &rest indices) ; => object residing at indices
Accesses a .Net ARRAY
at INDICES
.
(setf (dnaref array &rest indices) new-value) ; => new-value
Collects elements of a .Net VECTOR
into a list, starting from START
index and below END
index.
(bike-vector-to-list vector &key (start 0) end) ; => a list
Converts a LIST
to a one-dimensional .Net array with the specified ELEMENT-TYPE
.
START
and END
designate LIST
bounds and the resulting vector size, correspondingly
(list-to-bike-vector list &key (start 0) end (element-type "System.Object")) ; => a .Net vector
Evaluates BODY
forms in a loop where ELT-VAR
is subsequently bound to
each element of a VECTOR
, which should evaluate to .Net array of rank 1.
Returns RESULT
form.
(do-bike-vector (elt-var vector &optional result) &body body) ; => RESULT form value
A direct analog of C# foreach
statement.
Binds VAR
to each element of an ENUMERABLE
and executes BODY
forms on each iteration.
Returns a result of an execution of RESULT
form.
(do-enumerable (var enumerable &optional result) &body body) ; => results of BODY forms execution
Resolves a .Net type designated by TYPE
specifier from an internal type cache.
ASSEMBLY
, unless NIL
, can either be an assembly object, or a string, designating an assembly.
(resolve-type type &key (errorp t) error-value assembly)
;; => a DOTNET-TYPE object or ERROR-VALUE value, depending on whether the type exists and whether ERRORP argument is NIL.
Directly asks .NET for a type metaobject by name.
(get-type name &optional (errorp t) error-value)
;; => a DOTNET-TYPE object or ERROR-VALUE value, depending on whether the type exists and whether ERROP argument is NIL.
Returns non-NIL in case of the type being a subclass of another type.
The function only accepts DOTNET-TYPE
objects, and not type designators.
(bike-subclass-p type-object1 type-object2) ; => T or NIL
Sometimes, as in the case of COM interop, or dynamic languages running on .NET, the usual invocation API, which performs member resolution, runtime compilation and aggressive caching, may not be suitable for object invocation.
So, these are the functions which perform invocation using .NET reflection API. The results and parameter processing of these functions may differ from the usual API.
Using reflection, creates an instance of the specified TYPE
.
In case of the TYPE
being a delegate type, first,
and only, argument, must be a lisp function-like
object.
(reflection-new type &rest constructor-args) ; => an instance of the TYPE
Using reflection, invokes a method designated by METHOD
on a TARGET
which can
either be a type specifier, in which case a static method is
invoked, or an instance, which would lead to instance method
invocation.
(reflection-invoke target method &rest args) ; => result
Using reflection, retrieves a value of property named NAME
from a TARGET
, which
can either be a type specifier, in which case a static property
is accessed, or an instance, which would lead to instance property
access.
(reflection-property target name) ; => property value
Using reflection, changes a value of property named NAME
of a TARGET
, which
can either be a type specifier, in which case a static property
is accessed, or an instance, which would lead to instance property
access.
(setf (reflection-property target name) new-value) ; => new-value
Using reflection, retrieves a value of an indexer from a TARGET
, which
must be an instance.
(reflection-ref target index &rest indices) ; => indexer value
Using reflection, changes a value of an indexer from a TARGET
, which
must be an instance.
(setf (reflection-ref target index &rest indices) new-value) ; => new-value
Using reflection, retrieves a value of field named NAME
from TARGET
, which
can either be a type specifier, in which case a static field
is accessed, or an instance, which would lead to instance field
access.
(reflection-field target name) ; => field value
Using reflection, changes a value of field named NAME
of a TARGET
, which
can either be a type specifier, in which case a static field
is accessed, or an instance, which would lead to instance field
access.
(setf (reflection-field target name) new-value) ; => new-value
Exceptions are marshaled on Lisp/.Net boundaries.
Lisp errors coming from callbacks are wrapped into BikeInterop.LispException
objects.
.NET exceptions coming from callbacks are wrapped into TargetInvocationException
by the interop layer.
.NET exceptions coming to Lisp are wrapped into DOTNET-EXCEPTION
instances, which are then wrapped into DOTNET-ERROR
condition objects.
These are the macros that are specifically designed to work with DOTNET-EXCEPTION
objects directly.
These macros mimic Lisp condition system and project a similar system to .Net exceptions.
Note that because of exception marshaling, stack unwinding is always performed, and it is performed up to the closest .Net/Lisp boundary.
Executes BODY
forms in a dynamic context where the given handler bindings are in
effect. Each handler must take the exception being signalled as an argument.
The bindings are searched first to last in the event of a thrown exception
(exception-bind (&rest (exception-type handler)) &body body) ; => Unless NLX occurs, the results of BODY forms execution
Executes FORM
in a context with handlers established for the exception types. A
peculiar property allows type to be :NO-EXCEPTION
. If such a clause occurs, and
form returns normally, all its values are passed to this clause as if by
MULTIPLE-VALUE-CALL
. The :NO-EXCEPTION
clause accepts more than one var
specification.
(exception-case form &rest (exception-type (&optional var) &body handler-body)) ; => Unless NLX occurs, the results of FORM execution
Constructs an exception object of the specified .Net TYPE
using constructor ARGS
and signals a DOTNET-ERROR
(using ERROR
function) which holds this exception object.
(dotnet-error type &rest args) ; => non-local exit
.NET does not actually have a first-class concept of a namespace. Type namespace is simply a prefix in its name.
Nevertheless, the library provides some related functionality for convenience.
For example, the library allows you to "use" a namespace so that you won't need to type the full name of a type that belongs to that namespace.
Note that, however, unlike with CL packages and readtables, the list of used namespaces is global, and is not tied to files and compilation units.
Adds a namespace (or a list of namespaces) to the list of used namespaces.
Each namespace must be a string designator
.
(use-namespace namespace-or-namespaces)
Removes a namespace (or a list of namespaces) from the list of used namespaces
Each namespace must be a string designator
.
(unuse-namespace namespace-or-namespaces)
Clears the list of used namespaces.
(unuse-all-namespaces)
Tests whether a NAMESPACE
is already in the list of used namespaces.
The NAMESPACE
must be a string designator
.
(namespace-used-p namespace) ; => generalized boolean
Returns a list of used namespaces.
(get-used-namespaces) ; => a list of strings
Returns a convenient string representation of a type name stripped of used namespaces.
The function only accepts DOTNET-TYPE
objects, not the type designators.
(normalized-type-name type-object) ; => a string
A type alias is simply a string designator
which refers to another type.
The library allows you to define custom type aliases, to save the typing time, for example.
The library itself also defines some type aliases which mimic C# ones:
:object
->System.Object
:string
->System.String
:char
->System.Char
:bool
->System.Boolean
:float
->System.Single
:double
->System.Double
:byte
->System.Byte
:sbyte
->System.SByte
:short
->System.Int16
:ushort
->System.UInt16
:int
->System.Int32
:uint
->System.UInt32
:long
->System.Int64
:ulong
->System.UInt64
:decimal
->System.Decimal
:type
->System.Type
:void
->System.Void
Adds an ALIAS
for the TYPE
in the type cache. TYPE
designator must
not contain actual type objects if the current lisp image is to be restored.
(use-type-alias alias type)
Removes an ALIAS
from the current type cache.
(use-type-alias alias)
Remove all type aliases from the type cache.
(unuse-all-type-aliases)
All the types the library is working with must be available through the internal type cache.
RESOLVE-TYPE
function, which analyzes a type designator into a type object, and is used everywhere throughout the library,
won't be able to work correctly unless the cache is properly populated.
The library attempts to restore the cache on image restore, therefore, the image restore must happen within at least the same assembly environment as it has been dumped(i.e. all the assemblies loaded, must be present on image restore). The only exceptions are the dynamic types from dynamic assemblies, which are discarded by the type cache.
The library currently uses default assembly load context, and so does not allow for assembly unloading(you can experiment with additional load contexts, however).
A list of additional assembly names that are loaded by default, including on lisp image restore.
Returns a list of currently loaded assemblies.
(get-loaded-assemblies) ; => a list of assembly objects
Loads an assembly designated by ASSEMBLY-STRING
which designates an assembly name.
N.B.: The function does not import the assembly into the type cache. You should do that manually.
(load-assembly assembly-string) ; => assembly
Loads an assembly by ASSEMBLY-PATH
which designates a filesystem path to the assembly file.
N.B.: The function does not import the assembly into the type cache. You should do that manually.
(load-assembly-from assembly-path) ; => assembly
Imports an assembly designated by ASSEMBLY-DESIGNATOR
into the type cache.
(import-assembly assembly-designator) ; => assembly
Loads an assembly from the filesystem by ASSEMBLY-PATH
and imports it into the type cache.
(import-assembly-from assembly-path) ; => assembly
Imports all currently loaded assemblies into the type cache.
Useful when loading big frameworks with lots of dependencies.
(import-loaded-assemblies)
Clears the type cache and restores it to current defaults.
Re-imports all the default and currently loaded assemblies.
Resets type aliases and clears the list of used namespaces.
(clear-type-cache)
The library provides you the ability to directly access the memory of several .NET object types, namely arrays and strings amongst others.
This is achieved by utilization of pinned GCHandles.
A direct analog of C# fixed
statement.
Executes BODY
forms in a dynamic environment where POINTER-VAR
is bound to the pinned data of an OBJECT
.
(with-fixed (pointer-var object) &body body) ; => results of the BODY forms execution
Executes BODY
forms in a dynamic environment where
BINDINGS
are bound to the pinned data of corresponding objects.
BINDINGS
are processed as in LET
operator.
(with-fixeds (&rest bindings) &body body) ; => results of the BODY forms execution
Executes BODY
forms in a dynamic environment where BINDINGS
are bound to the pinned data of corresponding objects.
BINDINGS
are processed as in LET*
operator.
(with-fixeds* (&rest bindings) &body body) ; => results of the BODY forms execution
The library provides an extensible printer facility, based on the Lisp pretty printer.
It allows you to redefine text representation of .NET objects.
It does take .Net type hierarchy into account, and also allows you to define printer methods on interfaces, which are used in case none is defined for the specific class hierarchy.
Non-NIL value forces printing of .Net object contents.
Non-NIL value forces printing of IEnumerable contents.
Value of NIL forces omitting printing namespaces while printing type names.
Value of :NORMALIZE omits printing namespaces whenever these are currently used by means of USE-NAMESPACE
function.
Other values force the printing of type namespaces.
Non-NIL value forces printing of generic type parameters.
Non-NIL value forces printing of assembly qualified type names whenever possible.
Non-NIL value forces printing of pointer type name suffix (i.e. *
).
Non-NIL value forces printing of ByRef type name suffix (i.e. &
).
Outputs name of a TYPE
to the specified STREAM
, defaulting to *STANDARD-OUTPUT*
:NAMESPACES
- whether to print type namespaces. NIL
- do not print namespaces. :NORMALIZE
- omit used namespaces. Other value - print all namespaces.
:PARAMETERS
- whether to print generic type parameters
:QUALIFIED
- whether to print assembly-qualified type names
:POINTER
- whether to print *
suffix for pointer types
:REF
- whether to print &
suffix for ByRef types
:STREAM
- stream to output to
(write-type-name type &key (namespaces *print-dotnet-type-namespaces*)
(parameters *print-dotnet-type-parameters*)
(qualified *print-dotnet-type-qualified*)
(pointer *print-dotnet-type-pointer*)
(ref *print-dotnet-type-ref*)
(stream *standard-output*))
;; => TYPE
PRINT-UNREADABLE-OBJECT
analog for .Net objects
Output OBJECT
to STREAM
with #<
prefix, >
suffix, optionally
with object-type prefix and object-identity suffix,
and post-type suffix and pre-identity prefix and executing the
code in BODY
to provide possible further output.
(pprint-dotnet-object (object stream &key type
identity
(prefix (when type " "))
(suffix (when identity " ")))
&body body)
;; => results of BODY forms exectuion
Installs a print function designated by PRINTER
for a .Net TYPE
.
PRINTER
function must accept two arguments - an OBJECT
to print, and a STREAM
to print to.
(set-dotnet-object-printer type printer)
A macro version of SET-DOTNET-OBJECT-PRINTER
.
Installs a printer function for a .Net TYPE
.
The function accepts two arguments - an OBJECT
to print, and a STREAM
to print to.
(define-dotnet-object-printer type (object stream) &body body)
The library allows for convenient querying for .NET types and type members.
Evaluates BODY
forms in a dynamic environment where TYPE-VAR
is subsequently bound
to each of the types found during the search based on the following filter arguments:
:SEARCH
- a string-designator or NIL
.
Designates a substring to search for in the type name(excluding namespace).
:ASSEMBLY
- Designates a specific assembly to search in. Can be a name,
a System.Reflection.Assembly
object, or NIL
, in which case
all loaded assemblies are searched.
:NAMESPACE
- Designates a namespace to search for types. In case of it being null,
all loaded namespaces are processed.
:EXPORTED-ONLY
- Defaults to T
. In case of it being non-null, only exported(i.e. public)
types of assemblies are processed.
Otherwise, all of the defined types are processed.
TYPE-VAR
may be a symbol or a list of form (TYPE-VAR
RESULT-FORM
) where RESULT-FORM
designates a form to be evaluated and returned at the end of the iteration.
(do-types (type-var &key search assembly namespace (exported-only t)) &body body)
;; => the result of the evaluation of the RESULT-FORM
Briefly describes all types found according to the search given the following filter arguments:
STRING-DESIGNATOR
- A substring to search for in the type name(excluding namespace).
Can be NIL
, in which case all type names are processed.
:ASSEMBLY
- Designates a specific assembly to search in. Can be a name,
a System.Reflection.Assembly
object, or NIL
, in which case
all loaded assemblies are searched.
:NAMESPACE
- Designates a namespace to search for types. In case of it being null,
all loaded namespaces are processed.
:EXPORTED-ONLY
- Defaults to T
. In case of it being non-null, only exported(i.e. public)
types of assemblies are processed.
Otherwise, all of the defined types are processed.
(type-apropos string-designator &key assembly namespace (exported-only t))
Like TYPE-APROPOS
, but returns a list of types instead of describing them.
(type-apropos-list string-designator &key assembly namespace (exported-only t))
;; => a list of types
Briefly describes all namespaces found according to the search given the following filter arguments:
STRING-DESIGNATOR
- A substring to search for in the namespace name.
Can be NIL
, in which case all names are processed.
:ASSEMBLY
- Designates a specific assembly to search in. Can be a name,
a System.Reflection.Assembly
object, or NIL
, in which case
all loaded assemblies are searched.
:EXPORTED-ONLY
- Defaults to T
. In case of it being non-null, only exported(i.e. public)
types of assemblies are processed.
Otherwise, all of the defined types are processed.
(namespace-apropos string-designator &key assembly (exported-only t))
Briefly describes all namespaces found according to the search given the following filter arguments:
STRING-DESIGNATOR
- A substring to search for in the namespace name.
Can be NIL
, in which case all names are processed.
:ASSEMBLY
- Designates a specific assembly to search in. Can be a name,
a System.Reflection.Assembly
object, or NIL
, in which case
all loaded assemblies are searched.
:EXPORTED-ONLY
- Defaults to T
. In case of it being non-null, only exported(i.e. public)
types of assemblies are processed.
Otherwise, all of the defined types are processed.
(namespace-apropos-list string-designator &key assembly (exported-only t))
;; => a list of strings
Evaluates BODY
forms in a dynamic environment where VAR
is subsequently bound
to each of the members of the TYPE
found during the search based on the following
filter arguments:
:SEARCH
- a string-designator or NIL
.
Designates a substring to search for in the member name.
:INSTANCE
- whether to search for instance members.
:STATIC
- whether to search for static members.
:PUBLIC
- whether to search for public members.
:NON-PUBLIC
- whether to search for non-public members(i.e. private and protected).
:DECLARED-ONLY
- whether to only include members declared in the type directly and
omit inherited ones.
:CONSTRUCTORS
- whether to search for constructors.
:EVENTS
- whether to search for events.
:FIELDS
- whether to search for fields.
:METHODS
- whether to search for methods.
:PROPERTIES
- whether to search for properties.
:CUSTOM
- whether to search for custom type members.
:NESTED-TYPES
- whether to search for nested types.
VAR
can either be a symbol or a list of form (VAR
RESULT-FORM
) where RESULT-FORM
designates a form to be evaluated and returned at the end of the iteration.
(do-members (var type &key search
(instance t)
(static t)
(public t)
non-public
declared-only
(constructors t)
(events t)
(fields t)
(methods t)
(properties t)
(custom t)
(nested-types t))
&body body)
;; => the result of the execution of the RESULT-FORM
Briefly describes members of the TYPE
found during the search based on the following
filter arguments:
STRING-DESIGNATOR
- A substring to search for in the member name.
Can be NIL
, in which case all names are processed.
:INSTANCE
- whether to search for instance members.
:STATIC
- whether to search for static members.
:PUBLIC
- whether to search for public members.
:NON-PUBLIC
- whether to search for non-public members(i.e. private and protected).
:DECLARED-ONLY
- whether to only include members declared in the type directly and
omit inherited ones.
:CONSTRUCTORS
- whether to search for constructors.
:EVENTS
- whether to search for events.
:FIELDS
- whether to search for fields.
:METHODS
- whether to search for methods.
:PROPERTIES
- whether to search for properties.
:CUSTOM
- whether to search for custom type members.
:NESTED-TYPES
- whether to search for nested types.
(member-apropos type string-designator &key (instance t)
(static t)
(public t)
non-public
declared-only
(constructors t)
(events t)
(fields t)
(methods t)
(properties t)
(custom t)
(nested-types t))
Like MEMBER-APROPOS
but returns a list of members instead of describing them.
(member-apropos-list type string-designator &key (instance t)
(static t)
(public t)
non-public
declared-only
(constructors t)
(events t)
(fields t)
(methods t)
(properties t)
(custom t)
(nested-types t))
;; => a list of member info objects
Like MEMBER-APROPOS
but only describes constructors and does not allow for name search.
(constructor-apropos type &key (instance t) (static t) (public t) non-public declared-only)
Like CONSTRUCTOR-APROPOS
but returns a list of constructors instead of describing them.
(constructor-apropos-list type &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of constructor info objects
Like MEMBER-APROPOS
but only describes events.
(event-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like EVENT-APROPOS
but returns a list of events instead of describing them.
(event-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of event info objects
Like MEMBER-APROPOS
but only describes fields.
(field-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like FIELD-APROPOS
but returns a list of fields instead of describing them.
(field-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of field info objects
Like MEMBER-APROPOS
but only describes methods.
(method-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like METHOD-APROPOS
but returns a list of methods instead of describing them.
(method-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of method info objects
Like MEMBER-APROPOS
but only describes properties.
(property-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like PROPERTY-APROPOS
but returns a list of properties instead of describing them.
(property-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of property info objects
Like MEMBER-APROPOS
but only describes custom type members.
(custom-member-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like CUSTOM-MEMBER-APROPOS
but returns a list of custom type members instead of describing them.
(custom-member-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of custom member objects
Like MEMBER-APROPOS
but only describes nested types.
(nested-type-apropos type string-designator &key (instance t) (static t) (public t) non-public declared-only)
Like NESTED-TYPE-APROPOS
but returns a list of nested types instead of describing them.
(nested-type-apropos-list type string-designator &key (instance t) (static t) (public t) non-public declared-only)
;; => a list of nested type metaobjects
The library allows for the definition of special CLOS classes, which generate proxy types on the .NET side.
These proxy types delegate their member invocation to the Lisp code.
This is achieved by utilization of System.Reflection.Emit
infrastructure at run-time.
Currently, only instance member generation is supported. Attributes on classes, members and parameters are not supported yet.
The callable class metamodel can be extended by the user. Take a look at DOTNET-SLOT-DEFINITION
class and its subclasses.
TODO: The metamodel description.
Defines a CLOS class, which members could be invoked by .NET code.
(define-dotnet-callable-class class-name-and-options (&rest superclasses) &body doc-and-slots)
Full syntax
class-name-and-options ::= name | (name class-option*)
class-option ::= (:BASE-TYPE typespec) | (:INTERFACES typespec*) | CLOS-class-option
superclasses ::= name*
doc-and-slots ::= [ docstring ] slot*
slot ::= event-slot | property-slot | method-slot | indexer-slot | CLOS-slot
event-slot ::= (:EVENT event-name-and-options typespec CLOS-slot-option*)
event-name-and-options ::= name | (name-pair [ raise-method-option ])
raise-method-option ::= :RAISE-METHOD-DOTNET-NAME dotnet-name
property-slot ::= auto-property-slot | long-form-property-slot
auto-property-slot ::= (:PROPERTY auto-property-name-and-options typespec CLOS-slot-option*)
auto-property-name-and-options ::= name | (name-pair { getter-option | setter-option }*)
name-pair ::= name | dotnet-name | { name dotnet-name } | { dotnet-name name }
getter-option ::= :GETTER { T | NIL | function-name }
setter-option ::= :SETTER { T | NIL | function-name }
long-form-property-slot ::= (:PROPERTY long-form-property-name-and-options typespec property-accessors)
long-form-property-name-and-options ::= name | (name-pair [ defmethodp-option ] CLOS-slot-option*)
defmethodp-option ::= :DEFMETHODP generalized-boolean
property-accessors ::= { getter-form | setter-form } [ { getter-form | setter-form } ]
getter-form ::= existing-getter-form | getter-definition-form
existing-getter-form ::= ({ :GET | :GETTER } function-name)
getter-definition-form ::= ({ :GET | :GETTER } function-name accessor-body)
setter-form ::= existing-setter-form | setter-definition-form
existing-setter-form ::= ({ :SET | :SETTER } function-name)
setter-definition-form ::= ({ :SET | :SETTER } function-name accessor-body)
method-slot ::= ({ :METHOD | :DEFMETHOD } method-name-options-and-generic-parameters return-type parameter-list function-body)
return-type ::= method-parameter-typespec
method-name-options-and-generic-parameters ::= name | (method-name-and-options generic-parameter*)
method-name-and-options ::= name | (name-pair { defmethodp-option | function-name-option }* CLOS-slot-option*)
function-name-option ::= :FUNCTION-NAME name
generic-parameter ::= name | (name generic-parameter-qualifier*)
generic-parameter-qualifier ::= :IN | :OUT | :NEW | :STRUCT | :CLASS | (:BASE-TYPE typespec) | (:INTERFACES typespec*)
parameter-list ::= (parameter* [ &REST params-array-parameter ])
parameter ::= (name typespec parameter-option*)
parameter-option ::= { :DIRECTION { :IN | :OUT | :IO } } | { :DOTNET-NAME dotnet-name } | { :REF generalized-boolean }
params-array-parameter ::= (name (array typespec) [ :DOTNET-NAME dotnet-name ])
indexer-slot ::= (:indexer [ indexer-name-and-options ] typespec indexer-parameter-list property-accessors)
indexer-name-and-options ::= name | (name-pair [ defmethodp-option ] CLOS-slot-option*)
indexer-parameter-list ::= (parameter parameter* [ &REST params-array-parameter ])
-
name - a symbol
-
dotnet-name - a string
-
function-name - either a symbol or a list of form
(SETF name)
-
function-body - as in
DEFUN
orDEFMETHOD
-
accessor-body - as in
DEFUN
orDEFMETHOD
, but must contain at least one form -
generalized-boolean - any object.
NIL
designates false value. -
CLOS-class-option - any
DEFCLASS
option, such as:DEFAULT-INITARGS
, except for:DOCUMENTATION
-
CLOS-slot-option - any CLOS slot option, such as
:INITARG
or:ACCESSOR
-
CLOS-slot - a usual CLOS slot, not prefixed by a keyword
-
typespec - a valid bike typespec
-
method-parameter-typespec - a bike typespec, with the exception that generic parameter names are also allowed
-
docstring - a string
Base class for objects which contain proxies to dotnet objects.
Instances of this class can mimic true .NET objects and could be passed to .NET methods.
- DOTNET-PROXY-OBJECT-VALUE - returns a .NET proxy object(of type
DOTNET-OBJECT
).
(deftype dotnet-object* () '(or dotnet-object dotnet-proxy-object))
Base class for objects which could be invoked by .NET code.
- DOTNET-CALLABLE-OBJECT-PROXY - returns a .NET proxy object(of type
DOTNET-OBJECT
).
Base metaclass for classes that rely on a proxy .NET type.
- DOTNET-PROXY-CLASS-OBJECT-TYPE - returns a .NET proxy type(of type
DOTNET-TYPE
).
Base metaclass for dotnet callable classes.
- DOTNET-CALLABLE-CLASS-PROXY-TYPE - returns a .NET proxy type(of type
DOTNET-TYPE
).
Returns Non-NIL when the object's proxy is initialized and ready to be passed to .NET code.
(dotnet-callable-object-proxy-initialized-p object) ; => T or NIL
Return non-NIL in case of an OBJECT
being a callable proxy of a DOTNET-CALLABLE-OBJECT
instance.
(dotnet-callable-proxy-p object) ; => T or NIL
Returns non-NIL in case of a DOTNET-TYPE
is a proxy type for a DOTNET-CALLABLE-CLASS
.
(dotnet-callable-proxy-type-p dotnet-type) ; => T or NIL
Retrieves an object associated with the PROXY
.
(dotnet-callable-proxy-object proxy) ; => an object
Returns an underlying object of a dotnet callable proxy in case an OBJECT
is a proxy.
(unwrap-dotnet-callable-proxy object) ; => an object or NIL
The library exposes a named readtable BIKE-SYNTAX
, which allows for the following reader extensions:
[:typespec] ; == (resolve-type 'typespec)
[:typespec MethodDesignator . args] ; == (invoke 'typespec 'MethodDesignator . args)
[object MethodDesignator . args] ; == (invoke object 'MethodDesignator . args)
[:typespec %PropertyName] ; == (property 'typespec 'PropertyName)
[object %PropertyName] ; == (property object 'PropertyName)
[:typespec $FieldName] ; == (field 'typespec 'FieldName)
[object $FieldName] ; == (field object 'FieldName)
#[object index . indices] ; == (ref object index . indices)
#e(EnumTypeName Value1 Value2 . Values) ; == (enum 'EnumTypeName 'Value1 'Value2 . Values) ; no value is evaluated
[:typespec +EventName handler] ; == (event-add 'typespec 'EventName handler)
[object +EventName handler] ; == (event-add object 'EventName handler)
[:typespec -EventName handler] ; == (event-remove 'typespec 'EventName handler)
[object -EventName handler] ; == (event-remove object 'EventName handler)
Represents a .NET exception.
- DOTNET-ERROR-OBJECT
Represents a generic condition.
Represents an erroneous condition.
Represents a subnormal but not erroneous condition.
Represents a condition in which the library encountered an invalid assembly designator.
- INVALID-ASSEMBLY-DESIGNATOR-DATUM
Represents a condition when a type resolution has failed for one reason or another.
- TYPE-RESOLUTION-ERROR-DATUM
Represents a condition when an object supplied does not represent a proper type specifier.
- INVALID-TYPE-DESIGNATOR-DATUM
Represents a condition when an interned typespec AST is ill-formed.
- INVALID-TYPE-AST-DATUM
Represents a condition when a type name supplied is invalid for one reason or another.
- INVALID-TYPE-NAME-DATUM
Represents a condition when a :REF
typespec occurs in a place where it is not allowed. (like e.g. in a property type)
- INVALID-REF-TYPE-DATUM
Represents a condition when the typespec parser encountered a :REF
typespec despite it not being a "top-level" one.
- INNER-REF-TYPE-ERROR-DATUM
Represents a condition when the typespec parser encountered an unexpected :QUALIFIED
typespec.
- INNER-QUALIFIED-TYPE-ERROR-DATUM
Represents an error that occurred during the type name parsing.
- TYPE-NAME-PARSER-ERROR-CHARACTER
- TYPE-NAME-PARSER-ERROR-POSITION
- TYPE-NAME-PARSER-ERROR-STRING
Represents a condition when the type name parser encountered an unexpected token.
- TYPE-NAME-UNEXPECTED-TOKEN-ERROR-VALUE
- TYPE-NAME-UNEXPECTED-TOKEN-ERROR-TOKEN
Represents a condition when the type name parser encountered a mismatch between generic parameter count and the number embedded into the generic definition name.
Represents a condition when the type name parser encountered an unexpected end of input.
Represents a condition when the library is unable to resolve an Enum type.
- ENUM-RESOLUTION-ERROR-DATUM
Represents a condition when the library is unable to resolve a specific member of a class.
- MEMBER-RESOLUTION-ERROR-TYPE
- MEMBER-RESOLUTION-ERROR-STATIC-P
- MEMBER-RESOLUTION-ERROR-MEMBER
- MEMBER-RESOLUTION-ERROR-ARGS
- MEMBER-RESOLUTION-ERROR-ACCESSOR-KIND
- MEMBER-RESOLUTION-ERROR-MEMBER-KIND
Represents a condition when the library is unable to resolve a specific field of a class.
- FIELD-RESOLUTION-ERROR-FIELD
Represents a condition when the library is unable to resolve a specific property of a class.
- PROPERTY-RESOLUTION-ERROR-PROPERTY
Represents a condition when the library is unable to resolve a specific event of a class.
- EVENT-RESOLUTION-ERROR-EVENT
Represents a condition when the library is unable to resolve an indexer of a class.
Represents a condition when the library is unable to resolve a specific method of a class.
- METHOD-RESOLUTION-ERROR-METHOD
- METHOD-RESOLUTION-ERROR-ARGS
Represents a condition when the library is unable to resolve a type constructor.
Represents a condition when the library is unable to resolve a proper accessor for an event or a property.
- ACCESSOR-RESOLUTION-ERROR-ACCESSOR-KIND
- ACCESSOR-RESOLUTION-ERROR-MEMBER-KIND
- ACCESSOR-RESOLUTION-ERROR-MEMBER
Represents a reader error which happened during custom syntax parsing.
- BIKE-READER-ERROR-MESSAGE
Represents a condition when there exists a duplicate dotnet member name in a class.
- DUPLICATE-DOTNET-NAME-VALUE
- DUPLICATE-DOTNET-NAME-CLASS
Represents a condition when there's a duplicate indexer slot defined on a class.
- DUPLICATE-INDEXER-CLASS
Represents a condition when a proxy trampoline realizes that there is no slot of the specified dotnet name defined on a class.
- DOTNET-SLOT-MISSING-CLASS
- DOTNET-SLOT-MISSING-NAME
Represents a condition when an :EVENT
slot handler type is not a delegate type.
- DELEGATE-TYPE-EXPECTED-DATUM
Represents a condition when there's a non-interface type in an (:INTERFACES)
option of a DOTNET-CALLABLE-CLASS
.
- INTERFACE-TYPE-EXPECTED-DATUM
Represents a condition when there's an attempt to inherit from a sealed type.
- SEALED-INHERITANCE-TYPE
Represents a condition when an :IO
or :OUT
parameter is defined to not be passed by reference.
- PARAMETER-DIRECTION-MISMATCH-DATUM
Represents a condition when a &rest
method or indexer parameter is ill-formed.
- INVALID-PARAMS-ARRAY-DEFINITION-DATUM
Represents a condition when an indexer or method argument list contains duplicate names.
- DUPLICATE-PARAMETER-NAME-DATUM
- DUPLICATE-PARAMETER-NAME-VALUE
Represents a condition when a DOTNET-CALLABLE-CLASS
initializer encountered an invalid constraint on a generic method argument of a generic method.
- INVALID-GENERIC-CONSTRAINT-MESSAGE
- INVALID-GENERIC-CONSTRAINT-LIST
Represents a condition when there's an attempt to invoke (SETF SLOT-VALUE)
on a dotnet slot.
- DOTNET-SLOT-WRITE-ATTEMPT-OBJECT
- DOTNET-SLOT-WRITE-ATTEMPT-SLOT-NAME
- DOTNET-SLOT-WRITE-ATTEMPT-VALUE
Represents a condition when there's an attempt to invoke (SETF SLOT-VALUE)
on a .NET method slot.
- METHOD-SLOT-WRITE-ATTEMPT-OBJECT
- METHOD-SLOT-WRITE-ATTEMPT-SLOT-NAME
- METHOD-SLOT-WRITE-ATTEMPT-VALUE
Represents a condition when there's an attempt to invoke (SETF SLOT-VALUE)
on a .NET indexer slot.
- INDEXER-SLOT-WRITE-ATTEMPT-OBJECT
- INDEXER-SLOT-WRITE-ATTEMPT-SLOT-NAME
- INDEXER-SLOT-WRITE-ATTEMPT-VALUE
Represents a condition when there's an attempt to invoke SLOT-MAKUNBOUND
on a .NET slot.
- DOTNET-SLOT-MAKUNBOUND-ATTEMPT-OBJECT
- DOTNET-SLOT-MAKUNBOUND-ATTEMPT-SLOT-NAME
Represents a condition when there's an attempt to invoke SLOT-MAKUNBOUND
on a .NET method slot.
- METHOD-SLOT-MAKUNBOUND-ATTEMPT-OBJECT
- METHOD-SLOT-MAKUNBOUND-ATTEMPT-SLOT-NAME
Represents a condition when there's an attempt to invoke SLOT-MAKUNBOUND
on a .NET-only property slot.
- PROPERTY-SLOT-MAKUNBOUND-ATTEMPT-OBJECT
- PROPERTY-SLOT-MAKUNBOUND-ATTEMPT-SLOT-NAME
Represents a condition when there's an attempt to invoke SLOT-MAKUNBOUND
on a .NET indexer slot.
- INDEXER-SLOT-MAKUNBOUND-ATTEMPT-OBJECT
- INDEXER-SLOT-MAKUNBOUND-ATTEMPT-SLOT-NAME
Represents a condition in which the library detected that a proxy outlived its DOTNET-CALLABLE-OBJECT
.
- DOTNET-CALLABLE-OBJECT-ORPHAN-PROXY-VALUE
- DOTNET-CALLABLE-OBJECT-ORPHAN-PROXY-OPERATION
- DOTNET-CALLABLE-OBJECT-ORPHAN-PROXY-MEMBER-NAME
- DOTNET-CALLABLE-OBJECT-ORPHAN-PROXY-ARGUMENTS
The library relies heavily on runtime compilation and code generation, both on the Lisp side and the .NET side.
This means it won't be usable in a restricted environment like iOS.
This also means that ECL and similar implementations would constantly invoke their C compiler program, so keep that in mind.
.NET runtime is not as dynamic as Common Lisp implementations.
.NET runtime, once loaded, can not be unloaded from the process without proper deinitialization. Moreover, it can not be loaded once again, after it has been shut down.
To solve both problems, the library simply does not expose any API for shutting the .NET runtime down.
So, if something goes south, your best bet is a Lisp image restart.