Skip to content

Commit 83e0539

Browse files
authored
Merge branch 'master' into losttech-perf-interop
2 parents 39add65 + f1da55e commit 83e0539

File tree

6 files changed

+91
-64
lines changed

6 files changed

+91
-64
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- Jan Krivanek ([@jakrivan](https://github.com/jakrivan))
3535
- Jeff Reback ([@jreback](https://github.com/jreback))
3636
- Joe Frayne ([@jfrayne](https://github.com/jfrayne))
37+
- Joe Lidbetter ([@jmlidbetter](https://github.com/jmlidbetter))
3738
- John Burnett ([@johnburnett](https://github.com/johnburnett))
3839
- John Wilkes ([@jbw3](https://github.com/jbw3))
3940
- Luke Stratman ([@lstratman](https://github.com/lstratman))

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1717
- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures
1818
- Removes PyLong_GetMax and PyClass_New when targetting Python3
1919
- Improved performance of calls from Python to C#
20+
- Added support for converting python iterators to C# arrays
21+
- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer<TDelegate>(IntPtr)
2022

2123
### Fixed
2224

2325
- Fixed runtime that fails loading when using pythonnet in an environment
2426
together with Nuitka
27+
- Fixes bug where delegates get casts (dotnetcore)
2528

2629
## [2.4.0][]
2730

src/runtime/converter.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -837,50 +837,53 @@ private static void SetConversionError(IntPtr value, Type target)
837837

838838
/// <summary>
839839
/// Convert a Python value to a correctly typed managed array instance.
840-
/// The Python value must support the Python sequence protocol and the
840+
/// The Python value must support the Python iterator protocol or and the
841841
/// items in the sequence must be convertible to the target array type.
842842
/// </summary>
843843
private static bool ToArray(IntPtr value, Type obType, out object result, bool setError)
844844
{
845845
Type elementType = obType.GetElementType();
846-
var size = Runtime.PySequence_Size(value);
847846
result = null;
848847

849-
if (size < 0)
850-
{
848+
bool IsSeqObj = Runtime.PySequence_Check(value);
849+
var len = IsSeqObj ? Runtime.PySequence_Size(value) : -1;
850+
851+
IntPtr IterObject = Runtime.PyObject_GetIter(value);
852+
853+
if(IterObject==IntPtr.Zero) {
851854
if (setError)
852855
{
853856
SetConversionError(value, obType);
854857
}
855858
return false;
856859
}
857860

858-
Array items = Array.CreateInstance(elementType, size);
861+
Array items;
862+
863+
var listType = typeof(List<>);
864+
var constructedListType = listType.MakeGenericType(elementType);
865+
IList list = IsSeqObj ? (IList) Activator.CreateInstance(constructedListType, new Object[] {(int) len}) :
866+
(IList) Activator.CreateInstance(constructedListType);
867+
IntPtr item;
859868

860-
// XXX - is there a better way to unwrap this if it is a real array?
861-
for (var i = 0; i < size; i++)
869+
while ((item = Runtime.PyIter_Next(IterObject)) != IntPtr.Zero)
862870
{
863871
object obj = null;
864-
IntPtr item = Runtime.PySequence_GetItem(value, i);
865-
if (item == IntPtr.Zero)
866-
{
867-
if (setError)
868-
{
869-
SetConversionError(value, obType);
870-
return false;
871-
}
872-
}
873872

874873
if (!Converter.ToManaged(item, elementType, out obj, true))
875874
{
876875
Runtime.XDecref(item);
877876
return false;
878877
}
879878

880-
items.SetValue(obj, i);
879+
list.Add(obj);
881880
Runtime.XDecref(item);
882881
}
882+
Runtime.XDecref(IterObject);
883883

884+
items = Array.CreateInstance(elementType, list.Count);
885+
list.CopyTo(items, 0);
886+
884887
result = items;
885888
return true;
886889
}

src/runtime/nativecall.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,21 @@ internal class NativeCall
3232

3333
public static void Void_Call_1(IntPtr fp, IntPtr a1)
3434
{
35-
((Void_1_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Void_1_Delegate)))(a1);
35+
var d = Marshal.GetDelegateForFunctionPointer<Interop.DestructorFunc>(fp);
36+
d(a1);
3637
}
3738

3839
public static IntPtr Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3)
3940
{
40-
var d = (Interop.TernaryFunc)Marshal.GetDelegateForFunctionPointer(fp, typeof(Interop.TernaryFunc));
41+
var d = Marshal.GetDelegateForFunctionPointer<Interop.TernaryFunc>(fp);
4142
return d(a1, a2, a3);
4243
}
4344

4445

4546
public static int Int_Call_3(IntPtr fp, IntPtr a1, IntPtr a2, IntPtr a3)
4647
{
47-
return ((Int_3_Delegate)Marshal.GetDelegateForFunctionPointer(fp, typeof(Int_3_Delegate)))(a1, a2, a3);
48+
var d = Marshal.GetDelegateForFunctionPointer<Interop.ObjObjArgFunc>(fp);
49+
return d(a1, a2, a3);
4850
}
4951
#else
5052
private static AssemblyBuilder aBuilder;

src/runtime/typemanager.cs

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,8 @@ public static NativeCode Active
512512
return I386;
513513
case MachineType.x86_64:
514514
return X86_64;
515-
case MachineType.armv7l:
516-
return Armv7l;
517-
case MachineType.armv8:
518-
return Armv8;
519515
default:
520-
throw new NotImplementedException($"No support for {Runtime.MachineName}");
516+
return null;
521517
}
522518
}
523519
}
@@ -552,34 +548,6 @@ public static NativeCode Active
552548
/// <see cref="NativeCode.X86_64"/>
553549
/// </summary>
554550
public static readonly NativeCode I386 = X86_64;
555-
556-
public static readonly NativeCode Armv7l = new NativeCode()
557-
{
558-
Return0 = 0,
559-
Return1 = 0x08,
560-
Code = new byte[]
561-
{
562-
0xe3, 0xa0, 0x00, 0x00, // mov r0, #0
563-
0xe1, 0x2f, 0xff, 0x1e, // bx lr
564-
565-
0xe3, 0xa0, 0x00, 0x01, // mov r0, #1
566-
0xe1, 0x2f, 0xff, 0x1e, // bx lr
567-
}
568-
};
569-
570-
public static readonly NativeCode Armv8 = new NativeCode()
571-
{
572-
Return0 = 0,
573-
Return1 = 0x08,
574-
Code = new byte[]
575-
{
576-
0x52, 0x80, 0x00, 0x00, // mov w0, #0x0
577-
0xd6, 0x5f, 0x03, 0xc0, // ret
578-
579-
0x52, 0x80, 0x00, 0x20, // mov w0, #0x1
580-
0xd6, 0x5f, 0x03, 0xc0, // ret
581-
}
582-
};
583551
}
584552

585553
/// <summary>
@@ -702,7 +670,7 @@ internal static void InitializeNativeCodePage()
702670
Marshal.Copy(NativeCode.Active.Code, 0, NativeCodePage, codeLength);
703671
mapper.SetReadExec(NativeCodePage, codeLength);
704672
}
705-
#endregion
673+
#endregion
706674

707675
/// <summary>
708676
/// Given a newly allocated Python type object and a managed Type that
@@ -745,21 +713,40 @@ internal static void InitializeSlots(IntPtr type, Type impl)
745713
impl = impl.BaseType;
746714
}
747715

748-
// See the TestDomainReload test: there was a crash related to
749-
// the gc-related slots. They always return 0 or 1 because we don't
750-
// really support gc:
716+
var native = NativeCode.Active;
717+
718+
// The garbage collection related slots always have to return 1 or 0
719+
// since .NET objects don't take part in Python's gc:
751720
// tp_traverse (returns 0)
752721
// tp_clear (returns 0)
753722
// tp_is_gc (returns 1)
754-
// We can't do without: python really wants those slots to exist.
755-
// We can't implement those in C# because the application domain
756-
// can be shut down and the memory released.
757-
InitializeNativeCodePage();
758-
InitializeSlot(type, NativeCodePage + NativeCode.Active.Return0, "tp_traverse");
759-
InitializeSlot(type, NativeCodePage + NativeCode.Active.Return0, "tp_clear");
760-
InitializeSlot(type, NativeCodePage + NativeCode.Active.Return1, "tp_is_gc");
723+
// These have to be defined, though, so by default we fill these with
724+
// static C# functions from this class.
725+
726+
var ret0 = Interop.GetThunk(((Func<IntPtr, int>)Return0).Method);
727+
var ret1 = Interop.GetThunk(((Func<IntPtr, int>)Return1).Method);
728+
729+
if (native != null)
730+
{
731+
// If we want to support domain reload, the C# implementation
732+
// cannot be used as the assembly may get released before
733+
// CPython calls these functions. Instead, for amd64 and x86 we
734+
// load them into a separate code page that is leaked
735+
// intentionally.
736+
InitializeNativeCodePage();
737+
ret1 = NativeCodePage + native.Return1;
738+
ret0 = NativeCodePage + native.Return0;
739+
}
740+
741+
InitializeSlot(type, ret0, "tp_traverse");
742+
InitializeSlot(type, ret0, "tp_clear");
743+
InitializeSlot(type, ret1, "tp_is_gc");
761744
}
762745

746+
static int Return1(IntPtr _) => 1;
747+
748+
static int Return0(IntPtr _) => 0;
749+
763750
/// <summary>
764751
/// Helper for InitializeSlots.
765752
///

src/tests/test_array.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,3 +1337,34 @@ def test_array_abuse():
13371337
with pytest.raises(TypeError):
13381338
desc = Test.PublicArrayTest.__dict__['__setitem__']
13391339
desc(0, 0, 0)
1340+
1341+
1342+
@pytest.mark.skipif(PY2, reason="Only applies in Python 3")
1343+
def test_iterator_to_array():
1344+
from System import Array, String
1345+
1346+
d = {"a": 1, "b": 2, "c": 3}
1347+
keys_iterator = iter(d.keys())
1348+
arr = Array[String](keys_iterator)
1349+
1350+
Array.Sort(arr)
1351+
1352+
assert arr[0] == "a"
1353+
assert arr[1] == "b"
1354+
assert arr[2] == "c"
1355+
1356+
1357+
@pytest.mark.skipif(PY2, reason="Only applies in Python 3")
1358+
def test_dict_keys_to_array():
1359+
from System import Array, String
1360+
1361+
d = {"a": 1, "b": 2, "c": 3}
1362+
d_keys = d.keys()
1363+
arr = Array[String](d_keys)
1364+
1365+
Array.Sort(arr)
1366+
1367+
assert arr[0] == "a"
1368+
assert arr[1] == "b"
1369+
assert arr[2] == "c"
1370+

0 commit comments

Comments
 (0)