Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
Support .NET Framework 4.6.1
  • Loading branch information
Metadorius committed Mar 26, 2026
commit 63cf5a790ea39d2c618be54f549a0c62b3f277aa
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def finalize_options(self):
dotnet_libs = [
DotnetLib(
"python-runtime",
"src/runtime/Python.Runtime.csproj",
"src/compat/Python.Runtime.Compat.csproj",
output="pythonnet/runtime",
)
]
Expand Down
13 changes: 13 additions & 0 deletions src/compat/Python.Runtime.Compat.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- A dummy project to force MSBuild to package .NET 4.6.1 requirements -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<OutputType>Library</OutputType>
<!-- Don't copy the dummy assembly/pdb to output; we only need the resolved references -->
<CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
<CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\runtime\Python.Runtime.csproj" />
</ItemGroup>
</Project>
44 changes: 44 additions & 0 deletions src/runtime/AppConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Linq;
using System.Reflection;

namespace Python.Runtime
{
static class AppConfig
{
/// <summary>
/// Override the AppDomain's config file path and reset the
/// ConfigurationManager cache so the CLR re-reads binding redirects.
/// Uses reflection to avoid a compile-time dependency on System.Configuration.
/// </summary>
public static void SetConfigFile(string path)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
ResetConfigMechanism();
}

private static void ResetConfigMechanism()
{
var configManager = Type.GetType(
"System.Configuration.ConfigurationManager, System.Configuration, "
+ "Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");

if (configManager == null)
return;

configManager
.GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static)
?.SetValue(null, 0);

configManager
.GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static)
?.SetValue(null, null);

configManager.Assembly
.GetTypes()
.FirstOrDefault(x => x.FullName == "System.Configuration.ClientConfigPaths")
?.GetField("s_current", BindingFlags.NonPublic | BindingFlags.Static)
?.SetValue(null, null);
}
}
}
17 changes: 17 additions & 0 deletions src/runtime/Loader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Text;

namespace Python.Runtime
Expand All @@ -12,6 +13,22 @@ public unsafe static int Initialize(IntPtr data, int size)
{
try
{
// On .NET Framework, the host is python.exe which has no binding
// redirects for netstandard2.0 shims (e.g. RuntimeInformation
// Version=0.0.0.0 vs the 4.0.2.0 shim on disk). Binding redirects
// via config files can't be injected after AppDomain creation, so
// resolve assemblies from our runtime directory directly.
AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
{
var name = new System.Reflection.AssemblyName(args.Name);
var dir = Path.GetDirectoryName(typeof(Loader).Assembly.Location);
var path = Path.Combine(dir, name.Name + ".dll");

return File.Exists(path)
? System.Reflection.Assembly.LoadFrom(path)
: null;
};

var dllPath = Encodings.UTF8.GetString((byte*)data.ToPointer(), size);

if (!string.IsNullOrEmpty(dllPath))
Expand Down