|
1 | 1 | using System; |
2 | | -using System.Collections; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Linq; |
3 | 4 | using System.Reflection; |
4 | 5 | using System.Reflection.Emit; |
| 6 | +using System.Text; |
5 | 7 |
|
6 | 8 | namespace Python.Runtime |
7 | 9 | { |
@@ -11,23 +13,20 @@ namespace Python.Runtime |
11 | 13 | /// </summary> |
12 | 14 | internal class DelegateManager |
13 | 15 | { |
14 | | - private Hashtable cache; |
15 | | - private Type basetype; |
16 | | - private Type listtype; |
17 | | - private Type voidtype; |
18 | | - private Type typetype; |
19 | | - private Type ptrtype; |
20 | | - private CodeGenerator codeGenerator; |
| 16 | + private readonly Dictionary<Type,Type> cache = new Dictionary<Type, Type>(); |
| 17 | + private readonly Type basetype = typeof(Dispatcher); |
| 18 | + private readonly Type arrayType = typeof(object[]); |
| 19 | + private readonly Type voidtype = typeof(void); |
| 20 | + private readonly Type typetype = typeof(Type); |
| 21 | + private readonly Type ptrtype = typeof(IntPtr); |
| 22 | + private readonly CodeGenerator codeGenerator = new CodeGenerator(); |
| 23 | + private readonly ConstructorInfo arrayCtor; |
| 24 | + private readonly MethodInfo dispatch; |
21 | 25 |
|
22 | 26 | public DelegateManager() |
23 | 27 | { |
24 | | - basetype = typeof(Dispatcher); |
25 | | - listtype = typeof(ArrayList); |
26 | | - voidtype = typeof(void); |
27 | | - typetype = typeof(Type); |
28 | | - ptrtype = typeof(IntPtr); |
29 | | - cache = new Hashtable(); |
30 | | - codeGenerator = new CodeGenerator(); |
| 28 | + arrayCtor = arrayType.GetConstructor(new[] { typeof(int) }); |
| 29 | + dispatch = basetype.GetMethod("Dispatch"); |
31 | 30 | } |
32 | 31 |
|
33 | 32 | /// <summary> |
@@ -58,10 +57,9 @@ private Type GetDispatcher(Type dtype) |
58 | 57 | // unique signatures rather than delegate types, since multiple |
59 | 58 | // delegate types with the same sig could use the same dispatcher. |
60 | 59 |
|
61 | | - object item = cache[dtype]; |
62 | | - if (item != null) |
| 60 | + if (cache.TryGetValue(dtype, out Type item)) |
63 | 61 | { |
64 | | - return (Type)item; |
| 62 | + return item; |
65 | 63 | } |
66 | 64 |
|
67 | 65 | string name = $"__{dtype.FullName}Dispatcher"; |
@@ -103,34 +101,77 @@ private Type GetDispatcher(Type dtype) |
103 | 101 |
|
104 | 102 | MethodBuilder mb = tb.DefineMethod("Invoke", MethodAttributes.Public, method.ReturnType, signature); |
105 | 103 |
|
106 | | - ConstructorInfo ctor = listtype.GetConstructor(Type.EmptyTypes); |
107 | | - MethodInfo dispatch = basetype.GetMethod("Dispatch"); |
108 | | - MethodInfo add = listtype.GetMethod("Add"); |
109 | | - |
110 | 104 | il = mb.GetILGenerator(); |
111 | | - il.DeclareLocal(listtype); |
112 | | - il.Emit(OpCodes.Newobj, ctor); |
| 105 | + // loc_0 = new object[pi.Length] |
| 106 | + il.DeclareLocal(arrayType); |
| 107 | + il.Emit(OpCodes.Ldc_I4, pi.Length); |
| 108 | + il.Emit(OpCodes.Newobj, arrayCtor); |
113 | 109 | il.Emit(OpCodes.Stloc_0); |
114 | 110 |
|
| 111 | + bool anyByRef = false; |
| 112 | + |
115 | 113 | for (var c = 0; c < signature.Length; c++) |
116 | 114 | { |
117 | 115 | Type t = signature[c]; |
118 | 116 | il.Emit(OpCodes.Ldloc_0); |
| 117 | + il.Emit(OpCodes.Ldc_I4, c); |
119 | 118 | il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); |
120 | 119 |
|
| 120 | + if (t.IsByRef) |
| 121 | + { |
| 122 | + // The argument is a pointer. We must dereference the pointer to get the value or object it points to. |
| 123 | + t = t.GetElementType(); |
| 124 | + if (t.IsValueType) |
| 125 | + { |
| 126 | + il.Emit(OpCodes.Ldobj, t); |
| 127 | + } |
| 128 | + else |
| 129 | + { |
| 130 | + il.Emit(OpCodes.Ldind_Ref); |
| 131 | + } |
| 132 | + anyByRef = true; |
| 133 | + } |
| 134 | + |
121 | 135 | if (t.IsValueType) |
122 | 136 | { |
123 | 137 | il.Emit(OpCodes.Box, t); |
124 | 138 | } |
125 | 139 |
|
126 | | - il.Emit(OpCodes.Callvirt, add); |
127 | | - il.Emit(OpCodes.Pop); |
| 140 | + // args[c] = arg |
| 141 | + il.Emit(OpCodes.Stelem_Ref); |
128 | 142 | } |
129 | 143 |
|
130 | 144 | il.Emit(OpCodes.Ldarg_0); |
131 | 145 | il.Emit(OpCodes.Ldloc_0); |
132 | 146 | il.Emit(OpCodes.Call, dispatch); |
133 | 147 |
|
| 148 | + if (anyByRef) |
| 149 | + { |
| 150 | + // Dispatch() will have modified elements of the args list that correspond to out parameters. |
| 151 | + for (var c = 0; c < signature.Length; c++) |
| 152 | + { |
| 153 | + Type t = signature[c]; |
| 154 | + if (t.IsByRef) |
| 155 | + { |
| 156 | + t = t.GetElementType(); |
| 157 | + // *arg = args[c] |
| 158 | + il.Emit(OpCodes.Ldarg_S, (byte)(c + 1)); |
| 159 | + il.Emit(OpCodes.Ldloc_0); |
| 160 | + il.Emit(OpCodes.Ldc_I4, c); |
| 161 | + il.Emit(OpCodes.Ldelem_Ref); |
| 162 | + if (t.IsValueType) |
| 163 | + { |
| 164 | + il.Emit(OpCodes.Unbox_Any, t); |
| 165 | + il.Emit(OpCodes.Stobj, t); |
| 166 | + } |
| 167 | + else |
| 168 | + { |
| 169 | + il.Emit(OpCodes.Stind_Ref); |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + |
134 | 175 | if (method.ReturnType == voidtype) |
135 | 176 | { |
136 | 177 | il.Emit(OpCodes.Pop); |
@@ -218,7 +259,7 @@ public void Dispose() |
218 | 259 | GC.SuppressFinalize(this); |
219 | 260 | } |
220 | 261 |
|
221 | | - public object Dispatch(ArrayList args) |
| 262 | + public object Dispatch(object[] args) |
222 | 263 | { |
223 | 264 | IntPtr gs = PythonEngine.AcquireLock(); |
224 | 265 | object ob; |
@@ -235,7 +276,7 @@ public object Dispatch(ArrayList args) |
235 | 276 | return ob; |
236 | 277 | } |
237 | 278 |
|
238 | | - public object TrueDispatch(ArrayList args) |
| 279 | + private object TrueDispatch(object[] args) |
239 | 280 | { |
240 | 281 | MethodInfo method = dtype.GetMethod("Invoke"); |
241 | 282 | ParameterInfo[] pi = method.GetParameters(); |
@@ -259,20 +300,108 @@ public object TrueDispatch(ArrayList args) |
259 | 300 | throw e; |
260 | 301 | } |
261 | 302 |
|
262 | | - if (rtype == typeof(void)) |
| 303 | + try |
263 | 304 | { |
264 | | - return null; |
265 | | - } |
| 305 | + int byRefCount = pi.Count(parameterInfo => parameterInfo.ParameterType.IsByRef); |
| 306 | + if (byRefCount > 0) |
| 307 | + { |
| 308 | + // By symmetry with MethodBinder.Invoke, when there are out |
| 309 | + // parameters we expect to receive a tuple containing |
| 310 | + // the result, if any, followed by the out parameters. If there is only |
| 311 | + // one out parameter and the return type of the method is void, |
| 312 | + // we instead receive the out parameter as the result from Python. |
| 313 | + |
| 314 | + bool isVoid = rtype == typeof(void); |
| 315 | + int tupleSize = byRefCount + (isVoid ? 0 : 1); |
| 316 | + if (isVoid && byRefCount == 1) |
| 317 | + { |
| 318 | + // The return type is void and there is a single out parameter. |
| 319 | + for (int i = 0; i < pi.Length; i++) |
| 320 | + { |
| 321 | + Type t = pi[i].ParameterType; |
| 322 | + if (t.IsByRef) |
| 323 | + { |
| 324 | + if (!Converter.ToManaged(op, t, out object newArg, true)) |
| 325 | + { |
| 326 | + Exceptions.RaiseTypeError($"The Python function did not return {t.GetElementType()} (the out parameter type)"); |
| 327 | + throw new PythonException(); |
| 328 | + } |
| 329 | + args[i] = newArg; |
| 330 | + break; |
| 331 | + } |
| 332 | + } |
| 333 | + return null; |
| 334 | + } |
| 335 | + else if (Runtime.PyTuple_Check(op) && Runtime.PyTuple_Size(op) == tupleSize) |
| 336 | + { |
| 337 | + int index = isVoid ? 0 : 1; |
| 338 | + for (int i = 0; i < pi.Length; i++) |
| 339 | + { |
| 340 | + Type t = pi[i].ParameterType; |
| 341 | + if (t.IsByRef) |
| 342 | + { |
| 343 | + IntPtr item = Runtime.PyTuple_GetItem(op, index++); |
| 344 | + if (!Converter.ToManaged(item, t, out object newArg, true)) |
| 345 | + { |
| 346 | + Exceptions.RaiseTypeError($"The Python function returned a tuple where element {i} was not {t.GetElementType()} (the out parameter type)"); |
| 347 | + throw new PythonException(); |
| 348 | + } |
| 349 | + args[i] = newArg; |
| 350 | + } |
| 351 | + } |
| 352 | + if (isVoid) |
| 353 | + { |
| 354 | + return null; |
| 355 | + } |
| 356 | + IntPtr item0 = Runtime.PyTuple_GetItem(op, 0); |
| 357 | + if (!Converter.ToManaged(item0, rtype, out object result0, true)) |
| 358 | + { |
| 359 | + Exceptions.RaiseTypeError($"The Python function returned a tuple where element 0 was not {rtype} (the return type)"); |
| 360 | + throw new PythonException(); |
| 361 | + } |
| 362 | + return result0; |
| 363 | + } |
| 364 | + else |
| 365 | + { |
| 366 | + string tpName = Runtime.PyObject_GetTypeName(op); |
| 367 | + if (Runtime.PyTuple_Check(op)) |
| 368 | + { |
| 369 | + tpName += $" of size {Runtime.PyTuple_Size(op)}"; |
| 370 | + } |
| 371 | + StringBuilder sb = new StringBuilder(); |
| 372 | + if (!isVoid) sb.Append(rtype.FullName); |
| 373 | + for (int i = 0; i < pi.Length; i++) |
| 374 | + { |
| 375 | + Type t = pi[i].ParameterType; |
| 376 | + if (t.IsByRef) |
| 377 | + { |
| 378 | + if (sb.Length > 0) sb.Append(","); |
| 379 | + sb.Append(t.GetElementType().FullName); |
| 380 | + } |
| 381 | + } |
| 382 | + string returnValueString = isVoid ? "" : "the return value and "; |
| 383 | + Exceptions.RaiseTypeError($"Expected a tuple ({sb}) of {returnValueString}the values for out and ref parameters, got {tpName}."); |
| 384 | + throw new PythonException(); |
| 385 | + } |
| 386 | + } |
| 387 | + |
| 388 | + if (rtype == typeof(void)) |
| 389 | + { |
| 390 | + return null; |
| 391 | + } |
266 | 392 |
|
267 | | - object result; |
268 | | - if (!Converter.ToManaged(op, rtype, out result, true)) |
| 393 | + object result; |
| 394 | + if (!Converter.ToManaged(op, rtype, out result, true)) |
| 395 | + { |
| 396 | + throw new PythonException(); |
| 397 | + } |
| 398 | + |
| 399 | + return result; |
| 400 | + } |
| 401 | + finally |
269 | 402 | { |
270 | 403 | Runtime.XDecref(op); |
271 | | - throw new PythonException(); |
272 | 404 | } |
273 | | - |
274 | | - Runtime.XDecref(op); |
275 | | - return result; |
276 | 405 | } |
277 | 406 | } |
278 | 407 | } |