-
Notifications
You must be signed in to change notification settings - Fork 546
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
[FEATURE] Native library name versioning #1252
Comments
I'll see what we can do. This is a real problem, but we might break a lot of people if we just change the names. The pinvoke doesn't really matter, so that is easy. But, this is more the user scripts and things to copy files might stop working. But, we do need to do this to support cases where side-by-side loading is required. We might have to wait for the v2 where we can be a little more break-y with file names. I'll move it into that project now so we can make sure we address this. |
Doing a few tests, seems that this Looks really ugly, but I suppose good looks are not what we are going for here. |
I think I may have found a nice way to actually solve 2 big issues... First, this issue. Supporting the loading of two versions of libSkiaSharp.* Second, #713. Supporting the actually, intelligent loading of 32/64 bit native binaries. This is the hacked code for a library... I would just use this on using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Library
{
public class Class1
{
public static int Act() => (test_func ??= (Load<test_delegate>())).Invoke();
static IntPtr libraryHandle;
private static T Load<T>()
{
// TODO: proper loading logic here
string assemblyPath = Assembly.GetAssembly(typeof(Class1)).Location;
if (!string.IsNullOrEmpty(assemblyPath))
assemblyPath = Path.GetDirectoryName(assemblyPath);
else
assemblyPath = Directory.GetCurrentDirectory();
// TODO: A default fallback of loading "Native" as in the [DllImport] reverts back to the default logic
libraryHandle = UnsafeNativeMethods.LoadLibrary(Path.Combine(assemblyPath, "Native.dll"));
var ptr = UnsafeNativeMethods.Win32GetProcAddress(libraryHandle, "test");
return Marshal.GetDelegateForFunctionPointer<T>(ptr);
}
private static test_delegate test_func;
[DllImport("Native", CallingConvention = CallingConvention.Cdecl)]
private static extern int test();
}
static class UnsafeNativeMethods
{
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("Kernel32.dll", EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr Win32GetProcAddress(IntPtr hModule, string lpProcName);
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int test_delegate();
} |
Don't attempt loading nuget-shipped libraries with LoadLibrary/dlsym. That will break loading those from |
Ah, yeah, thanks. I am just doing this for .NET Framework now, so we should be OK. At least this will fix the issues with the 32/64 bit issues on desktop. Things like VS will need to be build as a netfx/net45+ library. With this CoreCLR work, not sure if this change is actually helpful... But, at least we fixed one issue :| |
I think we are OK with .NET Core App as that uses the runtimes folder and we never have to worry about anything. I think this issue only really exists with netfx. Unless I am mistaken. If you are using a .NET Standard library in netfx, you will have the same issue because this is all the runtime... But, since we have a separate netfx library that you should be using anyway, this should never come up. Unless you copy the netstandard dll and use it in a netfx app. But if you shouldn't do that... |
1 similar comment
I think we are OK with .NET Core App as that uses the runtimes folder and we never have to worry about anything. I think this issue only really exists with netfx. Unless I am mistaken. If you are using a .NET Standard library in netfx, you will have the same issue because this is all the runtime... But, since we have a separate netfx library that you should be using anyway, this should never come up. Unless you copy the netstandard dll and use it in a netfx app. But if you shouldn't do that... |
Confirmed this works in a VS extensions. So that is nice, and if nothing else we now have fixed 2 issues - the bitness and the VS extension. |
So I have been playing around and trying a few things. There are a few issues relating to loading of native libraries, so as I was testing things out I think I got to a decent idea. As I type this out, I will think and see what sense I make... First things first, the issues currently:
Now, the world in which we live:
And, now the features we need:
Finally, we have a few techniques:
... todo ... |
So far, a decent looking solution seems to be a combination.
Even though is may be nice to just have a .NET Standard library for both .NET Core and .NET Fx, this is not really possible as it doesn't support the delegates AND the magic. This means that we should combine 2. with 1. and 3. with 4. This leaves us with 3 modes:
Testing:
|
I can confirm that things seem to be working with, net45+, netcoreapp3.1+ and normal apps. I create a sample repo: https://github.com/mattleibow/SkiaSharpExtensionsPlayground |
Note that there are things like ILRepack with |
Also note that leaving plain |
@kekekeks Thanks for the continued feedback.
I was only referring to app store packages/bundles, which has less need for things. With regards to the IL merging, I don't think this will really be a supported way to distribute libraries. I am investigating a statically linking option for mobile apps that support it (iOS/macOS) and this will result in duplicate symbols. So far my testing has not shown any dramatic size changes, so that is why we are still using dynamic. (or it may be my compiler options for iOS...) The best option would be to have a hybrid solution with dynamic for Debug/Hot Reload and a static for Release/AOT. In this case, for the App Package format, I don't think we want to support multiple versions of SkiaSharp. If you really need to IL merge two versions of SkiaSharp into an iOS app, then maybe that is a separate case that we can look at when it comes up. If the worst comes to the worst, then you can always Mono.Cecil the attributes and change it to different versions. A bit of work, but can fit into a pretty small MSBuild task.
I have been looking at versioning the native libraries for the "desktop" platforms (Windows, macOS, Linux) to do this. I have been avoiding this because it makes life a tiny bit harder to maintain from SkiaSharp's perspective, and any additional third party scripting. But it looks to be the best way since the version number is in the name.
I assume you re talking about people pulling down a JoesCodes.SkiaSharp.Linux v60 and using it with SkiaSharp v80? I suppose adding a version number to the bits will make it more obvious (libSkiaSharp_80 not found vs a crash) But, another alternative for the last 2 is to add a new API to the native library that provides the version information of the native library. |
I prefer having an API to query the current version of the native library. Many libraries already have that out of the box. In theory the newer binding could still use an older version of the library if ABI is unchanged. HarfBuzz for example has such a promise. Most Linux distributions already come with HarfBuzz or offer a way to install the library. |
@Gillibald I was talking to the folks on the team here, and that is what they also said. Not only does it allow for direct library queries, but also for shared versions across managed libraries - just as you said. In fact, looking at the releases of SkiaSharp, many of them use the exact same git sha between releases (ie: 1.68.2, 1.68.2.1, 1.68.3) so we could potentially support this far better than just a filename version. And, we can also make use of the fact that the API is being versioned, not the library. So, for example, if the C++ library has a bug and we fix that but do not touch the C API, then we do not have to bump the second version number. There are probably going to be two numbers used to make up a version: the skia milestone and the C API iteration (restarting with each milestone). For example, the new v2.80.0 will be "80.0". If we do a managed-only update, then the native version is unchanged. If we do a new binding and add a new C API, then the native version becomes "80.1". If we then do another bugfix somewhere in the native code, but do not touch the C API, then we leave the version number as the same. With regards to backwards compat, I'll make sure that the C API never breaks for a major milestone and is always backwards compatible in that milestone. So, if the native library is "80.2", then this will work with the managed libraries that are compatible with "80.0" to "80.2". But not with a managed library built against "80.3" or later.
We do skip iteration values, but that is not so important. |
Consider the following scenario:
Solution: use library name suffix, e. g.:
libSkiaSharp.1.68.0.dll
libSkiaSharp.1.68.0.dylib
libSkiaSharp.1.68.0.so
The text was updated successfully, but these errors were encountered: