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

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

  • 4 months later...

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+ 😮

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

  • BoltBait unfeatured this topic
  • 4 weeks later...

I'm using ILRepack to merge some ComputeSharp dlls into my plugin DLL and that's working for me (thanks @BoltBait I saw how to use ILRepack in the CodeLab source on GitHub). But I'm stuck on trimming. The trim option in VS only wants to work for a native exe, not for a portable DLL. Do you guys have a suggestion for a tool or method to trim my plugin DLL's dependencies?

Link to comment
Share on other sites

1 hour ago, Robot Graffiti said:

Do you guys have a suggestion for a tool or method to trim my plugin DLL's dependencies?

 

I am not sure if ILRepack supports trimming the merged assemblies, I did not find any mention of it in the documentation.

 

My PSFilterPdn plugin uses ILMerge.Fody to merge the ComunityToolkit.HighPerformance DLL.

ILMerge.Fody trims unused types from the merged assembly by default.

  • Like 1

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

11 hours ago, Robot Graffiti said:

I'm using ILRepack to merge some ComputeSharp dlls into my plugin DLL and that's working for me (thanks @BoltBait I saw how to use ILRepack in the CodeLab source on GitHub). But I'm stuck on trimming. The trim option in VS only wants to work for a native exe, not for a portable DLL. Do you guys have a suggestion for a tool or method to trim my plugin DLL's dependencies?

 

For my plugin pack, I'm using ILRepack (to create a single release .DLL file when using ComputeSharp--not for debug) after trimming with ILLink.  This method was taught to me by user @otuncelli

 

I will leave it to him to explain how to make it work so you can give him the up-vote.

 

  • Like 1
Link to comment
Share on other sites

7 hours ago, Robot Graffiti said:

I'm using ILRepack to merge some ComputeSharp dlls into my plugin DLL and that's working for me (thanks @BoltBait I saw how to use ILRepack in the CodeLab source on GitHub). But I'm stuck on trimming. The trim option in VS only wants to work for a native exe, not for a portable DLL. Do you guys have a suggestion for a tool or method to trim my plugin DLL's dependencies?

 

You'll need to use both IL Linker Tool (included in dotnet SDK) and ILRepack for this.

 

Trimming works for DLLs as well if you call it from the command line (using dotnet exec). 

 

