Microsoft KB Archive/271442

{|
 * width="100%"|

PRB: LoadLibrary Function May Fail Because of Resource Limitations

 * }

Q271442

-

The information in this article applies to:


 * Microsoft Win32 Application Programming Interface (API), used with:
 * Microsoft Windows 95
 * the operating system: Microsoft Windows 98
 * the operating system: Microsoft Windows Millennium Edition

-

SYMPTOMS
This article describes a number of scenarios in which the 32-bit LoadLibrary function may fail on a computer that is running Windows 95, Windows 98, or Windows Millennium Edition because of constraints regarding resources (bitmaps, icons, menus, and so forth) within a dynamic-link library (DLL).

CAUSE
All of the limitations, which are described in detail in the &quot;More Information&quot; section of this article, are necessary to support backward compatibility with 16-bit versions of Windows. They do not apply to Windows NT or Windows 2000. However, for compatibility across Windows platforms, it is best to ensure that all 32-bit DLLs conform to the constraints of Windows 95, Windows 98, and Windows Millennium Edition.

RESOLUTION
When you design a DLL, all resource IDs should be kept below 32,768 to eliminate problem #1 (described in the &quot;More Information&quot; section). Named resources should be avoided entirely to eliminate problems 2 and 3.

When a DLL's resources exceed any of these limitations, the DLL should be separated into multiple smaller DLLs.

Problem 1: Resource ID Exceeds 32 KB
A resource can be identified by either a number (called a resource ID) or by a name. With most types of resources, the maximum valid value for a resource ID is 32,767. String resources are an exception in that a string can have a resource ID as large as 65,535. If the value of a resource ID exceeds these limits, the LoadLibrary call will fail with error code 11 (ERROR_BAD_FORMAT). For additional information, click the article number below to view the article in the Microsoft Knowledge Base:

"Q137248 PRB: Cannot Load Module with Resource ID Greater Than 32767 (0x7FFF)" A good resource editor, such as the editor that is included with Microsoft Visual Studio, prevents the DLL designer from entering invalid resource IDs.

The code sample provided later in this section can be used to compile a utility that determines whether the resource IDs within a given DLL are valid.

Problem 2: Size of In-Memory Resource Table Exceeds 64 KB
When a 32-bit DLL is loaded on a computer that is running Windows 95, Windows 98, or Windows Millennium Edition, the operating system creates an in-memory resource table to index the various types of resources that are contained within the DLL. The maximum size for this table is 65,535 bytes. If the table size exceeds this limit, the LoadLibrary function call fails with error code 8 (ERROR_OUT_OF_MEMORY).

The in-memory table requires 16 bytes for each resource within the DLL. A named resource requires an additional number of bytes equal to the length of the string plus one. Finally, about 100 bytes is required for alignment and indexing within the table. Thus, a DLL has an upper limit of about 4,089 resources on Windows 95, Windows 98, and Windows Millennium Edition, assuming none are named.

The sample code provided in this article can be used to compile a utility that determines the required size of the in-memory resource table that is generated for a given DLL.

Problem 3: Name Offset Exceeds 64 KB
When a DLL is generated, the resource compiler stores the names of all named resources in a contiguous block of memory in the DLL. This block of memory makes up the resource Names Table. Another portion of the file is used to index all resources (both named and unnamed). This part of the file is the Resource Directory. For each named resource, the entry in the directory contains an offset value that points to the name of the resource in the Names Table. If any of these offset values exceeds 65,535, the LoadLibrary function call fails with error code 8 (ERROR_OUT_OF_MEMORY).

The generation of this table is completely handled by the resource compiler and cannot be controlled by the developer. For this reason, it is best to avoid named resources.

The following sample code can be used to compile a utility that determines whether the name offsets within a given DLL are valid.

Sample Code
The following sample code can be used to generate a utility that can analyze a DLL to ensure that it satisifes all of the constraints described in this article.

//////////////////////////////////////////////////////////////////////// // // ResScan.cpp // // This program scans a DLL to determine if its resources are compatible // with Windows 95, Windows 98, and Windows Millennium Edition. // It supplements article Q271442 in the Microsoft Knowledge Base. // // THIS CODE AND INFORMATION IS PROVIDED &quot;AS IS&quot; WITHOUT WARRANTY OF //  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED // TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A //  PARTICULAR PURPOSE. // // Copyright (C) 2000 Microsoft Corporation. All rights reserved. // ////////////////////////////////////////////////////////////////////////


 * 1) include 
 * 2) include 

//////////////////////////////////////////////////////////////////////// // // FUNCTION:     VerifyFormat - This function verifies the file's //                format and returns the offset of the resource //               directory within the file. // // PARAMETERS:   hFile - file pointer for I/O operations. //               ppResDir - address of a pointer that receives the //                  address of the resource directory structure. // // RETURN VALUE: If a valid DLL file, TRUE; otherwise, FALSE. // ////////////////////////////////////////////////////////////////////////

