DLL Ordinals Explained: A Deep Dive into Windows Export Tables
- William Mackins
- Jun 23
- 3 min read
What Is a DLL Ordinal?
A DLL ordinal is a numerical identifier assigned to an exported function (or variable) in a Windows DLL. Instead of referring to an export by its name, clients can refer to it by this number (the “ordinal”).
How It Works
In the PE (Portable Executable) format, the DLL’s export directory can list exports in two ways: by name (string) and by ordinal (integer).
Each export has an associated ordinal value (usually starting at a base, e.g., 1 or a specified base in a .def file). The export directory holds:
A table of function RVAs indexed by ordinal.
A table of names and their corresponding ordinals for name-based lookup.
When a module imports by ordinal, the loader directly looks up the function pointer by index, skipping the name lookup.
Viewing Ordinals
Common tools to inspect a DLL’s exports and ordinals:
dumpbin (from Visual Studio Developer Command Prompt):
dumpbin /EXPORTS MyLibrary.dll
This shows exported functions with their ordinals and names.
Dependency Walker (depends.exe): shows exports with both name and ordinal.
PE viewers (e.g., CFF Explorer, PE-bear): display the Export Table including ordinal values.
Example output snippet from dumpbin /EXPORTS:
ordinal hint RVA name
1 0 00011000 FunctionA
2 1 00011100 FunctionB
3 2 00011200 @3 ; exported only by ordinal
Here FunctionA has ordinal 1, FunctionB ordinal 2, and an unnamed export at ordinal 3.
Exporting by Ordinal
When building a DLL, you can control ordinals via:
Module-Definition (.def) file:
EXPORTS
FunctionA @1
FunctionB @2
SomeInternalFunc @3 PRIVATE
The @n specifies the ordinal.
You can mark some exports PRIVATE (name not in name table) so only ordinal import works.
__declspec(dllexport) without .def: the linker assigns ordinals automatically (usually sequential), but names are used. You can still import by ordinal if you know the assigned value, though this is fragile if it changes.
Importing by Ordinal
In client code or import tables:
Linker directive: In a .lib or via pragma, you can specify importing by ordinal, e.g., using a .def for the import side or manually editing import tables. Rare in C/C++ directly; more common in dynamic loading:
LoadLibrary + GetProcAddress:
HMODULE h = LoadLibrary("MyLibrary.dll");
FARPROC fn = GetProcAddress(h, MAKEINTRESOURCEA(ordinal));
// MAKEINTRESOURCEA casts the ordinal integer to LPCSTR
Here you pass the ordinal value instead of a string name.
In some cases, import libraries (.lib) may encode ordinal imports, but this is less common in typical builds.
Advantages and Disadvantages
Advantages
Slightly smaller import tables: omitting names can reduce size in very constrained environments.
Obfuscation: hiding the name makes reverse engineering a bit harder.
Version stability (if managed carefully): if names might change but ordinals remain stable across versions, clients can still bind. (However, this is rare in practice.)
Disadvantages
Fragile across versions: if ordinals shift between DLL versions, imports break. Name-based imports are more robust.
Less readable/debuggable: developers and tools typically rely on names.
Harder maintenance: tracking which ordinal corresponds to which function requires careful documentation.
Compatibility issues: many build systems and languages assume name-based exports.
Best Practices
Prefer name-based exports unless there is a compelling reason (e.g., legacy API compatibility requiring fixed ordinals).
If you must use ordinals:
Define a stable .def file early and maintain it strictly across versions.
Document each export’s ordinal clearly.
Avoid inserting or removing exports in the middle of the ordinal sequence; append new exports at higher ordinals.
When using LoadLibrary/GetProcAddress by ordinal, wrap calls in helper functions and check for failure gracefully.
A DLL ordinal is simply the numerical index of an exported function in a DLL’s export table. While Windows supports import-by-ordinal, it’s generally more error-prone than name-based imports. You can inspect and control ordinals via tools (dumpbin, Dependency Walker) and .def files. Use ordinals only when necessary, and manage them carefully to avoid versioning pitfalls.
Comments