2using System.Collections;
3using System.Collections.Generic;
4using System.Globalization;
6using System.Reflection;
21 private static readonly BindingFlags
ClrMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
22 private static readonly BindingFlags
ClrInstanceMemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
23 private static readonly BindingFlags
ClrStaticMemberFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
30 internal static object? ScriptNullToClr(
object? v) => v is NullReference ? null : v;
37 throw new ExecutionException($
"CLR interop is disabled or access denied for type '{t.FullName}'.");
42 if (!
_policy.IsReturnValueAllowed(value))
43 throw new ExecutionException($
"CLR interop blocked return value in {context} (type '{value?.GetType().FullName ?? "null"}').");
47 public void SetMember(
object target,
string memberName,
object scriptValue) =>
SetMemberValue(target, memberName, scriptValue);
50 public object Invoke(
object target,
string methodName, List<object> args) =>
InvokeMethod(target, methodName, args);
56 private static object CoerceTo(
object value, Type targetType)
58 value = ScriptNullToClr(value);
61 var underlyingNullable = Nullable.GetUnderlyingType(targetType);
62 if (underlyingNullable !=
null)
63 targetType = underlyingNullable;
68 return targetType.IsValueType ? Activator.CreateInstance(targetType)! :
null!;
71 var srcType = value.GetType();
72 if (targetType.IsAssignableFrom(srcType))
79 if (targetType.IsArray)
81 var elemType = targetType.GetElementType()!;
95 if (targetType.IsEnum)
96 return Enum.Parse(targetType, value.ToString()!, ignoreCase:
true);
99 if (value is IConvertible)
100 return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
110 if (scriptArr.Count == 0)
111 return Array.CreateInstance(elementType, 0);
114 var keys =
new List<int>(scriptArr.Count);
115 foreach (var k
in scriptArr.Keys)
120 throw new ExecutionException($
"Associatives Array kann nicht zu '{elementType.Name}[]' konvertiert werden (Key-Typ: {k?.GetType().Name ?? "null"}).");
124 for (
int i = 0; i < keys.Count; i++)
127 throw new ExecutionException($
"Array kann nicht zu '{elementType.Name}[]' konvertiert werden: Keys müssen 0..n-1 ohne Lücken sein.");
130 var arr = Array.CreateInstance(elementType, keys.Count);
131 for (
int i = 0; i < keys.Count; i++)
133 var v = scriptArr[i];
134 var coerced =
CoerceTo(v, elementType);
135 arr.SetValue(coerced, i);
142 if (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == genericDefinition)
145 return type.GetInterfaces()
146 .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericDefinition);
162 var elemType = iface.GetGenericArguments()[0];
165 Type concrete = targetType;
166 if (targetType.IsInterface || targetType.IsAbstract)
167 concrete = typeof(List<>).MakeGenericType(elemType);
172 instance = Activator.CreateInstance(concrete)!;
177 instance = Activator.CreateInstance(typeof(List<>).MakeGenericType(elemType))!;
178 concrete = instance.GetType();
182 var add = concrete.GetMethod(
"Add",
new[] { elemType })
183 ?? concrete.GetMethods().FirstOrDefault(m => m.Name ==
"Add" && m.GetParameters().Length == 1);
189 var intKeys =
new List<int>();
190 foreach (var k
in scriptArr.Keys)
192 if (k is
int i) intKeys.Add(i);
197 foreach (var k
in intKeys)
199 var v = scriptArr[k];
200 var coerced =
CoerceTo(v, elemType);
201 add.Invoke(instance,
new[] { coerced });
204 collection = instance;
216 var ga = iface.GetGenericArguments();
220 Type concrete = targetType;
221 if (targetType.IsInterface || targetType.IsAbstract)
222 concrete = typeof(Dictionary<,>).MakeGenericType(keyType, valType);
227 instance = Activator.CreateInstance(concrete)!;
231 instance = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(keyType, valType))!;
232 concrete = instance.GetType();
235 var add = concrete.GetMethod(
"Add",
new[] { keyType, valType })
236 ?? concrete.GetMethods().FirstOrDefault(m => m.Name ==
"Add" && m.GetParameters().Length == 2);
241 foreach (var k
in scriptArr.Keys)
243 var v = scriptArr[k];
246 add.Invoke(instance,
new[] { ck, cv });
253 private static bool TryCoerceTo(
object value, Type targetType, out
object? coerced)
257 coerced =
CoerceTo(value, targetType);
273 if (target ==
null || target is NullReference)
274 return NullReference.Instance;
276 bool isStatic = target is Type;
277 var t = isStatic ? (Type)target : target.GetType();
282 var field = t.GetField(memberName, flags);
285 if (!
_policy.IsMemberAllowed(field))
286 throw new ExecutionException($
"CLR member access denied: '{t.FullName}.{field.Name}'.");
288 var v = field.GetValue(isStatic ?
null : target);
290 return v ?? NullReference.Instance;
293 var prop = t.GetProperty(memberName, flags);
294 if (prop !=
null && prop.CanRead)
296 if (!
_policy.IsMemberAllowed(prop))
297 throw new ExecutionException($
"CLR member access denied: '{t.FullName}.{prop.Name}'.");
299 var v = prop.GetValue(isStatic ?
null : target);
301 return v ?? NullReference.Instance;
304 return NullReference.Instance;
307 private void SetMemberValue(
object target,
string memberName,
object scriptValue)
309 if (target ==
null || target is NullReference)
310 throw new ExecutionException($
"Null reference bei Zuweisung auf Member '{memberName}'.");
312 var t = target.GetType();
318 if (!
_policy.IsMemberAllowed(field))
319 throw new ExecutionException($
"CLR member write denied: '{t.FullName}.{field.Name}'.");
321 var coerced =
CoerceTo(scriptValue, field.FieldType);
322 field.SetValue(target, coerced);
327 if (prop !=
null && prop.CanWrite)
329 if (!
_policy.IsMemberAllowed(prop))
330 throw new ExecutionException($
"CLR member write denied: '{t.FullName}.{prop.Name}'.");
332 var coerced =
CoerceTo(scriptValue, prop.PropertyType);
333 prop.SetValue(target, coerced);
337 throw new ExecutionException($
"Member '{memberName}' nicht gefunden oder nicht schreibbar auf Typ '{t.FullName}'.");
344 private object InvokeMethod(
object target,
string methodName, List<object> args)
346 if (target ==
null || target is NullReference)
347 return NullReference.Instance;
349 bool isStatic = target is Type;
350 var t = isStatic ? (Type)target : target.GetType();
354 var methods = t.GetMethods(flags);
356 MethodInfo? best =
null;
357 object[]? bestArgs =
null;
358 int bestScore =
int.MaxValue;
360 bool anyNameMatch =
false;
361 bool anyAllowedCandidate =
false;
363 foreach (var m
in methods)
365 if (!
string.Equals(m.Name, methodName, StringComparison.OrdinalIgnoreCase))
373 anyAllowedCandidate =
true;
375 var ps = m.GetParameters();
376 if (ps.Length != args.Count)
380 var coercedArgs =
new object[ps.Length];
383 for (
int i = 0; i < ps.Length; i++)
385 var pType = ps[i].ParameterType;
386 var a = ScriptNullToClr(args[i]);
391 if (pType.IsValueType && Nullable.GetUnderlyingType(pType) ==
null)
397 coercedArgs[i] =
null!;
402 var aType = a.GetType();
404 if (pType.IsAssignableFrom(aType))
407 score += (pType == aType) ? 0 : 1;
425 if (score < bestScore)
429 bestArgs = coercedArgs;
435 if (anyNameMatch && !anyAllowedCandidate)
436 throw new ExecutionException($
"CLR method call denied: '{t.FullName}.{methodName}(...)'.");
438 throw new ExecutionException($
"Methode '{methodName}' mit {args.Count} Parametern nicht gefunden auf Typ '{t.FullName}'.");
444 object? result = best.Invoke(isStatic ?
null : target, bestArgs);
445 if (best.ReturnType == typeof(
void))
446 return NullReference.Instance;
451 catch (TargetInvocationException tie)
454 throw new ExecutionException($
"Fehler in CLR-Methode '{t.FullName}.{best.Name}': {tie.InnerException?.Message ?? tie.Message}");
458 throw new ExecutionException($
"Fehler beim Aufruf von CLR-Methode '{t.FullName}.{best.Name}': {ex.Message}");
468 value = NullReference.Instance;
470 if (target ==
null || target is NullReference)
473 var t = target.GetType();
477 if (target is IList list)
479 var idxObj = ScriptNullToClr(scriptKey);
480 if (idxObj is
int idx)
492 if (target is IDictionary dict)
494 object? key = ScriptNullToClr(scriptKey);
497 var keyType = target.GetType().GetInterfaces()
498 .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
499 ?.GetGenericArguments()[0];
501 if (keyType !=
null && key !=
null &&
TryCoerceTo(key, keyType, out var ck) && ck !=
null)
520 var ip = p.GetIndexParameters();
521 if (ip.Length != 1 || !p.CanRead)
524 if (!
_policy.IsMemberAllowed(p))
527 if (!
TryCoerceTo(scriptKey, ip[0].ParameterType, out var ck))
532 var v = p.GetValue(target,
new object[] { ck });
548 if (target ==
null || target is NullReference)
551 var t = target.GetType();
555 if (target is IList list)
557 var idxObj = ScriptNullToClr(scriptKey);
558 if (idxObj is not
int idx)
562 Type? elemType =
null;
563 var tt = target.GetType();
565 elemType = tt.GetElementType();
568 var gi = tt.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>));
569 if (gi !=
null) elemType = gi.GetGenericArguments()[0];
572 object v = scriptValue;
573 if (elemType !=
null &&
TryCoerceTo(scriptValue, elemType, out var cv) && cv !=
null)
576 list[idx] = ScriptNullToClr(v);
581 if (target is IDictionary dict)
583 object? key = ScriptNullToClr(scriptKey);
584 object? val = ScriptNullToClr(scriptValue);
586 var iface = target.GetType().GetInterfaces()
587 .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>));
590 var ga = iface.GetGenericArguments();
593 if (key !=
null &&
TryCoerceTo(key, keyType, out var ck) && ck !=
null) key = ck;
594 if (val !=
null &&
TryCoerceTo(val, valType, out var cv) && cv !=
null) val = cv;
611 var ip = p.GetIndexParameters();
612 if (ip.Length != 1 || !p.CanWrite)
615 if (!
_policy.IsMemberAllowed(p))
618 if (!
TryCoerceTo(scriptKey, ip[0].ParameterType, out var ck))
621 object v = scriptValue;
622 if (
TryCoerceTo(scriptValue, p.PropertyType, out var cv) && cv !=
null)
627 p.SetValue(target, ScriptNullToClr(v),
new object[] { ck });
bool TrySetIndex(object target, object scriptKey, object scriptValue)
static bool TryCoerceTo(object value, Type targetType, out object? coerced)
object GetMember(object target, string memberName)
void SetMember(object target, string memberName, object scriptValue)
static object CoerceTo(object value, Type targetType)
static bool TryConvertScriptArrayListToGenericCollection(ArrayList scriptArr, Type targetType, out object? collection)
static readonly BindingFlags ClrInstanceMemberFlags
static readonly BindingFlags ClrMemberFlags
object GetMemberValue(object target, string memberName)
object Invoke(object target, string methodName, List< object > args)
static object ConvertScriptArrayListToClrArray(ArrayList scriptArr, Type elementType)
static object ClrNullToScript(object? v)
ClrBridge(IClrPolicy policy)
bool TryGetIndexedValue(object target, object scriptKey, out object value)
static readonly BindingFlags ClrStaticMemberFlags
static bool TryConvertScriptArrayListToGenericDictionary(ArrayList scriptArr, Type targetType, out object? dict)
static ? Type GetGenericInterface(Type type, Type genericDefinition)
bool TrySetIndexedValue(object target, object scriptKey, object scriptValue)
void EnsureTypeAllowed(Type t)
bool TryGetIndex(object target, object scriptKey, out object value)
readonly IClrPolicy _policy
void SetMemberValue(object target, string memberName, object scriptValue)
object InvokeMethod(object target, string methodName, List< object > args)
void EnsureReturnAllowed(object? value, string context)
Denies all CLR interop. Default Policy!
Policy hook for CLR interop. Implementations decide what is accessible from scripts.