|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsIntroductionVisual Studio comes up with a user interface tool to manage resources. The Resource Designer allows you to add/edit resources in an assembly at design-time. But the resources might not be available at design-time: consider a scenario where the user provides some pictures to be embedded into a slideshow application. In that case, the Resource Designer is helpless. When you need to add resources at runtime, you are just alone. This article shows how to embed resources at runtime by creating dynamic assemblies. It also shows how to compress files using the .NET Framework. BackgroundWhy embed resources into an existing executable file at runtime? I was working on a script-driven testing tool. The tool is used both by advanced users and by validation users. Advanced users edit script files and validation users run scripts. To make the job of validation users easier, advanced users should be able to deploy their scripts as standalone executables. I decided to package the script file and all its dependencies into the executable script engine as embedded resources. I finally came up with the solution described in this article. I found the result interesting enough to share it with you. Since the script-driven testing tool is not a suitable demonstration application, I looked for something else and the self-extractor compiler is the first idea that came across my mind. Using the CodeThe code is organized in three parts:
1. Demo AppThe self-extracted archive compiler (see screenshot above) is provided as an example. It demonstrates the dynamic creation of assemblies with embedded resources. Drop some files on the form and click the bottom-right button to create a self-extracted archive: using (SelfExtractor archive = new SelfExtractor())
{
foreach (IconFileInfo file in files)
{
archive.AddFile(file.FullName);
}
archive.CompileArchive(saveFileDialog1.FileName, checkBox1.Checked);
}
The 'Run first item after extraction' option defines the 2. SelfExtractor Class
The The public void AddFile(string filename)
{
// Compress input file using System.IO.Compression
using (Stream file = File.OpenRead(filename))
{
byte[] buffer = new byte[file.Length];
if (file.Length != file.Read(buffer, 0, buffer.Length))
throw new IOException("Unable to read " + filename);
using (Stream gzFile = File.Create(filename + ".gz"))
{
using (Stream gzip = new GZipStream
(gzFile, CompressionMode.Compress))
{
gzip.Write(buffer, 0, buffer.Length);
}
}
}
// Store filename so we can embed it on CompileArchive() call
filenames.Add(filename + ".gz");
}
The To keep things simple, the SelfExtractor.cs file is deployed side by side with the assembly containing the public void CompileArchive(string archiveFilename, bool run1stItem)
{
CodeDomProvider csc = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = true;
cp.OutputAssembly = archiveFilename;
cp.CompilerOptions = "/target:winexe";
// Add a custom option to run a file after extraction
if (run1stItem)
{
cp.CompilerOptions += " /define:RUN_1ST_ITEM";
}
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
// Add compressed files as embedded resources
cp.EmbeddedResources.AddRange(filenames.ToArray());
// Compile standalone executable with input files embedded as resource
CompilerResults cr = csc.CompileAssemblyFromFile(cp, sourceName);
// yell if compilation error
if (cr.Errors.Count > 0)
{
string msg = "Errors building " + cr.PathToAssembly;
foreach (CompilerError ce in cr.Errors)
{
msg += Environment.NewLine + ce.ToString();
}
throw new ApplicationException(msg);
}
}
3. SelfExtractor.csThe SelfExtractor.cs file is the source code file compiled at runtime by the private void Form1_Load(object sender, EventArgs e)
{
Visible = false;
ShowInTaskbar = false;
Assembly ass = Assembly.GetExecutingAssembly();
string[] res = ass.GetManifestResourceNames();
try
{
foreach (string name in res)
{
Stream rs = ass.GetManifestResourceStream(name);
using (Stream gzip = new GZipStream
(rs, CompressionMode.Decompress, true))
{
// remove ".gz"
using (Stream file = File.Create
(Path.GetFileNameWithoutExtension(name)))
{
for (int b = gzip.ReadByte();
b != -1; b = gzip.ReadByte())
{
file.WriteByte((byte)b);
}
}
}
}
#if RUN_1ST_ITEM
if (res.Length > 0)
{
Process.Start(Path.GetFileNameWithoutExtension(res[0]));
}
#endif
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, ass.GetName().Name,
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Close();
}
ConclusionIn this article, we saw how to compress files with the As mentioned in the Background section, creating a self-extracted archive compiler is not our primary objective. To keep the code as simple as possible, I deliberately ignore some issues. First, the original directory structure is not preserved. Secondly, write permission is required in every directory of archived files in order to create the temporary compressed files. Points of InterestAn alternative to the dynamic creation of assemblies could be the I chose the History
|
|||||||||||||||||||||||||||||||||||||||||||||||