-
Notifications
You must be signed in to change notification settings - Fork 257
TypeLoadException when trying to attach to event of hosting application #109
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
Comments
Here it is: It's a test solution that condenses the relevant items of my hosting application. The solution consists of... To reproduce:
Note that running the scripts in PS1: PS2 for FastInvoker: |
What is wrong with it?In your scenario, you have two major players a host and a script. You want a script to be notified about some host events via a generic event handling mechanism. The problem is that this mechanism is not fit for the task. And I am not even talking about the notorious mem-leaking associated with the default .NET events implementation, which is based on strong (instead of weak) references. I am talking about the assembly hosting aspect of it. BTW the problem is a generic .NET assembly hosting problem and it has nothing to do with scripting as such. At startup, the host is instantiated in the static string[] loaded_scripts()
{
return AppDomain.CurrentDomain
.GetAssemblies()
.Where(x => x.FullName.StartsWith("Script")
.Select(x => x.Location)
.ToArray();
} Place the checking in your Then you build and load the second script with the same assembly name "Script, 0.0.0.0". All good. But when you try to call How to fix it?You need to stop your script assembly from being loaded in the host You can only rely on interfaces. This is your script code: using System;
using System.Windows.Forms;
using ScriptHost;
namespace Test.Issue_109.SameNamedScriptA
{
public static class Script
{
public static int Main(ScriptHost.IScriptHost host)
{
host.WriteLine("This is ScriptA");
var obj = new TypeX(host);
obj.AttachEventHandler();
host.Close();
obj.DetachEventHandler();
return (0);
}
}
public class TypeX : MarshalByRefObject, IScriptHostEventHandler
{
protected IScriptHost Host { get; set; }
public TypeX(IScriptHost host)
{
Host = host;
}
public virtual void AttachEventHandler()
{
Host.AttachEventHandler(this);
// Host.Closing += Host_Closing;
}
public virtual void DetachEventHandler()
{
Host.DetachEventHandler(this);
// Host.Closing -= Host_Closing;
}
// public void Host_Closing(object sender, FormClosingEventArgs e)
public void Host_Closing(object sender, CloseReason e)
{
Host.WriteLine(string.Format("Host is closing due to '{0}'...", e));
}
}
} And this is your hosting solution: namespace ScriptHost
{
public interface IScriptHostEventHandler
{
void Host_Closing(object sender, CloseReason e);
}
public interface IScriptHost
{
event EventHandler<FormClosingEventArgs> Closing;
void AttachEventHandler(object obj);
void DetachEventHandler(object obj);
void WriteLine(string format, params object[] args);
void Close();
}
public class Host : System.MarshalByRefObject, IScriptHost
{
List<IScriptHostEventHandler> scripts = new List<IScriptHostEventHandler>();
public event EventHandler<FormClosingEventArgs> Closing;
public void WriteLine(string format, params object[] args)
{
Debug.WriteLine(format, args);
}
public void AttachEventHandler(object obj)
{
scripts.Add((IScriptHostEventHandler)obj);
}
public void DetachEventHandler(object obj)
{
scripts.Remove((IScriptHostEventHandler)obj);
}
public void Close()
{
scripts.ForEach(s => s.Host_Closing(this, CloseReason.FormOwnerClosing));
}
}
}
...
static void Issue109()
{
var script_file_a = @"E:\...\Issue_109\SameNamedScriptA\Script.cs";
var script_file_b = @"E:\...\Issue_109\SameNamedScriptB\Script.cs";
var asm_file_a = CSScript.CompileFile(script_file_a, Path.ChangeExtension(script_file_a, ".dll"), true);
var asm_file_b = CSScript.CompileFile(script_file_b, Path.ChangeExtension(script_file_b, ".dll"), true);
IScriptHost host = new Host();
var local_dir = Path.GetDirectoryName(host.GetType().Assembly.Location);
int result;
using (SimpleAsmProbing.For(local_dir))
using (var helper = new AsmHelper(asm_file_a, null, true))
{
result = (int)helper.Invoke("Test.Issue_109.SameNamedScriptA.Script.Main", host);
}
var asms = loaded_scripts();
using (SimpleAsmProbing.For(local_dir))
using (var helper = new AsmHelper(asm_file_b, null, true))
{
result = (int)helper.Invoke("Test.Issue_109.SameNamedScriptB.Script.Main", host);
}
} |
Hmm... I agree that a script assembly should not get loaded into the host domain, what is done in this reduced test application. However, in my full application, I use a third domain:
Each time a script has finished, the Let me change "stop your script assembly being loaded in the host My host application implements a catch-all-handler which catches any exception thrown by a script and outputs full diagnostics information. Without this host-side catch-all-handler, each script would have to do that, quite inconvenient. In my application, this catch-all-handler is implemented in the The very same applies to the event handling mechanism. Whenever a script has finished, the I agree that it doesn't properly work with the I do agree that .NET standard event handling has issues, but still, it's standard, so people are used to it. And with some precautions, it does work fine. I really don't like doing things non-standard if the standard way is good enough. For future reference, I will improve the event handling of the reduced test application such it no longer uses the non-[Serializable] Though technically possible, I have my doubts that replacing the standard event handling mechanism by a non-standard approach will be beneficial for my hosting application, as I will have to document and explain more. Let's first see what you think about the more detailed explanations above. |
That's perfectly OK to have such a requirement. Simply the sample I had to work with did not have any reason to have the script assembly loaded into the caller domain. In fact I even see no reason why your ScriptRunner and the script have to be in the separate domains, as long as your host domain is untouched. If the answer is "because ScriptRunner hosts multiple scripts" then you have the same assembly probing problem that you sample demonstrated. Though I only assume your answer. I just want to state that having the script assembly loaded at the same time in multiple appdomains has its cost.
Don't overestimate the level of "being standard" for even handling. It has been known for years as a major cause of headache for GUI scenarios. If I am not mistaken WPF has ditched it in favor of a WeakreferenceEvents (Routed events) Caliburn.Micro (wonderful framework) uses WeakReferenced interfaces instead of events. The bottom line is that you have to have script assemblies loaded to the caller domain.
The choice is yours. :) |
Hmm again... Thanks for the hint on not necessarily requiring separate For the test application, the Just for curiosity, I quickly googled around for Caliburn.Micro, seems that not everybody got convinced by it: https://softwareengineering.stackexchange.com/questions/287322/how-to-choose-not-to-use-a-framework-caliburn-micro-etc-in-a-given-mvvm-appl. Regarding "my users", they typically don't do MVVM. If using a form, it's just a message box or something rather simple. For such cases, I will reconsider the I have one related question, then I suggest to close this issue: In |
This would be my guess too. That
Instead I always recommend using |
Just coudn't resist to reply. :)
There is a good bit in that discussion that you shared: "I have found both WPF and Caliburn.Micro to be quite a steep learning curve, coming from WinForms, however after some experience with both I have found them a pleasure to use as a pair." The learning extra effort is caused by the new binding paradigm but not by the framework event handling alternative (WeakReference+interface), which was the reason why I brought Caliburn.Micro as an example. In fact, I haven't found any criticism of Caliburn.Micro "events" at all. In that very thread they discuss MVVMLight as an alternative to Caliburn.Micro. And surprise surprise.... It also ignores events in favor of what they call "WeakAction". Thus that discussion only confirms my point. And on a non-technical note. There are two products that I am personally truly impressed with. Caliburn.Micro and ServiceStack. I mean truly. These two remarkable products do not stand out simply because they are practically flawless on technical level but because they truly understood the cause of the problem they are trying to solve. And they offered unorthodox direct solution to the problem. Note, I have no affiliation to these products whatsoever. :) |
I am closing the issue but not the conversation :) |
A hosted script 'A' defines a script specific class
TypeX
. ThatTypeX
accesses an event of the hosting application. The script can be started, the event can be handled. So far, all fine.Another hosted script 'B' defines another script specific class
TypeY
. ThatTypeY
also accesses the event of the hosting application. When trying the invoked that script, aTypeLoadException
is thrown:System.TypeLoadException: Der Typ "Test.ScriptB.TypeY" in der Assembly "Script, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" konnte nicht geladen werden.
Stack:
bei System.Reflection.Assembly._GetType(String name, Boolean throwOnError, Boolean ignoreCase)
bei System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo info, StreamingContext context) bei CSScriptLibrary.AsmBrowser.Invoke(Object obj, String methodName, Object[] list)
bei CSScriptLibrary.AsmRemoteBrowser.Invoke(String methodName, Object[] list)
Exception rethrown at [0]:
bei CSScriptLibrary.IAsmBrowser.Invoke(String methodName, Object[] list)
bei CSScriptLibrary.AsmHelper.Invoke(String methodName, Object[] list)
bei .InvokeAssembly(String assemblyFilePath, AsmHelper helper, String scriptMainMethod, Boolean scriptMainHasArgs)
Interestingly, this only happens if both scripts are compiled into an assembly with the same name, in this case
Script.dll
. If they are built intoScriptA.dll
andScriptB.dll
, all works fine.Additional findings:
MarshalByRefObject
or not.CSScript.ShareHostRefAssemblies
istrue
orfalse
.Script.dll
).Take your time in evaluating this issue. I can live with it for the moment, as it is a more or less rare use case of my hosting application.
The text was updated successfully, but these errors were encountered: