Thanks to visit codestin.com
Credit goes to github.com

Skip to content

pythonnet 3.0.x fails to expose types from .NET 8 assemblies (CoreCLR), works with .NET 9 #2595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
drache42 opened this issue May 21, 2025 · 2 comments

Comments

@drache42
Copy link

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 using pythonnet 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 in AttributeError when trying to access types directly from an imported namespace, or ImportError 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 same pythonnet version.

Observed Behavior (.NET 8):

  1. .NET 8 CoreCLR is loaded (confirmed via System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription and assemblies like System.Private.CoreLib, Version=8.0.0.0 in AppDomain).
  2. Target .NET 8 assembly is loaded into System.AppDomain.CurrentDomain.GetAssemblies().
  3. clr.AddReference("Path/To/Assembly.dll") completes without error.
  4. Importing the assembly's namespace (e.g., import MyNamespace) succeeds.
  5. Attempting to access a type (e.g., MyNamespace.MyClass) results in AttributeError: module 'MyNamespace' has no attribute 'MyClass'.
  6. Alternatively, if a package's Python module tries from MyNamespace import MyClass (after clr.AddReference in __init__.py), it fails with ImportError: cannot import name 'MyClass' from 'MyNamespace'.
  7. The clr module object consistently lacks the References attribute (i.e., hasattr(clr, "References") is False) after .NET CoreCLR initialization.
  8. clr.FindAssembly("AssemblyName") returns a string path to the DLL, not an Assembly object.

Expected Behavior:

  • Types from .NET 8 assemblies should be accessible in Python after clr.AddReference() and importing the namespace.
  • No AttributeError or ImportError should occur when accessing correctly referenced .NET types.
  • The behavior of clr.References and clr.FindAssembly() with .NET CoreCLR should ideally be consistent with .NET Framework or clearly documented if different.

Steps to Reproduce (Minimal Case):

  1. Create a simple .NET 8 Class Library (MinimalNet8Lib):

    • MinimalNet8Lib.csproj:
      <Project Sdk="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):
      namespace MinimalNet8Lib;
      
      public class Greeter
      {
          public static string SayHello(string name)
          {
              return $"Hello, {name} from .NET 8!";
          }
      
          public string InstanceHello(string name)
          {
              return $"Instance hello to {name} from .NET 8!";
          }
      }
    • Build the library: dotnet build -c Release
  2. Create a Python Test Script (test_minimal_net8.py):

    import os
    import sys
    from pathlib import Path
    
    print(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...")
        import pythonnet
        import clr_loader
        print(f"pythonnet version: {pythonnet.__version__}")
        print(f"clr_loader version: {clr_loader.__version__}")
    except Exception as e:
        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 exists
    script_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}")
    
    if not dll_path.exists():
        print(f"ERROR: DLL not found at {dll_path}")
        sys.exit(1)
    if not runtime_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 runtime
        rt = 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...")
        import clr
        print("Successfully imported clr.")
        print(f"clr module: {clr}")
        if hasattr(clr, "__version__"):
            print(f"clr (pythonnet) version attribute: {clr.__version__}")
    
    except Exception as e:
        print(f"ERROR during runtime initialization or clr import: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)
    
    print("\n--- Runtime and Assembly Diagnostics ---")
    try:
        from System import Environment, AppDomain
        from System.Runtime.InteropServices import RuntimeInformation
        
        print(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:")
        for assembly in AppDomain.CurrentDomain.GetAssemblies():
            print(f"  - {assembly.FullName}")
            if "MinimalNet8Lib" in assembly.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')}")
        if hasattr(clr, 'References') and clr.References is not None:
            print(f"Number of clr.References: {len(clr.References)}")
        else:
            print("clr.References attribute is missing or None.")
    
    except Exception as e:
        print(f"Error during diagnostics: {e}")
        import traceback
        traceback.print_exc()
        # Continue to type access attempt
    
    print("\n--- Attempting to Access .NET Types ---")
    try:
        print("Attempting: import MinimalNet8Lib")
        import MinimalNet8Lib
        print("Successfully imported MinimalNet8Lib namespace.")
        
        print("Attempting: greeter = MinimalNet8Lib.Greeter()")
        greeter = MinimalNet8Lib.Greeter() # This is where AttributeError typically occurs
        print(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!")
    
    except AttributeError as ae:
        print(f"FAILURE: AttributeError: {ae}")
        print("This indicates types from the .NET 8 assembly were not exposed correctly.")
        import traceback
        traceback.print_exc()
    except Exception as e:
        print(f"FAILURE: An unexpected error occurred: {e}")
        import traceback
        traceback.print_exc()
    
    print("\nScript finished.")
  3. Run the Python script:
    python test_minimal_net8.py
    This will show the AttributeError.

  4. Test with .NET 9 (Works):

    • Change TargetFramework in MinimalNet8Lib.csproj to net9.0.
    • Rebuild: dotnet build -c Release.
    • Update dll_path and runtime_config_path in test_minimal_net8.py to point to the net9.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.

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 only net8.0 and uses pythonnet.load("coreclr") in its __init__.py, leading to an ImportError when its Python submodules try to import the .NET types).
  • Setting PYTHONNET_RUNTIME=coreclr and using clr_loader.get_coreclr() followed by pythonnet.set_runtime() before import clr is a reliable way to initialize .NET CoreCLR. Simpler methods like just pythonnet.load("coreclr") or import 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 via pythonnet. The fact that it works seamlessly with .NET 9 suggests a specific interaction problem with .NET 8.

@lostmsu
Copy link
Member

lostmsu commented May 22, 2025

At the very least we should explicitly add .NET 8.0 to CI

@russell-rozenbaum
Copy link

I was experiencing this yesterday as well. Glad to see it wasn't just me. This seems fairly critical given .NET 8 is the go-to for long-term support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants