LibZ
LibZ copied to clipboard
Runtime exception merging .NET 4.6 assemblies
Hi Milosz,
This is the issue from our twitter conversation where I'm having issues trying to merge my .NET 4.6 project which uses Roslyn. Scenario 1 fails immediately when run with being unable to find the PCL ServiceStack.Interfaces.dll
, Scenario 4 is the closest to working where the App will run, but will fail after hitting "Play" button to execute a script which calls a back-end Web Service in Gistlyn.ServiceInterface.dll
with:
(FileNotFoundException) Could not load file or assembly 'Gistlyn.ServiceInterface, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
But it does work if you run the merged Gistlyn-merged.exe
within the same directory with the other .dlls, but it fails when you try to execute it stand-alone outside of the directory.
I've created a repro of this issue at Gistlyn-libz.zip.
It has all the .dlls for the app and the linbz-merge.bat
script I'm running:
COPY Gistlyn.exe Gistlyn-merged.exe
libz.exe add --libz all.libz --include *.dll --safe-load
libz.exe inject-libz --assembly Gistlyn-merged.exe --libz all.libz
libz.exe instrument --assembly Gistlyn-merged.exe --libz-resources
It it helps, the source code for the project is in ServiceStack/Gistlyn/src. Please let me know if there's any other info you need.
I will be answering in small chunks as there are multiple questions here, and because I don't know all the answer.
So, "Scenario 1" is not working with PCL. and it won't be. It is "not-a-bug" or "won't-fix" (I don't care which one). What I can do is to be more precise in documentation. For PCL use scenarios 2-7 with --safe-load
.
Why? PCLs need to be loaded from disk not from memory, the only way to force loading from disk is to use --safe-load
which is not available in on inject-dll
. A little bit more about that can be found here
Look complex so it would need a lot of you cooperation here. First, it is very important to understand how it works. It "hijacks" an event "AppDomain.ResolveAssembly" which is triggered when application cannot find an assembly. That's right - "when it cannot find an assembly". So if assembly is in application folder it does not trigger this event. I assume, and I have to emphasize that I don't know that, I just assume, that when you press "Play" it creates kind-of application domain for Roslyn and executes script inside this domain. It does have to be called "domain" it might be a "scope" or something like this. Now, when this script is executed I bet it does not use main assembly resolver, so when you reference something from inside the "scope" it does not even trigger "ResolveAssembly" on main domain.
I guess, the "scope" itself may have an event like "ResolveAssembly" but I don't know that.
I'm assuming you mean the AppDomain.AssemblyResolve event. Yeah it looks like the issue is likely due to creating our own AppDomains which are created without a custom AssemblyResolve event, is there something we can reassign this to, to hook it up with the libz custom AssemblyResolve handler? e.g. is it available in a static class using Reflection?
It can be done, but I never dealt with this myself. This guy did though.
ok thanks but I'd strongly prefer not to have a hard dependency on Libz inside my App if possible.
I've tried modifying my app to use reflection to assign all the event AssemblyResolve
event handlers registered on the CurrentDomain to the newly created AppDomain with:
var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), evidence, setup);
var fi = typeof(AppDomain).GetField("_AssemblyResolve", BindingFlags.Instance | BindingFlags.NonPublic);
if (fi != null)
{
var eventHandler = fi.GetValue(AppDomain.CurrentDomain) as ResolveEventHandler;
if (eventHandler != null)
{
foreach (ResolveEventHandler fn in eventHandler.GetInvocationList())
{
domain.AssemblyResolve += fn;
}
}
}
Which does have some effect as it changes the error from Gistlyn.ServiceInterface
to:
(FileNotFoundException) Could not load file or assembly 'Gistlyn.AppConsole, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I've also tried registering all ReflectionOnlyAssemblyResolve
events in-case you had any registered there also and I've also tried registering a fallback AssemblyResolve
event returning the Assembly of the AppHost (i.e. where Gistlyn.AppConsole lives) but that didn't help either.
It would be ideal if you could provide an official API we can hook into (i.e. easily find with reflection) so that Libz can wireup to handle the AssemblyResolve
events of any new AppDomains we create. Otherwise I suppose this use-case just isn't supported, but Libz does go further than all other merge utilities out there which chokes on either 4.6 classes or the PCL library.
I guess you are right. I should think about better support for applications with multiple app domains. I don't think you can get it wthough dependency on LibZ, though. I mean this NewDomain.AssemblyResolve needs to be configured by you in code (scanning code every use of AppDomain doesn't look right to me).
It would be great if I can call into a LibZ API to configure a new AppDomain, I could use reflection to call it to avoid the hard dependency on LibZ but this shouldn't have an impact on the API itself.
A static API like this would be nice:
LibZ.Bootstrap.LibZResolver.ConfigureAppDomain(domain);
Hello guys,
Sorry for old thread!
I really don't understand what does ivan5o write from "this guy"
Can I have example? I have to try hard but I don't have to get success. I want check if embedded Test.dll won't execute from Helper.
How do I see it.
using System;
using System.Security.AccessControl;
using System.Security.Policy;
using LibZ.Bootstrap;
namespace TestConsole
{
class Helper : MarshalByRefObject
{
internal static void InitalizeLibZFileContainer(Action startup = null)
{
LibZResolver.RegisterFileContainer("Test.dll");
if (null != startup)
LibZResolver.Startup(startup);
}
public Helper()
{
InitalizeLibZFileContainer();
}
static void Main(string[] args)
{
InitalizeLibZFileContainer(() =>
{
Console.WriteLine("Test");
Console.ReadLine();
Type test = Type.GetType("Test");
string testName = test.Assembly.FullName;
Console.WriteLine(testName);
Console.ReadLine();
});
Type type = typeof(Helper);
string assemblyName = type.Assembly.FullName;
Evidence evidence = type.Assembly.Evidence;
AppDomainSetup info = new AppDomainSetup();
AppDomain appDomain = AppDomain.CreateDomain("Helper.AppDomain", evidence, info);
appDomain.CreateInstance(assemblyName, type.FullName);
}
}
}
It is Helper.cs into exe ( console )
using System;
namespace Test
{
public class Test
{
public Test()
{
Console.WriteLine("Test from library Test.dll");
}
}
}
Test.cs into library dll.
How do I fix it?