- Notifications
You must be signed in to change notification settings - Fork749
Description
Environment:
- Python: 3.12.x
- pythonnet: 3.0.3, 3.0.5 (and other 3.0.x versions)
- clr-loader: 0.2.6 (also tested with 0.2.7.post0)
- .NET Runtimes Tested:
- .NET 8 (e.g., 8.0.16) -Fails
- .NET 9 (e.g., 9.0.5) -Works
- OS: Windows 10/11 (primarily tested on Windows)
Problem:
When usingpythonnet
3.0.x with Python 3.12 to interact with .NET 8 assemblies via CoreCLR,pythonnet
successfully loads the .NET 8 runtime and the target assemblies into the AppDomain. However, it fails to properly expose the .NET types to the Python environment. This results inAttributeError
when trying to access types directly from an imported namespace, orImportError
when a Python module within a package attempts to import these .NET types.
The same operations succeed when the .NET assembly is compiled for .NET 9 and the .NET 9 runtime is used with the samepythonnet
version.
Observed Behavior (.NET 8):
- .NET 8 CoreCLR is loaded (confirmed via
System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription
and assemblies likeSystem.Private.CoreLib, Version=8.0.0.0
in AppDomain). - Target .NET 8 assembly is loaded into
System.AppDomain.CurrentDomain.GetAssemblies()
. clr.AddReference("Path/To/Assembly.dll")
completes without error.- Importing the assembly's namespace (e.g.,
import MyNamespace
) succeeds. - Attempting to access a type (e.g.,
MyNamespace.MyClass
) results inAttributeError: module 'MyNamespace' has no attribute 'MyClass'
. - Alternatively, if a package's Python module tries
from MyNamespace import MyClass
(afterclr.AddReference
in__init__.py
), it fails withImportError: cannot import name 'MyClass' from 'MyNamespace'
. - The
clr
module object consistently lacks theReferences
attribute (i.e.,hasattr(clr, "References")
isFalse
) after .NET CoreCLR initialization. clr.FindAssembly("AssemblyName")
returns a string path to the DLL, not anAssembly
object.
Expected Behavior:
- Types from .NET 8 assemblies should be accessible in Python after
clr.AddReference()
and importing the namespace. - No
AttributeError
orImportError
should occur when accessing correctly referenced .NET types. - The behavior of
clr.References
andclr.FindAssembly()
with .NET CoreCLR should ideally be consistent with .NET Framework or clearly documented if different.
Steps to Reproduce (Minimal Case):
Create a simple .NET 8 Class Library (
MinimalNet8Lib
):MinimalNet8Lib.csproj
:<ProjectSdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> </PropertyGroup></Project>
Class1.cs
(or e.g.,Greeter.cs
):namespaceMinimalNet8Lib;publicclassGreeter{publicstaticstringSayHello(stringname){return$"Hello,{name} from .NET 8!";}publicstringInstanceHello(stringname){return$"Instance hello to{name} from .NET 8!";}}
- Build the library:
dotnet build -c Release
Create a Python Test Script (
test_minimal_net8.py
):importosimportsysfrompathlibimportPathprint(f"Python version:{sys.version}")print(f"Python executable:{sys.executable}")# --- Crucial: Set PYTHONNET_RUNTIME to coreclr BEFORE pythonnet import ---os.environ["PYTHONNET_RUNTIME"]="coreclr"try:print("Attempting to import pythonnet and clr_loader...")importpythonnetimportclr_loaderprint(f"pythonnet version:{pythonnet.__version__}")print(f"clr_loader version:{clr_loader.__version__}")exceptExceptionase:print(f"Error importing pythonnet/clr_loader:{e}")sys.exit(1)# --- Setup Paths ---# Adjust this path to your MinimalNet8Lib.dll# Assumes the script is run from a directory where MinimalNet8Lib/bin/Release/net8.0/MinimalNet8Lib.dll existsscript_dir=Path(__file__).parent.resolve()dll_path=script_dir/"MinimalNet8Lib"/"bin"/"Release"/"net8.0"/"MinimalNet8Lib.dll"runtime_config_path=script_dir/"MinimalNet8Lib"/"bin"/"Release"/"net8.0"/"MinimalNet8Lib.runtimeconfig.json"# Optional: Specify DOTNET_ROOT if not discoverable# dotnet_root = Path(os.environ.get("DOTNET_ROOT", "C:/Program Files/dotnet"))print(f"Target DLL:{dll_path}")print(f"Runtime Config:{runtime_config_path}")ifnotdll_path.exists():print(f"ERROR: DLL not found at{dll_path}")sys.exit(1)ifnotruntime_config_path.exists():print(f"ERROR: Runtime config not found at{runtime_config_path}")sys.exit(1)try:print("\n--- Initializing .NET CoreCLR Runtime ---")# Using clr_loader to get the runtimert=clr_loader.get_coreclr(runtime_config=str(runtime_config_path))#, dotnet_root=str(dotnet_root)print(f"CoreCLR runtime object:{rt}")print("Setting pythonnet runtime...")pythonnet.set_runtime(rt)print("Pythonnet runtime set.")print("Attempting to import clr...")importclrprint("Successfully imported clr.")print(f"clr module:{clr}")ifhasattr(clr,"__version__"):print(f"clr (pythonnet) version attribute:{clr.__version__}")exceptExceptionase:print(f"ERROR during runtime initialization or clr import:{e}")importtracebacktraceback.print_exc()sys.exit(1)print("\n--- Runtime and Assembly Diagnostics ---")try:fromSystemimportEnvironment,AppDomainfromSystem.Runtime.InteropServicesimportRuntimeInformationprint(f"PythonEngine.Version:{pythonnet.get_version()}")print(f"PythonEngine.IsInitialized:{pythonnet.is_initialized()}")print(f"System.Environment.Version:{Environment.Version}")print(f"RuntimeInformation.FrameworkDescription:{RuntimeInformation.FrameworkDescription}")print(f"Adding reference to:{str(dll_path)}")clr.AddReference(str(dll_path))print("clr.AddReference completed.")print("Assemblies in Current AppDomain:")forassemblyinAppDomain.CurrentDomain.GetAssemblies():print(f" -{assembly.FullName}")if"MinimalNet8Lib"inassembly.FullName:print(f" FOUND MinimalNet8Lib:{assembly.FullName}, Location:{assembly.Location}")print(f"clr.FindAssembly('MinimalNet8Lib'):{clr.FindAssembly('MinimalNet8Lib')}")print(f"hasattr(clr, 'References'):{hasattr(clr,'References')}")ifhasattr(clr,'References')andclr.ReferencesisnotNone:print(f"Number of clr.References:{len(clr.References)}")else:print("clr.References attribute is missing or None.")exceptExceptionase:print(f"Error during diagnostics:{e}")importtracebacktraceback.print_exc()# Continue to type access attemptprint("\n--- Attempting to Access .NET Types ---")try:print("Attempting: import MinimalNet8Lib")importMinimalNet8Libprint("Successfully imported MinimalNet8Lib namespace.")print("Attempting: greeter = MinimalNet8Lib.Greeter()")greeter=MinimalNet8Lib.Greeter()# This is where AttributeError typically occursprint(f"Successfully created Greeter instance:{greeter}")print(f"Calling instance method:{greeter.InstanceHello('Python')}")print(f"Calling static method:{MinimalNet8Lib.Greeter.SayHello('Python')}")print("\nSUCCESS: .NET 8 types accessed successfully!")exceptAttributeErrorasae:print(f"FAILURE: AttributeError:{ae}")print("This indicates types from the .NET 8 assembly were not exposed correctly.")importtracebacktraceback.print_exc()exceptExceptionase:print(f"FAILURE: An unexpected error occurred:{e}")importtracebacktraceback.print_exc()print("\nScript finished.")
Run the Python script:
python test_minimal_net8.py
This will show theAttributeError
.Test with .NET 9 (Works):
- Change
TargetFramework
inMinimalNet8Lib.csproj
tonet9.0
. - Rebuild:
dotnet build -c Release
. - Update
dll_path
andruntime_config_path
intest_minimal_net8.py
to point to thenet9.0
outputs. - Ensure .NET 9 SDK/Runtime is installed and discoverable.
- Run
test_minimal_net8.py
. The script should complete successfully, accessing the .NET types.
- Change
Additional Notes:
- The issue has been observed with
pythonnet
versions 3.0.3 and 3.0.5. - The problem is reproducible with both a minimal class library and more complex, real-world libraries (e.g.,
Tableau.Migration.dll
v5.0.1, which targets onlynet8.0
and usespythonnet.load("coreclr")
in its__init__.py
, leading to anImportError
when its Python submodules try to import the .NET types). - Setting
PYTHONNET_RUNTIME=coreclr
and usingclr_loader.get_coreclr()
followed bypythonnet.set_runtime()
beforeimport clr
is a reliable way to initialize .NET CoreCLR. Simpler methods like justpythonnet.load("coreclr")
orimport clr
can sometimes lead to .NET Framework being loaded if not carefully managed, but the type exposure issue persists even with explicit and correct .NET 8 CoreCLR initialization.
We believe this is a significant issue for users attempting to leverage .NET 8 libraries with Python viapythonnet
. The fact that it works seamlessly with .NET 9 suggests a specific interaction problem with .NET 8.