Converting Files to C# Source Code






4.19/5 (9 votes)
A trick you can do with enhanced C#
Introduction
One way you can include a file in a C# project is to embed it in a resx file. However, sometimes, you might prefer to embed the file in the source code itself, as either a string
or a byte array.
How?
To do this, download the LeMP preprocessor. From here, you can either install it in Visual Studio or run it as a command-line tool.
The quickest approach is to run LeMP.exe --editor
, which shows a demo window where you can play with LeMP. In that case, all you have to do is insert code like...
byte[] file = includeFileBinary(@"C:\Path\To\BinaryFile.bin");
string code = includeFileText(@"C:\Path\To\Readme.txt");
...on the left side of the demo window. The output appears on the right.
includeFileBinary
creates a new byte[]
array, while includeFileText
creates a string
.
Adding LeMP to a C# Project
- You can also install LeMP into Visual Studio so that it runs automatically whenever you save an ecs (Enhanced C#) file.
Note: Visual Studio 2017 no longer supports the LeMP installer because it no longer uses the system registry. However, you can run LeMP as a pre-build event instead; see the end of step 5. Arguably, a pre-build event is actually better for the task of converting files to source code because the source code will be updated whenever you build your project. In contrast, if you use LeMP as a single-file generator, the output code is only updated when you save the ecs file in Visual Studio; it won't automatically notice when the included file changes.
- Create a C# file, let's call it Files.cs, that specifies the file(s) you want to include:
using System; using System.Collections.Generic; using System.Linq; namespace LeMPIncludeFileExample { class Files { public static readonly byte[] SmileGif = includeFileBinary(@"icon_smile.gif"); public static readonly byte[] SadGif = includeFileBinary(@"icon_sad.gif"); public static readonly string Program = includeFileText(@"Program.cs"); } }
The files need to exist in the same folder as the source code (or you can use a relative path).
- Rename the file to Files.ecs (Enhanced C#) to indicate it is not a standard C# file.
- Right-click Files.ecs in Solution Explorer and choose "Properties" to make sure the properties panel is visible.
- One of the properties is "Custom Tool". Set this property to "LeMP" (without the quotes).
If LeMP is installed, then a new source file will appear in your project, Files.out.cs.
Check the Error panel to see if any errors occurred. If not, you should see contents of your files in Files.out.cs:
using System; using System.Collections.Generic; using System.Linq; namespace LeMPIncludeFileExample { class Files { static readonly byte[] smileGif = new byte[] { 0x47,0x49,0x46,0x38,0x39,0x61,0xF,0x0,0xF,0x0,0xF3,0x0,0x0,0xFF,0xEA, 0x0,0x45,0x45,0x45,0x0,0x0,0x0,0xFF,0xCE,0x0,0xFF,0xC9,0x0,0xFF,0xB4,0x0, 0xFE,0x9D,0x0,0xFF,0xFE,0x93,0xFF,0xFD,0x13,0xFF,0xFF,0xFF,0xFF,0xFF,0xC7,0x33, 0x33,0x33,0xFF,0xFF,0xEB,0xFF,0xE5,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x21,0xF9, 0x4,0x1,0x0,0x0,0xE,0x0,0x2C,0x0,0x0,0x0,0x0,0xF,0x0,0xF,0x0,0x0, 0x4,0x5B,0xD0,0x49,0x19,0x6A,0x9D,0xD8,0x55,0xA6,0xCE,0x19,0x17,0x16,0x70,0x8, 0x2,0x9C,0x83,0x11,0x4C,0x41,0x27,0x8,0x27,0xF0,0xA6,0xAB,0x76,0x98,0x71,0xE, 0x6A,0xA,0xE,0x9F,0x3F,0x82,0x2A,0x70,0x3,0xC6,0x82,0x85,0xCA,0x21,0xC7,0x4, 0x10,0x92,0x1,0xD3,0xA2,0x9,0x58,0x3C,0x2B,0x3,0xD9,0x6B,0xBB,0x15,0xAE,0x2, 0x59,0x41,0x62,0x3C,0x16,0x10,0x76,0x9A,0xC1,0xA0,0xC1,0x35,0xD3,0x58,0x6A,0x82, 0x5C,0xFE,0x16,0x5,0xA,0xF8,0xC2,0x30,0xC3,0xB2,0xD4,0x26,0x11,0x0,0x3B }; static readonly byte[] sadGif = new byte[] { 0x47,0x49,0x46,0x38,0x39,0x61,0xF,0x0,0xF,0x0,0xF3,0x0,0x0,0xFF,0xEA, 0x0,0x45,0x45,0x45,0x0,0x0,0x0,0xFF,0xCE,0x0,0xFF,0xC9,0x0,0xFE,0x9D,0x0, 0xFF,0xB4,0x0,0xFF,0xFE,0x93,0xFF,0xFD,0x13,0xFF,0xFF,0xC7,0xFF,0xE5,0x0,0xFF, 0xFF,0xEB,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x21,0xF9, 0x4,0x1,0x0,0x0,0xC,0x0,0x2C,0x0,0x0,0x0,0x0,0xF,0x0,0xF,0x0,0x0, 0x4,0x58,0x90,0x49,0x19,0x6A,0x9D,0x98,0xD5,0x95,0xCE,0x19,0x17,0x16,0x70,0x8, 0x2,0x9C,0x43,0x11,0x4C,0x41,0x27,0x9C,0xB0,0x90,0xAE,0xDA,0xF1,0xC2,0x31,0xA8, 0x25,0x26,0x70,0xFB,0x27,0x82,0x2A,0x70,0xE8,0xFD,0x6E,0x4,0x43,0xE5,0x80,0x6B, 0x2,0x92,0x95,0x9E,0x33,0xA8,0xC,0xC,0xA6,0x27,0x85,0x70,0x65,0x3D,0x9,0xBE, 0xDF,0xA7,0x4E,0x33,0x18,0xFC,0x0,0xA,0x99,0x8A,0x55,0x26,0xB8,0xDD,0xB3,0x4C, 0xC5,0x40,0x37,0xC,0x33,0x2C,0xB,0x6D,0x12,0x1,0x0,0x3B }; static readonly string program = @"using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LeMPIncludeFileExample { class Program { static void Main(string[] args) { } } } "; } }
Visual Studio will rebuild the file every time.
Note: You can also run LeMP on the command line or as a pre-build event. To take the latter approach, go to the project properties and, in the "Build Events" section, add a command like this to the "Pre-build event command line":
C:\LOCATION-OF-LEMP\LeMP.exe $(ProjectDir)Files.ecs --outext:out.cs
This should produce a Files.out.cs file that you can include in your project manually (the Show all files icon in Solution Explorer will allow you to see the file. Right-click and "Include in project".)
If you use this method, then "Custom Tool" should not be set to LeMP in the Properties window. It should be blank.
How Does It Work?
LeMP is a tool that lets you generate code by calling special methods called "macros" in your C# code. For example, the includeFileText
method that was used in the example above is bundled with LeMP. Here is its source code:
[LexicalMacro(@"includeFileText(""Filename"")",
"Reads a UTF-8 text file into a string literal.
Newlines become '\n'."
+"The single argument is macro-preprocessed.")]
public static LNode includeFileText(LNode node, IMacroContext context)
{
string filename;
if (node.ArgCount == 1 &&
(filename = context.PreProcess(node[0]).Value as string) != null) {
var inputFolder = context.ScopedProperties.TryGetValue
((Symbol)"#inputFolder", "").ToString();
var path = System.IO.Path.Combine(inputFolder, filename);
var text = File.ReadAllText(path, Encoding.UTF8);
return F.Literal(text).SetBaseStyle(NodeStyle.TDQStringLiteral);
}
return null;
}
// elsewhere
static LNodeFactory F =
new LNodeFactory(new EmptySourceFile("StandardMacros.cs"))
The key part of this method is at the end, where it calls the standard File.ReadAllText
method to load a file, then calls the factory method F.Literal(text)
to convert it into a source code literal (for example, F.Literal("Hello")
essentially converts "Hello"
into a syntax tree, which will later be printed as C# code.)
To find out what else you can do with LeMP, check out the home page. If you'd like to write a macro of your own, please leave a message here and I will be delighted to help you! Basically, the steps are:
- Create a class library called MyMacros.dll to hold your macro
- Add the
Loyc.Syntax
NuGet package to it - Write a
public static
method like the one above with a[LexicalMacro("","")]
attribute on it (the twostring
s are documentary and have no effect) - Put a
[ContainsMacros]
attribute on the containing class, which must bepublic
- Use LNodeFactory to generate some code
- Use the command-line argument
--macros:MyMacros.dll
to load the macros into LeMP - Put
#importMacros(MyMacros);
at the top of the input file (whereMyMacros
is the namespace that contains your macros) to gain access to your macro (it's like ausing
directive for macros), and - Call your macro! If you'd like to do something complicated, you might want to read this article in addition to contacting me.