When you instantiate a .NET class via COM, the evidence of the created AppDomain is null.
I am trying to analyze a problem that only occurs when the AppDomain evidence is null, and I'd like to create an mcve purely in .NET, i.e., without having to register the class in COM and call it from there. For that, I need an AppDomain with an empty evidence.
How would I do that? When passing null to AppDomain.CreateDomain, the evidence of the current AppDomain is reused.
Truly, there is no way of creating an AppDomain with a null evidence in .Net 4.0 +. Is your code running on an earlier version?
The AppDomain.Evidence property returns the internal AppDomain.EvidenceNoDemand property and the decompiled source code for both getters are below,
It seems from the source code that no app domain can ever have a null evidence, no matter it' is created via COM or via a managed assembly.
public Evidence Evidence
{
[SecuritySafeCritical, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries"), SecurityPermission(SecurityAction.Demand, ControlEvidence=true)]
get
{
return this.EvidenceNoDemand;
}
}
internal Evidence EvidenceNoDemand
{
[SecurityCritical]
get
{
if (this._SecurityIdentity != null)
{
return this._SecurityIdentity.Clone();
}
if (!this.IsDefaultAppDomain() && this.nIsDefaultAppDomainForEvidence())
{
return GetDefaultDomain().Evidence;
}
return new Evidence(new AppDomainEvidenceFactory(this));
}
}
Related
I have a C# dll that references a 3rd party dll. There are different versions of the 3rd party dll.
As you might expect if the latest 3rd Party dll is present I want to use the new functionality if not I want to execute the old functionality.
I wasn't sure how to achieve this but I thought the first thing to try would be a simple if statement that decides which function to call.
So I find the assembly, get its location and hence its version info. (I need the file version as the product versions are the same).
Then a simple
if (version >= 3) do x() else do y()
When I execute the code on a machine with version 2 installed I get a MissingMethodException regarding x(). I thought I had made a stupid mistake but the logic was correct. The version is 2 so x(); should not be executed. I decided to remove the offending method and replace it with a throw new Exception(). The exception is not thrown and the code completes successfully.
Here is the danger - I am thinking that this is due to branch prediction. This is dangerous because it is not an area I have any knowledge of and therefore making assumptions is a dangerous thing.
So my questions are:
Am I tacking this problem the wrong way - is there a more obvious solution that I am missing?
or
Is there a way to disable branch prediction (if that is the cause) or to somehow enforce/flag the if condition as a point that must be executed before continuing.
Here is the code being executed:
On a machine with version 3 installed then it is fine.
On a machine with version 2 installed I get a MissingMethodException regarding method x().
It I removed the call to x(); and uncomment the throwing of the exception - no exception is thrown.
Relevant code:
Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(3rdPartyClass));
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
if (fileVersionInfo.FileMajorPart >= 3)
{
// throw new Exception("aagghh");
x();
}
else
{
y();
}
Using reflection, it's possible to get a list of Methods available for a particular DLL (more specifically: Type).
You could use this methodinfo to dynamically invoke the method as specified in Vlad's solution.
In fact, you could leave out the version check and just try to find the intended method directly.
var methodX = assembly.GetType("sometype").GetMethod("X");
if (methodX != null)
{
methodX.Invoke(params);
}
else
{
assembly.GetType("sometype").GetMethod("Y").Invoke(otherParams);
}
Edit: This is not exactly what you want, but with this kind of reflection you can find the correct methods, also for your own assembly.
There is no "branch prediction": the runtime binding seems to happen as the method is executed.
So the workaround would be like this:
if (fileVersionInfo.FileMajorPart >= 3)
{
CallX();
}
else
{
CallY();
}
void CallX()
{
DependentClass.X();
}
void CallY()
{
DependentClass.Y();
}
However, anyway this seems to be a hack: you need to execute with the version of DLL you were linking against.
This is actually a more accurate answer :
Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(String));
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
ObjectHandle oh = Activator.CreateInstanceFrom("AssemblyName.dll", "namespace.class");
object o = oh.Unwrap();
Type to = o.GetType();
if (fileVersionInfo.FileMajorPart >= 3)
{
to.InvokeMember("Method X", BindingFlags.InvokeMethod, null, o, null);
}
else
{
to.InvokeMember("Method Y", BindingFlags.InvokeMethod, null, o, null);
}
What is controlling the Evidence of the current app domain?
var evidence = Thread.GetDomain().Evidence;
What controls if it is null or non-null, and what determines it contents?
When my application queries these host evidence objects from the domain evidence
var z = evidence.GetHostEvidence<Zone>
var p = evidence.GetHostEvidence<Publisher>
var s = evidence.GetHostEvidence<Site>
var n = evidence.GetHostEvidence<StrongName>
var u = evidence.GetHostEvidence<Url>
it appears as if they are sometimes all null when executing in some environments. The reason I believe this is an exception thrown inside IsolatedStorage._GetAccountingInfo(...), where by looking at the code in reflector it is clear that this exception will only be thrown if the domain evidence contains null for all of the above host evidence objects. This will cause isolated storage to fail to initialize.
Unfortunately I can't reproduce it on my own system. The Zone value for example will always be a proper value saying "My Computer", so I'm struggling to solve this.
What controls the contents of these values in the default app domain of a windows forms desktop application?
A similar situation occurs when your code is a COM object accessed by a native Win32 application (the Default AppDomain's Evidence is empty), or when it is loaded and run inside of the command line version of PowerShell.exe. I ran into this problem when using an OpenXML (specifically EPPlus) assembly that uses IsolatedStorage when the office document is over a certain file size.
Rather than spinning up another AppDomain inside of the default one and dealing with an additional level of Marshaling/Remoting, I prefer to use reflection to muck with the current AppDomain's evidence.
Here's the proof of concept in C#:
using System;
namespace AppDomainEvidence
{
class Program
{
static void Main(string[] args)
{
var initialAppDomainEvidence = System.Threading.Thread.GetDomain().Evidence; // Setting a breakpoint here will let you inspect the current AppDomain's evidence
try
{
var usfdAttempt1 = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForDomain(); // This will fail when the current AppDomain Evidence is instantiated via COM or in PowerShell
}
catch (Exception e)
{
// Set breakpoint here to inspect Exception "e"
}
// Create a new Evidence that include the MyComputer zone
var replacementEvidence = new System.Security.Policy.Evidence();
replacementEvidence.AddHostEvidence(new System.Security.Policy.Zone(System.Security.SecurityZone.MyComputer));
// Replace the current AppDomain's evidence using reflection
var currentAppDomain = System.Threading.Thread.GetDomain();
var securityIdentityField = currentAppDomain.GetType().GetField("_SecurityIdentity", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
securityIdentityField.SetValue(currentAppDomain,replacementEvidence);
var latestAppDomainEvidence = System.Threading.Thread.GetDomain().Evidence; // Setting a breakpoint here will let you inspect the current AppDomain's evidence
var usfdAttempt2 = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForDomain(); // This should work
}
}
}
And here's the workaround I implemented in PowerShell:
# This one will fail
$usfd = [System.IO.IsolatedStorage.IsolatedStorageFile]::GetUserStoreForDomain()
# Inspect the current AppDomain's Evidence
[threading.thread]::GetDomain().Evidence
# Modify the current AppDomain's Evidence
$evidence = new-object System.Security.Policy.Evidence
$zone = new-object System.Security.Policy.Zone('MyComputer')
$evidence.AddHost($zone)
$currentAppDomain = [threading.thread]::GetDomain()
$securityIdentityField=$currentAppDomain.GetType().GetField('_SecurityIdentity','Instance,NonPublic')
$securityIdentityField.SetValue($currentAppDomain, $evidence)
# Inspect the current AppDomain's Evidence
[threading.thread]::GetDomain().Evidence
# This one will succeed
$usfd = [System.IO.IsolatedStorage.IsolatedStorageFile]::GetUserStoreForDomain()
It turns out the culprit was indeed an "unusual deployment scenario" as was suggested by Hans in a comment on my question. We use encryption (enveloping) for a few assemblies, and apparently this tampers with the evidence required by isolated storage.
Ok, here is the deal:
I want to load a user defined Assembly into my AppDomain, but I only want to do so if the specified Assembly matches some requirements. In my case, it must have, among other requirements, an Assembly level Attribute we could call MandatoryAssemblyAttribute.
There are two paths I can go as far as I see:
Load the Assembly into my current AppDomain and check if the Attribute is present. Easy but inconvenient as I'm stuck with the loaded Assembly even if it doesnt have the MandatoryAssemblyAttribute. Not good.
I can create a new AppDomain and load the Assembly from there and check if my good old MandatoryAddemblyAttribute is present. If it is, dump the created AppDomain and go ahead and load the Assembly into my CurrentAppDomain, if not, just dump the new AppDomain, tell the user, and have him try again.
Easier said than done. Searching the web, I've found a couple of examples on how to go about this, including this previous question posted in SO:Loading DLLs into a separate AppDomain
The problem I see with this solution is that you actually have to know a type (full name) in the Assembly you want to load to begin with. That is not a solution I like. The main point is trying to plug in an arbitrary Assembly that matches some requirements and through attributes discover what types to use. There is no knowledge beforehand of what types the Assembly will have. Of course I could make it a requirement that any Assembly meant to be used this way should implement some dummy class in order to offer an "entry point" for CreateInstanceFromAndUnwrap. I'd rather not.
Also, if I go ahead and do something along this line:
using (var frm = new OpenFileDialog())
{
frm.DefaultExt = "dll";
frm.Title = "Select dll...";
frm.Filter = "Model files (*.dll)|*.dll";
answer = frm.ShowDialog(this);
if (answer == DialogResult.OK)
{
domain = AppDomain.CreateDomain("Model", new Evidence(AppDomain.CurrentDomain.Evidence));
try
{
domain.CreateInstanceFrom(frm.FileName, "DummyNamespace.DummyObject");
modelIsValid = true;
}
catch (TypeLoadException)
{
...
}
finally
{
if (domain != null)
AppDomain.Unload(domain);
}
}
}
This will work fine, but if then I go ahead and do the following:
foreach (var ass in domain.GetAssemblies()) //Do not fret, I would run this before unloading the AppDomain
Console.WriteLine(ass.FullName);
I get a FileNotFoundException. Why?
Another path I could take is this one: How load DLL in separate AppDomain But I'm not getting any luck either. I'm getting a FileNotFoundException whenever I choose some random .NET Assembly and besides it defeats the purpose as I need to know the Assembly's name (not file name) in order to load it up which doesn't match my requirements.
Is there another way to do this barring MEF (I am not targeting .NET 3.5)? Or am I stuck with creating some dummy object in order to load the Assembly through CreateInstanceFromAndUnwrap? And if so, why can't I iterate through the loaded assemblies without getting a FileNotFoundException? What am I doing wrong?
Many thanks for any advice.
The problem I see with this solution is that you actually have to know
a type (full name) in the Assembly
That is not quite accurate. What you do need to know is a type name is some assembly, not necessarily the assembly you are trying to examine. You should create a MarshalByRef class in your assembly and then use CreateInstanceAndUnwrap to create an instance of it from your own assembly (not the one you are trying to examine). That class would then do the load (since it lives in the new appdomain) and examine and return a boolean result to the original appdomain.
Here is some code to get you going. These classes go in your own assembly (not the one you are trying to examine):
This first class is used to create the examination AppDomain and to create an instance of your MarshalByRefObject class (see bottom):
using System;
using System.Security.Policy;
internal static class AttributeValidationUtility
{
internal static bool ValidateAssembly(string pathToAssembly)
{
AppDomain appDomain = null;
try
{
appDomain = AppDomain.CreateDomain("ExaminationAppDomain", new Evidence(AppDomain.CurrentDomain.Evidence));
AttributeValidationMbro attributeValidationMbro = appDomain.CreateInstanceAndUnwrap(
typeof(AttributeValidationMbro).Assembly.FullName,
typeof(AttributeValidationMbro).FullName) as AttributeValidationMbro;
return attributeValidationMbro.ValidateAssembly(pathToAssembly);
}
finally
{
if (appDomain != null)
{
AppDomain.Unload(appDomain);
}
}
}
}
This is the MarshalByRefObject that will actually live in the new AppDomain and will do the actual examination of the assembly:
using System;
using System.Reflection;
public class AttributeValidationMbro : MarshalByRefObject
{
public override object InitializeLifetimeService()
{
// infinite lifetime
return null;
}
public bool ValidateAssembly(string pathToAssembly)
{
Assembly assemblyToExamine = Assembly.LoadFrom(pathToAssembly);
bool hasAttribute = false;
// TODO: examine the assemblyToExamine to see if it has the attribute
return hasAttribute;
}
}
This can be easily done by using a managed assembly reader, such as Mono.Cecil. You'd check whether the assembly is decorated with an attribute, without loading the assembly in the AppDomain, and actually, without messing with AppDomains at all. For instance, with Cecil:
bool HasMandatoryAttribute (string fileName)
{
return AssemblyDefinition.ReadAssembly (fileName)
.CustomAttributes
.Any (attribute => attribute.AttributType.Name == "MandatoryAttribute");
}
That's basically what are doing most of he plugins systems, as creating an AppDomain and tearing it down is quite expensive for the runtime.
I have an AppDomain that I'm using to load modules into a sandbox with:
class PluginLoader
{
public static AppDomain PluginSandbox;
static PluginLoader()
{
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationName = "Plugin Modules";
PermissionSet trustedLoadFromRemoteSourceGrantSet =
new PermissionSet(PermissionState.Unrestricted);
PluginSandbox =
AppDomain.CreateDomain("Plugin App Domain",
null, ads, trustedLoadFromRemoteSourceGrantSet);
}
And then later on, I'll pull in the DLL I need and create an object instance:
public IPlugin FindPlugin(string pluginName)
{
ObjectHandle handle =
PluginSandbox.CreateInstance(pluginName,
"Plugins." + pluginName);
IPlugin ip = (IPlugin)handle.Unwrap();
return ip;
}
I run through this a couple of times with no problems. Getting instances of various objects out in the Sandbox, with no problems.
A bit later in the code, in another method, I need to find the assembly to get an embedded resource (a compiled in data file, with ManifestResource). So I call:
Assembly [] ar = PluginSandbox.GetAssemblies();
And the error gets thrown:
A first chance exception of type 'System.IO.FileNotFoundException'
occurred in PluginRunner.dll.
Additional information: Could not load file or assembly '10wl4qso,
Version=1.0.3826.25439, culture info=neutral, PublicKeyToken=null'
or one of its dependencies. The system cannot find the file specified.
I'm not surprised. '10wl4qso' isn't the name of the assembly, the dll, or anything like it. In fact it seems pseudo-random for each run. Plus the added fun of GetAssemblies isn't even documented to throw this exception.
Now I can call GetAssemblies right after I get the initial object just fine, and everything is peachy. But a couple of seconds later, in a different method I get this. Being remoted, PluginSandbox has no useful information at all in the debugger.
I'm catching UnhandledException and DomainUnload on the AppDomain and neither is being triggered.
Why does my AppDomain suddenly not know about its assemblies?
Where's that garbage data coming from?
What can I do to prevent either/both of these from happening?
This weird named assembly you're seeing is probably generated by XmlSerializer. The XML serializer will output a dynamic assembly to be able to quickly serialize and deserialize a specific type quickly. Check your code for uses of XmlSerializer, comment them out and see if the problem occurs again.
I don't know if it helps you...
Try to override InitializeLifeTimeService on IPlugin. Your IPlugin implementation should inherits from MarshalByRefObject first.
public class PluginSample : MarshalByRefObject, IPlugin
{
public overrides object InitializeLifetimeService()
{
return null; //Return null to infinite object remote life.
}
//...implementation
}
Take a look at this article:
RemotingException when raising events across AppDomains
I have been trying to get the following code to work(everything is defined in the same assembly) :
namespace SomeApp{
public class A : MarshalByRefObject
{
public byte[] GetSomeData() { // }
}
public class B : MarshalByRefObject
{
private A remoteObj;
public void SetA(A remoteObj)
{
this.remoteObj = remoteObj;
}
}
public class C
{
A someA = new A();
public void Init()
{
AppDomain domain = AppDomain.CreateDomain("ChildDomain");
string currentAssemblyPath = Assembly.GetExecutingAssembly().Location;
B remoteB = domain.domain.CreateInstanceFromAndUnwrap(currentAssemblyPath,"SomeApp.B") as B;
remoteB.SetA(someA); // this throws an ArgumentException "Object type cannot be converted to target type."
}
}
}
What I'm trying to do is pass a reference of an 'A' instance created in the first AppDomain to the child domain and have the child domain execute a method on the first domain. In some point on 'B' code I'm going to call 'remoteObj.GetSomeData()'. This has to be done because the 'byte[]' from 'GetSomeData' method must be 'calculated' on the first appdomain.
What should I do to avoid the exception, or what can I do to achieve the same result?
The actual root cause was your dll was getting loaded from different locations in the two different app domains. This causes .NET to think they are different assemblies which of course means the types are different (even though they have the same class name, namespace etc).
The reason Jeff's test failed when run through a unit test framework is because unit test frameworks generally create AppDomains with ShadowCopy set to "true". But your manually created AppDomain would default to ShadowCopy="false". This would cause the dlls to be loaded from different locations which leads to the nice "Object type cannot be converted to target type." error.
UPDATE: After further testing, it does seem to come down to the ApplicationBase being different between the two AppDomains. If they match, then the above scenario works. If they are different it doesn't (even though I've confirmed that the dll is loaded into both AppDomains from the same directory using windbg) Also, if I turn on ShadowCopy="true" in both of my AppDomains, then it fails with a different message: "System.InvalidCastException: Object must implement IConvertible".
UPDATE2: Further reading leads me to believe it is related to Load Contexts. When you use one of the "From" methods (Assembly.LoadFrom, or appDomain.CreateInstanceFromAndUnwrap), if the assembly is found in one of the normal load paths (the ApplicationBase or one of the probing paths) then is it loaded into the Default Load Context. If the assembly isn't found there, then it is loaded into the Load-From Context. So when both AppDomains have matching ApplicationBase's, then even though we use a "From" method, they are both loaded into their respective AppDomain's Default Load Context. But when the ApplicationBase's are different, then one AppDomain will have the assembly in its Default Load Context while the other has the assembly in it's Load-From Context.
I can duplicate the issue, and it seems to be related to TestDriven.net and/or xUnit.net. If I run C.Init() as a test method, I get the same error message. However, if I run C.Init() from a console application, I do not get the exception.
Are you seeing the same thing, running C.Init() from a unit test?
Edit: I'm also able to duplicate the issue using NUnit and TestDriven.net. I'm also able to duplicate the error using the NUnit runner instead of TestDriven.net. So the problem seems to be related to running this code through a testing framework, though I'm not sure why.
This is a comment to #RussellMcClure but as it is to complex for a comment I post this as an answer:
I am inside an ASP.NET application and turning off shadow-copy (which would also solve the problem) is not really an option, but I found the following solution:
AppDomainSetup adSetup = new AppDomainSetup();
if (AppDomain.CurrentDomain.SetupInformation.ShadowCopyFiles == "true")
{
var shadowCopyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (shadowCopyDir.Contains("assembly"))
shadowCopyDir = shadowCopyDir.Substring(0, shadowCopyDir.LastIndexOf("assembly"));
var privatePaths = new List<string>();
foreach (var dll in Directory.GetFiles(AppDomain.CurrentDomain.SetupInformation.PrivateBinPath, "*.dll"))
{
var shadowPath = Directory.GetFiles(shadowCopyDir, Path.GetFileName(dll), SearchOption.AllDirectories).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(shadowPath))
privatePaths.Add(Path.GetDirectoryName(shadowPath));
}
adSetup.ApplicationBase = shadowCopyDir;
adSetup.PrivateBinPath = String.Join(";", privatePaths);
}
else
{
adSetup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
adSetup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
}
This will use the shadow-copy directory of the main app-domain as the application-base and add all shadow-copied assemblies to the private path if shadow-copy is enabled.
If someone has a better way of doing this please tell me.