Bibliothèque dotnet
La bibliothèque dotnet donne à un script un accès réflexif complet au monde .NET du process hôte :
charger des assemblies, résoudre des types par nom, construire des instances, appeler leurs
méthodes, lire/écrire leurs propriétés et champs, construire des tableaux et types génériques,
s'abonner à des évènements .NET.
Avertissement — bibliothèque volontairement non chargée automatiquement
dotnet n'est pas incluse dans OpenLibs(), et ce pour des raisons de sécurité : à la
différence des autres bibliothèques standard, qui sont conçues comme un bac à sable, dotnet donne
à un script la même surface d'accès que du code C# tournant dans le process hôte lui-même —
n'importe quel type public de n'importe quelle assembly chargée, y compris des opérations sur le
système de fichiers, le réseau, les processus, ou l'introspection d'objets internes de
l'application hôte. Un hôte doit l'activer explicitement, en toute connaissance de cause :
// côté hôte .NET
script.OpenLibs();
script.OpenDotnetLib(); // n'activer que pour des scripts de confiance
Si l'hôte n'a pas ouvert la bibliothèque, require("dotnet") échoue comme n'importe quel searcher
qui ne trouve pas de module :
local ok, err = pcall(require, "dotnet")
if not ok then
print("dotnet indisponible : " .. tostring(err))
end
N'activez jamais cette bibliothèque pour des scripts dont vous ne maîtrisez pas l'origine.
Vue d'ensemble
| Fonction | Description |
|---|---|
dotnet.using(namespace [, ...]) |
Ajoute un/des espaces de noms à la liste de résolution des noms courts |
dotnet.loadassembly(fileOrName) |
Charge une assembly .Net (fichier ou nom) |
dotnet.new(typename [, ...]) |
Construit une nouvelle instance de typename |
dotnet.type(typename) |
Référence vers le type typename (membres statiques, constructeur) |
dotnet.generic(typename, typeArg [, ...]) |
Construit un type générique fermé (ex. List<int>) |
dotnet.array(typename, length) |
Crée un tableau .Net natif de length éléments de type typename |
dotnet.isinstance(value, typename) |
Teste si value est une instance de typename |
dotnet.cast(value, typename) |
Rebind un objet vers un autre type de vue |
dotnet.dispose(value) |
Appelle IDisposable.Dispose() sur value si applicable |
dotnet.addevent(value, eventName, fn) |
Abonne la fonction fn à l'évènement eventName de value |
dotnet.removeevent(value, eventName, handle) |
Désabonne un handler précédemment abonné |
dotnet.totable(value [, depth]) |
Copie récursive de value en table de script |
Accès aux membres d'un objet .Net
Un objet .Net renvoyé par dotnet.new/une méthode/une propriété est exposé comme un userdata avec
métatable dynamique : obj.Membre lit une propriété/un champ public, obj.Membre = v l'écrit,
obj.Methode(...) appelle une méthode publique — la surcharge est résolue automatiquement selon le
nombre et le type des arguments fournis. Un objet indexable (indexeur .Net, ex. this[int])
s'utilise avec obj[cle]/obj[cle] = v.
obj.Methode est déjà liée à obj au moment de l'accès au membre (comparable à un bound method
Python) — un appel en deux-points (obj:Methode(...)) repasse obj une deuxième fois en argument
implicite. Le moteur détecte ce cas et l'ignore automatiquement quand une surcharge matche alors
tous les arguments restants, donc obj:Methode(...) et obj.Methode(...) sont équivalents la
plupart du temps (sb:Append("hello") fonctionne). Ce n'est cependant qu'une heuristique — voir
interop-dotnet.md
pour le cas limite connu (un appel qui repasse délibérément l'objet lui-même comme argument réel).
Le point reste la syntaxe recommandée dans cette documentation, sans surprise possible.
local dotnet = require("dotnet")
local sb = dotnet.new("System.Text.StringBuilder")
sb.Append("hello")
sb.Append(" world")
print(sb.ToString()) --> hello world
print(sb.Length) --> 11
Un objet implémentant ICollection/IEnumerable/IDictionary/IEnumerator supporte #obj
(longueur, ICollection) et pairs(obj) (itération — clé/valeur pour un dictionnaire, index/valeur
sinon).
dotnet.using(namespace [, ...])
Ajoute un ou plusieurs espaces de noms à la liste consultée pour résoudre un nom de type court
(sans namespace complet) passé à dotnet.new/dotnet.type/dotnet.generic/dotnet.array/
dotnet.isinstance/dotnet.cast. Par défaut : System, System.Text,
System.Collections.Generic, System.Linq.
dotnet.using("System.IO")
local fi = dotnet.new("FileInfo", "data.txt") -- résolu comme System.IO.FileInfo
dotnet.loadassembly(fileOrName)
Charge une assembly .Net, soit depuis un fichier (si fileOrName existe sur le disque de l'hôte),
soit par nom d'assembly (déjà référencée, ou dans le GAC). Retourne true en cas de succès, ou
nil plus un message d'erreur en cas d'échec.
local ok, err = dotnet.loadassembly("MyPlugin.dll")
if not ok then print(err) end
dotnet.new(typename [, ...])
Construit une nouvelle instance de typename, en résolvant automatiquement le constructeur public
qui correspond le mieux aux arguments fournis. Lève une erreur si typename est introuvable, ou si
aucun constructeur ne correspond aux arguments.
local list = dotnet.new("List`1[[System.Int32]]") -- via nom fully-qualified générique
-- ou, plus lisible :
local ListInt = dotnet.generic("System.Collections.Generic.List", "System.Int32")
local list2 = ListInt()
dotnet.type(typename)
Retourne une référence vers le type typename (sans le construire) : ref.Membre lit un membre
statique, ref.Membre = v l'écrit, et appeler la référence comme une fonction
(ref(arg1, ...)) construit une nouvelle instance — équivalent à dotnet.new(typename, ...).
local Math = dotnet.type("System.Math")
print(Math.PI)
print(Math.Sqrt(2)) -- Sqrt est une méthode statique
local StringBuilder = dotnet.type("System.Text.StringBuilder")
local sb = StringBuilder() -- équivalent à dotnet.new("System.Text.StringBuilder")
dotnet.generic(typename, typeArg [, ...])
Construit un type générique fermé à partir du type ouvert typename (sans le suffixe `N, ajouté
automatiquement selon le nombre d'arguments de type) et d'une liste d'arguments de type (chacun un
nom de type résolu comme pour dotnet.new). Retourne une référence de type, comme dotnet.type.
Au moins un argument de type est requis.
local DictSI = dotnet.generic("System.Collections.Generic.Dictionary", "System.String", "System.Int32")
local d = DictSI()
d["un"] = 1
dotnet.array(typename, length)
Crée un tableau .Net natif de length éléments de type typename. L'indexation côté script est
1-based (arr[1] est le premier élément), contrairement à la convention 0-based de .Net —
alignée sur les tables de script plutôt que sur .Net. length doit être positif ou nul.
local arr = dotnet.array("System.Int32", 3)
arr[1], arr[2], arr[3] = 10, 20, 30
print(#arr) --> 3
print(arr[2]) --> 20
dotnet.isinstance(value, typename)
Retourne true si value est une instance de typename (ou d'un type qui en dérive/l'implémente).
dotnet.cast(value, typename)
Rebind value (qui doit déjà être un userdata .Net) pour être vu comme typename, sans changer
l'objet .Net sous-jacent — utile pour désambiguïser une résolution de surcharge, ou pour accéder
aux membres d'une interface sur un objet dont la métatable enregistrée correspond à un type moins
spécifique. Lève une erreur si value n'est pas réellement une instance de typename.
dotnet.dispose(value)
Appelle Dispose() sur value s'il implémente IDisposable ; ne fait rien sinon.
dotnet.addevent(value, eventName, fn) / dotnet.removeevent(value, eventName, handle)
addevent abonne la fonction de script fn à l'évènement .Net eventName de value, en la
wrappant dans un vrai délégué du type attendu par l'évènement. Retourne un handle opaque à
conserver pour un désabonnement ultérieur via removeevent. Lève une erreur si eventName est
introuvable sur le type de value, ou si fn n'est pas une fonction.
local timer = dotnet.new("System.Timers.Timer", 1000)
local handle = dotnet.addevent(timer, "Elapsed", function(sender, e)
print("tic")
end)
timer.Enabled = true
-- plus tard :
dotnet.removeevent(timer, "Elapsed", handle)
dotnet.totable(value [, depth])
Copie récursivement value en une table de script ordinaire, sans lien vivant vers l'objet .Net
d'origine (contrairement à l'accès direct aux membres d'un userdata, qui reflète l'état réel de
l'objet) : un dictionnaire devient une table indexée par ses clés (convertibles), une liste
générique devient une séquence 1..n, tout autre objet est décomposé via ses champs/propriétés
publics d'instance. depth (défaut 32) borne la profondeur de récursion, contre les graphes
d'objets cycliques.
local list = dotnet.generic("System.Collections.Generic.List", "System.Int32")()
list.Add(1); list.Add(2); list.Add(3)
local t = dotnet.totable(list)
print(t[1], t[2], t[3]) --> 1 2 3