otuncelli Posted October 24, 2021 Share Posted October 24, 2021 (edited) 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 October 24, 2021 by otuncelli Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted October 24, 2021 Author Share Posted October 24, 2021 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. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
null54 Posted October 24, 2021 Share Posted October 24, 2021 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. Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint 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 More sharing options...
otuncelli Posted October 24, 2021 Share Posted October 24, 2021 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. Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted October 25, 2021 Author Share Posted October 25, 2021 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. 1 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Rick Brewster Posted October 26, 2021 Author Share Posted October 26, 2021 @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. 1 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
midora Posted November 3, 2021 Share Posted November 3, 2021 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? Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted November 4, 2021 Author Share Posted November 4, 2021 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 ... Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
midora Posted November 4, 2021 Share Posted November 4, 2021 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": "" } } } Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted March 29, 2022 Author Share Posted March 29, 2022 There's currently a bug (up through v4.3.10) in the assembly resolver for plugins, such that if your plugin wants to use the same nuget package that Paint.NET is using, such as Newtonsoft.Json or TerraFX.Interop.Windows (etc.), and even if you're including a .deps.json, it'll just load and bind to the DLL that Paint.NET is already using (it won't load your copy). It may or may not work depending on what version you're trying to use, and what functionality you're making use of. I trim those DLLs so that they only include the classes and other things that Paint.NET uses. So if you're using the same or compatible version of the nuget package's DLL(s), and only referencing the same subset of the DLL that Paint.NET is using, it'll work fine. Otherwise you'll probably get a random crash. I'm currently fixing this for v4.4, but I could also backport it for the next v4.3.x servicing release if there's any demand for it. I think I've only seen 1 plugin that had a .deps.json where it was referencing Newtonsoft.Json, and apparently it's only making use of the same (or smaller) subset that Paint.NET is also using. You should still include a .deps.json; otherwise your plugin might fail to load or execute once this fix is shipped. The trimming part is important -- otherwise TerraFX.Interop.Windows.dll would be 15MB+ 😮 Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.