Compile Assembly on the fly - c#

I have this code:
static void Main(string[] args)
{
CompilerParameters cp = new CompilerParameters
{
GenerateInMemory = true,
IncludeDebugInformation = false,
};
cp.ReferencedAssemblies.AddRange(new string[]{
"System.dll",
"System.Data.dll",
"System.Xml.dll",
"Microsoft.mshtml.dll",
"System.Windows.Forms.dll"
});
Assembly _assembly = Assembly.GetExecutingAssembly();
StreamReader _textStreamReader = new StreamReader(_assembly.GetManifestResourceStream("myprog.restext.txt"));
string src = _textStreamReader.ReadToEnd();
byte[] code = Convert.FromBase64String(src);
src = Encoding.UTF8.GetString(code);
CompilerResults cr = CSharpCodeProvider.CreateProvider("CSharp").
CompileAssemblyFromSource(cp, src);
Assembly asm = cr.CompiledAssembly;
Type typ = asm.GetType("clicker.Program");
MethodInfo method = typ.GetMethod("DoStart");
method.Invoke(null, new[] { (object)args });
}
I thows FileNotFoundException becouse CompileAssemblyFromSource returns the same error. Source using mshtml.
Then I'm trying to compile it using csc.exe, it says:
error CS0006. (no Metadata for "Microsoft.mshtml.dll")
I think it because mshtml is ActiveX library. So The question is how to assemble source usings activeX mshtml.
p.s.
Source has no errors and successfully has compiled from VS but can't be compiled by "on the fly" compilation.

I thows FileNotFoundException
That's normal, Microsoft.mshtml.dll is a primary interop assembly. It is not part of the .NET Framework so cannot be located automatically. It also won't be available on the user's machine, PIAs have to be installed.
The best way to go about it is to ensure that the assembly is present in your build directory so it will be deployed along with your program and can always be found. Project + Add Reference, select Microsoft.mshtml. Select it from the References node and set the Isolated property to False, Copy Local to True. Rebuild and verify that you now have Microsoft.mshtml.dll present in your bin\Debug directory.
And modify your code to pass the full path name to the file. Like this:
var referenceAssemblies = new List<string> {
"System.dll",
"System.Data.dll",
"System.Xml.dll",
"System.Windows.Forms.dll"
};
var homedir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var mshtml = Path.Combine(homedir, "Microsoft.mshtml.dll");
referenceAssemblies.Add(mshtml);
cp.ReferencedAssemblies.AddRange(referenceAssemblies.ToArray());

Related

System.TypeLoadException: Could not load type 'System.Object' from assembly 'System.Private.CoreLib' because the parent does not exist

