Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
909ed1f
dropped net40 target from modern projects
lostmsu Nov 30, 2020
47e926e
use .NET Standard 2.0 platform detection features
lostmsu Dec 2, 2020
21683b3
drop NativeCodePage alltogether
lostmsu Dec 2, 2020
972c41d
WIP: use C# 9 function pointers for PInvoke
lostmsu Dec 4, 2020
51e5184
allow setting PythonDLL
lostmsu Dec 10, 2020
2498d47
always explicitly specify the way strings are marshaled
lostmsu Jan 22, 2021
70fc803
CI: figure out DLL name from environment
lostmsu Jan 22, 2021
28a5dab
use Roslyn preview in CI
lostmsu Jan 22, 2021
c75229a
fixed Linux and Mac DLL loaders breaking dll path
lostmsu Jan 22, 2021
a0a1dc1
correctly detect DLL on *nix when running from Python
lostmsu Jan 22, 2021
1b88783
Windows library loader: add support for hModule == 0
lostmsu Jan 22, 2021
2c1aaef
fix dll loading in tests
lostmsu Jan 22, 2021
39e41d0
mentiond PythonDLL in changelog
lostmsu Jan 22, 2021
17040fe
set PYDLL in AppVeyor
lostmsu Jan 22, 2021
b7410b6
revert automatically added 'm' suffix for *nix default dll name
lostmsu Jan 22, 2021
275cae9
specify full DLL name instead of PYVER in GH Actions
lostmsu Jan 22, 2021
b4cb37e
use Microsoft.Net.Compilers.Toolset instead of Microsoft.Net.Compilers
lostmsu Jan 23, 2021
f68e581
in CI MacOS python DLL has 'm' suffix
lostmsu Jan 23, 2021
cda604a
only set PYTHONNET_PYDLL for test runs from .NET
lostmsu Jan 23, 2021
3982892
workaround for pytest/clr module not preloading python C API library
lostmsu Jan 23, 2021
a6cbe20
addressed a few code comments
lostmsu Jan 26, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
always explicitly specify the way strings are marshaled
  • Loading branch information
lostmsu committed Jan 28, 2021
commit 2498d474a3ae8e84d505680de5a467be5b5c4e9f
4 changes: 2 additions & 2 deletions src/embed_tests/References.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public void Dispose()
public void MoveToPyObject_SetsNull()
{
var dict = new PyDict();
NewReference reference = Runtime.PyDict_Items(dict.Handle);
NewReference reference = Runtime.PyDict_Items(dict.Reference);
try
{
Assert.IsFalse(reference.IsNull());
Expand All @@ -41,7 +41,7 @@ public void MoveToPyObject_SetsNull()
public void CanBorrowFromNewReference()
{
var dict = new PyDict();
NewReference reference = Runtime.PyDict_Items(dict.Handle);
NewReference reference = Runtime.PyDict_Items(dict.Reference);
try
{
PythonException.ThrowIfIsNotZero(Runtime.PyList_Reverse(reference));
Expand Down
9 changes: 5 additions & 4 deletions src/embed_tests/TestDomainReload.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ static void RunAssemblyAndUnload(string domainName)
// assembly (and Python .NET) to reside
var theProxy = CreateInstanceInstanceAndUnwrap<Proxy>(domain);

theProxy.Call("InitPython", ShutdownMode.Soft);
theProxy.Call(nameof(PythonRunner.InitPython), ShutdownMode.Soft, PyRuntime.PythonDLL);
// From now on use the Proxy to call into the new assembly
theProxy.RunPython();

Expand Down Expand Up @@ -400,7 +400,7 @@ static void RunDomainReloadSteps<T1, T2>() where T1 : CrossCaller where T2 : Cro
try
{
var theProxy = CreateInstanceInstanceAndUnwrap<Proxy>(domain);
theProxy.Call("InitPython", ShutdownMode.Reload);
theProxy.Call(nameof(PythonRunner.InitPython), ShutdownMode.Reload, PyRuntime.PythonDLL);

var caller = CreateInstanceInstanceAndUnwrap<T1>(domain);
arg = caller.Execute(arg);
Expand All @@ -418,7 +418,7 @@ static void RunDomainReloadSteps<T1, T2>() where T1 : CrossCaller where T2 : Cro
try
{
var theProxy = CreateInstanceInstanceAndUnwrap<Proxy>(domain);
theProxy.Call("InitPython", ShutdownMode.Reload);
theProxy.Call(nameof(PythonRunner.InitPython), ShutdownMode.Reload, PyRuntime.PythonDLL);

var caller = CreateInstanceInstanceAndUnwrap<T2>(domain);
caller.Execute(arg);
Expand Down Expand Up @@ -478,8 +478,9 @@ public static void RunPython()

private static IntPtr _state;

public static void InitPython(ShutdownMode mode)
public static void InitPython(ShutdownMode mode, string dllName)
{
PyRuntime.PythonDLL = dllName;
PythonEngine.Initialize(mode: mode);
_state = PythonEngine.BeginAllowThreads();
}
Expand Down
8 changes: 5 additions & 3 deletions src/embed_tests/TestRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,15 @@ public static void PyCheck_Iter_PyObject_IsIterable_ThreadingLock_Test()
// TypeFlags.HaveIter set in Python 2. This tests a different code path in PyObject_IsIterable and PyIter_Check.
var threading = Runtime.Runtime.PyImport_ImportModule("threading");
Exceptions.ErrorCheck(threading);
var threadingDict = Runtime.Runtime.PyModule_GetDict(threading);
var threadingDict = Runtime.Runtime.PyModule_GetDict(new BorrowedReference(threading));
Exceptions.ErrorCheck(threadingDict);
var lockType = Runtime.Runtime.PyDict_GetItemString(threadingDict, "Lock");
if (lockType == IntPtr.Zero)
if (lockType.IsNull)
throw new KeyNotFoundException("class 'Lock' was not found in 'threading'");

var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType, Runtime.Runtime.PyTuple_New(0));
var args = Runtime.Runtime.PyTuple_New(0);
var lockInstance = Runtime.Runtime.PyObject_CallObject(lockType.DangerousGetAddress(), args);
Runtime.Runtime.XDecref(args);
Exceptions.ErrorCheck(lockInstance);

Assert.IsFalse(Runtime.Runtime.PyObject_IsIterable(lockInstance));
Expand Down
65 changes: 0 additions & 65 deletions src/embed_tests/TestTypeManager.cs

This file was deleted.

11 changes: 6 additions & 5 deletions src/runtime/CustomMarshaler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ public int GetNativeDataSize()
/// </summary>
internal class UcsMarshaler : MarshalerBase
{
internal static readonly int _UCS = Runtime.PyUnicode_GetMax() <= 0xFFFF ? 2 : 4;
internal static readonly Encoding PyEncoding = _UCS == 2 ? Encoding.Unicode : Encoding.UTF32;
private static readonly MarshalerBase Instance = new UcsMarshaler();
private static readonly Encoding PyEncoding = Runtime.PyEncoding;

public override IntPtr MarshalManagedToNative(object managedObj)
{
Expand Down Expand Up @@ -91,13 +92,13 @@ public static int GetUnicodeByteLength(IntPtr p)
var len = 0;
while (true)
{
int c = Runtime._UCS == 2
int c = _UCS == 2
? Marshal.ReadInt16(p, len * 2)
: Marshal.ReadInt32(p, len * 4);

if (c == 0)
{
return len * Runtime._UCS;
return len * _UCS;
}
checked
{
Expand Down Expand Up @@ -147,7 +148,7 @@ public static string PtrToPy3UnicodePy2String(IntPtr p)
internal class StrArrayMarshaler : MarshalerBase
{
private static readonly MarshalerBase Instance = new StrArrayMarshaler();
private static readonly Encoding PyEncoding = Runtime.PyEncoding;
private static readonly Encoding PyEncoding = UcsMarshaler.PyEncoding;

public override IntPtr MarshalManagedToNative(object managedObj)
{
Expand All @@ -159,7 +160,7 @@ public override IntPtr MarshalManagedToNative(object managedObj)
}

int totalStrLength = argv.Sum(arg => arg.Length + 1);
int memSize = argv.Length * IntPtr.Size + totalStrLength * Runtime._UCS;
int memSize = argv.Length * IntPtr.Size + totalStrLength * UcsMarshaler._UCS;

IntPtr mem = Marshal.AllocHGlobal(memSize);
try
Expand Down
4 changes: 4 additions & 0 deletions src/runtime/Python.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@
<ItemGroup>
<PackageReference Include="System.Security.Permissions" Version="4.4.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="Lost.NonCopyableAnalyzer" Version="0.7.0-m04">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
6 changes: 4 additions & 2 deletions src/runtime/exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,16 @@ internal static void SetArgsAndCause(IntPtr ob)
/// Shortcut for (pointer == NULL) -&gt; throw PythonException
/// </summary>
/// <param name="pointer">Pointer to a Python object</param>
internal static void ErrorCheck(IntPtr pointer)
internal static void ErrorCheck(BorrowedReference pointer)
{
if (pointer == IntPtr.Zero)
if (pointer.IsNull)
{
throw new PythonException();
}
}

internal static void ErrorCheck(IntPtr pointer) => ErrorCheck(new BorrowedReference(pointer));

/// <summary>
/// Shortcut for (pointer == NULL or ErrorOccurred()) -&gt; throw PythonException
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/runtime/native/PyCompilerFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Python.Runtime.Native
{
[Flags]
enum PyCompilerFlags
{
SOURCE_IS_UTF8 = 0x0100,
DONT_IMPLY_DEDENT = 0x0200,
ONLY_AST = 0x0400,
IGNORE_COOKIE = 0x0800,
}
}
73 changes: 73 additions & 0 deletions src/runtime/native/StrPtr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Python.Runtime.Native
{
[StructLayout(LayoutKind.Sequential)]
struct StrPtr : IDisposable
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe NativeString would be more appropriate?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming things :-D

Not sure about NativeString. It has connotations to native string type supported on the platform, but this is not it.

It might make sense to have a StrPtr type per encoding for clarity. Then signatures could enforce it. But maybe that would be overengineering.

Copy link
Copy Markdown
Member

@filmor filmor Jan 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use any encoding apart from UTF8? I thought all FromString functions nowadays used that.

Indeed, I find exactly one usage with Encoding.ASCII, everything else is UTF8. And the one case that uses ASCII is PyBuffer_SizeFromFormat, which doesn't specify ASCII encoding either, I highly doubt it will break if it's passed UTF8 instead as it will probably just compare bytes directly and bail out on anything that it doesn't understand.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing I find "problematic" is that this object is not a pure pointer, it has ownership over the buffer it points to.

{
public IntPtr RawPointer { get; set; }
unsafe byte* Bytes => (byte*)this.RawPointer;

public unsafe StrPtr(string value, Encoding encoding)
{
if (value is null) throw new ArgumentNullException(nameof(value));
if (encoding is null) throw new ArgumentNullException(nameof(encoding));

var bytes = encoding.GetBytes(value);
this.RawPointer = Marshal.AllocHGlobal(checked(bytes.Length + 1));
try
{
Marshal.Copy(bytes, 0, this.RawPointer, bytes.Length);
this.Bytes[bytes.Length] = 0;
}
catch
{
this.Dispose();
throw;
}
}

public unsafe string? ToString(Encoding encoding)
{
if (encoding is null) throw new ArgumentNullException(nameof(encoding));
if (this.RawPointer == IntPtr.Zero) return null;

return encoding.GetString((byte*)this.RawPointer, byteCount: checked((int)this.ByteCount));
}

public unsafe nuint ByteCount
{
get
{
if (this.RawPointer == IntPtr.Zero) throw new NullReferenceException();

nuint zeroIndex = 0;
while (this.Bytes[zeroIndex] != 0)
{
zeroIndex++;
}
return zeroIndex;
}
}

public void Dispose()
{
if (this.RawPointer == IntPtr.Zero)
return;

Marshal.FreeHGlobal(this.RawPointer);
this.RawPointer = IntPtr.Zero;
}

internal static Encoding GetEncodingByPythonName(string pyEncodingName)
{
// https://stackoverflow.com/a/7798749/231238
if (pyEncodingName == "mbcs") return Encoding.Default;

return Encoding.GetEncoding(pyEncodingName);
}
}
}
2 changes: 1 addition & 1 deletion src/runtime/pyint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public PyInt(sbyte value) : this((int)value)

private static IntPtr FromString(string value)
{
IntPtr val = Runtime.PyInt_FromString(value, IntPtr.Zero, 0);
IntPtr val = Runtime.PyLong_FromString(value, IntPtr.Zero, 0);
PythonException.ThrowIfIsNull(val);
return val;
}
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/pyobject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public bool HasAttr(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));

return Runtime.PyObject_HasAttrString(obj, name) != 0;
return Runtime.PyObject_HasAttrString(Reference, name) != 0;
}


Expand Down
Loading