Note : Some features listed in this page reflect the current status of the master branch (so, they might be features missing from the latest release).
A handy feature of MoonSharp is the ability to share a .NET object with a script.
A type by default will share all its public methods, public properties, public events and public fields with Lua scripts. The MoonSharpVisible attribute can be used to override this default visibility.
It is recommended to use dedicated objects as an interface between CLR code and script code (as opposed to exposing the application internal model to the script). A number of design patterns (Adapter, Facade, Proxy, etc.) may help in designing such an interface layer. This is particularly important to:
For these reasons MoonSharp by default requires an explicit registration of the types which will be available to scripts.
If you are in a scenario where scripts can be trusted, you can globally enable autoregistration with UserData.RegistrationPolicy = InteropRegistrationPolicy.Automatic;
. It’s dangerous, you have been warned.
So, let’s see what we have on the menu:
A lot, so let’s start.
First some little theory of how interop is implemented. Every CLR type is wrapped into a “type descriptor” which has the role of describing the CLR type to scripts. Registering a Type
for
interop means associating the Type
with a descriptor (which MoonSharp can create itself) which will be used to dispatch methods, properties, etc.
From the next section on, we will refer to the “automatic” descriptors MoonSharp offers, but you can implement your own descriptors for speed, added funcionality, security, etc.
If you want to implement your own descriptors (which is not easy and should not be done unless you need to) you can follow these paths:
IUserDataDescriptor
to describe your own type(s) - this is the hardest wayIUserDataType
interface. This is easier, but means you cannot handle static members without an object instance.StandardUserDataDescriptor
and change the aspects you need while keeping the rest of the behaviour.In order to help the creation of descriptors, the following classes are available:
StandardUserDataDescriptor
- this is the type descriptor MoonSharp implementsStandardUserDataMethodDescriptor
- this is a descriptor for a single method/functionStandardUserDataOverloadedMethodDescriptor
- this is a descriptor for overloaded and/or extended methodsStandardUserDataPropertyDescriptor
- this is a descriptor for a single propertyStandardUserDataFieldDescriptor
- this is a descriptor for a single fieldA little note about interop with value types as userdata.
Just as if calling a function passing a value type as a parameter, the script would operate on a copy of the userdata, so, for example, changing a field in the userdata would not reflect on the original value. Again, this is not any different from standard behavior of value types, but it’s enough to catch people by surprise.
Additionally, value types do not support the whole spectrum of optimizations as reference types do, so some operations might be slower on value types than reference types.
Ok, let’s go with the first example.
Here we:
[MoonSharpUserData]
attributeMyClass
object as a global in the scriptMyClass
method from a script. All the mapping rules for callbacks applyLet’s try a little more complex example.
The big differences here are:
[MoonSharpUserData]
attribute. We don’t need it anymore.RegisterAssembly
, we call RegisterType
to register a specific type.DynValue
explicitely.Also, note how the method was called CalcHypotenuse
in C# code, but is called as calcHypotenuse
by Lua scripts.
As long as the other versions do not exist, MoonSharp automatically adjusts the case in some limited ways to match members, for better consistency between different languages syntax conventions.
For example, a member called SomeMethodWithLongName
can be accessed from a lua script also as someMethodWithLongName
or some_method_with_long_name
.
Let’s say our class has the calcHypotenuse
method static.
We can call it in two ways.
First way - Static methods can be called from an instance transparently - no need to do anything, everything is automatic
Alternative way - A placeholder userdata can be created, by directly passing the type (or using UserData.CreateStatic
method) :
A good question is, should (given the code in the above examples) this syntax be used
or this ?
99.999% of the time, it makes no difference. MoonSharp knows that a call is being done on a userdata and behaves accordingly.
There are corner cases where it might make a difference - for example if a property returns a delegate and you are going to call that delegate immediately, with the original object as an instance. It’s a remote scenario, and you have to handle it manually when that happens.
Overloaded methods are supported. The dispatch of overloaded method is somewhat a dark magic and is not as deterministic as C# overload dispatch is. This is due to the fact that some ambiguities exist. For example, an object can declare these two methods:
How can MoonSharp know which method to dispatch to, given that all numbers in Lua are double ?
To solve this issue, MoonSharp calculates an heuristic factor for all overloads given the input types and chooses the best overload. If you think MoonSharp is resolving an overload in a wrong way, please report to the forums or discord, for the heuristic to be calibrated.
MoonSharp tries as much to be stable with the heuristic weights, and in case of a draw of scores between methods, it always deterministically choses the same one (to provide a consistent experience among builds and platforms).
This said, it’s entirely possible that MoonSharp picks an overload which is different than the one you think of. It’s extremely important then that overloads perform equivalent jobs so that the impact of calling the wrong overload is minimized. This should be a best practice anyway, but it’s worth reinforcing the concept here.
ByRef
method parameters are correctly marshalled by MoonSharp, as multiple return values.
This support is not without side effects, as methods with ByRef
parameters cannot be optimized.
Let’s say we have this C# method (exposed in a myobj
userdata for the sake of the argument)
We can call (and get the results) the method from Lua code in this way:
While supported, ByRef params causes the method to always be invoked using reflection, thus potentially slowing down performance on non-AOT platforms (AOT platforms are already slow.. send your complaints to Apple, not me).
C# allows indexer methods to be created. For example:
As an extension to the Lua language, MoonSharp allows an expression list inside brackets to index userdata.
For example these are valid lines of code, given that o
is an instance of the above class:
Note that using multiple indices on anything which is not a userdata will raise an error. This includes
scenarios going through metamethods, but if the __index
field of the metatable is set to a userdata (also, recursively),
multi-indexing is supported.
In short, this works:
and this does not:
Overloaded operators are supported.
Following is the description on how the standard descriptor dispatches operators, but you can see working examples in this unit test code.
First, if one or more static methods decorated with MoonSharpUserDataMetamethod are implemented, these are used to dispatch the corresponding metamethod. Note that these methods exist, they will take over any other of the following criteria.
__pow
, __concat
, __call
, __pairs
and __ipairs
can only be implemented this way (short of using a custom descriptor).
For example these will implement the concat (..
) operator:
Arithmetic operators are automatically handled by operator overloads if found.
it will be possible to use the operator +
between numbers and this object in Lua scripts.
The addition, subtraction, multiplication, division, modulus, and unary negation operators are supported this way.
Equality operators (==
and ~=
) are automatically resolved using System.Object.Equals
.
Comparison operators (<
, >=
, etc.) are automatically resolved using IComparable.CompareTo
, if the object implements IComparable
.
The length (#
) operator is dispatched to the Length
or Count
properties if the object implements those properties.
Finally the __iterator
metamethod is automatically dispatched to GetEnumerator
, if the class implements System.Collections.IEnumerable
.
Extension methods are supported.
Extension methods must be registered with UserData.RegisterExtensionType
or through a RegisterAssembly(<assembly>, true)
.
The first will register a single type containing extension methods, the second registers all extension types contained in the specified assembly.
Extension methods are resolved along with other overloads on the methods.
Events are also supported, but in a rather minimalistic way. Only events matching these constraints are supported:
These constraints are present to avoid building code at runtime as much as possible.
While they might seem limiting, for the most part they actually reflect some best practices in the design of events; they are more than enough
to support event handlers of EventHandler
and EventHandler<T>
types, which are by far the most common ones (provided at least EventArgs
is registered as a user data).
Here is a simple example using an event:
Note how the event is raised by Lua code this time, but it might be raised by C# as well, without any issue.
Adding and removing event handlers are slow operations, being performed with reflection under a thread lock. On the other side, there aren’t big performance penalties in handling events themselves.
If you typed all the examples so far in an IDE you might have noticed that most methods have an optional parameter of InteropAccessMode
type.
An InteropAccessMode
defines how the standard descriptors will handle callbacks to CLR things. The following values are available:
There is a UserData.DefaultAccessMode
static property to specify which value is to be considered the default (currently, it’s LazyOptimized
, unless changed).
Reflection | Optimization is not performed and reflection is used everytime to access members. This is the slowest approach but saves a lot of memory if members are seldomly used. |
---|---|
LazyOptimized | This is a hint, and MoonSharp is free to "downgrade" this to Reflection . Optimization is done on the fly the first time a member is accessed. This saves memory for all members that are never accessed, at the cost of an increased script execution time. |
Preoptimized | This is a hint, and MoonSharp is free to "downgrade" this to Reflection . Optimization is done in a background thread which starts at registration time. If a member is accessed before optimization is completed, reflection is used. |
BackgroundOptimized | This is a hint, and MoonSharp is free to "downgrade" this to Reflection . Optimization is done at registration time. |
HideMembers | Members are simply not accessible at all. Can be useful if you need a userdata type whose members are hidden from scripts but can still be passed around to other functions. See also AnonWrapper and AnonWrapper<T> . |
Default | Use the default access mode |
Note that many modes - specifically
LazyOptimized
,Preoptimized
andBackgroundOptimized
- are just “hints” and MoonSharp is free to downgrade them toReflection
. This happens, for example, in the case of platforms where code is compiled ahead of time, like the iPhone and the iPad.
It’s possible to use the MoonSharpHidden
and/or MoonSharpVisible
attribute to override the default visibility of members (MoonSharpHidden
is a shortcut for MoonSharpVisible(false)
). Here are some examples with comments - nothing hard:
Sometimes it’s needed to remove members from a registered type to hide them from scripts. There are several ways of doing this. One is to remove them manually after registration of the type:
Otherwise, simply add this attribute to the type declaration:
This is pretty important as you might wish to hide, for example, inherited members you don’t override.