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;
    }
}

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.