The module loader for Microsoft Windows CE devices uses an algorithm to load a DLL that is different from the algorithm that loads a DLL on the desktop versions of Windows. This applies to Handheld and Pocket PC devices based on Windows CE versions 2.x through 3.0.
A DLL may not load because of several reasons. This article focuses on what occurs when a lack of process address space (slot) exists for the DLLs that you request to load. You can use this information to help you troubleshoot why a DLL cannot load. Workarounds are described at the end of this article.
A DLL may not load for several reasons:
- The module is not found.
- A dependent module is not found.
- The DLL returns a failure from its DLLMain() function.
For applications that use the LoadLibrary()
function to load the DLL, on failure the application can call the GetLastError()
to get the error code and then to determine the reason for the failure.
This article discusses how Windows CE loads a DLL so that you can understand why a DLL may not load because of a lack of address space on the part of the calling application. When an application lacks address space, LoadLibrary
returns a NULL value. GetLastError
returns error code 14, ERROR_OUTOFMEMORY
Windows CE loads a DLL differently than Microsoft Windows 98, Microsoft Windows Millennium Edition, Microsoft Windows 2000, or Microsoft Windows XP do. Because of these differences, a DLL may not load although there appears to be enough memory or address space for the DLL.
The following two examples describe how Windows CE loads DLLs.
- The ROM of the Windows CE device includes a ROM-based DLL. The ROM of the Windows CE device includes a ROM-based DLL. The ROM-based DLL is designated to map and to run from ROM. A module that maps and runs from ROM is also known as execute in place (XIP).
- A RAM-based DLL is one that maps into RAM when the DLL loads. The RAM-based DLL may be a custom DLL that you install on the device. A RAM-based DLL may also come with the Windows CE operating system. The RAM-based DLL is designated to be copied from ROM into RAM when the operating system starts, before the application loads the DLL.
The process address space for an active Windows CE process begins at 0x00000000 and then ends at 0x02000000, for a total of 32 MB. You must share that address space with ROM-based DLLs, RAM-based DLLs, the EXE module for the process, stacks, heaps, and with any other allocations that you create for the process.
The size of the DLL file does not indicate the actual space that the DLL requires to load. You can determine the initial address space that you require to load a DLL when you run Dumpbin.exe for your DLL. dumpbin
is a command-line tool that is included with eMbedded Visual Tools. dumpbin
outputs information to the command window. The summary information includes the size of each section. To load the DLL, you must have address space equal to the sum of the section sizes plus 4 KB.
To determine where in a process address space to load a DLL, Windows CE finds the highest address and then seeks down until it finds enough free virtual address space to fit the DLL module. For example, on the Pocket PC 2002 device of one OEM, this address is 0x00AB0000. The address varies between different device makes and models. The device maker build tools of the Windows CE image automatically calculates the value.
Windows CE does not map a RAM-based DLL from anywhere between 0x02000000 and the address where the lowest ROM module is mapped.
- That area of address space is reserved for ROM modules during the build of the operating system image.
- When the operating system is built, the modules that are designated as XIP load only in that area of memory.
- If a process loads a DLL that is in ROM and is designated as XIP, the Windows CE loader maps it at its predetermined address.
The Windows CE loader ignores the image base address for the RAM-based DLL. When a DLL is compiled and linked, the linker writes an address that is labeled the "image base" in the portable executable (PE) header. The image base address tells the module loader the preferred address to load the DLL. Although the image base is present in the DLL file, the Windows CE loader does not use it.
When a DLL is loaded in one process, Windows CE reserves that address space in every process address space. Multiple processes that use the same DLL share it at the same relative address in every process. If your application does not use the same DLL, it cannot use the memory that is reserved for that DLL. In other words, Windows CE does not map a RAM-based DLL at an address where another process maps another DLL. For example, if Process A loads DLL X
at address 0x00970000, Windows CE does not map DLL Y
in the Process B address space at 0x00970000. Instead, Windows CE seeks the next lower address that is available, depending on the size of DLL Y
. So if DLL Y
is in the size range of 128 KB, Windows CE selects 0x00950000 if that address is available for Process B. Because of the way that Windows CE allows processes to share a common DLL, Windows CE does not permit two different processes to load two different DLLs at the same relative address of each process.
Before a DLL is mapped in memory the Windows CE loader resolves the implicit links to other DLLs, known as dependent DLLs. If any of those modules is not yet loaded, then Windows CE stops the load of the current DLL and starts the process to load the dependent DLLs. To do this, Windows CE uses the same algorithm recursively. This is one point where Windows CE is similar to desktop versions of Windows. It is mentioned here because a DLL may not load because of factors of the dependent DLLs, not the DLL itself.
If you determine that your application cannot load a DLL because of a lack of address space for the DLL, consider the following workarounds:
- If your application is implemented in Microsoft C or Microsoft C ++, or if your eMbedded Visual Basic application uses custom DLLs, determine whether you use the VirtualAlloc function with the MEM_TOP_DOWN flag. When you use this flag, you allocate memory as high as possible, and you can cause DLLs to not load. VirtualAlloc must not use this flag.
- If your application uses a lot of small custom DLLs, consolidate the code in one or two DLLs. The minimum address space that a DLL consumes is 64 KB. Therefore, for example, if you design your application so that thirty DLLs all fall between 8 and 10 KB, you waste 1,620 KB of address space.
- Break the application down into more than one process. Use one process to load one set of DLLs and then use another to load a different set of DLLs. (Based on the application design, this may not be practical. If both processes must run simultaneously, then it does not help to use two separate processes.)
- Load all your essential DLLs when you first load the application. If possible, keep the DLLs loaded. After you make this change in your application, watch to see if memory allocations or other processes do not work as a result. If they do, you must redesign your application to minimize DLL or memory.
- Redesign the application to use less memory overall. Use only essential DLLs and then unload them if you do not require them. Minimize heap usage, as this impacts how many DLLs can load. Note that references to ActiveX objects do not unload when you set the object variable to Nothing.