Click here to Skip to main content
Click here to Skip to main content

File Resource Management Library (.NET)

, 16 Sep 2009
Rate this:
Please Sign up or sign in to vote.
A .NET implementation of a file resource management, with complete support for VS_VERSIONINFO version resources.

Introduction

There are several good articles about reading and writing resources from and back to a Windows PE executable or DLL. Most publications focus on retrieving module version information and modifying version information, mostly in C++. Some detail the same operations for cursors, icons or dialog resources. Others have limitations and can only edit structures in-place. There's, however, no single managed .NET library to retrieve and save any type of resources, no library to edit or generate version resources and no library that has a consistent programming model for all resource types.

This implementation is a framework that enumerates resources and implements both reading and writing of the file version (VS_VERSIONINFO), string (company, copyright and product information), bitmap (RT_BITMAP), icon (RT_GROUP_ICON, RT_ICON), dialog (RT_DIALOG), menu (RT_MENU), cursor (RT_GROUP_CURSOR, RT_CURSOR), accelerator (RT_ACCELERATOR) and SxS manifest (RT_MANIFEST) resources. Over time, this library was extended through these resource types and can be easily completed for all the remaining ones.

Background

Initially, I started porting the version resource implementation from Denis Zabavchik's C++ VerInfoLib for the dotNetInstaller Bootstrapper open-source project. Then, it grew bigger ...

Using the Code

Enumerating Resources

The following example demonstrates enumerating resources by type.

string filename = Path.Combine(Environment.SystemDirectory, "atl.dll");
using (ResourceInfo vi = new ResourceInfo())
{
 vi.Load(filename);
 foreach (ResourceId type in vi.ResourceTypes)
 {
  foreach (Resource resource in vi.Resources[type])
  {
    Console.WriteLine("{0} - {1} ({2}) - {3} byte(s)",
        resource.TypeName, resource.Name, resource.Language, resource.Size);
  }
 }
}

From the Windows Vista atl.dll, you will typically get the following resources:

MUI - 1 (1033) - 232 byte(s)
REGISTRY - 101 (1033) - 335 byte(s)
TYPELIB - 1 (1033) - 7132 byte(s)
RT_STRING - 1 (1033) - 72 byte(s)
RT_STRING - 7 (1033) - 38 byte(s)
RT_VERSION - 1 (1033) - 828 byte(s)

Reading Version Information

You can load file version information without enumerating resources.

string filename = Path.Combine(Environment.SystemDirectory, "atl.dll");
VersionResource versionResource = new VersionResource();
versionResource.LoadFrom(filename);
Console.WriteLine("File version: {0}", versionResource.FileVersion);
StringFileInfo stringFileInfo = (StringFileInfo) versionResource["StringFileInfo"];
foreach (KeyValuePair<ResourceId, StringTableEntry> 
	versionStringTableEntry in stringFileInfo.Default.Strings)
{
 Console.WriteLine("{0} = {1}", versionStringTableEntry.Value.Key, 
			versionStringTableEntry.Value.StringValue);
}

Writing Version Information

You can write updated version information back into the executable. The easiest way is to load an existing binary resource, update it and save it back. Note that internally string resources are stored with an extra null terminator. The library is consistent with this and always stores the value with two null terminators while doing the dirty work for you and appending it only when required.

string filename = Path.Combine(Environment.SystemDirectory, "atl.dll");
VersionResource versionResource = new VersionResource();
versionResource.LoadFrom(filename);
Console.WriteLine("File version: {0}", versionResource.FileVersion);
versionResource.FileVersion = "1.2.3.4";
StringFileInfo stringFileInfo = (StringFileInfo) versionResource["StringFileInfo"];
stringFileInfo["CompanyName"] = "My Company\0";
stringFileInfo["Weather"] = "Sunshine, beach weather.";
versionResource.SaveTo(filename);   

Generating a complete version resource header allows you to save version information into a file that doesn't have any. This is more involved because you must generate all the structures. ResourceLib makes it easy since you don't have to worry about structure sizes or data alignment.

