Interop .NET
Le namespace YScript.Dotnet fait le pont entre le monde C#/.NET et les valeurs manipulées par un script :
exposer un objet/type .NET existant à un script (sans réécrire de bibliothèque de fonctions à la
main), et à l'inverse laisser un script explorer le monde .NET par réflexion via la bibliothèque
optionnelle dotnet.
Ce document couvre le point de vue de l'hôte (comment exposer du C#). Le comportement scriptable de
dotnet.* une fois ouverte est hors périmètre ici (bibliothèque standard, pas API du moteur) — seul
le principe général est rappelé en fin de page.
Pousser un objet .NET existant
PushObject pousse n'importe quel objet C# sur la pile ; les types primitifs (bool, entiers,
flottants, string, enum) sont convertis en valeur script native, tout le reste est poussé comme
userdata avec une métatable qui pilote son comportement vu du script (indexation, appel,
opérateurs...) :
engine.PushObject(myOrder); // pousse un userdata pour l'instance
engine.SetGlobal("order");
print(order.Total) -- accès à une propriété .NET via __index
order:MarkAsPaid() -- appel d'une méthode .NET via __index puis __call
Par défaut (sans métatable enregistrée pour le type), l'accès aux membres passe par réflexion pure
sur le type de l'objet (DynamicMetatable, décrite plus bas). Pour un type utilisé fréquemment ou
qui a besoin d'un comportement sur mesure, enregistrez-le explicitement une fois au démarrage plutôt
que de laisser la résolution par réflexion se faire à chaque nouvel accès :
engine.RegisterType<Order>();
Métatables : MetaTableAttribute et DynamicMetatable
Une métatable .NET est une classe ordinaire dont les méthodes taguées [MetaEvent("__index")],
[MetaEvent("__call")], etc. implémentent les évènements Lua-like du type (DynamicMetatable est
l'implémentation par défaut : __index/__newindex délèguent à la réflexion sur le type .NET lié,
__call invoque l'objet s'il est invocable).
[MetaTable] sur le type .NET lui-même indique quelle classe utiliser comme métatable :
[MetaTable(typeof(OrderMetatable))]
public class Order { public decimal Total { get; set; } /* ... */ }
public class OrderMetatable : DynamicMetatable
{
[MetaFunction("markpaid")]
public int MarkAsPaid(IScript script)
{
var order = (Order)script.CheckType(1, ScriptValueType.UserData).ToUserData(1);
order.MarkAsPaid();
return 0;
}
}
[MetaFunction("nom")]ajoute une fonction supplémentaire à la métatable, en plus des membres découverts automatiquement par réflexion sur le type lié — utile pour un nom scriptable différent du nom .NET, ou une opération qui n'existe pas comme méthode .NET ordinaire.[MetaProperty("nom", MetaPropertyKind.Get\|Set)]ajoute un accesseur de propriété personnalisé, avec la même signatureint Method(IScript script)qu'une fonction : l'objet lié est à l'indice de pile 1, et pour un setter la nouvelle valeur est à l'indice 3 (2 étant le nom de la propriété).[MetaEvent("__xxx")](sur une méthode de la métatable) l'associe à un évènement précis (__index,__newindex,__call, ...) — c'est ce queDynamicMetatableutilise en interne pourIndex/NewIndex/Call.[MetaAsType(typeof(Base))](sur le type .NET) force l'enregistrement/la résolution de sa métatable sous un type différent du type réel de l'objet — utile pour une hiérarchie de types où toutes les sous-classes doivent partager la métatable de la classe de base.
RegisterType<T>()/RegisterType<TObj, TMeta>()/RegisterMetatable<T>(name) enregistrent
explicitement une association type ↔ métatable avant le premier usage (PushObject le fait aussi
paresseusement au premier objet rencontré d'un type donné s'il n'a pas déjà été enregistré).
Convertir une valeur de script vers .NET
Dans l'autre sens, IScriptValue.ToObject<T>(...)/ToObject(Type, ...) convertit une valeur du
script vers un type .NET cible (utilisé en interne pour convertir les arguments d'une méthode .NET
appelée depuis un script) :
double a = script.Get(1).ToObject(0.0, engine.Culture);
La bibliothèque dotnet côté script
OpenDotnetLib() (voir démarrage.md) publie une bibliothèque dotnet donnant à un
script un accès réflexif au CLR : charger une assembly, résoudre/instancier un type par son nom,
convertir un objet, créer un tableau .NET typé, s'abonner à un évènement .NET... Comme évoqué dans
démarrage.md, c'est délibérément la seule bibliothèque non incluse dans OpenLibs()
et à n'ouvrir que pour des scripts de confiance : elle contourne entièrement les métatables décrites
plus haut et donne accès à n'importe quel type .NET atteignable depuis le process hôte, pas
seulement à ceux explicitement exposés via PushObject/RegisterType. Le détail de ses fonctions
scriptables (dotnet.new, dotnet.type, dotnet.using, ...) relève de la bibliothèque standard,
pas de cette section.