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
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:
IUserDataDescriptorto describe your own type(s) - this is the hardest way
IUserDataTypeinterface. This is easier, but means you cannot handle static members without an object instance.
StandardUserDataDescriptorand 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 implements
StandardUserDataMethodDescriptor- this is a descriptor for a single method/function
StandardUserDataOverloadedMethodDescriptor- this is a descriptor for overloaded and/or extended methods
StandardUserDataPropertyDescriptor- this is a descriptor for a single property
StandardUserDataFieldDescriptor- this is a descriptor for a single field
A 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.
MyClassobject as a global in the script
MyClassmethod from a script. All the mapping rules for callbacks apply
Let’s try a little more complex example.
The big differences here are:
[MoonSharpUserData]attribute. We don’t need it anymore.
RegisterAssembly, we call
RegisterTypeto register a specific type.
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
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.
__ipairs can only be implemented this way (short of using a custom descriptor).
For example these will implement the concat (
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 (
~=) are automatically resolved using
Comparison operators (
>=, etc.) are automatically resolved using
IComparable.CompareTo, if the object implements
The length (
#) operator is dispatched to the
Count properties if the object implements those properties.
__iterator metamethod is automatically dispatched to
GetEnumerator, if the class implements
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<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 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
|Preoptimized||This is a hint, and MoonSharp is free to "downgrade" this to
|BackgroundOptimized||This is a hint, and MoonSharp is free to "downgrade" this to
|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
|Default||Use the default access mode|
Note that many modes - specifically
BackgroundOptimized- are just “hints” and MoonSharp is free to downgrade them to
Reflection. 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
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.