SevenZipSharp icon indicating copy to clipboard operation
SevenZipSharp copied to clipboard

This nuget package is abnormal in. Net single file publishing

Open wf-soft opened this issue 4 years ago • 4 comments

When this nuget package is published independently in .Net5, WPF and other applications, the application will not run

public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { SevenZipBase.SetLibraryPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "7z.dll")); base.OnStartup(e);

    }
}

wf-soft avatar Feb 25 '21 10:02 wf-soft

@squid-box Is there a solution

wf-soft avatar Feb 25 '21 10:02 wf-soft

I've never tested it with .NET 5 or single-file publishing. Could you elaborate a bit:

  • What's the error you're getting?
  • Does it work when you're not using single-file publishing?

squid-box avatar Mar 04 '21 23:03 squid-box

While I'm not the original poster, I've been trying to use various extraction libraries in a single file .NET 5 app, and have come across this issue before.

When using single file publishing, native libraries are not included in the single file bundle by default. This means 7z.dll will not automatically be included. If 7z.dll is manually placed next to the .exe, it should work correctly, and there is no problem. However, this isn't ideal as it's no longer a single file.

If you add <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> to the project file, the single file bundle will include the 7z.dll file, and it will be extracted to a temporary directory when running the published .exe.

This doesn't help resolve the problem though, as the single file bundle specifically avoids having a known extraction location. For debugging, you can inspect %localappdata%\Temp\.net to view where the files are extracted to, but this shouldn't be relied on programmatically. You also cannot use a relative path, as this is relative to the current directory, which is usually the one containing the .exe (the single file bundle) and not the extraction directory. Without knowing the path to where 7z.dll was extracted, it's not possible to call SetLibraryPath.

I think the intended way to load a native library when using <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> is to use the NativeLibrary.Load method, which is available in .NET Core 3.0, 3.1, and .NET 5. It does not appear to be available in .NET Standard.

To load the library from the extraction directory, a relative path can be passed to the method, like so:

NativeLibrary.Load("7z.dll", Assembly.GetEntryAssembly(), null);

The Assemby.GetEntryAssembly() part seems necessary to use the single-file app's customised loader, that knows to look in the extraction directory. NativeLibrary.Load can therefore find 7z.dll where other methods fail.

It doesn't look very easy to incorporate this into SevenZipSharp while it targets only .NET 4.5 and .NET Standard 2.0, but perhaps an alternative is to allow the user to manage the loading and unloading of unmanaged libraries themselves? They could then hook in to override the default loading, and use the NativeLibrary.Load and NativeLibrary.Free methods as needed.

mgpreston avatar Mar 12 '21 21:03 mgpreston

I think you folks are honestly overcomplicating things. I'm pretty certain this issue could be resolved with a simple 2 line patch here: https://github.com/squid-box/SevenZipSharp/blob/8463abcdeec2f3448b46ce53a07a5cca1dd62541/SevenZip/LibraryManager.cs#L38-L51

Under a .NET 5 style Single File publish, a number of APIs such as GetExecutingAssembly().Location are supposed to return empty string as their location is somewhat indeterminate as they are embedded in the Bundle.

The intended solution as I've come to understand it is to use the Assembly Resolver's base directory (AppContext.BaseDirectory) to resolve for the location of the single file bundle.

So as per the document above, you should test Assembly.Location for empty string, and if it is an empty string, instead use AppContext.BaseDirectory. This should be a 1/2 line fix. It might require a separate multi target build for net5.0 framework, but it should be an easy fix.


Here's a Temporary Library Consumer-side Workaround:

ConfigurationManager.AppSettings["7zLocation"] = Path.Combine(AppContext.BaseDirectory, Environment.Is64BitProcess ? "7z64.dll" : "7z.dll");

Sewer56 avatar Dec 18 '21 00:12 Sewer56