Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6e1f0a8

Browse files
committed
implements buffer interface for .NET arrays of primitive types
fixeslosttech/Gradient#27
1 parent1e32d8c commit6e1f0a8

File tree

10 files changed

+220
-3
lines changed

10 files changed

+220
-3
lines changed

‎.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
-name:Install dependencies
4242
run:|
4343
pip install --upgrade -r requirements.txt
44+
pip install numpy # for tests
4445
4546
-name:Build and Install
4647
run:|

‎CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1515
- Ability to implement delegates with`ref` and`out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
1616
-`PyType` - a wrapper for Python type objects, that also permits creating new heap types from`TypeSpec`
1717
- Improved exception handling:
18-
- exceptions can now be converted with codecs
19-
-`InnerException` and`__cause__` are propagated properly
18+
- exceptions can now be converted with codecs
19+
-`InnerException` and`__cause__` are propagated properly
20+
- .NET arrays implement Python buffer protocol
21+
2022

2123
###Changed
2224
- Drop support for Python 2, 3.4, and 3.5

‎src/embed_tests/TestExample.csrenamed to‎src/embed_tests/NumPyTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
usingSystem.Collections.Generic;
33
usingNUnit.Framework;
44
usingPython.Runtime;
5+
usingPython.Runtime.Codecs;
56

67
namespacePython.EmbeddingTest
78
{
8-
publicclassTestExample
9+
publicclassNumPyTests
910
{
1011
[OneTimeSetUp]
1112
publicvoidSetUp()
1213
{
1314
PythonEngine.Initialize();
15+
TupleCodec<ValueTuple>.Register();
1416
}
1517

1618
[OneTimeTearDown]
@@ -49,5 +51,23 @@ public void TestReadme()
4951

5052
Assert.AreEqual("[ 6. 10. 12.]",(a*b).ToString().Replace(" "," "));
5153
}
54+
55+
[Test]
56+
publicvoidMultidimensionalNumPyArray()
57+
{
58+
PyObjectnp;
59+
try{
60+
np=Py.Import("numpy");
61+
}catch(PythonException){
62+
Assert.Inconclusive("Numpy or dependency not installed");
63+
return;
64+
}
65+
66+
vararray=new[,]{{1,2},{3,4}};
67+
varndarray=np.InvokeMethod("asarray",array.ToPython());
68+
Assert.AreEqual((2,2),ndarray.GetAttr("shape").As<(int,int)>());
69+
Assert.AreEqual(1,ndarray[(0,0).ToPython()].InvokeMethod("__int__").As<int>());
70+
Assert.AreEqual(array[1,0],ndarray[(1,0).ToPython()].InvokeMethod("__int__").As<int>());
71+
}
5272
}
5373
}

‎src/embed_tests/TestPyBuffer.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
usingSystem;
12
usingSystem.Text;
23
usingNUnit.Framework;
34
usingPython.Runtime;
5+
usingPython.Runtime.Codecs;
46

57
namespacePython.EmbeddingTest{
68
classTestPyBuffer
@@ -9,6 +11,7 @@ class TestPyBuffer
911
publicvoidSetUp()
1012
{
1113
PythonEngine.Initialize();
14+
TupleCodec<ValueTuple>.Register();
1215
}
1316

1417
[OneTimeTearDown]
@@ -64,5 +67,15 @@ public void TestBufferRead()
6467
}
6568
}
6669
}
70+
71+
[Test]
72+
publicvoidArrayHasBuffer()
73+
{
74+
vararray=new[,]{{1,2},{3,4}};
75+
varmemoryView=PythonEngine.Eval("memoryview");
76+
varmem=memoryView.Invoke(array.ToPython());
77+
Assert.AreEqual(1,mem[(0,0).ToPython()].As<int>());
78+
Assert.AreEqual(array[1,0],mem[(1,0).ToPython()].As<int>());
79+
}
6780
}
6881
}

‎src/runtime/Util.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ internal static class Util
1010
internalconststringMinimalPythonVersionRequired=
1111
"Only Python 3.5 or newer is supported";
1212

13+
internalstaticboolCLongIs32Bit()=>Runtime.IsWindows||Runtime.Is32Bit;
14+
1315
internalstaticInt64ReadCLong(IntPtrtp,intoffset)
1416
{
1517
// On Windows, a C long is always 32 bits.

‎src/runtime/arrayobject.cs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
usingSystem;
22
usingSystem.Collections;
3+
usingSystem.Collections.Generic;
4+
usingSystem.Runtime.InteropServices;
35

46
namespacePython.Runtime
57
{
@@ -366,5 +368,166 @@ public static int sq_contains(IntPtr ob, IntPtr v)
366368

367369
return0;
368370
}
371+
372+
#region Buffer protocol
373+
staticintGetBuffer(BorrowedReferenceobj,outPy_bufferbuffer,PyBUFflags)
374+
{
375+
buffer=default;
376+
377+
if(flags==PyBUF.SIMPLE)
378+
{
379+
Exceptions.SetError(Exceptions.BufferError,"SIMPLE not implemented");
380+
return-1;
381+
}
382+
if((flags&PyBUF.F_CONTIGUOUS)==PyBUF.F_CONTIGUOUS)
383+
{
384+
Exceptions.SetError(Exceptions.BufferError,"only C-contiguous supported");
385+
return-1;
386+
}
387+
varself=(Array)((CLRObject)GetManagedObject(obj)).inst;
388+
TypeitemType=self.GetType().GetElementType();
389+
390+
boolformatRequested=(flags&PyBUF.FORMATS)!=0;
391+
stringformat=GetFormat(itemType);
392+
if(formatRequested&&formatisnull)
393+
{
394+
Exceptions.SetError(Exceptions.BufferError,"unsupported element type: "+itemType.Name);
395+
return-1;
396+
}
397+
GCHandlegcHandle;
398+
try
399+
{
400+
gcHandle=GCHandle.Alloc(self,GCHandleType.Pinned);
401+
}catch(ArgumentExceptionex)
402+
{
403+
Exceptions.SetError(Exceptions.BufferError,ex.Message);
404+
return-1;
405+
}
406+
407+
intitemSize=Marshal.SizeOf(itemType);
408+
IntPtr[]shape=GetShape(self);
409+
IntPtr[]strides=GetStrides(shape,itemSize);
410+
buffer=newPy_buffer
411+
{
412+
buf=gcHandle.AddrOfPinnedObject(),
413+
obj=Runtime.SelfIncRef(obj.DangerousGetAddress()),
414+
len=(IntPtr)(self.LongLength*itemSize),
415+
itemsize=(IntPtr)itemSize,
416+
_readonly=false,
417+
ndim=self.Rank,
418+
format=format,
419+
shape=ToUnmanaged(shape),
420+
strides=(flags&PyBUF.STRIDES)==PyBUF.STRIDES?ToUnmanaged(strides):IntPtr.Zero,
421+
suboffsets=IntPtr.Zero,
422+
_internal=(IntPtr)gcHandle,
423+
};
424+
425+
return0;
426+
}
427+
staticvoidReleaseBuffer(BorrowedReferenceobj,refPy_bufferbuffer)
428+
{
429+
if(buffer._internal==IntPtr.Zero)return;
430+
431+
UnmanagedFree(refbuffer.shape);
432+
UnmanagedFree(refbuffer.strides);
433+
UnmanagedFree(refbuffer.suboffsets);
434+
435+
vargcHandle=(GCHandle)buffer._internal;
436+
gcHandle.Free();
437+
buffer._internal=IntPtr.Zero;
438+
}
439+
440+
staticIntPtr[]GetStrides(IntPtr[]shape,longitemSize)
441+
{
442+
varresult=newIntPtr[shape.Length];
443+
result[shape.Length-1]=newIntPtr(itemSize);
444+
for(intdim=shape.Length-2;dim>=0;dim--)
445+
{
446+
itemSize*=shape[dim+1].ToInt64();
447+
result[dim]=newIntPtr(itemSize);
448+
}
449+
returnresult;
450+
}
451+
staticIntPtr[]GetShape(Arrayarray)
452+
{
453+
varresult=newIntPtr[array.Rank];
454+
for(inti=0;i<result.Length;i++)
455+
result[i]=(IntPtr)array.GetLongLength(i);
456+
returnresult;
457+
}
458+
459+
staticvoidUnmanagedFree(refIntPtraddress)
460+
{
461+
if(address==IntPtr.Zero)return;
462+
463+
Marshal.FreeHGlobal(address);
464+
address=IntPtr.Zero;
465+
}
466+
staticunsafeIntPtrToUnmanaged<T>(T[]array)whereT: unmanaged
467+
{
468+
IntPtrresult=Marshal.AllocHGlobal(checked(Marshal.SizeOf(typeof(T))*array.Length));
469+
fixed(T*ptr=array)
470+
{
471+
var@out=(T*)result;
472+
for(inti=0;i<array.Length;i++)
473+
@out[i]=ptr[i];
474+
}
475+
returnresult;
476+
}
477+
478+
staticreadonlyDictionary<Type,string>ItemFormats=newDictionary<Type,string>
479+
{
480+
[typeof(byte)]="B",
481+
[typeof(sbyte)]="b",
482+
483+
[typeof(bool)]="?",
484+
485+
[typeof(short)]="h",
486+
[typeof(ushort)]="H",
487+
// see https://github.com/pybind/pybind11/issues/1908#issuecomment-658358767
488+
[typeof(int)]=Util.CLongIs32Bit()?"l":"i",
489+
[typeof(uint)]=Util.CLongIs32Bit()?"L":"I",
490+
[typeof(long)]=Util.CLongIs32Bit()?"q":"l",
491+
[typeof(ulong)]=Util.CLongIs32Bit()?"Q":"L",
492+
493+
[typeof(IntPtr)]="n",
494+
[typeof(UIntPtr)]="N",
495+
496+
// TODO: half = "e"
497+
[typeof(float)]="f",
498+
[typeof(double)]="d",
499+
};
500+
501+
staticstringGetFormat(TypeelementType)
502+
=>ItemFormats.TryGetValue(elementType,outstringresult)?result:null;
503+
504+
staticreadonlyGetBufferProcgetBufferProc=GetBuffer;
505+
staticreadonlyReleaseBufferProcreleaseBufferProc=ReleaseBuffer;
506+
staticreadonlyIntPtrBufferProcsAddress=AllocateBufferProcs();
507+
staticIntPtrAllocateBufferProcs()
508+
{
509+
varprocs=newPyBufferProcs
510+
{
511+
Get=Marshal.GetFunctionPointerForDelegate(getBufferProc),
512+
Release=Marshal.GetFunctionPointerForDelegate(releaseBufferProc),
513+
};
514+
IntPtrresult=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PyBufferProcs)));
515+
Marshal.StructureToPtr(procs,result,fDeleteOld:false);
516+
returnresult;
517+
}
518+
#endregion
519+
520+
/// <summary>
521+
/// <see cref="TypeManager.InitializeSlots(IntPtr, Type, SlotsHolder)"/>
522+
/// </summary>
523+
publicstaticvoidInitializeSlots(IntPtrtype,ISet<string>initialized,SlotsHolderslotsHolder)
524+
{
525+
if(initialized.Add(nameof(TypeOffset.tp_as_buffer)))
526+
{
527+
// TODO: only for unmanaged arrays
528+
intoffset=TypeOffset.GetSlotOffset(nameof(TypeOffset.tp_as_buffer));
529+
Marshal.WriteIntPtr(type,offset,BufferProcsAddress);
530+
}
531+
}
369532
}
370533
}

‎src/runtime/bufferinterface.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,15 @@ public enum PyBUF
103103
/// </summary>
104104
FULL_RO=(INDIRECT|FORMATS),
105105
}
106+
107+
internalstructPyBufferProcs
108+
{
109+
publicIntPtrGet;
110+
publicIntPtrRelease;
111+
}
112+
113+
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
114+
delegateintGetBufferProc(BorrowedReferenceobj,outPy_bufferbuffer,PyBUFflags);
115+
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
116+
delegatevoidReleaseBufferProc(BorrowedReferenceobj,refPy_bufferbuffer);
106117
}

‎src/runtime/exceptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ public static variables on the Exceptions class filled in from
413413

414414
publicstaticIntPtrAssertionError;
415415
publicstaticIntPtrAttributeError;
416+
publicstaticIntPtrBufferError;
416417
publicstaticIntPtrEOFError;
417418
publicstaticIntPtrFloatingPointError;
418419
publicstaticIntPtrEnvironmentError;

‎src/runtime/native/TypeOffset.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties)
159159
"GetClrType",
160160
"getPreload",
161161
"Initialize",
162+
"InitializeSlots",
162163
"ListAssemblies",
163164
"_load_clr_module",
164165
"Release",

‎src/runtime/typemanager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,9 @@ internal static void InitializeSlots(IntPtr type, Type impl, SlotsHolder slotsHo
745745
seen.Add(name);
746746
}
747747

748+
varinitSlot=impl.GetMethod("InitializeSlots",BindingFlags.Static|BindingFlags.Public);
749+
initSlot?.Invoke(null,parameters:newobject[]{type,seen,slotsHolder});
750+
748751
impl=impl.BaseType;
749752
}
750753

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp