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. Overtime this library was extended through these resource types and can be easily completed for all
the remaining ones.
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 ...
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)
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);
}
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.
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 (eg. 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");
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)
.
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.
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
}
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 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);
}
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 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 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.
With 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
encouters a resource of type 14
(RT_GROUP_ICON
), it
creates an object of type IconDirectoryResource
. The latter creates an
IconResource
, which loads an DeviceIndependentBitmap
.
IconDirectoryResource
represents RT_GROUP_ICON
, a
collection of icon resources.IconResource
represents a single RT_ICON
icon with
one or more images.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 an .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.
The next natural extension after icons is cursors. Cursor structure is virtually identical to icons with a few notable differences.
CursorDirectoryResource
represents RT_GROUP_CURSOR
, a
collection of cursor resources.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.
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
.
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
to be able to assign a System.Drawing.Image
instance to DeviceIndependentBitmap.Bitmap
.
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 (eg. 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 }
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
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
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.
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.
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.
The latest version of this article and source code can always be found on CodePlex at http://resourcelib.codeplex.com/.
This article combines, implements, ports, or obsoletes some or all of the functionalities of the following publications: