updating a string table with UpdateResource

String resources are different from any other resource format. They are not stored as individual entries but packaged into groups of 16 strings each. The first group stores strings 0 through 15, the second group stores strings 16 through 31, and so on. In your screenshots above the groups are displayed as the first level underneath the parent in the treeview towards the left.

String resources are also different in that they are stored as counted Unicode strings (without a zero terminator) as opposed to zero-terminated C-strings. So for example the C-string 'T' 'e' 's' 't' '\0' will be stored as 0004 0054 0065 0073 0074 where the first WORD indicates the length and the remaining 4 WORDs represent the Unicode characters.

A consequence of this resource format is that if there are gaps in the string IDs within a group the missing strings have to be accounted for with zero-length strings, or simply 0000 in resource format speak. So if your string table has strings with IDs 2 and 5 there would be a single group (1) with 16 entries: 0000 0000 <string 2> 0000 0000 <string 5> 0000 00000000.

There’s still one more piece of information required, namely which resource ID to pass for the lpName parameter in the call to UpdateResource: Since string resource groups can only be updated as a whole you have to provide the group ID, where the first group has ID 1. Calculating the group ID from a string ID is done with groupID = ( strID >> 4 ) + 1, while the relative (zero-based) offset within a group is strOffset = strID % 16. If you look at the result produced from passing MAKEINTRESOURCE(1) you can now see why it wound up in group 1 with an ID of 0.

Putting all the pieces together you can update a string resource using the following code:

void ReplaceStringTable() {

    HANDLE hRes = BeginUpdateResource( _T( "Output.exe" ), TRUE );
    if ( hRes != NULL ) {
        wstring data[] = { L"",   // empty string to skip string ID 0
                           L"Raymond",
                           L"Chen",
                           L"is",
                           L"my",
                           L"Hero!", 
                           // remaining strings to complete the group
                           L"", L"", L"", L"", L"", L"", L"", L"", L"", L""
                         };

        vector< WORD > buffer;
        for ( size_t index = 0;
              index < sizeof( data ) / sizeof( data[ 0 ] );
              ++index ) {

            size_t pos = buffer.size();
            buffer.resize( pos + data[ index ].size() + 1 );
            buffer[ pos++ ] = static_cast< WORD >( data[ index ].size() );
            copy( data[ index ].begin(), data[ index ].end(),
                  buffer.begin() + pos );
        }
        UpdateResource( hRes,
                        RT_STRING,
                        MAKEINTRESOURCE( 1 ),
                        MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
                        reinterpret_cast< void* >( &buffer[ 0 ] ),
                        buffer.size() * sizeof( WORD ) );

        EndUpdateResource( hRes, FALSE );
    }
}

Leave a Comment