This article explains that the Windows loader sets the bottom bit of an HMODULE handle to indicate the DLL was loaded as a data file (LOAD_LIBRARY_AS_DATAFILE), affecting resource lookup and freeing behavior without adding it to the module list.
<p>The numeric value of an <code>HMODULE</code> is normally the base address of the DLL or EXE it represents. These base addresses are <a title="Why is address space allocation granularity 64KB?" href="https://devblogs.microsoft.com/oldnewthing/20031008-00/?p=42223"> always multiples of 64KB</a>, so the bottom 16 bits are all zero. Yet you may run across one with the bottom bit set. What does that mean?</p>
<p>Normally, when you load a DLL, it gets an entry in the table of loaded modules. This table is consulted by functions like <code>GetModuleHandle</code> and <code>EnumProcessModules</code> to identify all the DLLs that have been loaded. It also is used to keep track of how many times each DLL has been loaded, so that the DLL is removed from memory when the correct number of <code>FreeLibrary</code> calls has been made.</p>
<p>Many of the flags to the <code>LoadLibraryEx</code> function alter how the system locates the DLL to load, but some of them alter how the DLL is itself loaded into memory. The interesting one here is the <code>LOAD_<wbr />LIBRARY_<wbr />AS_<wbr />DATAFILE</code> flag.</p>
<p>If you ask that a DLL be loaded as a data file, and there isn’t already a copy of the DLL loaded normally, then the loader will search the file system for the DLL in the manner described by the other flags, and then it will just map the DLL into memory without doing any of the usual stuff like applying fixups, and then returns you an <code>HMODULE</code> that represents the location where the DLL was mapped into memory, but it also sets the bottom bit as a note to itself to say “This wasn’t loaded the normal way.”</p>
<p>If the loader decides to map the DLL into memory directly, then the DLL does not get an entry in the list of loaded modules. While the module was loaded in a strict sense of the term, it was not loaded as a <i>functional</i> module. The code is not ready to execute: Its dependencies were not resolved. Its initialization was not run. It’s just a bunch of bytes mapped into memory. If you call <code>GetModuleHandle</code> or <code>EnumProcessModules</code>, the module won’t show up because those functions use the list of “properly” loaded modules, and your datafile DLL wasn’t put on that list.</p>
<p>Functions like <code>FindResource</code> recognize these “not really a module” modules. For example, if you ask to find a resource in a loaded-as-datafile module, the <code>FindResource</code> function knows that it has to convert RVAs in the PE header into physical file offsets.</p>
<p>And when you pass the <code>HMODULE</code> back to <code>FreeLibrary</code>, it sees that the bottom bit is set and knows, “Oh, this was never entered into the module list, so I don’t have to remove it from the module list either.”</p>
<p>This special behavior of the bottom bit is locked into the ABI thanks to this macros provided in the <code>LoadLibraryEx</code> documentation:</p>
<pre>#define LDR_IS_DATAFILE(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)1)
</pre>
<p>I don’t know if this use of the bottom bit was intended to be an implementation detail, or whether documenting it was an intentional decision, but what’s done is done, and it’s documented, so it’s too late to change it now.</p>
<p><b>Bonus chatter</b>: You can see in the documentation another macro that reveals that the second-from-bottom bit is also used as a special signal:</p>
<pre>#define LDR_IS_IMAGEMAPPING(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)2)
</pre>
<p>The post <a href="https://devblogs.microsoft.com/oldnewthing/20260619-00/?p=112447">What does it mean when the bottom bit of my <CODE>HMODULE</CODE> is set?</a> appeared first on <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>.</p>
# What does it mean when the bottom bit of my HMODULE is set? - The Old New Thing
Source: [https://devblogs.microsoft.com/oldnewthing/20260619-00?p=112447](https://devblogs.microsoft.com/oldnewthing/20260619-00?p=112447)
The numeric value of an`HMODULE`is normally the base address of the DLL or EXE it represents\. These base addresses are[always multiples of 64KB](https://devblogs.microsoft.com/oldnewthing/20031008-00/?p=42223), so the bottom 16 bits are all zero\. Yet you may run across one with the bottom bit set\. What does that mean?
Normally, when you load a DLL, it gets an entry in the table of loaded modules\. This table is consulted by functions like`GetModuleHandle`and`EnumProcessModules`to identify all the DLLs that have been loaded\. It also is used to keep track of how many times each DLL has been loaded, so that the DLL is removed from memory when the correct number of`FreeLibrary`calls has been made\.
Many of the flags to the`LoadLibraryEx`function alter how the system locates the DLL to load, but some of them alter how the DLL is itself loaded into memory\. The interesting one here is the`LOAD\_LIBRARY\_AS\_DATAFILE`flag\.
If you ask that a DLL be loaded as a data file, and there isn’t already a copy of the DLL loaded normally, then the loader will search the file system for the DLL in the manner described by the other flags, and then it will just map the DLL into memory without doing any of the usual stuff like applying fixups, and then returns you an`HMODULE`that represents the location where the DLL was mapped into memory, but it also sets the bottom bit as a note to itself to say “This wasn’t loaded the normal way\.”
If the loader decides to map the DLL into memory directly, then the DLL does not get an entry in the list of loaded modules\. While the module was loaded in a strict sense of the term, it was not loaded as a*functional*module\. The code is not ready to execute: Its dependencies were not resolved\. Its initialization was not run\. It’s just a bunch of bytes mapped into memory\. If you call`GetModuleHandle`or`EnumProcessModules`, the module won’t show up because those functions use the list of “properly” loaded modules, and your datafile DLL wasn’t put on that list\.
Functions like`FindResource`recognize these “not really a module” modules\. For example, if you ask to find a resource in a loaded\-as\-datafile module, the`FindResource`function knows that it has to convert RVAs in the PE header into physical file offsets\.
And when you pass the`HMODULE`back to`FreeLibrary`, it sees that the bottom bit is set and knows, “Oh, this was never entered into the module list, so I don’t have to remove it from the module list either\.”
This special behavior of the bottom bit is locked into the ABI thanks to this macros provided in the`LoadLibraryEx`documentation:
```
#define LDR_IS_DATAFILE(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)1)
```
I don’t know if this use of the bottom bit was intended to be an implementation detail, or whether documenting it was an intentional decision, but what’s done is done, and it’s documented, so it’s too late to change it now\.
**Bonus chatter**: You can see in the documentation another macro that reveals that the second\-from\-bottom bit is also used as a special signal:
```
#define LDR_IS_IMAGEMAPPING(handle) (((ULONG_PTR)(handle)) & (ULONG_PTR)2)
```
### Category
### Topics
## Author

Raymond has been involved in the evolution of Windows for more than 30 years\. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie\-jeebies\. The Web site spawned a book, coincidentally also titled The Old New Thing \(Addison Wesley 2007\)\. He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information\.
QuestDB engineers debug a sporadic Windows hang caused by a deadlock involving the Windows DLL Loader Lock, Rust thread-local storage destruction, JNI detach, and JVM garbage collection safepoint mechanics.
This article explains that Windows does not have a built-in concept of binary vs. text mode at the OS level; such distinctions are handled by runtime libraries like the C runtime. It clarifies that all files are treated as bytes by Windows and that content transformations must be done manually or via libraries.
Explains how the Mark-of-the-Web (MoTW) mechanism in Windows can be used to make installer applications behave differently depending on the website they are downloaded from, exploiting NTFS alternate data streams.
This article from Microsoft's Old New Thing blog explains the rationale behind best practices for Windows kernel callback functions, particularly why blocking or waiting on work items defeats their purpose, using a cautionary tale about drivers causing system hangs.
This article explains how the WM_COPYDATA message, introduced for 32-bit Windows, was retrofitted onto 16-bit Windows 3.1 by taking advantage of the shared address space, requiring no changes for inter-16-bit communication.