共用方式為


Reflection.Emit and Resources

Reflection.Emit is relatively new to me. Recently I had a chance to deal with a resource related emit issue and had a tough time wading through so many emit APIs with "resource" in their names. Seems they are kind of messy, and I feel the API naming is not as good as it can be.

The Windows Portable Executable (PE) file format has a .rsrc section; we normally call this section the standard Win32 resources or unmanaged resources section. Reflection.Emit was designed to support emitting the unmanaged resources to a PE file; however our customers recently told us that AssemblyBuilder.DefineUnmanagedResource and ModuleBuilder.DefineUnmanagedResource are broken in some aspects. We will have to wait for the next major release to get them fixed.

CLR extends the PE file format to contain CIL header and data sections (which reside in PE file's .text section). Managed resources are stored in this section too. For a better understanding of metadata, I review the ecma spec 335, particularly these 2 paragraphs:

From Partition II, section 6.2.2

A manifest resource is simply a named item of data associated with an assembly. A manifest resource is introduced using the .mresource directive, which adds the manifest resource to the assembly manifest begun by a preceding .assembly declaration.

From Partition II, section 22.24

The ManifestResource table has the following columns: 

  • Offset (a 4-byte constant)
  • Flags (a 4-byte bitmask of type ManifestResourceAttributes)
  • Name (an index into the String heap)
  • Implementation (an index into a File table, a AssemblyRef table, or null; more precisely, an Implementation coded index)

There are some important points in the fine print at the end of section 22.24, which I'll reprint here:

  • These rows in the ManifestResouce table are the result of .mresource directives
  • The Offset specifies the byte offset within the referenced file at which this resource record begins;
  • The Implementation specifies which file holds this resource. It can be null or non-null:
    • if null, it means the resource is stored in the current file; 
    • if not null, the resource is in another file. That whole file or part of that file can be resource.

Based on the Implementation value, we can split manifest resources into 2 categories: embedded or linked (in terms of C# compiler’s vocabulary). Check out part of C# compiler help message (via csc /?) and ILdasm output below; foo.cs is a simply C# file with only 1 Main method inside a class; any.file is a plain text file with a couple of characters inside.

/resource:<resinfo> 
         Embed the specified resource (Short form: /res)
/linkresource:<resinfo>
         Link the specified resource to this assembly (Short form: /linkres)
         Where the resinfo format is <file>[,<string name>[,public|private]]</body>

csc /out:embedded.exe /res:any.file,anyname,private foo.cs
csc /out:linked.exe   /linkres:any.file,anyname,public foo.cs

.mresource private anyname
{
  // Offset: 0x00000000 Length: 0x0000000E
}
.module embedded.exe

.file nometadata any.file
      .hash = (C8 C5 4E 11 B1 CB 27 C3 37 6F A8 25 20 D5 3E F9   // ..N...'.7o.% .>.
               93 2A 02 C0 )   // .*..

.mresource public anyname
{
  .file any.file at 0x00000000
}
.module linked.exe

From the ildasm output, we can guess the implementation value of embedded.exe's .mresource is null; on the contrary, linked.exe's .mresource directive shows its manifest resource "anyname" is associated with any.file.

From another aspect, BCL provides several classes (such as ResourceManager) in the System.Resources namespace to help create and manipulate resources; Reflection.Emit, as part of .NET library, is consistent. Different APIs are designed for the following 2 scenarios:

  • the user does not have .resources file ready, and will emit each resource item one by one during the emit procedure;
  • the user has .resources file (created with ResourceWriter, or generated by resgen.exe) or any other format files (such as Crystal Report (.rpt), logo (.gif)) ready to integrate.

To be consistent with the MSDN reflection emit documentation, I will call these 2 scenarios emitting "managed resource" and "resource blob" from now on.

We now know that we have 2 approaches to integrate a resource into an assembly: embedded or linked; we also have 2 emit scenarios: emitting "managed resource" or "resource blob". These 4 combinations are all supported in .NET 2.0. Note in v1.x, we were unable to embed the "resource blob", which has been fixed by the newly added API ModuleBuilder.DefineManifestResource(a good doc, btw).

The following list shows Reflection.Emit APIs to use for each combination:

  1. Embed " managed resource": Get IResourceWriter fom ModuleBuilder.DefineResource; follow with a series of IResourceWriter.AddResource or AddResourceData calls;
  2. Embed "resource blob": use ModuleBuilder.DefineManifestResource;
  3. Link "managed resource": Get IResourceWriter from AssemblyBuilder.DefineResource; follow with a series of IResourceWriter.AddResource or AddResourceData calls;
  4. Link "resource blob": use AssemblyBuilder.AddResourceFile;

If the .resources file is generated already, certainly we can use AssemblyBuilder.AddResouceFile to directly link it or ModuleBuilder.DefineManifestResource to embed it to the assembly being emitted.

Attached you can find a sample code that I used to explore these APIs. After downloading the sample, you can take a look at the code and try compiling and running it. "temp.exe" is emitted with some resources; I suggest using Ildasm to check its Manifest. You can then run "temp.exe" to examine some resource information of its' own. I already see a couple of strange spots from the output ...

EmitResource.cs

Comments

  • Anonymous
    May 03, 2006
    Tasty post. Thanks Haibo.
  • Anonymous
    July 31, 2006
    The comment has been removed