BOOL VerifyFormat(PVOID pFile, PIMAGE_RESOURCE_DIRECTORY *ppResDir) {

PIMAGE_DOS_HEADER       pIdh; PIMAGE_FILE_HEADER      pIfh; PIMAGE_OPTIONAL_HEADER32 pIoh; PDWORD                  pdwSignature; PIMAGE_SECTION_HEADER   pSection;

DWORD dwResourceAddress; int  i;

*ppResDir = NULL;

// Set header pointers. pIdh        = (PIMAGE_DOS_HEADER) pFile; pdwSignature = (PDWORD) ((DWORD) pIdh + pIdh->e_lfanew); pIfh        = (PIMAGE_FILE_HEADER) ((DWORD) pdwSignature + sizeof(*pdwSignature)); pIoh        = (PIMAGE_OPTIONAL_HEADER32) ((DWORD) pIfh + sizeof(*pIfh));

// Verify magic number. if (pIdh->e_magic != IMAGE_DOS_SIGNATURE) { printf(&quot;invalid magic number\n&quot;); return FALSE; }

// Verify PE signature. if (*pdwSignature != IMAGE_NT_SIGNATURE) {

switch (LOWORD(*pdwSignature)) {

case IMAGE_DOS_SIGNATURE: // 0x5A4D MZ            printf(&quot;File has DOS signature; LoadLibrary will fail\n&quot;); break;

case IMAGE_OS2_SIGNATURE: // 0x454E NE            printf(&quot;16-bit format; 32-bit LoadLibrary will fail\n&quot;); break;

case IMAGE_VXD_SIGNATURE: // 0x454C LE            printf(&quot;File has VXD signature; LoadLibrary will fail\n&quot;); break;

default: printf(&quot;Invalid PE header signature %x\n&quot;, *pdwSignature); break; }     return FALSE; }

// Verify x86 executable. if (pIfh->Machine != IMAGE_FILE_MACHINE_I386) { printf(&quot;Non-x86 file. Machine ID %x\n&quot;, pIfh->Machine); return FALSE; }

// Verify alignment. if (pIoh->SectionAlignment < 0x1000) { printf(&quot;Section alignment is %x, must be 0x1000\n&quot;,           pIoh->SectionAlignment); return FALSE; }

// Verify that there are resources in the DLL. dwResourceAddress = pIoh ->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; if (dwResourceAddress == 0) { printf(&quot;There are no resources in the DLL\n&quot;); return FALSE; }

// Determine the offset to the raw resource data pSection = IMAGE_FIRST_SECTION(pdwSignature); for (i = pIfh->NumberOfSections - 1; i >= 0; i--) {

if ((pSection + i)->VirtualAddress == dwResourceAddress) {

*ppResDir = (PIMAGE_RESOURCE_DIRECTORY) ((DWORD) pFile              + (pSection + i)->PointerToRawData); break; }  }

return ((BOOL) *ppResDir); }

//////////////////////////////////////////////////////////////////////// // // FUNCTION:     VerifyResources - This function scans through the //               resource directory and subdirectories and adds up the //               number of bytes required to store the resource table //               in memory. It also watches for resource name offsets //               greater than 0xFFFF. Finally, it watches for resource //               IDs greater than 0x7FFF (or > 0xFFFF for strings). // // PARAMETERS:   pMainDir - address of the main resource directory. //               pdwResTableSize - address of a DWORD to receive the //                  size of the in-memory resource table. //               pdwBadNames - address of a DWORD to receive the //                  number of resources with invalid name offsets. //               pdwBadIds - address of a DWORD to receive the number //                  of resources with invalid resource IDs. // // RETURN VALUE: Returns TRUE if the DLL will be loaded successfully //               on Win9x; otherwise, FALSE. // ////////////////////////////////////////////////////////////////////////

