pythonnet: .NET Framework 4.6.2 Long Paths: Illegal Characters in path exception

Environment

  • Pythonnet version: 2.5.2
  • Python version: 3.8.10
  • Operating System: Windows 10
  • .NET Runtime: 4.6.2

Details

  • .NET Framework 4.6.2 supports long paths by default. When calling an assembly that a targets .NET Framework 4.6.2, long path support is disabled and causes an Illegal characters in path exception when using a FileStream constructor:
System.ArgumentException: Illegal characters in path.
   at System.Security.Permissions.FileIOPermission.EmulateFileIOPermissionChecks(String fullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)

Workaround: For now, the workaround is to create a python.exe.config application config file with the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version=".NETFramework,Version=v4.6.2"/>
  </startup>
  <runtime>
    <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
  </runtime> 
</configuration>

This, however, creates a global solution which is not desired. An alternate workaround is to explicitly update the AppContext variables in the Python script before any code is executed (credit to https://stackoverflow.com/questions/53193280/long-path-workaround-not-working-on-some-installs):

type = Type.GetType("System.AppContext")
if type:
    AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", False)
    AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", False)

    switchType = Type.GetType("System.AppContextSwitches")
    if switchType:
        # We also have to reach into System.AppContextSwitches and manually update the cached private versions of these properties (don't ask me why):

        legacyField = switchType.GetField("_useLegacyPathHandling", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
        if legacyField:
            legacyField.SetValue(None, -1) # <- caching uses 0 to indicate no value, -1 for false, 1 for true.

        blockingField = switchType.GetField("_blockLongPaths", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
        if blockingField:
            blockingField.SetValue(None, -1) # <- caching uses 0 to indicate no value, -1 for false, 1 for true.

Again, not an ideal solution because this uses reflection to set values of private variables by name.

Ideally, the CLR loader would load the proper AppContext defaults based on the version of target framework like is shown here:

https://referencesource.microsoft.com/#mscorlib/system/AppContext/AppContextDefaultValues.Defaults.cs,60

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (7 by maintainers)

Most upvoted comments

The config file is important but is not what was being asked here. Unfortunately, the answers from the contributors devolved and started to be insulting so the discussion could not be continued in a respectful and logical manner. Configs should absolutely be honored and using them is still relevant. However, in absence of a config file, the defaults for the framework version under which the dll was compiled should be honored. It shouldn’t require a specific config file in order to enable settings that were already implicit in the version of the framework that was being used.

Lol, your point has no proof here. I just told you that the script should set the target framework, because the script is the entry point, and that’s how it works in .NET. Additionally, see Python’s motto.

When I create a .NET executable

That’s because your executable has target .NET version set, so it gets implicit config. To do that you set TargetFramework in .csproj. If you tried to load the DLL from an executable with older TargetFramework, it would fail similarly. The TargetFramework of your DLL does not matter at all.

Cause there’s no executable built from a .csproj when you launch .NET, only the Python script, logically, the Python script is the only place your setting could go.