Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to succeed in using NuGet packages with native binaries (like e.g. SkiaSharp) #397

Open
UweKeim opened this issue Dec 3, 2024 · 6 comments

Comments

@UweKeim
Copy link

UweKeim commented Dec 3, 2024

Introduction

There are several NuGet packages that ship with a managed assembly and also with additional native binaries that are copied to the "runtimes" folder below the binaries.

SkiaSharp is an example of such a library:

C:\P\MYPROJECT\SOURCE\WEBAPP\BIN\RELEASE\NET8.0\RUNTIMES
├───osx
│   └───native
│           libSkiaSharp.dylib
│
├───win-arm64
│   └───native
│           libSkiaSharp.dll
│
├───win-x64
│   └───native
│           libSkiaSharp.dll
│
└───win-x86
    └───native
            libSkiaSharp.dll

There is a "SkiaSharp.dll" (managed) directly in "C:\P\MYPROJECT\SOURCE\WEBAPP\BIN\RELEASE\NET8.0" and the above folder structure.

In a C# application (e.g. ASP.NET Core MVC, Console or Windows Forms) the build system somehow ensures that the native binaries are copied to the output folder.

In CS-Script this is not the case. Resulting in runtime errors.


Example

As an example, consider the following minimal CS-Script file "skipasharp-test.cs"::

//css_nuget SkiaSharp
//css_nuget SkiaSharp.NativeAssets.Win32

using SkiaSharp;

class Program
{
    static void Main()
    {
        try
        {
            int width = 800;
            int height = 600;

            using var bitmap = new SKBitmap(width, height);
        }
        catch (Exception x)
        {
            Console.WriteLine(x.ToString());
            while(x.InnerException!=null)
            {
                x = x.InnerException;
                Console.WriteLine();
                Console.WriteLine(x.ToString());
            }
        }
    }
}

Running it from my Windows 11 command line with this:

css /dbg "skiasharp-test.cs"

This results in the following:

System.TypeInitializationException: The type initializer for 'SkiaSharp.SKImageInfo' threw an exception.
 ---> System.DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: Das angegebene Modul wurde nicht gefunden. (0x8007007E)
   at SkiaSharp.SkiaApi.sk_colortype_get_default_8888()
   at SkiaSharp.SkiaApi.sk_colortype_get_default_8888()
   at SkiaSharp.SKImageInfo..cctor() in /_/binding/SkiaSharp/SKImageInfo.cs:line 48
   --- End of inner exception stack trace ---
   at SkiaSharp.SKBitmap..ctor(Int32 width, Int32 height, Boolean isOpaque) in /_/binding/SkiaSharp/SKBitmap.cs:line 33
   at Program.Main() in C:\Ablage\skiasharp-test.cs:line 15

System.DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: Das angegebene Modul wurde nicht gefunden. (0x8007007E)
   at SkiaSharp.SkiaApi.sk_colortype_get_default_8888()
   at SkiaSharp.SkiaApi.sk_colortype_get_default_8888()
   at SkiaSharp.SKImageInfo..cctor() in /_/binding/SkiaSharp/SKImageInfo.cs:line 48

The exception reads (in German):

System.DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: Das angegebene Modul wurde nicht gefunden. (0x8007007E)

And in English:

System.DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: The specified module could not be found. (0x8007007E)


I honestly do think that you thought about such cases and have a solution; I simply cannot figure it out for now.

My question

Is it possible that CS-Script supports NuGet packages both with managed and native binaries as e.g. with SkiaSharp?


More information

@oleg-shilo
Copy link
Owner

It's an interesting problem.
I think the LegacyNuget: false may even work right away.
If not then it can definitely be done. NewNuget algorithm has all the data required.
It's good you raised it right now because I was planning to release it tonight.

Will have a look at it and include it in the very next release.
This will also justify the switch to the LegacyNuget: false by default

@oleg-shilo
Copy link
Owner

Hm... this package does not rely on .NET interop locator (current dir + any dir from %PATH%).
Can you give any sample call that would touch the native dll so can see what can be done?

I tried Console.WriteLine(typeof(PlatformLock)); but it succeeds even if I delete runtimes folder

@oleg-shilo
Copy link
Owner

oleg-shilo commented Dec 4, 2024

