Jump to content

New packaging requirements for plugins starting with PDN 4.3 / .NET 5


Recommended Posts

11 hours ago, VcSaJen said:

I use Magick.NET library, which uses .deps.json for it's native DLLs. Paint.NET seemingly ignore .deps.json file located in my plugin's folder, resulting in error:

System.TypeInitializationException: The type initializer for 'NativeMagickSettings' threw an exception.
 ---> System.DllNotFoundException: Unable to load DLL 'Magick.Native-Q8-x64.dll' or one of its dependencies: The specified module could not be found. (0x8007007E)
   at ImageMagick.Environment.NativeMethods.X64.Environment_Initialize()
   at ImageMagick.Environment.NativeEnvironment.Initialize() in /_/src/Magick.NET/Native/Helpers/Environment.cs:line 50
   at ImageMagick.Environment.Initialize() in /_/src/Magick.NET/Helpers/Environment.cs:line 21
   at ImageMagick.MagickSettings.NativeMagickSettings..cctor() in /_/src/Magick.NET/Native/Settings/MagickSettings.cs:line 230
   --- End of inner exception stack trace ---
   at ImageMagick.MagickSettings.NativeMagickSettings..ctor() in /_/src/Magick.NET/Native/Settings/MagickSettings.cs:line 246
   at ImageMagick.MagickSettings..ctor() in /_/src/Magick.NET/Settings/MagickSettings.cs:line 33
   at ImageMagick.MagickImage..ctor() in /_/src/Magick.NET/MagickImage.cs:line 39
   at MyFileType.MyFileTypePlugin.OnLoad(Stream input) in C:\full_path\MyFileType\MyFileType\FileType.cs:line 210
   at PaintDotNet.FileType.Load(Stream input) in D:\src\pdn\src\Data\FileType.cs:line 498
   at PaintDotNet.Functional.Func.Eval[T1,TRet](Func`2 f, T1 arg1) in D:\src\pdn\src\Base\Functional\Func.cs:line 158

Magick.Native-Q8-x64.dll is located in runtimes/win-x64/native/Magick.Native-Q8-x64.dll directory inside my plugin's directory and present in .deps.json (targets -> .NETCoreApp,Version=v5.0 -> Magick.NET-Q8-AnyCPU/8.3.3 -> runtimeTargets -> runtimes/win-x86/native/Magick.Native-Q8-x86.dll).

 

I couldn't manage to do this either. Native library references in .deps.json file is not respected. I'd like to know if its possible.

 

There is DllImportResolver class (under System.Runtime.InteropServices namespace) in .NET 5 that can be useful. I don't know if it'll have any side-effects on other things though. If I'm intending to publish the plugin, can I use it? @Rick Brewster 

 

I won't use this code, this was just a test and it worked for me. Because, this specific plugin, unlike Magick.NET, all the native libraries for each platform are in the same directory as managed one and they're getting found automatically. But I might need to use this (or something similar) in another project.

 

This code also contains older method that works for Paint.NET 4.2.

 

        static PdfFileType()
        {
            SetDllSearchPath();
        }

        /// <exception cref="PlatformNotSupportedException"></exception>
        private static void SetDllSearchPath()
        {
            string lookupPath;

            if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
            {
                lookupPath = @"runtimes/win-x86/native";
            }
            else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
            {
                lookupPath = @"runtimes/win-x64/native";
            }
            else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
            {
                lookupPath = @"runtimes/win-arm64/native";
            }
            else
            {
                throw new PlatformNotSupportedException();
            }

            lookupPath = Path.Combine(Path.GetDirectoryName(typeof(PdfFileType).Assembly.Location), lookupPath);

#if NET5_0_OR_GREATER
            IntPtr ResolveCallback(string libraryName, System.Reflection.Assembly assembly, DllImportSearchPath? path)
            {
                if (assembly != typeof(PdfFileType).Assembly)
                {
                    return IntPtr.Zero;
                }

                NativeLibrary.TryLoad(Path.Combine(lookupPath, Path.GetFileNameWithoutExtension(libraryName) + ".dll"), out IntPtr handle);
                return handle;
            }
            DllImportResolver resolver = new DllImportResolver(ResolveCallback);
            NativeLibrary.SetDllImportResolver(typeof(PdfFileType).Assembly, resolver);
#else
            SetDllDirectory(lookupPath);
#endif
        }

        #endregion

#if !NET5_0_OR_GREATER
        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool SetDllDirectory(string lpPathName);
#endif

 

 

Edited by otuncelli
Link to comment
Share on other sites

Well first, definitely don't use SetDllDirectory. That's a process-wide setting and you run the risk of mucking with things happening in the app or other plugins!

 

As for NativeLibrary.SetDllImportResolver(), I'm not sure what alternative there is, and the documentation clearly state, "The callers of this method should register the resolver for their own assemblies only." (which your code is doing). That to me means that it would also be improper for my code (in the app, in the plugin loader) to call that method on your behalf. Especially because, for instance, let's say you publish a plugin that uses this. Then I go and fix or change something in the plugin loader and I start calling it (for plugin assemblies). That means your plugin's attempt to use this will throw an exception, and your plugin will crash. That just creates a bigger mess. I'd rather just stick to the letter of the law as laid out in that method's documentation.

 

So yes, feel free to use NativeLibrary.SetDllImportResolver() for your plugin's DLL. I'm not sure if it's a good idea for you to use it for your dependencies though, as they may need to call it themselves.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

2 hours ago, Rick Brewster said:

I'm not sure if it's a good idea for you to use it for your dependencies though, as they may need to call it themselves.

 

The documentation for SetDllImportResolver states that the DllImportResolver is only called for the assembly that registers it.

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Link to comment
Share on other sites

10 hours ago, null54 said:

 

The documentation for SetDllImportResolver states that the DllImportResolver is only called for the assembly that registers it.

 

I think he meant the assembly that DllImports are happening. I too think that'd be more appropriate place but I'm merging all managed dependencies. In this case, I think it'd better choice to put the resolver code in main assembly. For example, I might have other native library dependencies that are in different paths. If each dependency sets resolver for their own DllImports, after I merge, it'd be throwing InvalidOperationException.

 

From MSDN:

Only one resolver can be registered per assembly. Trying to register a second resolver fails with an InvalidOperationException.

 

Link to comment
Share on other sites

23 hours ago, null54 said:

 

The documentation for SetDllImportResolver states that the DllImportResolver is only called for the assembly that registers it.

 

It's called for the assembly that the resolver is registered for. The distinction is important -- from looking at SetDllImportResolver()'s code/disassembly, it's not looking at the method that calls it, only the assembly parameter that it's given. So you can call it for another assembly and it won't verify or do anything differently.

  • Like 1

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

@otuncelli

@VcSaJen

 

Okay I figured out the native DLL loading problem: it's due to a typo in my code 🤦🏼‍♂️ Once I change this line of code, the JPEGXL + ImageMagick test plugin seems to work fine. It's fixed for 4.3.3. I'll try and get a preview build out soon so you have something to work with.

 

You should not need to use SetDllImportResolver.

 

image.png

  • Like 1

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

I'm deploying .dlc files together with the .dll (i.e. ImZIP.FileType.dll and ImZIP.FileType.dlc). ImZIP is a slightly extended PropertyBasedFileType. The .dlc is not contained in the deps file. Is a folder required and a deps file?

midoras signature.gif

Link to comment
Share on other sites

Yes, you should still use a folder and include a .deps.json. The .deps.json makes sure that the right assembly is loaded from the correct location.

 

If a plugin requires multiple files to be installed, they should be put into a folder. It's too easy to end up re-using the same name (like accidentally in another plugin*), or for someone to have mismatched files from different versions of the plugin. Packaging things up just makes things a lot cleaner.

 

I'd like to see the .deps.json you end up with. I'm not sure VS is doing a good job with them right now. The one I saw included all of PDN's transitive dependencies, including Crc32.NET.dll and Newtonsoft.Json.dll, for instance.

 

* there's a plugin out there that includes a dll called CommonControls.dll. that's not ambiguous at all ...

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

No idea, why all these targets (i.e. WIA) are included.

 

ImZIP.FileType.deps.json:

 

Spoiler

{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v5.0",
    "signature": ""
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v5.0": {
      "ImZIP.FileType/1.4.0": {
        "dependencies": {
          "PaintDotNet.Base": "4.303.7965.39135",
          "PaintDotNet.Core": "4.303.7965.39135",
          "PaintDotNet.Data": "4.303.7965.39135"
        },
        "runtime": {
          "ImZIP.FileType.dll": {}
        }
      },
      "PaintDotNet.Base/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Base.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "PaintDotNet.Core/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Core.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "PaintDotNet.Data/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Data.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "Newtonsoft.Json/13.0.0.0": {
        "runtime": {
          "Newtonsoft.Json.dll": {
            "assemblyVersion": "13.0.0.0",
            "fileVersion": "13.0.1.25517"
          }
        }
      },
      "Crc32.NET/1.0.0.0": {
        "runtime": {
          "Crc32.NET.dll": {
            "assemblyVersion": "1.0.0.0",
            "fileVersion": "1.2.0.5"
          }
        }
      },
      "PaintDotNet.SystemLayer/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.SystemLayer.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "CommunityToolkit.HighPerformance/7.0.0.0": {
        "runtime": {
          "CommunityToolkit.HighPerformance.dll": {
            "assemblyVersion": "7.0.0.0",
            "fileVersion": "7.0.3.1"
          }
        }
      },
      "PaintDotNet.Framework/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Framework.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "TerraFX.Interop.Windows/10.0.20348.0": {
        "runtime": {
          "TerraFX.Interop.Windows.dll": {
            "assemblyVersion": "10.0.20348.0",
            "fileVersion": "10.0.20348.0"
          }
        }
      },
      "Interop.WIA/1.0.0.0": {
        "runtime": {
          "Interop.WIA.dll": {
            "assemblyVersion": "1.0.0.0",
            "fileVersion": "1.0.0.0"
          }
        }
      },
      "PaintDotNet.Plugins.Compatibility/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Plugins.Compatibility.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "PaintDotNet.SystemLayer.Native.x64/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.SystemLayer.Native.x64.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "System.Private.CoreLib/5.0.0.0": {
        "runtime": {
          "System.Private.CoreLib.dll": {
            "assemblyVersion": "5.0.0.0",
            "fileVersion": "5.0.1121.47308"
          }
        }
      },
      "PaintDotNet.Resources/4.303.7965.39135": {
        "runtime": {
          "PaintDotNet.Resources.dll": {
            "assemblyVersion": "4.303.7965.39135",
            "fileVersion": "4.303.7965.39135"
          }
        }
      },
      "System.Windows.Forms.Legacy/0.1.1.0": {
        "runtime": {
          "System.Windows.Forms.Legacy.dll": {
            "assemblyVersion": "0.1.1.0",
            "fileVersion": "0.1.1.0"
          }
        }
      }
    }
  },
  "libraries": {
    "ImZIP.FileType/1.4.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Base/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Core/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Data/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "Newtonsoft.Json/13.0.0.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "Crc32.NET/1.0.0.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.SystemLayer/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "CommunityToolkit.HighPerformance/7.0.0.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Framework/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "TerraFX.Interop.Windows/10.0.20348.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "Interop.WIA/1.0.0.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Plugins.Compatibility/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.SystemLayer.Native.x64/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "System.Private.CoreLib/5.0.0.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "PaintDotNet.Resources/4.303.7965.39135": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    },
    "System.Windows.Forms.Legacy/0.1.1.0": {
      "type": "reference",
      "serviceable": false,
      "sha512": ""
    }
  }
}

 

 

midoras signature.gif

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...