惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
罗磊的独立博客
雷峰网
雷峰网
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
B
Blog RSS Feed
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
D
Docker
Recent Announcements
Recent Announcements
T
Tailwind CSS Blog
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
小众软件
小众软件
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
I
InfoQ
S
SegmentFault 最新的问题

The Old New Thing

Why do Microsoft job levels start in the high 50's instead of starting at a sane number like 1? - The Old New Thing Why didn't Read­Directory­ChangesW provide a way to correlate the two sides of a rename operation? - The Old New Thing How can I remove the Close button from my window caption? - The Old New Thing Why is the x86 undefined instruction called ud2? Why 2? - The Old New Thing What algorithm did Windows XP use to choose your initial user picture? - The Old New Thing A sample use of the winstart.bat file in Windows 95 - The Old New Thing Why don't we allow stacks to be sparse, instead of forcing them to be contiguous? - The Old New Thing What happens if you change a window class's GCL_CB­WND­EXTRA? - The Old New Thing The case of the progress callback that never got called when progress happened - The Old New Thing The perils of binding to value types in XAML - The Old New Thing Microspeak: Funded / unfunded - The Old New Thing AWE does not require PAE, though PAE makes it much more useful - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 2 - The Old New Thing On forcing all derived classes to implement a specific non-virtual method, part 1 - The Old New Thing In the product end game, every change carries significant risk, episode 2 - The Old New Thing Why didn't the Windows Entertainment Pack just run the MS-DOS version inside an emulator? - The Old New Thing Comparing the two holograms on the Windows 95 box - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function, practical exam - The Old New Thing Reducing C++ template bloat by factoring out the type-dependent portions of the function - The Old New Thing On wrapping a callable in a lambda that just calls it with the same parameters - The Old New Thing Why did the Microsoft Entertainment Pack for Windows have a special sticker announcing that it also had Tetris? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing How do functions like alloca allocate memory from the stack? - The Old New Thing Forcing an ARM64X executable to run as a specific architecture - The Old New Thing A little helper class for managing LPPROC_THREAD_ATTRIBUTE_LISTs - The Old New Thing The comments that go into code versus those that go into the pull request description - The Old New Thing The little-known winstart.bat batch file - The Old New Thing How can I perform a Copy­File&shy in unbuffered mode? - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 - The Old New Thing Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4 - The Old New Thing
What does it mean when the bottom bit of my HMODULE is se...
Raymond Chen · 2026-06-19 · via The Old New Thing

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, 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 Chen

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.