OK, this one does:

using SkiaSharp;
using static System.Reflection.BindingFlags;

var type = typeof(GRGlInterface);
var method = type.GetMethod("CreateGl", Static | NonPublic);
var gl = method.Invoke(null, new object[0]);

Console.WriteLine("Success");

@oleg-shilo
Copy link
Owner

OK their probing algorithm does respect %PATH% probing so it's doable. But at runtime the script engine needs to know which OS specific subdir to add to the %PATH%.
Do you know if runtimes\win-x64\native is the standard way of encoding native dependencies in the packages?

@UweKeim
Copy link
Author

UweKeim commented Dec 4, 2024

Unfortunately I do not know😢. This is an area, I have literally nearly zero experiences in. Those ".targets" files are pure mystery to me.

Honestly, I found a workaround to not require SkipaSharp for now, so it is not an issue for me for now.

I just wanted to let you know that the NuGet support is limited in this specific area.

Another package is e.g. "Microsoft.SqlServer.Compact" (totally outdated, but I still need to use it) for which I found a workaround by using some reflection:

if(!_didSetBinariesPath)
{
	_didSetBinariesPath = true;
	
	var privateFolderPath = Afx.ReplacePlaceholders(
		@"${ScriptFolderPath}\..\..\..\..\..\_References\Direct\SqlCe\4.0.8876.1\NativeBinaries");
	
	var sqlCeAssembly = typeof(SqlCeConnection).Assembly;
	
	var nativeMethodsType = sqlCeAssembly.GetType("System.Data.SqlServerCe.NativeMethods");
	if (nativeMethodsType == null)
	{
		throw new Exception("[SQL-CE] Die Klasse 'NativeMethods' konnte nicht gefunden werden.");
	}

	var loadMethod = nativeMethodsType.GetMethod(
		"LoadNativeBinariesFromPrivateFolder",
		BindingFlags.NonPublic | BindingFlags.Static);

	if (loadMethod == null)
	{
		throw new Exception("[SQL-CE] Die Methode 'LoadNativeBinariesFromPrivateFolder' konnte nicht gefunden werden.");
	}

	var result = (bool)loadMethod.Invoke(null, new object[] { privateFolderPath });
}

@oleg-shilo
Copy link
Owner

oleg-shilo commented Dec 4, 2024

OK, I got teh answer it is in fact strictly embedded in the package structure (part of the nuget spec) so it's fully possible to achieve.

<package_root>/
├── lib/
│   └── <target_framework>/         # For managed assemblies
├── runtimes/
│   ├── win-x64/                   # Native DLLs for Windows 64-bit
│   │   └── native/
│   │       └── your_native.dll
│   ├── win-x86/                   # Native DLLs for Windows 32-bit
│   │   └── native/
│   │       └── your_native.dll
│   ├── linux-x64/                 # Native DLLs for Linux 64-bit
│   │   └── native/
│   │       └── your_native.so
│   ├── osx-x64/                   # Native DLLs for macOS 64-bit
│   │   └── native/
│   │       └── your_native.dylib
│   └── ...
├── build/                         # MSBuild props and targets
├── tools/                         # Tools for the developer
├── content/                       # Additional content to include in projects
└── ...

oleg-shilo added a commit that referenced this issue Dec 11, 2024
- #397: How to succeed in using NuGet packages with native binaries (like e.g. SkiaSharp)
  (Added support for nuget package native assets)
- script compilation cache now stores probing dirs to allow recreation of PATH environemnt variable during the cached execution (e.g. to cover nuget native assets)
oleg-shilo added a commit that referenced this issue Dec 14, 2024
### CLI
- #396 Some NuGet packages are not recognized and not referenced
- #397: How to succeed in using NuGet packages with native binaries (like e.g. SkiaSharp)
- Added new command `-list` for printing all currently running scripts.
- Added support for nuget package native assets
- LegacyNugetSupport by defauls made false
- script compilation cache now stores probing dirs to allow recreation of PATH environemnt variable during the cached execution (e.g. to cover nuget native assets)
- Added support for `-self-install` command to set global `CSSCRIPT_ROOT` envar.
- Updated `//css_nuget` syntax CLI documentation

### CSScriptLib
- no changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants