Search code examples
c#resourcespinvoke

Marshaling a message table resource


I need to read a Message Table resource in C#.

I'm basically trying to port to C# the code in the answer to this question: Listing message IDs and symbolic names stored in a resource-only library (DLL) using Win32 API

My problem is that I cannot correctly marshal the MESSAGE_RESOURCE_DATA and MESSAGE_RESOURCE_BLOCK structures, which are defined like this (in <winnt.h>):

typedef struct _MESSAGE_RESOURCE_BLOCK {
    DWORD LowId;
    DWORD HighId;
    DWORD OffsetToEntries;
} MESSAGE_RESOURCE_BLOCK, *PMESSAGE_RESOURCE_BLOCK;

typedef struct _MESSAGE_RESOURCE_DATA {
    DWORD NumberOfBlocks;
    MESSAGE_RESOURCE_BLOCK Blocks[ 1 ];
} MESSAGE_RESOURCE_DATA, *PMESSAGE_RESOURCE_DATA;

In MESSAGE_RESOURCE_DATA, NumberOfBlocks is the number of MESSAGE_RESOURCE_BLOCK entries in the Blocks array (even if it is declared as an array with a single element).

Since I do not know at compile time the array size, I tried to marshal the struct declaring Blocks as a pointer, and then using Marshal.PtrToStructure like this:

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
struct MESSAGE_RESOURCE_BLOCK {
    public IntPtr LowId;
    public IntPtr HighId;
    public IntPtr OffsetToEntries;
}

[StructLayout(LayoutKind.Sequential)]
struct MESSAGE_RESOURCE_DATA {
    public IntPtr NumberOfBlocks;
    public IntPtr Blocks;
}

class Program {

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr LoadLibrary(string fileName);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr FindResource(IntPtr hModule, int lpID, int lpType);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

    [DllImport("kernel32.dll")]
    public static extern IntPtr LockResource(IntPtr hResData);

    static void Main(string[] args) {
        const int RT_MESSAGETABLE = 11;
        IntPtr hModule = LoadLibrary(@"C:\WINDOWS\system32\msobjs.dll");
        IntPtr msgTableInfo = FindResource(hModule, 1, RT_MESSAGETABLE);
        IntPtr msgTable = LoadResource(hModule, msgTableInfo);
        var data = Marshal.PtrToStructure<MESSAGE_RESOURCE_DATA>(LockResource(msgTable));
        int blockSize = Marshal.SizeOf<MESSAGE_RESOURCE_BLOCK>();
        for (int i = 0; i < data.NumberOfBlocks.ToInt32(); i++) {
            IntPtr blockPtr = IntPtr.Add(data.Blocks, blockSize * i);
            // the following line causes an access violation
            var block = Marshal.PtrToStructure<MESSAGE_RESOURCE_BLOCK>(blockPtr);
        }
    }
}

This does not work, however, and I'm getting an access violation error.

How can I marshal a structure like this?


Solution

  • You are not close, these structures do not contain IntPtr. DWORD is a 32-bit integer. The variable-length structures used in resource formats are very awkward in C#, there is no decent way to declare them. Best thing to do is to use Marshal.ReadXxx() to read the fields.

    The only struct declaration that's still useful is:

    [StructLayout(LayoutKind.Sequential)]
    struct MESSAGE_RESOURCE_BLOCK {
        public int LowId;
        public int HighId;
        public int OffsetToEntries;
    }
    

    And then you slug it out like this:

    static void Main(string[] args) {
        const int RT_MESSAGETABLE = 11;
        IntPtr hModule = LoadLibrary(@"C:\WINDOWS\system32\msobjs.dll");
        IntPtr msgTableInfo = FindResource(hModule, 1, RT_MESSAGETABLE);
        IntPtr msgTable = LoadResource(hModule, msgTableInfo);
        IntPtr memTable = LockResource(msgTable);
    
        int numberOfBlocks = Marshal.ReadInt32(memTable);
        IntPtr blockPtr = IntPtr.Add(memTable, 4);
        int blockSize = Marshal.SizeOf<MESSAGE_RESOURCE_BLOCK>();
    
        for (int i = 0; i < numberOfBlocks; i++) {
            var block = Marshal.PtrToStructure<MESSAGE_RESOURCE_BLOCK>(blockPtr);
            IntPtr entryPtr = IntPtr.Add(memTable, block.OffsetToEntries);
    
            for (int id = block.LowId; id <= block.HighId; id++) {
                var length = Marshal.ReadInt16(entryPtr);
                var flags = Marshal.ReadInt16(entryPtr, 2);
                var textPtr = IntPtr.Add(entryPtr, 4);
                var text = "Bad flags??";
                if (flags == 0) {
                    text = Marshal.PtrToStringAnsi(textPtr);
                }
                else if (flags == 1) {
                    text = Marshal.PtrToStringUni(textPtr);
                }
                text = text.Replace("\r\n", "");
                Console.WriteLine("{0} : {1}", id, text);
                entryPtr = IntPtr.Add(entryPtr, length);
            }
            blockPtr = IntPtr.Add(blockPtr, blockSize);
        }
    }
    

    Output in both 32-bit and 64-bit mode:

    279 : Undefined Access (no effect) Bit 7
    1536 : Unused message ID
    1537 : DELETE
    1538 : READ_CONTROL
    1539 : WRITE_DAC
    1540 : WRITE_OWNER
    1541 : SYNCHRONIZE
    1542 : ACCESS_SYS_SEC
    1543 : MAX_ALLOWED
    1552 : Unknown specific access (bit 0)
    1553 : Unknown specific access (bit 1)
    1554 : Unknown specific access (bit 2)
    1555 : Unknown specific access (bit 3)
    1556 : Unknown specific access (bit 4)
    1557 : Unknown specific access (bit 5)
    ...etc...
    

    Do keep in mind that you will not be reading the file you think you are when you use the jitter forcing to run the program in 32-bit mode. The file system redirector will get you to read C:\WINDOWS\SysWow64\msobjs.dll instead.