Assembly loading in Visual Studio extension - c#

I'm writing a visual studio extension and I'm completely confused about how and where and when it's loading assemblies. What I have is this:
My Visual Studio extension project (let's call it MyExtension) references several assemblies including an assembly called Foo.dll.
MyExtension contains a class called FooManager that will be instantiated in response to a menu item being clicked.
When FooManager is instantiated it is passed the output path of a project in the current solution and it creates an AppDomain which should load that assembly, something like this:
public FooManager(string assemblyPath)
{
// The actual ApplicationBase of the current domain will be the one of VS and
// not of my plugin
// We need our new AppDomain to be able to find our assemblies
// without this even the CreateInstanceAndUnwrap will fail
var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var cachePath = Path.Combine(p, "Cache");
var pluginPath = Path.Combine(p, "Test");
if (Directory.Exists(cachePath))
{
Directory.Delete(cachePath, true);
}
if (Directory.Exists(pluginPath))
{
Directory.Delete(pluginPath, true);
}
Directory.CreateDirectory(cachePath);
Directory.CreateDirectory(pluginPath);
var newPath = Path.Combine(pluginPath, Path.GetFileName(assemblyPath));
File.Copy(assemblyPath, newPath, true);
var setup = new AppDomainSetup()
{
ApplicationBase = p,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath,
CachePath = cachePath
};
domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
// FooCollection is defined in MyExtension. but has several references
// to things defined in Foo.dll - it used MEF to load the assembly
// referenced by pluginPath
collection = domain.CreateInstanceAndUnwrap(
typeof(FooCollection).Assembly.FullName,
typeof(FooCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { pluginPath }, null, null) as FooCollection;
}
Now one property of FooManager looks like this (FooInfo is defined in
Foo.dll):
public IEnumerable<FooInfo> Spiders
{
get
{
return collection.Foos.Select(s => s.Metadata);
}
}
But when I try to access that I get a System.ArgumentException with the message Object type cannot be converted to target type. which I know happens if two copies of the same assembly are loaded from different locations, and I think, ultimately, that's what's happening here, but I can't figure out how to get it to load from the same place.
So after struggling with this a lot (and the above is only my latest attempt), I thought maybe I could serialize to byte[] and then deserialize again as a way to avoid the problem with types, so I tried something like this:
var msgBytes = collection.SerializeFooInfo();
var msg = FooInfo.DeserializeMessage(msgBytes);
Where my serialize and deserialize just use a BinaryFormatter (the classes are marked as Serializable). The serialization seems to work, but on deserialization, when I get to here:
public static List<FooInfo> DeserializeMessage(byte[] source)
{
using (var stream = new MemoryStream(source))
{
BinaryFormatter formatter = new BinaryFormatter();
var msg = formatter.Deserialize(stream);
return msg as List<FooInfo>;
}
}
msg comes back as null. If I try to run it in the debugger using the immediate window, I see that Deserialize threw a FileNotFoundException with the message:
Cannot load assembly 'C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\ProjectAssemblies\qesxy6ms01\Foo.dll'
But I don't understand where that path came from. It's not where my extension has been installed, which is C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\Extensions\MyCompany\FooTools\1.0 and was set as the ApplicationBase for my AppDomain and contains the file foo.dll. So why is it trying to load from the other mystery location? The other location seems to be created dynamically and contains only the foo.dll assembly.
I've do something very similar to this in a windows service (and using a lot of the same classes) and it works just fine, so this seems to be something particular to the way Visual Studio extensions. Can anybody shine a light here?
So I thought this might help: http://geekswithblogs.net/onlyutkarsh/archive/2013/06/02/loading-custom-assemblies-in-visual-studio-extensions-again.aspx
But if I try to attach an AssemblyResolve handler in package class as suggested it doesn't get called for anything interesting (which isn't surprising, it's not the domain I'm try to load from), but if I try to attach to the new domain I create then if I try something like this:
domain.AssemblyResolve += OnAssemblyResolve;
Then it fails because my FooManager isn't marked as serializable. So I created a proxy just for binding the AssemblyResolve, but the AssemblyResolve never fires. So I tried not setting the ApplicationBase when creating my domain, thinking that would force it to have to try to resolve, but then I can't create my proxy class in the created domain because it doesn't know where to load the assembly from!.

Related

AppDomain in Azure Function

I have tried to create an AppDomain within Azure Functions to run untrusted code. Creating the domain seems to work fine, but when I try to load in assemblies, it seems like they get loaded in incorrectly.
First I tried a simple AppDomain:
public class Sandboxer
{
public void Run()
{
AppDomain newDomain = AppDomain.CreateDomain("name");
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
I got an error
"System.IO.FileNotFoundException : Could not load file or assembly 'Sandboxer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2cd9cb1d6fdb50b4' or one of its dependencies. The system cannot find the file specified."
I then tried to set the appliactionBase to the folder with my dll in it.
public class Sandboxer
{
public void Run()
{
var location = typeof(OtherProgram).Assembly.Location;
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase = Path.GetDirectoryName(location);
AppDomain newDomain = AppDomain.CreateDomain("name", null, ads);
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
var other = obj as OtherProgram;
var other2 = obj as MarshalByRefObject;
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
How can I fix this problem? Thanks!
Cross posted here: https://social.msdn.microsoft.com/Forums/azure/en-US/59b119d8-1e51-4460-bf86-01b96ed55b12/how-can-i-create-an-appdomain-in-azure-functions?forum=AzureFunctions&prof=required
In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
According to your description, I could encounter the similar issue, I tried to create a Console application to check this issue and found that the code could work as expected under a Console application.
For Azure Function, obj as OtherProgram always returns null. Then I tried to instantiate OtherProgram under the current domain as follows:
var obj=AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(typeof(OtherProgram).Assembly.Location, typeof(OtherProgram).FullName);
OtherProgram op = obj as OtherProgram;
if (op != null)
op.PrintDomain(log);
The above code could work as expected, but I did not found why the object under a new AppDomain always returns null. You may try to add a issue under Azure/Azure-Functions.
This is how I would do it in a conventional .NET application, should work in Azure Functions:
Register to the AppDomain.AssemblyResolve event on the newly created AppDomain
In the event handler, resolve the assembly path using the Function Directory / Function App Directory in order to point to the bin folder
AppDomains are not usable with Azure Functions. In order to properly sandbox code in Azure Functions, you would have to create a new Azure Functions App and run the code there.
If you are allowing users to write scripts, you can use another language like Lua that allows easy sandboxing.

ShadowCopy assemblies

I have a problem and I really have no clue why it doesn't work. I've read a lot of tutorials (also on stackoverflow) and still nothing.
My goal is to use reflection on some .dll files (they are not use by any program yet) and get inheritance types, methods, constructors etc. Everything works correctly but the problem is that dlls are locked and cannot be deleted until I turn off the program. This is part of my code where I try to resolve the problem.
var apds = new AppDomainSetup();
apds.ApplicationName = "MyAssemblies";
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain apd = AppDomain.CreateDomain("newdomain", adevidence, apds);
apd.AppendPrivatePath("Assemblies");
apd.SetCachePath("C:\\Cache");
apd.SetShadowCopyFiles();
foreach (var type in apd.Load(AssemblyName.GetAssemblyName(file.Path)).GetTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
{ //rest is ok
I know I use some deprecated methods but it's one of the try. Dlls are succesfully copied into cache directory but they seems to be loaded into current domain too.
Where's the problem?
Thanks in advance.
I modified my code to and use Loader class but i still have locked files.
class Loader : MarshalByRefObject
{
public Assembly assembly;
public void LoadAssembly(string path)
{
assembly = Assembly.Load(AssemblyName.GetAssemblyName(path));
}
public Types[] getTypes()
{
return assembly.getTypes();
}
}
//...
if (file.Type == ".dll")
{
var apds = new AppDomainSetup
{
ApplicationName = "MyAssemblies",
ApplicationBase = Path,
ShadowCopyFiles = "true",
ShadowCopyDirectories = Path
};
AppDomain apd = AppDomain.CreateDomain("newdomain", null, apds);
Loader loader = (Loader)apd.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.LoadAssembly(file.Path);
foreach (var type in loader.getTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
//...
Any idea?
You can load your assembly in the following way.
var types = Assembly.Load(File.ReadAllBytes("YourAssembly.dll")).GetTypes();
Now you can extract the types from the assembly, and you can delete the assembly while the application is still running.

Load Assembly into AppDomain

if i use
Assembly assembly = Assembly.LoadFrom(file);
and later try to use the file , i get an exception stating that the file is in use .
i need to load it on to a new appdomain .
all i seem to find is examples of how to create an instance with in the Assembly ,
is there a way to load the entire assembly.
what i need is to :
(1) load the assembly into a new AppDomain from a file .
(2) extract an embedded resource (xml file) from the Dll .
(3) extract a type of class which implements an interface (which i know the interface type) .
(4) unload the entire appdomain in order to free the file .
2-4 is not a problem
i just can't seem to find how to load the Assembly into a new AppDomin , only examples of
create instance , which gives me an instace of the class from with in the Dll .
i need the entire thing.
like in this question : another example of Create instance .
Loading DLLs into a separate AppDomain
The most basic multidomain scenario is
static void Main()
{
AppDomain newDomain = AppDomain.CreateDomain("New Domain");
newDomain.ExecuteAssembly("file.exe");
AppDomain.Unload(newDomain);
}
Calling ExecuteAssembly on a seperate domain is convienient but does not offer the ability to interact with the domain itself. It also requires the target assembly to be an executable and forces the caller to a single entry point. To incorporate some flexibility you could also pass a string or args to the .exe.
I hope this helps.
Extension: Try something like the following then
AppDomainSetup setup = new AppDomainSetup();
setup.AppDomainInitializer = new AppDomainInitializer(ConfigureAppDomain);
setup.AppDomainInitializerArguments = new string[] { unknownAppPath };
AppDomain testDomain = AppDomain.CreateDomain("test", AppDomain.CurrentDomain.Evidence, setup);
AppDomain.Unload(testDomain);
File.Delete(unknownAppPath);
where the AppDomain can be initilised as follows
public static void ConfigureAppDomain(string[] args)
{
string unknownAppPath = args[0];
AppDomain.CurrentDomain.DoCallBack(delegate()
{
//check that the new assembly is signed with the same public key
Assembly unknownAsm = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(unknownAppPath));
//get the new assembly public key
byte[] unknownKeyBytes = unknownAsm.GetName().GetPublicKey();
string unknownKeyStr = BitConverter.ToString(unknownKeyBytes);
//get the current public key
Assembly asm = Assembly.GetExecutingAssembly();
AssemblyName aname = asm.GetName();
byte[] pubKey = aname.GetPublicKey();
string hexKeyStr = BitConverter.ToString(pubKey);
if (hexKeyStr == unknownKeyStr)
{
//keys match so execute a method
Type classType = unknownAsm.GetType("namespace.classname");
classType.InvokeMember("MethodNameToInvoke", BindingFlags.InvokeMethod, null, null, null);
}
});
}

Effect of LoaderOptimizationAttribute

I have written a small piece of code regarding the dynamic loading of assemblies and creating class instances from those assemblies, including an executable, a test lib to be dynamically loaded and a loader library to load dynamic assembly into a new Appdomain. Loader library is referenced by both executable and the dynamic library.
//executable
[System.STAThreadAttribute()]
[System.LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args)
{
AppDomainSetup domainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
LoaderOptimization = LoaderOptimization.MultiDomain
};
AppDomain childDomain = AppDomain.CreateDomain("MyDomain", null, domainSetup);
Console.WriteLine(AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString());
Console.WriteLine(childDomain.SetupInformation.LoaderOptimization.ToString());
byte[] assembly = null;
string assemblyName = "CSTestLib";
using (FileStream fs = new FileStream(assemblyName+".dll",FileMode.Open))
{
byte[] byt = new byte[fs.Length];
fs.Read(byt,0,(int)fs.Length);
assembly = byt;
}
object[] pararmeters = {assemblyName,assembly};
string LoaderAssemblyName = typeof(AssemblyLoader).Assembly.FullName;
string LoaderClassName = typeof(AssemblyLoader).FullName;
AssemblyLoader assloader = (AssemblyLoader)childDomain.CreateInstanceAndUnwrap(LoaderAssemblyName,LoaderClassName , true, BindingFlags.CreateInstance, null, parameters, null, null);
object obj = assloader.Load("CSTestLib.Class1");
object obj2 = assloader.Load("CSTestLib.Class2");
AppDomain.Unload(childDomain);
Console.ReadKey();
}
//Dynamic Lib
using System;
namespace CSTestLib
{
public class Class1 :MarshalByRefObject
{
public Class1() { }
}
public class Class2 : MarshalByRefObject
{
public Class2() { }
}
}
//Loader Library
using System;
namespace LoaderLibrary
{
public class AssemblyLoader : MarshalByRefObject
{
string assemblyName;
public AssemblyLoader(string assName, byte[] ass)
{
assemblyName = assName;
AppDomain.CurrentDomain.Load(ass);
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + " " + AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString());
}
public object Load(string className)
{
object ret = null;
try
{
ret = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, className);
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
return ret;
}
}
}
Here I set LoaderOptimizationAttribute on main() method but AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString(); says it is NotSpecified Why?
The differences between MultiDomain and MultiDomainHost is not so clear to me. Is MultiDomainHost for only GAC assemblies? For my situation which is more suitable?
According to this
JIT-compiled code cannot be shared for
assemblies loaded into the load-from
context, using the LoadFrom method of
the Assembly class, or loaded from
images using overloads of the Load
method that specify byte arrays.
So how can I detect if an assembly is loaded domain-neutral or not? How can assure I it is loaded domain-neutral?
This attribute has only an effect if you precompile your assemblies with NGen to speed up a warm start of your application. When you specify MultiDomain or MultiDomainHost you enable the usage of precompiled (ngenned) assemblies. You can verify this with Process Explorer where you can look at the list of loaded modules.
This is one of the biggest startup time savers if your application consists of several executable instances which share assemblies. This enables .NET to share the code pages between processes which in turn saves real memory (one assembly exists only once in the physical memory but it is shared between one or more processes) and prevents JITing the same code over and over again in each process which takes time at the cost that the generated code is a little less efficient as it could be when it would be compiled with the regular JIT which can use more dynamic data to generate the most efficient code.
In your example you load the assembly into a byte array which is located in the managed heap and increases your private byte count. This makes it impossible to share data between processes. Only read only pages which have a counterpart on your hard disc can be shared between processes. This is the reason why the attribute has no effect. If you are after a factor 2 of warm startup performance this is the attribute you were seeking for. For anything else it is not relevant.
Now back to your original question:
It is set but when you start your application under a debugger this MultiDomain attribute is ignored. When you start it outside of a debugger you will get the expected results.
Yes MultiDomainHost does enable AppDomain neutrality only for signed assemblies all others are not shared.
Code sharing can only happen when it is precompiled. The real question is: How to check if the assembly is precompiled? I do it with Process Explorer by looking at the list of loaded modules. When my loaded assembly shows up with a path to the Native Image cache and an .ni extension I am sure the precompiled image is beeing used. You can check this also with fuslogvw when you set the radio button to Native Images to check why a native images was not used by the runtime.

C# fully trusted assembly with SecuritySafeCritical funciton still throwing SecurityExceptions

I'm trying to create a sandboxed AppDomain for loading extensions/plugins. I have a MarshalByRefObject that in instantiate inside the appdomain to load the dll. I'm getting SecurityExceptions when trying to load the dll and I can't figure out how to get around them while still limiting what the third party code can do. All my projects are .net 4.
The InDomainLoader class is in a fully trusted domain, the method is marked SecuritySafeCritical. From everything I've read, I think this should work.
Here is my Loader class that creates the AppDomain and jumps into it:
public class Loader
{
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
// Create new AppDomain
var setup = AppDomain.CurrentDomain.SetupInformation;
var permissions = new PermissionSet(null);
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var strongname = typeof(InDomainLoader).Assembly.Evidence.GetHostEvidence<StrongName>();
var strongname2 = typeof(IPlugin).Assembly.Evidence.GetHostEvidence<StrongName>();
AppDomain domain = AppDomain.CreateDomain("plugin", null, setup, permissions, strongname, strongname2);
// Create instance
var loader = (InDomainLoader)domain.CreateInstanceAndUnwrap(
typeof (InDomainLoader).Assembly.FullName, typeof (InDomainLoader).FullName);
// Jump into domain
loader.Load(dll, typeName);
}
}
And here's the bootstrap loader that runs in the domain:
public class InDomainLoader : MarshalByRefObject
{
[SecuritySafeCritical]
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
var assembly = Assembly.LoadFrom(dll); // <!-- SecurityException!
var pluginType = assembly.GetType(typeName);
var demoRepository = new DemoRepository();
var plugin = (IPlugin)Activator.CreateInstance(pluginType, demoRepository);
Console.WriteLine(plugin.Run());
}
}
Some logging statements tell me that the assembly's IsFullyTrusted is true and the method has both IsSecurityCritical and IsSecuritySafeCritical set to true, IsSecurityTransparent is false.
I zipped up the whole project to http://davidhogue.com/files/PluginLoader.zip in case that makes this easier.
If anyone has any ideas, I'd be very grateful. I seem to be stuck at a dead end here.
Well for a start you probably shouldn't be marking the function as SecuritySafeCritical as that implies untrusted callers can call you, which you probably don't really want (not that it should be a major issue).
As for your problem the issue is that by default you still don't run with any special permissions, the normal easy way to do the assembly loading is you create your own AppDomainSetup and point it's ApplicationBase at a Plugin directory of some kind (which isn't a bad idea in general), you can then use the normal Assembly.Load("AssemblyName") to load out of the base. However if you must load an arbitrary file then you need to assert FileIOPermission for the plugin dll (full path), i.e.
private Assembly LoadAssemblyFromFile(string file)
{
FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.AllAccess, file);
perm.Assert();
return Assembly.LoadFile(file);
}

Categories

Resources