Compiling visual basic script in C# Application - c#

I am working on functionality that allows users to write VB scripts and attach them to controls as actions. If user wants to perform something more or something specific he can write it in VB script and attach it, for example, to button (whole application is something like powerpoint presentation but with dynamic data etc).
The problem is i have issue compiling the script in C# project.
Input is at least two files (one main script, and other are additional).
This is first method that prepares references adn calls the compilation method.
public CompilerErrorCollection CompileClient(string fname, string[] VbFiles)
{
string currentDir = CurrentWorkingDir();
List<string> names = new List<string>();
string[] anames = CompilerHelper.DefaultReferencedAssembliesWpf;
foreach (string s in anames)
{
names.Add(s);
}
AddReference(names, Path.Combine(currentDir, "PresentationBase.dll"), true);
var sFolderPath = currentDir + "\\S";
AddReference(names, Path.Combine(sFolderPath, "Client.WPF.Common.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "Client.WPF.CommonControls.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "Client.WPF.CommonModels.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "Client.WPF.Presentation.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "Client.WPF.SyncfusionControls.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "ClientAd.WPF.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "ClientCore.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "ClientInfrastructure.dll"), true);
AddReference(names, Path.Combine(sFolderPath, "ClientWcf.dll", true),
string filenamestamp = fname + ".stamp";
string stampPath = Path.Combine(currentDir, filenamestamp);
try
{
string uid = Funcs.UniqeID(true, 12);
TextWriter wr2 = File.CreateText(stampPath);
wr2.WriteLine(uid + " " + DateTime.Now);
wr2.Close();
}
catch (Exception ex)
{
/ ... /
}
CompilerErrorCollection lcErrors;
CompilerHelper.Compile(VbFiles, out lcErrors, names, fname);
return lcErrors;
}
Compile method:
public static Assembly Compile(string[] files, out CompilerErrorCollection errors,
List<string> referencedAssemblies, string outputAssemblyFileName)
{
try
{
FileInfo sourceFile = new FileInfo(files[0]);
CodeDomProvider provider = null;
errors = new CompilerErrorCollection();
bool compileOk = false;
if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".VB")
{
//provider = CodeDomProvider.CreateProvider("VisualBasic");
provider = new VBCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
}
else
{
Console.WriteLine("Source file must have a .vb extension");
}
if (provider != null)
{
CompilerParameters cp = new CompilerParameters();
// Generate an executable instead of
// a class library.
cp.GenerateExecutable = false;
// Specify the assembly file name to generate.
cp.OutputAssembly = outputAssemblyFileName; //exeName;
// Save the assembly as a physical file.
cp.GenerateInMemory = true;
// Set whether to treat all warnings as errors.
cp.TreatWarningsAsErrors = false;
cp.WarningLevel = 4;
cp.TempFiles.KeepFiles = false;
cp.IncludeDebugInformation = true;
string[] names = new string[referencedAssemblies.Count];
int nr = 0;
foreach (string name in referencedAssemblies)
{
names[nr] = name;
nr++;
}
cp.ReferencedAssemblies.AddRange(names);
// Invoke compilation of the source file.
//CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceName);
CompilerResults cr = provider.CompileAssemblyFromFile(cp, files);
errors = cr.Errors;
if (cr.Errors.Count > 0)
{
// Display compilation errors.
Debug.WriteLine("Errors building assembly into {0}", cr.PathToAssembly);
foreach (CompilerError ce in cr.Errors)
{
Debug.WriteLine(" {0}", ce.ToString());
Debug.WriteLine("");
}
}
else
{
// Display a successful compilation message.
Debug.WriteLine("Source built into {0} successfully.", cr.PathToAssembly);
}
// Return the results of the compilation.
if (cr.Errors.Count > 0)
{
return null;
}
else
{
return cr.CompiledAssembly;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
throw;
}
return null;
}
And this are references from system:
public static string[] DefaultReferencedAssembliesWpf =
{
NetRootFolderx86_En + "System.dll",
NetRootFolderx86_En + "System.Core.dll",
NetRootFolderx86_En + "System.Data.dll",
NetRootFolderx86_En + "System.Data.DataSetExtensions.dll",
NetRootFolderx86_En + "System.Windows.Forms.dll",
NetRootFolderx86_En + "PresentationCore.dll",
};
After line with CompilerResults cr = provider.CompileAssemblyFromFile(cp, files); i got those errors:
error BC30009: Reference required to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' containing the implemented interface 'System.Collections.Generic.IList`1'. Add one to your project.} object {System.CodeDom.Compiler.CompilerError}
error BC30451: 'MessageBox' is not declared. It may be inaccessible due to its protection level.} object {System.CodeDom.Compiler.CompilerError}
error BC30009: Reference required to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' containing the implemented interface 'System.Collections.Generic.IList`1'. Add one to your project.} object {System.CodeDom.Compiler.CompilerError}
After i add reference netstandard i get another error with System.Windows.Browser, then with System.Private.CoreLib.dll and adding those don't help.
Does someone have any experience in compiling in similar way or know what I am missing?
The scripts compiles fine without errors when i create VB Console app Project in visual studio.
Why do I even need netstandard in this project?

Ok, so i have reinstalled .Net Framework 4.8 and after attaching netstandard.dll from .Net Framework directory everything is fine now.

Related

How to load images from resources with GetManifestResourceStream?

How to load images from Resources, please? Some of my images are located inside a folder.
My image is saved as Resource (cf. Build Action). I don't use a .resx file.
I'm able to retrieve the list of all my resources with the help of this function:
public static string[] GetResourceNames()
{
var asm = Assembly.GetEntryAssembly();
string resName = asm.GetName().Name + ".g.resources";
using (var stream = asm.GetManifestResourceStream(resName))
using (var reader = new System.Resources.ResourceReader(stream))
{
return reader.Cast<DictionaryEntry>().Select(entry => (string)entry.Key).ToArray();
}
}
but I'm not able to load the resource (an image in my case).
Here are my tests:
string[] resourceNames = GetResourceNames();
Assembly assembly = Assembly.GetExecutingAssembly();
string projectName = assembly.GetName().Name;
string gresourceName = assembly.GetName().Name + ".g.resources";
//string gresourceName = assembly.GetName().Name + ".Properties.Resources";
var rm = new System.Resources.ResourceManager(gresourceName, typeof(Resources).Assembly);
var list = resourceNames.OrderBy(q => q).ToList(); //sort
//get all png images
foreach (string resourceName in list)
{
if (resourceName.EndsWith(".png"))
{
try
{
Console.WriteLine(resourceName.ToString());
//var test = (Bitmap)rm.GetObject(resourceName);
Stream imageStream = assembly.GetManifestResourceStream(gresourceName + "." + resourceName);
}
catch (Exception ex) {
Console.WriteLine("EXCEPTION: " + ex.Message);
}
}
}
In my case:
assembly = "VisualStudioTest"
resourceName = "testImages/add_32x32.png"
I've tried all combinations without success.
By example:
assembly.GetManifestResourceStream("VisualStudioTest.Properties.Resources.testImages.add_32x32.png")
assembly.GetManifestResourceStream("VisualStudioTest.g.resources.testImages.add_32x32.png")
According to Build actions page, Resource Build Type is for WPF projects. Are you working on a WPF project?
Using Embedded Resources instead would look like this:
var asm = Assembly.GetEntryAssembly();
foreach (string resourceName in asm.GetManifestResourceNames())
{
if (resourceName.EndsWith(".png"))
{
try
{
Console.WriteLine(resourceName.ToString());
Stream imageStream = assembly.GetManifestResourceStream(resourceName);
}
catch (Exception ex)
{
Console.WriteLine("EXCEPTION: " + ex.Message);
}
}
}
One trick I use in my own code is to query the resource names to match the short file name I'm looking for (e.g. "add_32x32.png") and just use that fully qualified name to pull the image.
private Image fromResource(string shortFileName)
{
var asm = GetType().Assembly;
var resource =
asm
.GetManifestResourceNames()
.First(_ => _.Contains(shortFileName));
using (var stream = asm.GetManifestResourceStream(resource))
{
return new Bitmap(stream);
}
}

How to access embedded resources inside a merged DLL?

I have managed to merge DLL files as embedded resource to one DLL, using AssemblyResolve event, but when I try to use that Dll at another console application, I do not have access inside the embedded resources this merged Dll has. For example, this merged Dll contains another Dll of a class library I need to use, and I want to access public methods/interfaces etc.. that this class library has, but it seems I cannot access them from this merged Dll. How can this issue be resolved ?
static Dictionary<string, Assembly> dic = null;
static void Main(string[] args)
{
var filesList = Directory.EnumerateFiles( //path to Resources folder inside console application, which holds all DLLs that are set as embedded resources );
foreach (var file in filesList)
{
var fname = Path.GetFileNameWithoutExtension(file);
var embRes = "ConsoleAppTest.Resources." + fname + ".dll";
Console.WriteLine(embRes);
Load(embRes, fname + ".dll");
}
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Console.ReadKey();
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return GetAssembly(args.Name);
}
static void Load(string embeddedResource, string fileName)
{
if (dic == null)
dic = new Dictionary<string, Assembly>();
byte[] assemblyData = null;
Assembly asm = null;
Assembly curAsm = Assembly.GetExecutingAssembly();
using (Stream stream = curAsm.GetManifestResourceStream(embeddedResource))
{
// Either the file is not existed or it is not mark as embedded resource
if (stream == null)
throw new Exception(embeddedResource + " is not found in Embedded Resources.");
// Get byte[] from the file from embedded resource
assemblyData = new byte[(int)stream.Length];
stream.Read(assemblyData, 0, (int)stream.Length);
try
{
asm = Assembly.Load(assemblyData);
// Add the assembly/dll into dictionary
dic.Add(asm.FullName, asm);
Console.WriteLine(asm.FullName);
return;
}
catch
{
// Purposely do nothing
// Unmanaged dll or assembly cannot be loaded directly from byte[]
// Let the process fall through for next part
}
}
bool fileOk = false;
string tempFile = "";
//load unmanaged assemblies
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
string fileHash = BitConverter.ToString(sha1.ComputeHash(assemblyData)).Replace("-", string.Empty); ;
tempFile = Path.GetTempPath() + fileName;
if (File.Exists(tempFile))
{
byte[] bb = File.ReadAllBytes(tempFile);
string fileHash2 = BitConverter.ToString(sha1.ComputeHash(bb)).Replace("-", string.Empty);
if (fileHash == fileHash2)
{
fileOk = true;
}
else
{
fileOk = false;
}
}
else
{
fileOk = false;
}
}
if (!fileOk)
{
System.IO.File.WriteAllBytes(tempFile, assemblyData);
}
asm = Assembly.LoadFile(tempFile);
dic.Add(asm.FullName, asm);
Console.WriteLine(asm.FullName);
}
static Assembly GetAssembly(string assemblyFullName)
{
if (dic == null || dic.Count == 0)
return null;
if (dic.ContainsKey(assemblyFullName))
return dic[assemblyFullName];
return null;
}
This console application is in .NET Framework 4.5.1 and when I build the solution, it produces an .exe file. Then I change the output type of the project to class library, to get a Dll instead of exe and build it again. After I get this merged Dll, ConsoleAppTest.dll, I add a reference to it at another test project, but I cannot access the dll that ConsoleAppTest has inside. For example, if it has ClientLibrary.dll inside, I want to access public methods/interfaces of this ClientLibrary.dll
using ConsoleAppTest;
//code to access public methods/interfaces of a dll that is inside ConsoleAppTest.dll included in using statement above

Problems with PDB file through CodeDom

I have been creating a plugin model where code will be written on the server and a dll output will be created and downloaded to the client side.
This works well, but as we also want to have debugging support for the code that is generated in server, I tried the following code to download on client.
public OutputResponse GetObject(string fileName, string sourceCode)
{
string[] assemblies = this.GetAssemblies();
string codeLanguage = "cs";
var dllFilePath = Path.Combine(Path.GetTempPath(), fileName + ".dll");
var pdbFilePath = Path.Combine(Path.GetTempPath(), fileName + ".pdb");
//Delete Existing file
if (File.Exists(dllFilePath))
File.Delete(dllFilePath);
if (File.Exists(pdbFilePath))
File.Delete(pdbFilePath);
Dictionary<string, string> compilerInfo = new Dictionary<string, string>();
compilerInfo.Add("CompilerVersion", "v4.0");
CodeDomProvider provider = CodeDomProvider.CreateProvider(codeLanguage, compilerInfo);
var compilerParameter = new CompilerParameters();
compilerParameter.WarningLevel = 3;
compilerParameter.GenerateExecutable = false;
compilerParameter.GenerateInMemory = false;
compilerParameter.IncludeDebugInformation = true;
compilerParameter.CompilerOptions = "/debug:pdbonly";
compilerParameter.TempFiles = new TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
compilerParameter.TempFiles.KeepFiles = true;
compilerParameter.OutputAssembly = dllFilePath;
foreach (var assembly in assemblies)
compilerParameter.ReferencedAssemblies.Add(assembly);
var results = provider.CompileAssemblyFromSource(compilerParameter, sourceCode);
string sourceFile = string.Empty;
string pdbFile = Path.Combine(Path.GetDirectoryName(results.PathToAssembly), string.Concat(Path.GetFileNameWithoutExtension(results.PathToAssembly), ".pdb"));
foreach(string file in results.TempFiles)
{
var extension = Path.GetExtension(file);
switch(extension)
{
case ".cs":
sourceFile = file;
break;
}
}
var response = new OutputResponse(results.PathToAssembly, pdbFile, sourceFile);
return response;
}
The dll is generated correctly, and I am renaming the pdb and source file to the dll name, and downloaded to the client folder.
Now when a method is called using in the application which plugs in the dll, the Visual Studio cannot attach the debugger. It says "A matching symbol file was not found in this folder".
Can anyone help me on how to solve this problem.

Hiding Command Prompt with CodeDomProvider

I've just got my own little custom c# compiler made, using the article from MSDN.
But, when I create a new Windows Forms application using my sample compiler, the MSDOS window also appears, and if I close the DOS window, my WinForms app closes too. How can I tell the Compiler? not to show the MSDOS window at all?
Thank you :)
Here's my code:
using System;
namespace JTS
{
public class CSCompiler
{
protected string ot,
rt,
ss, es;
protected bool rg, cg;
public string Compile(String se, String fe, String[] rdas, String[] fs, Boolean rn)
{
System.CodeDom.Compiler.CodeDomProvider CODEPROV = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp");
ot =
fe;
System.CodeDom.Compiler.CompilerParameters PARAMS = new System.CodeDom.Compiler.CompilerParameters();
// Ensure the compiler generates an EXE file, not a DLL.
PARAMS.GenerateExecutable = true;
PARAMS.OutputAssembly = ot;
PARAMS.CompilerOptions = "/target:winexe"; PARAMS.ReferencedAssemblies.Add(typeof(System.Xml.Linq.Extensions).Assembly.Location);
PARAMS.LinkedResources.Add("this.ico");
foreach (String ay in rdas)
{
if (ay.Contains(".dll"))
PARAMS.ReferencedAssemblies.Add(ay);
else
{
string refd = ay;
refd = refd + ".dll";
PARAMS.ReferencedAssemblies.Add(refd);
}
}
System.CodeDom.Compiler.CompilerResults rs = CODEPROV.CompileAssemblyFromFile(PARAMS, fs);
if (rs.Errors.Count > 0)
{
foreach (System.CodeDom.Compiler.CompilerError COMERR in rs.Errors)
{
es = es +
"Line number: " + COMERR.Line +
", Error number: " + COMERR.ErrorNumber +
", '" + COMERR.ErrorText + ";" +
Environment.NewLine + Environment.NewLine;
}
}
else
{
// Compilation succeeded.
es = "Compilation Succeeded.";
if (rn) System.Diagnostics.Process.Start(ot);
}
return es;
}
}
}
In C# compiler Console window is shown when /target switch is exe. When /target=winexe, Console window is not shown.
http://msdn.microsoft.com/en-us/library/6h25dztx.aspx
Try this:
System.CodeDom.Compiler.CompilerParameters PARAMS = new System.CodeDom.Compiler.CompilerParameters();
PARAMS->CompilerOptions = "/target:winexe";
See:
http://msdn.microsoft.com/en-us/library/system.codedom.compiler.compilerparameters.compileroptions.aspx
I don't know which MSDN Article you are referring to, but if you use the AssemblyBuilder then the "magic" is in the call to SetEntryPoint.
If you have a Windows Forms application, you need to specify PEFileKinds.WindowApplication:
var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(assemblyName), AssemblyBuilderAccess.Save);
var mod = asm.DefineDynamicModule(assemblyName, fileName);
var type = mod.DefineType("Program",
TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public);
var mainMethod = type.DefineMethod("Main",
MethodAttributes.Public | MethodAttributes.Static);
// ... Code for Main method and the rest ...
type.CreateType();
asm.SetEntryPoint(mainMethod,PEFileKinds.WindowApplication);
asm.Save(fileName);
Other PEFileKinds are ConsoleApplication and Dll, although I think the AssemblyBuilder automatically assumes it's a Dll if you don't specify an EntryPoint.

CodeDom.compiler memory

I am generating assembly on runtime . Problem is that i cant get rid-of it after i am done with it. I need to destroy this assambly what i have created
string _Errors = "";
object[] parms = null;
CodeDomProvider _CodeCompiler = CodeDomProvider.CreateProvider("CSharp"); //new Microsoft.CSharp.CSharpCodeProvider().CreateCompiler();
CompilerParameters _CompilerParameters = new CompilerParameters();
_CompilerParameters.GenerateExecutable = false;
_CompilerParameters.GenerateInMemory = true;
_CompilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
_CompilerParameters.TreatWarningsAsErrors = false;
_CompilerParameters.CompilerOptions = "/optimize";
try
{
System.CodeDom.Compiler.CompilerResults _CompilerResults = null;
_CompilerResults = _CodeCompiler.CompileAssemblyFromSource(_CompilerParameters, _SourceCode);
if (_CompilerResults.Errors.Count > 0)
{
_Errors = "";
foreach (System.CodeDom.Compiler.CompilerError CompErr in _CompilerResults.Errors)
{
_Errors += "Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";\r\n\r\n";
}
return false;
}
else
{
_Errors = null;
} _CompilerResults.CompiledAssembly = null;
_CompilerResults = null;
_CompilerParameters = null;
_CodeCompiler.Dispose();
GC.Collect();}catch{}
I suppose by "get rid of it" you mean "unload". Unfortunatelly (or not so) once loaded into an AppDomain, assemblies cannot be unloaded. To circumvent this, you can generate (or load, for that matter) assembly in a separate AppDomain and then destroy it as needed.

Categories