VersionResource versionResource = new VersionResource();
versionResource.FileVersion = "1.2.3.4";
versionResource.ProductVersion = "4.5.6.7";

StringFileInfo stringFileInfo = new StringFileInfo();
versionResource[stringFileInfo.Key] = stringFileInfo;
StringTable stringFileInfoStrings = new StringTable();
stringFileInfoStrings.LanguageID = 1033;
stringFileInfoStrings.CodePage = 1200;
stringFileInfo.Strings.Add(stringFileInfoStrings.Key, stringFileInfoStrings);
stringFileInfoStrings["ProductName"] = "ResourceLib";
stringFileInfoStrings["FileDescription"] = "File updated by ResourceLib";
stringFileInfoStrings["CompanyName"] = "Vestris Inc.";
stringFileInfoStrings["LegalCopyright"] = "All Rights Reserved";
stringFileInfoStrings["Comments"] = 
	"This file has a version resource updated by ResourceLib";
stringFileInfoStrings["ProductVersion"] = versionResource.ProductVersion;

VarFileInfo varFileInfo = new VarFileInfo();
versionResource[varFileInfo.Key] = varFileInfo;
VarTable varFileInfoTranslation = new VarTable("Translation");
varFileInfo.Vars.Add(varFileInfoTranslation.Key, varFileInfoTranslation);
varFileInfoTranslation[ResourceUtil.USENGLISHLANGID] = 1300;

versionResource.SaveTo(targetFilename);

A unit test, called VersionResourceTests.TestDeleteAndSaveVersionResource implements this behavior and can be found in the ResourceLibUnitTests project. It makes a copy of atl.dll from the Windows system directory into the temporary folder, deletes its version resource, generates a new one without copying any data and updates the copy.

Other Resource Types

You can load and save other resource types in a similar manner. The class that gives you access to all resources is ResourceInfo, while each resource class (e.g. MenuResource) can be used directly to LoadFrom an executable or SaveTo the same or another executable.

For example, the following code loads an English MenuResource with ID 204 from explorer.exe.

MenuResource menuResource = new MenuResource();
menuResource.Name = new ResourceId(204);
menuResource.Language = ResourceUtil.USENGLISHLANGID;
menuResource.LoadFrom("explorer.exe"); 

Implementation

Resource Identity and Type

Every resource has a resource identity and a resource type, implemented in ResourceId.cs. A resource identity can either be a positive integer or a string. The Windows API requires a raw pointer for each of these parameters. If the value is between 1-65535, it is an integer identity. Otherwise it's a pointer to a string. This adds a pointless complication into managed code - the correct PInvoke parameters for Win32 functions that manipulate resources are IntPtr and neither String nor int.