We are generating code dynamically to produce a .NET Core console application and then compiling it using:
var csharpParseOptions = new CSharpParseOptions(LanguageVersion.Latest);
csharpParseOptions = csharpParseOptions.WithPreprocessorSymbols(new[] { "TRACE", "DEBUG" });
var syntaxTree = CSharpSyntaxTree.ParseText(code, options: csharpParseOptions);
var compilationUnitSyntax = syntaxTree.GetCompilationUnitRoot();
var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Debug, platform: Platform.X64)
.WithModuleName("TestConsole")
.WithMetadataImportOptions(MetadataImportOptions.All)
.WithDeterministic(true)
.WithConcurrentBuild(true);
var csharpCompilation = CSharpCompilation.Create(#"TestConsole", syntaxTrees: new[] { syntaxTree }, references: references, options: options);
We can then work without any problems against the generated assembly (in memory) obtained using:
using (var memoryStream = new MemoryStream())
{
var emitResult = csharpCompilation.Emit(memoryStream);
memoryStream.Position = 0;
_assembly = Assembly.Load(memoryStream.ToArray());
}
However, when we write the console.exe to disk using:
csharpCompilation.Emit(fileNameOnDisk, Path.Combine(Path.GetDirectoryName(fileNameOnDisk), Path.GetFileNameWithoutExtension(fileNameOnDisk)) + ".pdb");
and try to run it from there we get the following exception:
System.TypeLoadException: Could not load type 'System.Object' from assembly 'System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' because the parent does not exist.
Copying the same generated code (Program.cs) into an empty Console project works perfectly but we notice that the size of the executable is significantly larger.
Does anyone have any ideas how to go about fixing this problem? Thanks.
I think your problem is the lack of runtime configuration. From what I read above if you add a file named testconsole.runtimeconfig.json with the following info or similar:
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
You'll see that it runs. I'm also attaching a complete example with compilation.Emit that I validated in LINQPad. It does not require an additional file since it generates it in the example. Best of luck and hope it is still useful.
var tree = CSharpSyntaxTree.ParseText (#"class Program
{
static void Main() => System.Console.WriteLine (""Hello"");
}");
string trustedAssemblies = (string)AppContext.GetData ("TRUSTED_PLATFORM_ASSEMBLIES");
var trustedAssemblyPaths = trustedAssemblies.Split (Path.PathSeparator);
var references = trustedAssemblyPaths.Select (path => MetadataReference.CreateFromFile (path));
var compilation = CSharpCompilation
.Create ("test")
.WithOptions (new CSharpCompilationOptions (OutputKind.ConsoleApplication))
.AddSyntaxTrees (tree)
.AddReferences (references);
string outputPath = "test.dll";
Path.GetFullPath (outputPath).Dump();
EmitResult result = compilation.Emit (outputPath);
Console.WriteLine (result.Success);
File.WriteAllText ("test.runtimeconfig.json", #$"{{
""runtimeOptions"": {{
""tfm"": ""net{Environment.Version.Major}.{Environment.Version.Minor}"",
""framework"": {{
""name"": ""Microsoft.NETCore.App"",
""version"": ""{Environment.Version.Major}.{Environment.Version.Minor}.{Environment.Version.Build}""
}}
}}
}}");
// Execute the program we just compiled.
Util.Cmd (#"dotnet.exe", "\"" + Path.GetFullPath (outputPath) + "\"");

Can't load assembly that isn't explicitly used in my project

I need to load these 4 assemblies on the fly:
"Microsoft.CodeAnalysis",
"Microsoft.CodeAnalysis.CSharp",
"Microsoft.CodeAnalysis.Features",
"Microsoft.CodeAnalysis.CSharp.Features"
They all come from nuget packages referenced in a project separate from the startup project.
But when I try loading them like this:
Assembly.Load("Microsoft.CodeAnalysis"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp"),
Assembly.Load("Microsoft.CodeAnalysis.Features"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp.Features")
// this doesn't work either:
// Assembly.Load("Microsoft.CodeAnalysis.CSharp.Features, Version=3.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
I get a FileNotFoundException on the Assembly.Load(Microsoft.CodeAnalysis.CSharp.Features) call.
However, loading the dll directly from the packages directory like so:
Assembly.LoadFile(#"D:\project\packages\Microsoft.CodeAnalysis.CSharp.Features.3.9.0-2.20525.2\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.Features.dll")
works perfectly fine.
I've also noticed that the Microsoft.CodeAnalysis.CSharp.Features.dll isn't copied to the startup project bin directory, while the three other dlls are. I suspect that it's because I'm not explicitly using that assembly in my own code, it's just loaded using reflection and then immediately sent to external code.
My complete intended usage/implementation looks like this
public static Document CreateDocument(string assemblyName, IEnumerable<PortableExecutableReference> referensMetadata, string documentName = "Script",
IEnumerable<Assembly> hostedAssemblies = null)
{
// To prevent "The language 'C#' is not supported." exception
var _ = typeof(Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOptions);
var mefHostRequiredAssemblies = new List<Assembly>
{
Assembly.Load("Microsoft.CodeAnalysis"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp"),
Assembly.Load("Microsoft.CodeAnalysis.Features"),
Assembly.Load("Microsoft.CodeAnalysis.CSharp.Features")
};
if (hostedAssemblies != null)
{
mefHostRequiredAssemblies.AddRange(hostedAssemblies);
}
var partTypes = MefHostServices.DefaultAssemblies.Concat(mefHostRequiredAssemblies)
.Distinct()
.SelectMany(x => x.GetTypes())
.ToArray();
var compositionContext = new ContainerConfiguration()
.WithParts(partTypes)
.CreateContainer();
var workspace = new AdhocWorkspace(MefHostServices.Create(compositionContext));
var project = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(),
assemblyName, assemblyName, LanguageNames.CSharp)
.WithMetadataReferences(referensMetadata);
var documentId = DocumentId.CreateNewId(project.Id);
var documentInfo = DocumentInfo.Create(documentId, documentName,
loader: TextLoader.From(
TextAndVersion.Create(
SourceText.From(string.Empty), VersionStamp.Create())
)
);
return workspace.CurrentSolution.AddProject(project)
.AddDocument(documentInfo)
.GetDocument(documentId);
}
My question is what am I doing wrong? How come I can't load references I've explicitly added to the project?

How to force CSharpCodeProvider to compile for a specific target framework?

I've got a solution which contains c# projects, some netstandard 2.0 and others .net4.7. The startup project is of course net47.
At one point, the project creates code using CodeDom and compiles it with CSharpCodeProvider. The problems is that on some machines, it tries to compile the assembly for .netstandard and it fails. The failure is expected: the generated assembly references EF which in only available for full .net framework.
How can I force CSharpCodeProvider to compile against .net47?
public bool GenerateAssembly(
CodeDomBusinessCode compileUnit
, string fileName
, string assembliesPath
, out IEnumerable<string> errors)
{
var provider = new CSharpCodeProvider();
var parameters = new CompilerParameters
{
GenerateExecutable = false,
OutputAssembly = fileName,
GenerateInMemory = false
};
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Runtime.dll");
parameters.ReferencedAssemblies.Add("System.Core.dll");
parameters.ReferencedAssemblies.Add("System.ComponentModel.Composition.dll");
parameters.ReferencedAssemblies.Add(Path.Combine(assembliesPath, "EntityFramework.dll"));
parameters.ReferencedAssemblies.Add("System.ComponentModel.DataAnnotations.dll");
parameters.ReferencedAssemblies.Add(Path.Combine(assembliesPath, "GlobalE.Server.Contracts.dll"));
var results = provider.CompileAssemblyFromDom(parameters, compileUnit.Code);
if (results.Errors.Count > 0)
{
errors = results.Errors.OfType<CompilerError>().Select(x => x.ToString());
return false;
}
errors = null;
return true;
}
The error:
error CS0012: The type 'System.IDisposable' is defined in an assembly
that is not referenced. You must add a reference to assembly
'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.
UPDATE:
If I change all projects to net47 (so that there is no netstandard project in the solution), the error will disappear, but I want to keep as many projects on netstandard as possible.
based on your error, you should add "netstandard.dll" as references and it may cause by this note that in .net 4.7 the "System.IDisposable" is in "mscorlib.dll" and in .netstatndard is in "netstandard.dll".
Try this
var options = new Dictionary<string, string>
{
{ "CompilerVersion", "v4.7" }
};
var provider = new CSharpCodeProvider(options);

Is it possible to generate Satellite Assemblies from within code?

I'm about to elaborate a solution for simplifying a translator tool. Therefore I currently try to automatically compile a Satellite Assembly from within my code.
So what I want to achive is to replace a manual run of the following command:
AL.exe /culture:de /out:de\TestResource.resources.dll /embed:TestResource.de.resources
So far I've tested generating a .dll file, which worked. But embedding/linking an ressource like shown below doesn't has any effect, but expanding the dll's size. So obviously it's there but not usable as if the resulting dll was a Satellite Assembly.
static void Main(string[] args)
{
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
parameters.OutputAssembly = "./output/satellite_test.dll";
parameters.EmbeddedResources.Add(#"./TestResource.en.resources");
parameters.LinkedResources.Add(#"./TestResource.de.resources");
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, "");
}
Is there any way to generate a dll programmatically which contains just the localized resources for one language, so that it's usable as a Satellite Assembly?
Finally I've managed to generate Satellite Assemblies from Code.
Following code generates an appropriate resourcefile:
// Already the resourcefilename has to match the
// exact namespacepath of the original resourcename.
var resourcefileName = #"TranslationTest.Resources.TestResource.de.resources";
// File has to be a .resource file. (ResourceWriter instead of ResXResourceWriter)
// .resx not working and has to be converted into .resource file.
using (var resourceWriter = new ResourceWriter(resourcefileName))
{
resourceWriter.AddResource("testtext", "Language is german!!");
}
Using this resourcefile there are some compileroptions which are necessary:
CompilerParameters parameters = new CompilerParameters();
// Newly created assembly has to be a dll.
parameters.GenerateExecutable = false;
// Filename has to be like the original resourcename. Renaming afterwards does not work.
parameters.OutputAssembly = "./de/TranslationTest.resources.dll";
// Resourcefile has to be embedded in the new assembly.
parameters.EmbeddedResources.Add(resourcefileName);
Finally compiling the assembly there is some required code which has to be compiled into:
// Culture information has to be part of the newly created assembly.
var assemblyAttributesAsCode = #"
using System.Reflection;
[assembly: AssemblyCulture(""de"")]";
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
CompilerResults results = codeProvider.CompileAssemblyFromSource(
parameters,
assemblyAttributesAsCode
);

CodeDOM compilation no errors but fails to launch console

I have created my project and now want to compile using a CodeDOM compiler.
I have a folder full of the .CS files that should be compiled to an EXE. The application is supposed to be a console application although it fails to launch any console. There are no building errors. The following is my compile method:
public static void Build(string AssemblyName, string OutputDirectory, string[] SourceFiles)
{
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = true;
parameters.GenerateInMemory = false;
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Data.dll");
parameters.ReferencedAssemblies.Add("System.Xml.dll");
parameters.OutputAssembly = OutputDirectory + #"\" + AssemblyName + ".exe";
parameters.CompilerOptions = "/unsafe /target:winexe /platform:x86";
if (codeProvider.Supports(GeneratorSupport.EntryPointMethod))
{
parameters.MainClass = "MyApp.Program";
}
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, SourceFiles);
if (results.Errors.Count > 0)
{
foreach (CompilerError error in results.Errors)
Console.WriteLine(error.ErrorText);
}
}
string[] SourceFiles correctly provides all .CS files (classes, structs and enums) located in the folder like follows:
"D:\\Development\\MyAppCodeDom\\Program.cs"
"D:\\Development\\MyAppCodeDom\\IniParser.cs"
And 26 more of those. I do not use any external DLL files as reference whatsoever. It fails, however, to launch the console window.
Any idea? Perhaps a console application requires certain options?
EDIT:
Using ILSpy, the assembly seems to contain ALL the classes etc it should have.
Thank you in advance.
I removed /target:winexe from the CompilerOptions and now it works.

Categories