(You'll probably want to fix the path below)

dotnet exec "C:\Program Files\dotnet\sdk\7.0.101\Sdks\Microsoft.NET.ILLink.Tasks\tools\net7.0\illink.dll" -a "$(TargetPath)" all --trim-mode copy --action copy -d $(TargetDir) --skip-unresolved --action link "ComputeSharp.Core" --action link "ComputeShare.D2D1" -out "$(TargetDir)output"

 

Here is the post-build event you can use for your plugin: 

  <PropertyGroup>
    <!-- Set Path Variables -->
    <PdnRoot>C:\Program Files\paint.net</PdnRoot>
    <illink>C:\Program Files\dotnet\sdk\7.0.101\Sdks\Microsoft.NET.ILLink.Tasks\tools\net7.0\illink.dll</illink>
  </PropertyGroup>

  <!-- Note: Debugging merged/trimmed assemblies can be problematic. So I set a condition that it only works with Release builds. -->
  <Target Name="TrimAndMerge" AfterTargets="PostBuildEvent" Condition="'$(ConfigurationName)' == 'Release'">
    <!-- 1st Step: Trimming with ILLink -->
    <Exec Command="dotnet exec &quot;$(illink)&quot; -a &quot;$(TargetPath)&quot; all &#45;&#45;trim-mode copy &#45;&#45;action copy -d $(TargetDir) &#45;&#45;skip-unresolved &#45;&#45;action link &quot;ComputeSharp.Core&quot; &#45;&#45;action link &quot;ComputeShare.D2D1&quot; -out &quot;$(TargetDir)output&quot;" />
    <!-- 2nd Step: Merge with ILRepack -->
    <Exec Command="ilrepack /internalize /union &quot;$(TargetDir)output\$(TargetName).dll&quot; &quot;$(TargetDir)output\ComputeSharp.Core.dll&quot; &quot;$(TargetDir)output\ComputeSharp.D2D1.dll&quot; /lib:&quot;$(PdnRoot)&quot; /out:&quot;$(TargetPath)&quot;" />
    <!-- 3rd Step: Remove the output directory and its contents created by illink -->
    <Delete Files="$(TargetDir)output" ContinueOnError="false" />
    <RemoveDir Directories="$(TargetDir)output" ContinueOnError="false" />
  </Target>

 

  • Like 1
  • Upvote 1
Link to comment
Share on other sites

46 minutes ago, BoltBait said:

FYI, you'll need to edit your .csproj file to put those sections in it.  I'm not sure you can do that with the Visual Studio UI.

 

Just double-click on the project in Solution Explorer and edit it

  • 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

13 hours ago, Robot Graffiti said:

I'm using ILRepack to merge some ComputeSharp dlls into my plugin DLL and that's working for me (thanks @BoltBait I saw how to use ILRepack in the CodeLab source on GitHub). But I'm stuck on trimming. The trim option in VS only wants to work for a native exe, not for a portable DLL. Do you guys have a suggestion for a tool or method to trim my plugin DLL's dependencies?

 

Would you be willing to share what you're working on? I'm excited you're using the new GpuEffect infrastructure but there's a lot of new stuff, and a lot of subtlety and nuance (like straight vs. premultiplied alpha management, as @BoltBait can attest). I can help make sure everything is correct in the way you intend.

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 porting my Bold Brushstrokes plugin to be a PropertyBasedGpuImageEffect. The new version draws the brushstrokes into a Direct2D CommandList and then uses a graph of IDeviceEffect shaders to copy the alpha channel from the original image and do some other effects.

Edited by Robot Graffiti
Link to comment
Share on other sites

3 hours ago, Robot Graffiti said:

I'm porting my Bold Brushstrokes plugin to be a PropertyBasedGpuImageEffect. The new version draws the brushstrokes into a Direct2D CommandList and then uses a graph of IDeviceEffect shaders to copy the alpha channel from the original image and do some other effects.

 

That sounds really cool -- please feel free to post a "beta" version here in the Plugin Developer's Central and we can help you make sure it's doing all the right things.

  • 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

  • 6 months later...

The ILLink command that @otuncelli posted does not work correctly for some of the types in CommunityToolkit.HighPerformace or TerraFX.Interop.Windows.

The linker removes the IDisposable implementation from types that implement it (e.g. ArrayPoolBufferWriter<T> or ComPtr<T>),  resulting in a crash when the runtime tries to call the now missing Dispose() method.

 

I have not been able to figure out the ILLink command to keep those references when trimming.

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

58 minutes ago, Rick Brewster said:

What is the command line that you're using?

 

<SDKRoot>$(NetCoreRoot)sdk\$(NETCoreSdkVersion)</SDKRoot>
<CurrentRuntimeId>net$([System.Version]::Parse($(NETCoreSdkVersion)).ToString(2))</CurrentRuntimeId>

<illink>$(SDKRoot)\Sdks\Microsoft.NET.ILLink.Tasks\tools\$(CurrentRuntimeId)\illink.dll</illink>
<!-- All of these warnings are related to the various Paint.NET DLLs, which are not being trimmed -->
<ILLinkIgnoredWarnings>IL2026;IL2028;IL2034;IL2050;IL2055;IL2057;IL2060;IL2065;IL2066;IL2067;IL2070;IL2075;IL2080;IL2104;IL2121</ILLinkIgnoredWarnings>

dotnet exec &quot;$(illink)&quot; -a &quot;$(TargetPath)&quot; all &#45;&#45;trim-mode copy &#45;&#45;action copy -d $(TargetDir) &#45;&#45;skip-unresolved &#45;&#45;action link &quot;CommunityToolkit.HighPerformance&quot; &#45;&#45;action link &quot;TerraFX.Interop.Windows&quot; -out &quot;$(TargetDir)trim&quot; &#45;&#45;nowarn $(ILLinkIgnoredWarnings)

 

The command is a slightly modified version of the one that otuncelli posted above, I changed the link targets and suppressed the trim warnings from the Paint.NET DLLs.

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

Here's the command-line I'm using when I build Paint.NET. There's a lot more stuff in there, mostly telling it to skip the runtime/framework DLLs, but notice at the very end I also have a -d for the runtime and desktop framework. What happens if you add in those? (hardcoding the paths for now of course)

 

dotnet exec "C:\Program Files\dotnet\sdk\7.0.400\Sdks\Microsoft.NET.ILLink.Tasks\tools\net7.0\illink.dll" -a D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r\paintdotnet.dll all --trim-mode copy --action copy --action copy PaintDotNet.Base --action copy PaintDotNet.Collections --action copy PaintDotNet.ComponentModel --action copy PaintDotNet.Core --action copy PaintDotNet.Data --action copy PaintDotNet.Effects --action copy PaintDotNet.Effects.Core --action copy PaintDotNet.Effects.Gpu --action copy PaintDotNet.Effects.Legacy --action copy PaintDotNet.Framework --action copy PaintDotNet.Fundamentals --action copy PaintDotNet.ObjectModel --action copy PaintDotNet.Plugins.Compatibility --action copy PaintDotNet.Primitives --action copy PaintDotNet.PropertySystem --action copy PaintDotNet.Resources --action copy PaintDotNet.Runtime --action copy PaintDotNet.SystemLayer --action copy PaintDotNet.Systrace --action copy PaintDotNet.UI --action copy PaintDotNet.Windows --action copy PaintDotNet.Windows.Core --action copy PaintDotNet.Windows.Framework --action copy AvifFileType --action copy DdsFileTypePlus --action copy WebPFileType --action link CommunityToolkit.HighPerformance --action link ComputeSharp.Core --action link ComputeSharp.D2D1 --action link Crc32.NET --action link K4os.Compression.LZ4 --action link Newtonsoft.Json --action link PhotoSauce.MagicScaler --action link PointerToolkit.TerraFX.Interop.Windows --action link TerraFX.Interop.Windows --action skip Interop.WIA --action skip Mono.Cecil --action skip Mono.Cecil.Mdb --action skip Mono.Cecil.Pdb --action skip Mono.Cecil.Rocks --action skip PointerToolkit --action skip System.Reflection.MetadataLoadContext --action skip System.Windows.Forms.Legacy --action skip Microsoft.CSharp --action skip Microsoft.VisualBasic.Core --action skip Microsoft.VisualBasic --action skip Microsoft.Win32.Primitives --action skip Microsoft.Win32.Registry --action skip System.AppContext --action skip System.Buffers --action skip System.Collections.Concurrent --action skip System.Collections.Immutable --action skip System.Collections.NonGeneric --action skip System.Collections.Specialized --action skip System.Collections --action skip System.ComponentModel.Annotations --action skip System.ComponentModel.DataAnnotations --action skip System.ComponentModel.EventBasedAsync --action skip System.ComponentModel.Primitives --action skip System.ComponentModel.TypeConverter --action skip System.ComponentModel --action skip System.Configuration --action skip System.Console --action skip System.Core --action skip System.Data.Common --action skip System.Data.DataSetExtensions --action skip System.Data --action skip System.Diagnostics.Contracts --action skip System.Diagnostics.Debug --action skip System.Diagnostics.DiagnosticSource --action skip System.Diagnostics.FileVersionInfo --action skip System.Diagnostics.Process --action skip System.Diagnostics.StackTrace --action skip System.Diagnostics.TextWriterTraceListener --action skip System.Diagnostics.Tools --action skip System.Diagnostics.TraceSource --action skip System.Diagnostics.Tracing --action skip System.Drawing.Primitives --action skip System.Drawing --action skip System.Dynamic.Runtime --action skip System.Formats.Asn1 --action skip System.Formats.Tar --action skip System.Globalization.Calendars --action skip System.Globalization.Extensions --action skip System.Globalization --action skip System.IO.Compression.Brotli --action skip System.IO.Compression.FileSystem --action skip System.IO.Compression.ZipFile --action skip System.IO.Compression --action skip System.IO.FileSystem.AccessControl --action skip System.IO.FileSystem.DriveInfo --action skip System.IO.FileSystem.Primitives --action skip System.IO.FileSystem.Watcher --action skip System.IO.FileSystem --action skip System.IO.IsolatedStorage --action skip System.IO.MemoryMappedFiles --action skip System.IO.Pipes.AccessControl --action skip System.IO.Pipes --action skip System.IO.UnmanagedMemoryStream --action skip System.IO --action skip System.Linq.Expressions --action skip System.Linq.Parallel --action skip System.Linq.Queryable --action skip System.Linq --action skip System.Memory --action skip System.Net.Http.Json --action skip System.Net.Http --action skip System.Net.HttpListener --action skip System.Net.Mail --action skip System.Net.NameResolution --action skip System.Net.NetworkInformation --action skip System.Net.Ping --action skip System.Net.Primitives --action skip System.Net.Quic --action skip System.Net.Requests --action skip System.Net.Security --action skip System.Net.ServicePoint --action skip System.Net.Sockets --action skip System.Net.WebClient --action skip System.Net.WebHeaderCollection --action skip System.Net.WebProxy --action skip System.Net.WebSockets.Client --action skip System.Net.WebSockets --action skip System.Net --action skip System.Numerics.Vectors --action skip System.Numerics --action skip System.ObjectModel --action skip System.Private.CoreLib --action skip System.Private.DataContractSerialization --action skip System.Private.Uri --action skip System.Private.Xml.Linq --action skip System.Private.Xml --action skip System.Reflection.DispatchProxy --action skip System.Reflection.Emit.ILGeneration --action skip System.Reflection.Emit.Lightweight --action skip System.Reflection.Emit --action skip System.Reflection.Extensions --action skip System.Reflection.Metadata --action skip System.Reflection.Primitives --action skip System.Reflection.TypeExtensions --action skip System.Reflection --action skip System.Resources.Reader --action skip System.Resources.ResourceManager --action skip System.Resources.Writer --action skip System.Runtime.CompilerServices.Unsafe --action skip System.Runtime.CompilerServices.VisualC --action skip System.Runtime.Extensions --action skip System.Runtime.Handles --action skip System.Runtime.InteropServices.JavaScript --action skip System.Runtime.InteropServices.RuntimeInformation --action skip System.Runtime.InteropServices --action skip System.Runtime.Intrinsics --action skip System.Runtime.Loader --action skip System.Runtime.Numerics --action skip System.Runtime.Serialization.Formatters --action skip System.Runtime.Serialization.Json --action skip System.Runtime.Serialization.Primitives --action skip System.Runtime.Serialization.Xml --action skip System.Runtime.Serialization --action skip System.Runtime --action skip System.Security.AccessControl --action skip System.Security.Claims --action skip System.Security.Cryptography.Algorithms --action skip System.Security.Cryptography.Cng --action skip System.Security.Cryptography.Csp --action skip System.Security.Cryptography.Encoding --action skip System.Security.Cryptography.OpenSsl --action skip System.Security.Cryptography.Primitives --action skip System.Security.Cryptography.X509Certificates --action skip System.Security.Cryptography --action skip System.Security.Principal.Windows --action skip System.Security.Principal --action skip System.Security.SecureString --action skip System.Security --action skip System.ServiceModel.Web --action skip System.ServiceProcess --action skip System.Text.Encoding.CodePages --action skip System.Text.Encoding.Extensions --action skip System.Text.Encoding --action skip System.Text.Encodings.Web --action skip System.Text.Json --action skip System.Text.RegularExpressions --action skip System.Threading.Channels --action skip System.Threading.Overlapped --action skip System.Threading.Tasks.Dataflow --action skip System.Threading.Tasks.Extensions --action skip System.Threading.Tasks.Parallel --action skip System.Threading.Tasks --action skip System.Threading.Thread --action skip System.Threading.ThreadPool --action skip System.Threading.Timer --action skip System.Threading --action skip System.Transactions.Local --action skip System.Transactions --action skip System.ValueTuple --action skip System.Web.HttpUtility --action skip System.Web --action skip System.Windows --action skip System.Xml.Linq --action skip System.Xml.ReaderWriter --action skip System.Xml.Serialization --action skip System.Xml.XDocument --action skip System.Xml.XPath.XDocument --action skip System.Xml.XPath --action skip System.Xml.XmlDocument --action skip System.Xml.XmlSerializer --action skip System.Xml --action skip System --action skip WindowsBase --action skip mscorlib --action skip netstandard --action skip Accessibility --action skip DirectWriteForwarder --action skip Microsoft.VisualBasic.Forms --action skip Microsoft.Win32.Registry.AccessControl --action skip Microsoft.Win32.SystemEvents --action skip PresentationCore --action skip PresentationFramework-SystemCore --action skip PresentationFramework-SystemData --action skip PresentationFramework-SystemDrawing --action skip PresentationFramework-SystemXml --action skip PresentationFramework-SystemXmlLinq --action skip PresentationFramework.Aero --action skip PresentationFramework.Aero2 --action skip PresentationFramework.AeroLite --action skip PresentationFramework.Classic --action skip PresentationFramework.Luna --action skip PresentationFramework.Royale --action skip PresentationFramework --action skip PresentationUI --action skip ReachFramework --action skip System.CodeDom --action skip System.Configuration.ConfigurationManager --action skip System.Design --action skip System.Diagnostics.EventLog.Messages --action skip System.Diagnostics.EventLog --action skip System.Diagnostics.PerformanceCounter --action skip System.DirectoryServices --action skip System.Drawing.Common --action skip System.Drawing.Design --action skip System.IO.Packaging --action skip System.Printing --action skip System.Resources.Extensions --action skip System.Security.Cryptography.Pkcs --action skip System.Security.Cryptography.ProtectedData --action skip System.Security.Cryptography.Xml --action skip System.Security.Permissions --action skip System.Threading.AccessControl --action skip System.Windows.Controls.Ribbon --action skip System.Windows.Extensions --action skip System.Windows.Forms.Design.Editors --action skip System.Windows.Forms.Design --action skip System.Windows.Forms.Primitives --action skip System.Windows.Forms --action skip System.Windows.Input.Manipulations --action skip System.Windows.Presentation --action skip System.Xaml --action skip UIAutomationClient --action skip UIAutomationClientSideProviders --action skip UIAutomationProvider --action skip UIAutomationTypes --action skip WindowsFormsIntegration -d D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r -d D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r\Bundled\AvifFileType -d D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r\Bundled\DDSFileTypePlus -d D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r\Bundled\WebPFileType -d C:\Users\Rick\.nuget\packages\Microsoft.NETCore.App.runtime.win-x64\7.0.10\runtimes\win-x64\lib\net7.0 -d C:\Users\Rick\.nuget\packages\Microsoft.WindowsDesktop.App.runtime.win-x64\7.0.10\runtimes\win-x64\lib\net7.0 --skip-unresolved --reduced-tracing --nowarn IL2026 --nowarn IL2046 --nowarn IL2050 --nowarn IL2057 --nowarn IL2065 --nowarn IL2067 --nowarn IL2070 --nowarn IL2072 --nowarn IL2075 --nowarn IL2080 --nowarn IL2087 --nowarn IL2091 --nowarn IL2094 --nowarn IL2096 -out D:\src\pdn\src\Setup\Release\staging\zip.scd.x64.r2r\__illink

 

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've used "Costura.Fody" in many different projects so far. It is as easy as adding the NuGet package to your project, and *poof* the next build should yield one big DLL instead of multiple small ones, no questions asked. At least that's been my experience so far.

 

Installing the package will create a "FodyWeavers.xml" config file and add it to your project plus an xsd (XML schema definition for XML validation). Don't worry about those. It essentially contains nothing but an empty configuration node, which you could edit to manage linking to a finer detail, but the default config yielded from that empty config should be fine for most people and use cases.

 

Also, I wanted to address the concerns about "loading a shared library twice":
The proper way to distribute actual shared libraries would be to register them in the GAC (global assembly cache) which is a folder full of DLLs in all versions ever registered, that the assembly resolver looks into to find DLLs of a specific version [range]. Doing this however requires elevated process rights upon installation, and I think also special preparations for the DLLs (namely signing them with strong name keys, but then again IIRC only for .NET Framework not for .NET Core bla bla bla). Long story short, it's stuff that is a lot to ask for from your hobbyist programmer.

 

So the main benefit of "sharing" has become not so much about RAM conservation but rather about code deduplication. A shared library from the perspective of a programmer is often just generic code that they use in multiple projects, so it's a library of shared code, that you only needed to write once, and maintain once, and bugfix once, etc. for all the projects to benefit from the work. Now that computer memory (permanent or RAM) has been increasing by orders of magnitude, loading the same DLL three or four times in isolated contexts is itching the perfectionist's brain because "this is not how it should be", but it's rarely ever a problem in the real world.

Link to comment
Share on other sites

The GAC no longer exists, that is not how you deal with shared libraries anymore.

 

The memory use savings is not really a big deal in this scenario (app w/ plugins). The much more important issues are versioning, isolation, and trimming. Paint.NET uses trimmed versions of some of its libraries (e.g. ComputeSharp, TerraFX.Interop.Windows). If a plugin were to compile against their own copy (via nuget) but then bind to the copy that Paint.NET has (at runtime), they wouldn't necessarily be using the same things. Many classes and other elements would have been removed while trimming, and the plugin would just crash.

 

Versioning and isolation go together. Paint.NET uses various nuget packages / libraries, but if those were available for a plugin to use them Paint.NET would be locked into a specific version of that library, or could only update to a 100% compatible version of it. So I wouldn't be able to update ComputeSharp.D2D1 from v2.0 to v2.1 or v3.0, etc. This would be very very bad. I also wouldn't be able to remove a nuget package / library. So it's important for Paint.NET to be able to have "private references". This barrier is enforced by the custom assembly resolution logic in Paint.NET's use of AssemblyLoadContexts.

 

It's also important for two different plugins, which are using the same library but different versions of that library, to not bind to the same DLL. They need their own copy to be loaded so that things will work correctly.

 

Really the notion of "shared library" is now obsolete in the case of Paint.NET. Do not think in those terms. There is no sharing. The RAM conservation / code deduplication is not really important these days (as you point out), but if it does become an issue then it is possible for Paint.NET to do some deduplication magic and only load one copy of some library that strongly benefits from it. For instance, let's say you have 1,000 plugins that all want to use an untrimmed 17MB TerraFX.Interop.Windows.dll, and they're all the same version, and bit-for-bit identical. Obviously those plugins should have been built with trimming, but if that isn't the case then it's a useful problem to tackle.

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

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.

×
×
  • Create New...