[DllImport("kernel32.dll", EntryPoint = 
	"FindResourceExW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr FindResourceEx
	(IntPtr hModule, IntPtr type, IntPtr name, UInt16 language);

In order to make an IntPtr from an int, we use new IntPtr(int) and in order to make an IntPtr from a string, we use Marshal.PtrToStringUni(string).

Header Structures

Every resource structure has a similar header, implemented in ResourceTable.cs.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct RESOURCE_HEADER
{
 public UInt16 wLength;
 public UInt16 wValueLength;
 public UInt16 wType;
}  

The header is usually followed by a Unicode string (a key) and an array of data structures, each with a similar resource header.

Alignment

Most resource structures have evolved from 16-bit Windows and are aligned to 16-bit WORD boundaries. Newer structures in 32-bit Windows are aligned to 32-bit DWORD boundaries. For example, all version resource structures are aligned to DWORD, while icon resources are aligned to WORD. Other resource types may have specific alignment requirements for various fields. The resource library uses both math to align pointers ( ResourceUtil.Align) to align pointers to DWORD, and struct alignment (Pack = 2) to align structures to WORD. The latter is preferred where possible because it's automatic.

public static IntPtr Align(Int32 p)
{
    return new IntPtr((p + 3) & ~3);
}

public static IntPtr Align(IntPtr p)
{
    return Align(p.ToInt32());
} 
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct GRPICONDIR
{
    public UInt16 wReserved; 	// reserved
    public UInt16 wType; 		// type, 1 = icon, 2 = cursor
    public UInt16 wImageCount; 	// image count
} 

Reading

Because of a uniform type of header, you'll find the same pattern in reading structured data throughout the code.

Here's an example of StringTable:

public override IntPtr Read(IntPtr lpRes)
{
 IntPtr pChild = base.Read(lpRes);
 while (pChild.ToInt32() < (lpRes.ToInt32() + _header.wLength))
 {
  StringTableEntry res = new StringTableEntry(pChild);
  _strings.Add(res.Key, res);
  pChild = ResourceUtil.Align(pChild.ToInt32() + res.Header.wLength);
 }
 return new IntPtr(lpRes.ToInt32() + _header.wLength);
}

Each StringTableEntry is the endpoint structure without any children.

public void Read(IntPtr lpRes)
{
 _header = (Kernel32.RESOURCE_HEADER) Marshal.PtrToStructure
			( lpRes, typeof(Kernel32.RESOURCE_HEADER));
 IntPtr pKey = new IntPtr(lpRes.ToInt32() + Marshal.SizeOf(_header));
 _key = Marshal.PtrToStringUni(pKey);
 IntPtr pValue = ResourceUtil.Align(pKey.ToInt32() + (_key.Length + 1) * 2);
 _value = _header.wValueLength > 0 ? Marshal.PtrToStringUni
			(pValue, _header.wValueLength) : null;
}

Writing

Writing is the reverse operation of reading, but the header must be updated to the correct length. It is easier to align the structure and to calculate the difference between the end of the structure and the beginning of it after it's written. The sizes don't include any padding.

public override void Write(BinaryWriter w)
{
 long headerPos = w.BaseStream.Position;
 base.Write(w);
 Dictionary<string, StringTable>.Enumerator stringsEnum = _strings.GetEnumerator();
 while (stringsEnum.MoveNext())
 {
  stringsEnum.Current.Value.Write(w);
 }
 ResourceUtil.WriteAt(w, w.BaseStream.Position - headerPos, headerPos);
 ResourceUtil.PadToDWORD(w);
}

Binary Compatibility

It is very important to ensure that the library is capable of generating correct binary resources with proper sizes and correctly aligned structures. Each top-level resource is capable of reading from raw data and writing back the raw data. All lengths and sizes are recalculated at write time or preserved between loading and saving. An interesting complication exists in side-by-side manifest resources: rewriting an identical manifest does not necessarily have to preserve the size of the RT_MANIFEST resource because new lines in attribute values are normalized per W3C spec section 3.3.3.

A series of unit tests ensure that data read is identical to the data written. The easy start is the ResourceTests.TestReadWriteResourceBytes unit test which ensures that data read is identical to data written.

[Test]
public void TestReadWriteResourceBytes()
{
    Uri uri = new Uri(Assembly.GetExecutingAssembly().CodeBase);
    string uriPath = Path.GetDirectoryName(HttpUtility.UrlDecode(uri.AbsolutePath));
    foreach (string filename in Directory.GetFiles(Path.Combine(uriPath, "Binaries")))
    {
        Console.WriteLine(filename);
        using (ResourceInfo ri = new ResourceInfo())
        {
            ri.Load(filename);
            foreach (Resource rc in ri)
            {
                Console.WriteLine("Resource: {0} - {1}", rc.TypeName, rc.Name);
                GenericResource genericResource = 
			new GenericResource(rc.Type, rc.Name, rc.Language);
                genericResource.LoadFrom(filename);
                byte[] data = rc.WriteAndGetBytes();
                ByteUtils.CompareBytes(genericResource.Data, data);
            }
        }
    }
} 

A more extensive test is needed to ensure that data is correct even if resource contents change: this is accomplished by loading resources from an existing file, performing a deep copy of the data without any structural fields (lengths, number of elements, etc.), writing the copy to a vector of bytes and comparing the two. A good example of such unit test is VersionResourceTests.TestDeepCopyBytes.

Extending to Other Types

Extending the library to other types means implementing a new class that derives from Resource and implements both Read and Write functions. For example, for RT_VERSION we'll start with the following skeleton:

public class VersionResource : Resource
{
    public VersionResource()
        : base(IntPtr.Zero,
            IntPtr.Zero,
            new ResourceId(Kernel32.ResourceTypes.RC_VERSION),
            null,
            ResourceUtil.NEUTRALLANGID,
            0)
    {

    }

    public VersionResource(IntPtr hModule, IntPtr hResource, 
	ResourceId type, ResourceId name, UInt16 language, int size)
        : base(hModule, hResource, type, name, language, size)
    {

    }

    internal override IntPtr Read(IntPtr hModule, IntPtr lpRes)
    {
        return lpRes;
    }

    internal override void Write(System.IO.BinaryWriter w)
    {
    }
}

The new resource type must also be added to ResourceInfo.CreateResource in order to create a specialized instance when such a resource is encountered.

switch (type.ResourceType)
{
    case Kernel32.ResourceTypes.RT_VERSION:
        return new VersionResource
		(hModule, hResourceGlobal, type, name, wIDLanguage, size);
}

Version

Version resources are amongst the most complex resource structures. Their evolution is well described in Raymond Chen's "The Old New Thing". The top of the version resource is a VS_VERSION_INFO resource table header followed by a VS_FIXEDFILEINFO structure that depicts the static portion of the version resource which contains binary file version information that you see in file properties in Windows Explorer. The default Windows fixed file information for a dynamic link library looks like this:

public static VS_FIXEDFILEINFO GetWindowsDefault()
{
    VS_FIXEDFILEINFO fixedFileInfo = new VS_FIXEDFILEINFO();
    fixedFileInfo.dwSignature = Winver.VS_FFI_SIGNATURE;
    fixedFileInfo.dwStrucVersion = Winver.VS_FFI_STRUCVERSION;
    fixedFileInfo.dwFileFlagsMask = Winver.VS_FFI_FILEFLAGSMASK;
    fixedFileInfo.dwFileOS = (uint) Winver.FileOs.VOS__WINDOWS32;
    fixedFileInfo.dwFileSubtype = (uint) Winver.FileSubType.VFT2_UNKNOWN;
    fixedFileInfo.dwFileType = (uint) Winver.FileType.VFT_DLL;
    return fixedFileInfo;
}

This is followed by two resource tables, StringFileInfo and VarFileInfo. These contain the version information that can be displayed for a particular language and code page and information not dependent on a particular language and code page combination respectively. This is also what you see in the file properties in Windows Explorer, but the information may be different depending on the language and region of the operating system and the user logged-in.

Icons

With the above infrastructure in place and with support for the most complicated of all resources, version resource structures, it is possible to extend the library to one of the two dozen other known resource types. We've started with icons.

Extending the library to support icons means implementing the data structures for icon storage and hooking up ResourceInfo callbacks. When ResourceInfo encounters a resource of type 14 (RT_GROUP_ICON), it creates an object of type IconDirectoryResource. The latter creates an IconResource, which loads an DeviceIndependentBitmap.

  • An IconDirectoryResource represents RT_GROUP_ICON, a collection of icon resources.
  • An IconResource represents a single RT_ICON icon with one or more images.
  • An DeviceIndependentBitmap is not a resource, but raw data embedded in the file at an offset defined by the icon resource and represents a single icon bitmap in a .bmp format.

In order to embed an existing icon from a .ico file into an executable (.exe or .dll), we load the .ico file and convert it to a IconDirectoryResource. The structure in a .ico file is similar to the structure of the icon in an executable. The only difference is that the executable headers store the icon ID, while a .ico header contains the offset of icon data. See IconFile and IconFileIcon classes for implementation details. The IconDirectoryResource is written to the target file, then each icon resource is written separately. Note that the current implementation would replace icons with the same Id in the executable, but doesn't delete old icons if you're storing less icon images than the previous number - it probably should since these icons become orphaned.

The ease of extending the library to icons validated our initial design model.

Cursors

The next natural extension after icons is cursors. Cursor structure is virtually identical to icons with a few notable differences.

  • A CursorDirectoryResource represents RT_GROUP_CURSOR, a collection of cursor resources.
  • A CursorResource represents a single RT_CURSOR cursor with a cursor image. RT_CURSOR describes a single cursor image's resource data as a two-byte x hotspot value, followed by a two-byte y hotspot value followed by a BITMAPINFOHEADER structure. The hot spot of a cursor is the point to which Windows refers in tracking the cursor's position.
  • An DeviceIndependentBitmap is not a resource, but raw data embedded in the file as a resource and represents a single cursor bitmap. This data includes the image's XOR bitmap followed by its AND bitmap. These two bitmaps are used together to support transparency.

The .cur files contain hot spot data in the wPlanes and the wBitsPerPixel fields. These are copied to the top of the RT_CURSOR resource when transforming an DeviceIndependentBitmap into a CursorResource.

All common functions between icons and cursors are implemented in a shared class, DirectoryResource. The differences are implemented in two derived CursorDirectoryResource and IconDirectoryResource.

Bitmaps

Bitmap resources are device-independent bitmaps stored as-is. Most implementation complications belong in the DeviceIndependentBitmap class that is capable of separating bitmap mask and color information. While technically somewhat challenging, this is not required for resource manipulation and is beyond the scope of this article. The only desirable improvement in the current implementation would be the ability to assign a System.Drawing.Image instance to DeviceIndependentBitmap.Bitmap.

Dialogs

There're two dialog resource formats. The original one is described in the Win32 documentation as part of the DIALOGTEMPLATE structure. A single DIALOGTEMPLATE can contain controls in the DIALOGITEMTEMPLATE 16-bit format. In Windows NT 3.51, Microsoft introduced DIALOGEXTEMPLATE and DIALOGEXITEMTEMPLATE, a 32-bit format. This evolution is explained in detail in Raymond Chen's "The Old New Thing" on MSDN.

Reading dialog structures involves making a decision of whether the dialog is in a standard or extended format. Extended dialogs start with a different 0xFFFF header.

internal override IntPtr Read(IntPtr hModule, IntPtr lpRes)
{
    switch ((uint)Marshal.ReadInt32(lpRes) >> 16)
    {
        case 0xFFFF:
            _dlgtemplate = new DialogExTemplate();
            break;
        default:
            _dlgtemplate = new DialogTemplate();
            break;
    }

    // dialog structure itself
    return _dlgtemplate.Read(lpRes);
}

Subsequent structures are straightforward with the additional difficulty of variable length arrays and different alignments within the structure. For example, the typeface name is aligned on a 32-bit boundary, but only when present. The DIALOGITEMTEMPLATE and the DIALOGEXITEMTEMPLATE controls are aligned on a 32-bit boundary.

The optional fields (e.g. control class id) are generally identified in three ways: a 0x0000 value indicating that the value is not present, 0xFFFF indicating one additional element that specifies the ordinal value of the resource and a null-terminated Unicode string otherwise. This is generically implemented in the DialogTemplateUtil.ReadResourceId and DialogTemplateUtil.WriteResourceId pair of functions.

internal static IntPtr ReadResourceId(IntPtr lpRes, out ResourceId rc)
{
    rc = null;

    switch ((UInt16) Marshal.ReadInt16(lpRes))
    {
        case 0x0000: // no predefined resource
            lpRes = new IntPtr(lpRes.ToInt32() + 2);
            break;
        case 0xFFFF: 	// one additional element that specifies 
			// the ordinal value of the resource
            lpRes = new IntPtr(lpRes.ToInt32() + 2);
            rc = new ResourceId((UInt16)Marshal.ReadInt16(lpRes));
            lpRes = new IntPtr(lpRes.ToInt32() + 2);
            break;
        default: // null-terminated Unicode string that specifies the name of the resource
            rc = new ResourceId(Marshal.PtrToStringUni(lpRes));
            lpRes = new IntPtr(lpRes.ToInt32() + (rc.Name.Length + 1) * 2);
            break;
    }

    return lpRes;
}

internal static void WriteResourceId(BinaryWriter w, ResourceId rc)
{
    if (rc == null)
    {
        w.Write((UInt16) 0);
    }
    else if (rc.IsIntResource())
    {
        w.Write((UInt16) 0xFFFF);
        w.Write((UInt16) rc.Id);
    }
    else
    {
        ResourceUtil.PadToWORD(w);
        w.Write(Encoding.Unicode.GetBytes(rc.Name));
        w.Write((UInt16)0);
    }
}

The implementation in the library also attempts to return a standard string representation of the dialog and its controls overriding the ToString methods.

DialogResource: STRINGINPUT, RT_DIALOG
"STRINGINPUT" DIALOG 6, 18, 166, 96
STYLE WS_SYSMENU | WS_DLGFRAME | WS_BORDER | WS_CAPTION | 
WS_VISIBLE | WS_POPUP | DS_SETFONT | DS_SHELLFONT | DS_MODALFRAME
EXSTYLE WS_OVERLAPPED | WS_EX_LTRREADING | WS_EX_LTRREADING | WS_EX_LTRREADING
CAPTION "Set Options"
FONT 8, "MS Shell Dlg"
{
 Static "Prompt goes here" 301, 9, 12, 140, 8, WS_GROUP | WS_VISIBLE | WS_CHILD
 Edit "" 302, 18, 30, 101, 12, WS_TABSTOP | WS_BORDER | 
	WS_CAPTION | WS_VISIBLE | WS_CHILD | DS_MODALFRAME| ES_AUTOHSCROLL
 Button "OK" 1, 63, 55, 40, 14, WS_TABSTOP | WS_VISIBLE | 
	WS_CHILD | DS_ABSALIGN| BS_DEFPUSHBUTTON
 Button "Cancel" 2, 108, 55, 40, 14, WS_TABSTOP | WS_VISIBLE | WS_CHILD| BS_TEXT
} 

String Tables

Unlike the other resource formats, where the resource identifier is the same as the value listed in the *.rc file, string resources are packaged in blocks. Each string resource block has sixteen strings. To find the block id for a given string, we need some math.

public static UInt16 GetBlockId(int stringId)
{
    return (UInt16)((stringId / 16) + 1);
}

Reading string resources is a loop over 16 strings where each string is prefixed by its length. Since there's always 16 strings in each RT_STRING resource block, missing strings are identified by a length of zero. Hence there's no way to add an empty string. A string id is derived from the block Id (resource Name).

public UInt16 BlockId
{
    get
    {
        return (UInt16) Name.Id.ToInt32();
    }
    set
    {
        Name = new ResourceId(value);
    }
}

internal override IntPtr Read(IntPtr hModule, IntPtr lpRes)
{
    for (int i = 0; i < 16; i++)
    {
        UInt16 len = (UInt16)Marshal.ReadInt16(lpRes);
        if (len != 0)
        {
            UInt16 id = (UInt16) ((BlockId - 1) * 16 + i);
            IntPtr lpString = new IntPtr(lpRes.ToInt32() + 2);
            string s = Marshal.PtrToStringUni(lpString, len);
            _strings.Add(id, s);
        }
        lpRes = new IntPtr(lpRes.ToInt32() + 2 + (len * Marshal.SystemDefaultCharSize));
    }

    return lpRes;
}

For example, explorer.exe contains many string tables, including this one.

STRINGTABLE
BEGIN
 300 Store letters, reports, notes, and other kinds of documents.
 301 Displays recently opened documents and folders.
 302 Store and play music and other audio files.
 303 Store pictures and other graphics files.
END

Accelerators

An accelerator is a keystroke defined by the application to give the user a quick way to perform a task. Accelerators' storage is a straightforward list of ACCEL structures aligned on WORD boundaries. The number of items in an RT_ACCELERATOR resource is the size of the resource divided by the size of each structure. ACCEL contains a combination of flags that make any combination of Control, Alt and/or Shift keys and either an ASCII character or a special key.

The list of special keys is one that tells some interesting stories of Microsoft partnerships and Windows evolution as it includes such keys as the Fujitsu/OASYS keyboard 'Left OYAYUBI' key (VK_OEM_FJ_TOUROKU) or NEC PC-9800 keyboard '=' key (VK_OEM_NEC_EQUAL).

Here's an example of the accelerator table with id 251 from Windows Vista explorer.exe:

251 ACCELERATORS
BEGIN
 VK_F4, 305, ALT
 VK_TAB, 41008, VIRTKEY, NOINVERT
 VK_TAB, 41008, VIRTKEY, NOINVERT, SHIFT
 VK_TAB, 41008, VIRTKEY, NOINVERT, CONTROL
 VK_TAB, 41008, VIRTKEY, NOINVERT, SHIFT, CONTROL
 VK_F5, 41061, VIRTKEY, NOINVERT
 VK_F6, 41008, VIRTKEY, NOINVERT
 VK_RETURN, 413, VIRTKEY, NOINVERT, ALT
 Z, 416, VIRTKEY, NOINVERT, CONTROL
 VK_F3, 41093, VIRTKEY, NOINVERT
 M, 419, VIRTKEY, NOINVERT, ALT
END

Menus

There're two kinds of menu resources: classic 16 bit menus and 32-bit extended menus. The latter was introduced in Windows 95 and remains the menu format through Windows Vista. This evolution is explained in detail in Raymond Chen's "The Old New Thing" on MSDN. Similarly to dialogs, we implement a MenuResource that contains a menu in either a MenuTemplate or MenuExTemplate format. Each menu contains a MenuTemplateItemCollection or MenuExTemplateItemCollection of menu entries. The latter can be MenuTemplateItemCommand and MenuTemplateItemPopup for 16-bit classic menus and MenuExTemplateItemCommand and MenuExTemplateItemPopup for 32-bit extended menus.

A MenuTemplate is a straightforward header and a collection of menu items. A popup (one additional level deeper) is indentified by the MF_POPUP flag in the mtOption field in the menu item's header, while the last item in a collection by the MF_END flag. Each item and each menu string are aligned on WORD boundaries.

A MenuExTemplate is more complicated. The MENUEXTEMPLATE header has a strange offset construct: the first item starts at an offset from the offset header value itself. Therefore the implementation of reading and writing the menu may seem odd.

internal override IntPtr Read(IntPtr lpRes)
{
    _header = (User32.MENUEXTEMPLATE) Marshal.PtrToStructure(
        lpRes, typeof(User32.MENUEXTEMPLATE));

    IntPtr lpMenuItem = new IntPtr(lpRes.ToInt32()
        + _header.wOffset // offset from offset field
        + 4 // offset of the offset field
        );

    return _menuItems.Read(lpMenuItem);
}

internal override void Write(System.IO.BinaryWriter w)
{
    long head = w.BaseStream.Position;
    // write header
    w.Write(_header.wVersion);
    w.Write(_header.wOffset);
    w.Write(_header.dwHelpId);
    // pad to match the offset value
    ResourceUtil.Pad(w, (UInt16) (_header.wOffset - 4));
    // seek to the beginning of the menu item per offset value
    // this may be behind, ie. the help id structure is part of the first popup menu
    w.BaseStream.Seek(head + _header.wOffset + 4, System.IO.SeekOrigin.Begin);
    // write menu items
    _menuItems.Write(w);
}

Each collection of items has a prefix that contains the a context help ID. The popup items and last items in a collection are identified by a special field reserved for this purpose and not a flag as in the 16-bit structure. Each item and each menu string are aligned on DWORD boundaries.

Menu separators are an interesting special case. There're two ways of identifying a separator: the menu flags or type field contains the MFT_SEPARATOR option or both the type and the menu string are zero and NULL respectively.

Fonts

The library implements limited support for fonts.

Font resources are different from the other types of resources in that they are not usually added to the resources of a specific application program. Font resources are added to .exe files that are renamed to be .fon files. These files are libraries as opposed to applications.

A FontDirectoryResource is composed of one or more FontDirectoryEntry instances. Each instance contains a FONTDIRENTRY structure that describes the font. Note that this is the only structure that is not aligned on an even boundary.

Side-by-Side Manifests

Since Windows XP, Windows reserves a new type of resource RT_MANIFEST, 24 for Side-by-Side manifests. The manifest name can be one of the following values, best described in this MSDN post.

  • CREATEPROCESS_MANIFEST_RESOURCE_ID
  • ISOLATIONAWARE_MANIFEST_RESOURCE_ID
  • ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID

Reading and writing the manifest resource is completely straightforward. The data is loaded in an XmlDocument. The library avoids using XmlDocument unless it's used to make changes by the caller - this ensures binary compatibility between reading and writing. When loading and resaving an XmlDocument, new lines in attribute values are normalized per W3C spec section 3.3.3.

Source Code

The latest version of this article and source code can always be found on CodePlex at http://resourcelib.codeplex.com/.

Links

This article combines, implements, ports, or obsoletes some or all of the functionalities of the following publications:

History

  • 2008-06-30: Initial version
  • 2008-09-28: Added support for icons
  • 2009-02-19: Moved to CodePlex, released 1.1
  • 2009-09-15: Added support for cursors, bitmaps, dialogs, string tables, accelerators, menus and SxS manifests

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

dB.
Team Leader Application Security Inc., www.appsecinc.com
United States United States
Daniel Doubrovkine has been in software engineering for twelve years and is currently development manager at Application Security Inc. in New York City. He has been involved in many software ventures, including Xo3 and Vestris Inc, was a development lead at Microsoft Corp. in Redmond, and director of Engineering at Visible Path Corp. in New York City. Daniel also builds and runs a foodie website, http://www.foodcandy.com.

Comments and Discussions

 
QuestionAdding or replacing icons PinmemberStew Ponsford29-Apr-13 3:37 
AnswerRe: Adding or replacing icons PinmemberdB.29-Apr-13 3:40 
QuestionSave changes in GenericResource PinmemberRaffael Herrmann28-Aug-12 7:51 
AnswerRe: Save changes in GenericResource PinmemberdB.28-Aug-12 8:03 
Questionsigning PinmemberCup of Coffee25-Aug-12 10:54 
AnswerRe: signing PinmemberdB.27-Aug-12 3:56 
QuestionGetting Error "The specified resource language ID cannot be found in the image file" Pinmemberneoknux_00926-Apr-12 19:58 
AnswerRe: Getting Error "The specified resource language ID cannot be found in the image file" PinmemberdB.27-Apr-12 1:53 
QuestionWhy I can only see RT_Version and RT_Manifest resources in a .net 4 assembly? Pinmemberzwu_ca16-Jan-11 8:18 
AnswerRe: Why I can only see RT_Version and RT_Manifest resources in a .net 4 assembly? PinmemberdB.16-Jan-11 11:27 
GeneralUpdating resource.h file... PinmemberMarina100022-Sep-09 7:55 
GeneralRe: Updating resource.h file... PinmemberdB.22-Sep-09 8:06 
GeneralRC_DATA Pinmembertonyt18-Sep-09 18:34 
GeneralRe: RC_DATA PinmemberdB.20-Sep-09 17:41 
GeneralNo VersionResource being Writtten Pinmemberknogas30-Jul-09 6:37 
GeneralRe: No VersionResource being Writtten PinmemberdB.30-Jul-09 8:34 
GeneralRe: No VersionResource being Writtten Pinmemberknogas30-Jul-09 13:11 
GeneralRe: No VersionResource being Writtten PinmemberdB.1-Aug-09 4:45 
Generalgood good good good PinmemberKoaQiu2-Jul-09 18:00 
GeneralRe: good good good good PinmemberdB.11-Aug-09 2:49 
GeneralGreat Work PinmemberHarshdeep Khatri5-Mar-09 23:39 
GeneralRe: Great Work PinmemberdB.18-Mar-09 12:49 
GeneralProject moved to CodePlex, 1.1 released PinmemberdB.19-Feb-09 4:42 
GeneralNew build 1.0.1605.0 PinmemberdB.5-Jan-09 17:15 
QuestionFile size changing Pinmemberdenisejanse27-Oct-08 4:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 16 Sep 2009
Article Copyright 2008 by dB.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid