Is it possible to allow dynamically generated assemblies to access dependencies that exist in the project that generates the new assembly? I am working with Unity and C#, and I add an assembly that contains dependencies that exist in the project it now belongs to, but I get this error: FileNotFoundException: Could not load file or assembly 'ModAssembly000.dll' or one of its dependencies . I get this error because I try to put 'using UnityEngine' at the top of the script. This is the code that already exists in the project that gets the new assembly and invokes a method:
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.OutputAssembly = generatedName;
CompilerResults r = CodeDomProvider.CreateProvider("CSharp").CompileAssemblyFromFile(parameters, filePath);
r.CompiledAssembly.GetType("ModData").GetMethod("Run").Invoke(null, BindingFlags.Static, null, null, null);
This is the source for the dynamically created assembly:
using UnityEngine;
public class ModData {
public static string modName = "Super kool mod";
public static string modVersion = "1.2.1";
public static void Run() {
Debug.Log("it worked :D");
}
}
UnityEngine (thus, Debug.Log) exists in the code that is generating this assembly. Is there a way I can get the new created assembly to use the UnityEngine that exists above it so that I could allow the new code to do anything in the project above it? I know ModAssembly000.dll exists because if I remove the 'using UnityEngine' line, then I can access the static string filds of the dynamic assembly without issue.
My guess is that you need to add Unity to the list of referenced assemblies:
parameters.ReferencedAssemblies.Add("UnityEngine.dll");
Related
I'm currently writing an application that currently loads a project via Roslyn's workspace API, turns a specified C# file into a syntax tree then creates an in memory assembly form it, then eventually extracts the IL.
This is all working fine, however as soon as I reference any external libraries within the said C# file, the compilation fails as Roslyn doesn't know where to resolve those references.
Here's a simplified version of what I'm currently doing:
MetadataReference[] metaDatareferences = {
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Uri).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DynamicAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(AssemblyMetadata).GetTypeInfo().Assembly.Location),
};
var sourceLanguage = new CSharpLanguage();
var syntaxTree = sourceLanguage.ParseText(sourceCode, SourceCodeKind.Regular);
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Debug,
allowUnsafe: true
);
CSharpCompilation compilation = CSharpCompilation.Create("ExampleAssembly", options: options);
var stream = new MemoryStream();
var result = compilation.
AddReferences(metaDatareferences)
.AddSyntaxTrees(syntaxTree)
.Emit(stream);
// Success is false
if (!emitResult.Success)
{
foreach (var diagnostic in emitResult.Diagnostics)
{
Debug.WriteLine(diagnostic.ToString());
}
}
The output of the Debug.WriteLine is:
(1,7): error CS0246: The type or namespace name 'MediatR' could not be found (are you missing a using directive or an assembly reference?)
(9,32): error CS0246: The type or namespace name 'Mediator' could not be found (are you missing a using directive or an assembly reference?)
And the file my Roslyn project is reading is simply this:
using MediatR;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
var mediator = new Mediator(null, null);
}
}
}
My question is, does Roslyn provide an API for automatically load any dependencies a file may have? I was hopeful that the Roslyn workspace would allow this to be done, but I've not been able to find anything.
If the MediatR console project is a project.json project, then you can use ProjectJsonWorkspace from "Microsoft.DotNet.ProjectModel.Workspaces": "1.0.0-preview2-1-003177". You can point it at your project.json and get a Compilation object, this will have done all the hard work for you of getting the project references, file references, etc... Then you can just emit your IL from here.
Here is an example:
var compilation = new ProjectJsonWorkspace(#"PathToYour\project.json").CurrentSolution.Projects.First().GetCompilationAsync().Result;
var stream = new MemoryStream();
var emitResult = compilation.Emit(stream);
Or if you need total control, you could continue to use CSharpCompilation.Create, copying in what you need from the compilation object here, and passing in a SyntaxTree.
Hope that helps.
I'm having an issue when compiling text into dynamic objects at runtime.
I wrote a simple piece of code to compile the text:
public class CompileFactory
{
public dynamic Compile(String classCode, String mainClass, Object[] requiredAssemblies)
{
CSharpCodeProvider provider = new CSharpCodeProvider(new Dictionary<string, string>
{
{ "CompilerVersion", "v4.0" }
});
CompilerParameters parameters = new CompilerParameters
{
GenerateExecutable = true, // Create a dll
GenerateInMemory = true, // Create it in memory
WarningLevel = 3, // Default warning level
CompilerOptions = "/optimize", // Optimize code
TreatWarningsAsErrors = false // Better be false to avoid break in warnings
};
// Add all extra assemblies required
foreach (var extraAsm in requiredAssemblies)
{
parameters.ReferencedAssemblies.Add(extraAsm as string);
}
CompilerResults results = provider.CompileAssemblyFromSource(parameters, classCode);
if (results.Errors.Count != 0)
{
return "FAILED";
}
return results.CompiledAssembly.CreateInstance(mainClass); ;
}
}
This is how I am using the Compile method.
List<string> assemblies = new List<string>{"System.Net.Mail.dll", "System.Net.dll"};
dynamic obj = compile.Compile(fileText, pluginName, assemblies.ToArray());
As you can see I'm adding references to extra assemblies at some point. For some reason when I add using System.Net; to the text file, it will not be referenced and I get errors. The text I'm compiling is literally a .cs file saved as text. I thought of working around this by extracting the using * and adding them separately, however for when adding System.Net.Mail.dll, the metadata file cannot be found.
Has anyone experienced something similar? I really would like to just add the using * to the file and be ready with it.
Any input would be greatly appreciated.
The issue here is that System.Net.dll does not exist. You can check in which assembly a .Net type is by right clicking somewhere it is referenced and choosing "Go to definition". This will bring up a tab with the class definition "from metadata". At the top of this file, you've got a #region showing where this type comes from. In the case of a TcpClient, we can see this:
#region Assembly System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll
#endregion
Change your call to Compile with "System.dll" instead of "System.Net.dll" and it should work just fine
Edit/Clarification: It is not possible to get an assembly name from a using statement.
Let's say you are developing a third-party DLL called "ThirdParty.dll" which has a class called JustTryToInstantiateMe. Let's say your customer has an EXE that has a reference to "ThirdParty.dll" or maybe the EXE doesn't have a reference to "ThirdParty.dll" but the EXE can find "ThirdParty.dll" in the PATH if somehow requested. You have no control over the EXE as you are the vendor for the "Thirdparty.dll" and you can only modify what the DLL does. Your customer, the one writing the EXE, agrees to either make a reference to your DLL or put your DLL in the PATH. Nothing more.
Is there a way to instantiate JustTryToInstantiateMe when running the EXE without explicitly using new or invoking a static/Shared method/field/property?
Maybe just using the static/Shared constructor somehow? Maybe on AssemblyLoad() event? Maybe using the registry? a startup process? Looking for ideas...
The nearest way I know how to do this is to dynamically load the assembly and then invoke a method from an instance of a class that you create.
Here is a template in c# for the code:
//add using for system.reflection
String className = "[NAME OF CLASS WITH FULL NAMESPACE GOES HERE]";
String methodName = "[METHOD NAME GOES HERE]"
String dllPath = "[FILE PATH FOR DLL GOES HERE]";
Assembly assembly = Assembly.LoadFile(dllPath);
Type type = assembly.GetType(className);
MethodInfo method = type.GetMethod(methodName);
object context = Activator.CreateInstance(type);
//optionally set up parameters here
object[] parameters = new object[0];
String result = (String) method.Invoke(context, parameters);
For example, I could create a class like so (I know, it's VB, but what follows after is C#.
Public Class my_class
Private hello = "hello world"
Public Function gethello()
Return hello
End Function
End Class
Then after it is compiled, I can use the following code in C# to do what I think it is you are asking:
//using system.reflection
String className = "mytestlibrary.my_class";
String dllPath = "...mytestlibrary.dll";
String methodName = "gethello";
Assembly assembly = Assembly.LoadFile(dllPath);
Type type = assembly.GetType(className);
MethodInfo method = type.GetMethod(methodName);
object context = Activator.CreateInstance(type);
object[] parameters = new object[0];
String result = (String) method.Invoke(context, parameters);
//result is "hello world"
Now, if you want to do the same, but with an already compiled EXE file, then the code is hardly different, but you will need to create a desktop/console application to shell the other executable and properly namespace your DLL. So in YOUR application, add the following code to grab an instance of a class from the DLL referenced inside the EXE.
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
String className = "My_Class";
String ExePath = "[PATH TO EXE]";
String methodName = "gethello";
Assembly assembly = Assembly.LoadFile(ExePath);
Type type = assembly.GetType(className);
MethodInfo method = type.GetMethod(methodName);
object context = Activator.CreateInstance(type);
object[] parameters = new object[0];
String result = (String) method.Invoke(context, parameters);
It is also possible for you to dynamically load assemblies into the EXE, but that is a bit more tricky, so I will leave it at this for now unless you really need to dynamically load your DLL into the EXE.
From research I found out that the simple answer to this question is that this is not possible. The short answer is that in order for a class in a DLL to be loaded the class needs to be referenced by something. If a DLL were able to self-load itself by virtue of JUST being in the PATH this would bring about all kinds of security issues.
I used to have some code which scanned the bin directory of my application for assemblies which weren't loaded in the AppDomain yet and loaded them. It basically looked like:
foreach (var assemblyPath in Directory.GetFiles("path\to\bin", "*.dll"))
{
var inspected = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
Assembly.Load(inspected.GetName());
}
I skipped the try/catch clauses, etc for brevity.
This allowed me to drop assemblies in the bin folder at run-time with implementations for certain interfaces and let the IoC container pick them up automatically. Now with the new Roslyn magic, there are no physical DLL's anymore when debugging. Is there any way to retrieve assembly names, project names or dependency names (in project.json) dynamically.
I guess I have to implement something like this example in the Entropy repo, but I don't know how to implement it for my scenario.
You can use the IAssemblyLoadContextAccessor interface to load ASP.NET 5 class library (.xproj) projects dynamically. The following example code works with Beta 4:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var assemblyLoadContextAccessor = app.ApplicationServices.GetService<IAssemblyLoadContextAccessor>();
var loadContext = assemblyLoadContextAccessor.Default;
var loadedAssembly = loadContext.Load("NameOfYourLibrary");
}
}
What you are looking for is ILibraryManager implementation which provides access to the complete graph of dependencies for the application. This is already flowed through the ASP.NET 5 DI system. So, you can reach out to it from there.
Sample usage can be found inside RoslynCompilationService.
I solved this issue partly using the ILibraryManager as suggested by #tugberk. I changed the approach a bit which dropped the need of scanning the bin folder for new assemblies. I just want all the loaded assemblies in the current AppDomain.
I injected an instance of the ILibraryManager interface in my type finder class and used the GetReferencingLibraries() method with the name of the core assembly, which is referenced by all the other assemblies in the application.
A sample implementation can be found here, where this is the important part:
public IEnumerable<Assembly> GetLoadedAssemblies()
{
return _libraryManager.GetReferencingLibraries(_coreAssemblyName.Name)
.SelectMany(info => info.Assemblies)
.Select(info => Assembly.Load(new AssemblyName(info.Name)));
}
For .net core users, here is my code for loading assemblies from a specific path. I had to use directives, as it's slightly different for .Net Framework and .Net Core.
In your class header you'll need to declare the using something similar to:
#if NET46
#else
using System.Runtime.Loader;
#endif
And in your function something similar to the following:
string assemblyPath = "c:\temp\assmebly.dll";
#if NET46
Assembly assembly = Assembly.LoadFrom(assemblyPath);
#else
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(assemblyPath);
#endif
Its not ASP.NET but it can be converted easily to asp.net.
bellow if function for loading an assembly, and invoke a method inside a class on that assembly.
private static FormCustomized loadLayout(global::System.String layoutFilename, global::System.String layoutNameSpace)
{
FormCustomized mainForm = default;
Type typeMainLayout = default;
FileInfo layoutFile;
layoutFile = new FileInfo(layoutFilename);
layoutFile.Refresh();
if (!layoutFile.Exists)
{
MessageBox.Show("Layout file not found. You need to reinstall the program");
return default;
}
try
{
Assembly assemblyRaw = Assembly.LoadFrom(layoutFilename);
AssemblyLoadContext context = AssemblyLoadContext.Default;
Assembly assembly = context.LoadFromAssemblyPath(layoutFilename);
Type typeMainLayoutIni = assembly.GetType(layoutNameSpace + ".initializeLayoutClass");
Object iniClass = Activator.CreateInstance(typeMainLayoutIni, true);
MethodInfo methodInfo = typeMainLayoutIni.GetMethod("AssembliesToLoadAtStart");
enVars.assemblies = (Dictionary<string, Environment.environmentAssembliesClass>)methodInfo.Invoke(iniClass, default);
typeMainLayout = assembly.GetType(layoutNameSpace + ".mainAppLayoutForm");
mainForm = Activator.CreateInstance(typeMainLayout, enVars) as FormCustomized;
}
catch (Exception ex)
{
return default;
}
return default;
}
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);
}