BOOL VerifyResources(PIMAGE_RESOURCE_DIRECTORY pMainDir,     PDWORD pdwResTableSize, PDWORD pdwBadNames, PDWORD pdwBadIds) {

PIMAGE_RESOURCE_DIRECTORY pSubDir;

PIMAGE_RESOURCE_DIRECTORY_ENTRY pMainDirEntry; PIMAGE_RESOURCE_DIRECTORY_ENTRY pSubDirEntry;

PIMAGE_RESOURCE_DIRECTORY_STRING pResName;

DWORD dwSubDir; DWORD dwNumIdSubDirs; DWORD dwNumNamedSubDirs;

DWORD dwEntry; DWORD dwNumIdEntries; DWORD dwNumNamedEntries;

// For alignment, 8 bytes are always used. DWORD dwResTableSize = 8; DWORD dwBadNames    = 0; DWORD dwBadIds      = 0;

// Enumerate subdirectories in main resource directory. dwNumIdSubDirs   = pMainDir->NumberOfIdEntries; dwNumNamedSubDirs = pMainDir->NumberOfNamedEntries; pMainDirEntry    = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) ((DWORD) pMainDir + sizeof(*pMainDir));

for (dwSubDir = 0; dwSubDir < dwNumIdSubDirs + dwNumNamedSubDirs;        dwSubDir++, pMainDirEntry++) {

// Each subdirectory consumes 8 bytes. dwResTableSize += 8;

pSubDir = (PIMAGE_RESOURCE_DIRECTORY) ((DWORD) pMainDir +           pMainDirEntry->OffsetToDirectory);

// Enumerate entries in resource subdirectories. dwNumIdEntries   = pSubDir->NumberOfIdEntries; dwNumNamedEntries = pSubDir->NumberOfNamedEntries; pSubDirEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY) ((DWORD) pSubDir           + sizeof(*pSubDir));

for (dwEntry = 0; dwEntry < dwNumIdEntries + dwNumNamedEntries;           dwEntry++, pSubDirEntry++) {

// Each entry consumes 16 bytes. dwResTableSize += 16;

if (pSubDirEntry->NameIsString) { // Named resource.

// If named entry has an offset greater than 0xFFFF, // the DLL won't work on Win9x. if (pSubDirEntry->NameOffset > 0xFFFF) dwBadNames++;

// Each named entry consumes an additional number of           // bytes equal to the string length + 1 and // strings longer than 255 bytes will be truncated.

pResName = (PIMAGE_RESOURCE_DIRECTORY_STRING) ((DWORD) pMainDir + pSubDirEntry->NameOffset); dwResTableSize += pResName->Length > 255 ? 256 :                 pResName->Length + 1;

} else { // ID resource

if (pMainDirEntry->Name == (WORD) RT_STRING) {

// If a string's ID is greater than 0xFFFF, // the DLL won't work on Win9x. if ((pSubDirEntry->Name << 4) > 0xFFFF) dwBadIds++;

} else {

// If resource ID for any other type of resource is              // greater than 0x7FFF, the DLL won't work on Win9x. if (pSubDirEntry->Name > 0x7FFF) dwBadIds++; }        }      }   }

*pdwResTableSize = dwResTableSize; *pdwBadNames    = dwBadNames; *pdwBadIds      = dwBadIds;

return (dwResTableSize <= 0xFFFF        && dwBadNames == 0         && dwBadIds == 0); }

//////////////////////////////////////////////////////////////////////// // // FUNCTION:     main - This is the entry point for the program. It //               expects a single command-line parameter to be passed. //               This parameter should identify the name and path of //                the DLL to be scanned. // // PARAMETERS:   argc - the number of command-line arguments. //               argv[] - an array of command-line arguments. // // RETURN VALUE: If successful, an offset address to the resource //               directory within the DLL file; otherwise, 0. // ////////////////////////////////////////////////////////////////////////

void main(int argc, char *argv[]) {

HANDLE hFile       = NULL; HANDLE hFileMapping = NULL; PVOID pView        = NULL;

PIMAGE_RESOURCE_DIRECTORY pResDir;

DWORD dwResTableSize; DWORD dwBadNames; DWORD dwBadIds;

if (argc < 2) { printf(&quot;usage: ResScan \n&quot;); return; }

__try {

// Open the file for shared read access. hFile = CreateFile( argv[1], GENERIC_READ, FILE_SHARE_READ,           NULL, OPEN_EXISTING, 0, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) __leave;

// Create a read-only file mapping object for the file. hFileMapping = CreateFileMapping( hFile, NULL,           PAGE_READONLY, 0, 0, NULL); if ( !hFileMapping ) __leave;

// Map a view of the file. pView = MapViewOfFile( hFileMapping, FILE_MAP_READ, 0, 0, 0 ); if ( !pView ) __leave;

if (!VerifyFormat(pView, &pResDir)) __leave;

if (VerifyResources(pResDir, &dwResTableSize, &dwBadNames, &dwBadIds)) printf(&quot;Library should load fine on Win9x\n&quot;); else printf(&quot;Library will not work on Win9x\n&quot;               &quot;   Resource Table Size:         %d bytes\n&quot;                &quot;   Number of bad name offsets:  %d\n&quot;                &quot;   Number of bad resource IDs:  %d\n&quot;,                dwResTableSize, dwBadNames, dwBadIds);

} __finally {

// Clean up resources. if ( pView ) UnmapViewOfFile( pView );

if ( hFileMapping ) CloseHandle( hFileMapping );

if ( hFile != INVALID_HANDLE_VALUE ) CloseHandle( hFile ); } } Additional query words:

Keywords : kbResource kbSDKWin32 kbGrpDSUser kbDSupport

Issue type : kbprb

Technology : kbAudDeveloper kbWin32sSearch kbWin32API