Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Converting Files to C# Source Code

0.00/5 (No votes)
15 Mar 2017 1  
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

  1. 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.

  2. 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).

  3. Rename the file to Files.ecs (Enhanced C#) to indicate it is not a standard C# file.
  4. Right-click Files.ecs in Solution Explorer and choose "Properties" to make sure the properties panel is visible.
  5. 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:

  1. Create a class library called MyMacros.dll to hold your macro
  2. Add the Loyc.Syntax NuGet package to it
  3. Write a public static method like the one above with a [LexicalMacro("","")] attribute on it (the two strings are documentary and have no effect)
  4. Put a [ContainsMacros] attribute on the containing class, which must be public
  5. Use LNodeFactory to generate some code
  6. Use the command-line argument --macros:MyMacros.dll to load the macros into LeMP
  7. Put #importMacros(MyMacros); at the top of the input file (where MyMacros is the namespace that contains your macros) to gain access to your macro (it's like a using directive for macros), and
  8. Call your macro! If you'd like to do something complicated, you might want to read this article in addition to contacting me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here