Skip to content
WindowsDeep Dive Published Updated 5 min readViews unavailable

Windows API Sets: Resolve Contract DLL Names Without Guessing Host Modules

Understand the Windows API-set namespace, schema-to-host mapping, optional API availability, and delay loading without confusing contracts with DLL search paths.

Names such as api-ms-win-core-*.dll often appear in PE imports, debugger output, or dependency reports. They look like ordinary DLL filenames, but an API-set name identifies a functional contract, not necessarily a file on disk. The Windows loader resolves that contract through an API-set schema on the current device and routes it to the module that hosts the implementation.

This indirection lets Windows reorganize components without requiring every consumer to hard-code a physical host DLL. The same contract can map to different host modules on different Windows devices, or be unavailable on a device that lacks the represented capability. A successful load or link therefore does not answer every question about which implementation exists or whether an optional function can run.

Contract namespace versus filesystem lookup

API-set contract names begin with api- or ext-. A versioned name can end with a version such as -l1-1-0; aliases can omit that version. The .dll suffix is part of the spelling used in import tables, not proof that a same-named file exists. When the loader sees an API-set prefix, it consults the schema rather than searching the current directory or normal DLL directories for that literal file.

The loader’s conceptual path is: identify the contract name from an import or LoadLibrary, find its mapping in the device’s schema, load the host module, and bind the requested export. Each step can fail independently. The contract may be absent or unmapped; the host may not load; or the host may not export the requested entry point. These failures are not all equivalent to “DLL missing.”

This is adjacent to, but separate from, DLL search-order hardening. Search flags and trusted directories govern how an actual module path is selected. API sets define a loader namespace mapping from a contract to a host. Do not try to secure a DLL load by inventing a physical path for an API-set contract, and do not treat API-set resolution as a replacement for explicit search policy when loading ordinary application libraries.

Optional features need an availability strategy

Microsoft documents IsApiSetImplemented for checking contract or named-group availability on supported Windows targets. This is more reliable than assuming LoadLibrary or GetProcAddress proves that an optional feature exists: compatibility reverse forwarders can allow a legacy name to resolve to a stub even when the underlying implementation is absent. Availability checks are also granular to the named contract or group; they do not guarantee that every function in a module has the expected behavior.

An availability check cannot protect startup from an unconditional static import. Static imports are resolved before application code reaches the check. If the target may not exist, put the optional call behind a delay-loaded or dynamically resolved boundary, query the documented contract, then handle missing module, missing export, and the API’s own return errors. Microsoft documents apiquery2.h and the OneCore library options for IsApiSetImplemented; check the target SDK and platform partition for the binary you are building.

#include <windows.h>
#include <apiquery2.h>

bool hasOptionalSessionApi() {
    // This contract name is the one shown in Microsoft's WTS example.
    // A production caller must also delay-load or dynamically resolve the
    // optional API so process startup does not fail before this test runs.
    return IsApiSetImplemented("ext-ms-win-session-wtsapi32-l1-1-0") != FALSE;
}

The fragment checks a documented contract; it does not test an arbitrary export or load a module. Use the individual API’s reference page to find its requirements and API-set name. Do not derive a contract name from a function name by guesswork. If the header provides an Is...Present helper for that API, Microsoft recommends preferring that helper because it carries the appropriate contract or group.

Inspect imports without treating inspection as runtime proof

For a release artifact, inspect imports on the actual binary rather than relying on a developer-machine DLL list. With the Visual Studio tools installed, dumpbin /imports app.exe can show imported module names, including API-set contract spellings. Treat that as a build-time inventory: it says what the image asks the loader to resolve, not which host a specific target device’s schema selects.

For a runtime support matrix, test on every Windows edition or device class the product claims to support. Record the OS build, architecture, contract name, availability result, load outcome, export outcome, and actual API result. Test the absent-contract path, a present contract with a missing optional capability, a delay-load failure, and an ordinary desktop system. Keep GetApiSetModuleBaseName separate from a filesystem check: Microsoft documents it as a schema lookup that reports a mapped host name, not as an operation that confirms a file exists, loads the module, or checks its exports.

Avoid parsing private API-set schema files from the Windows directory as an application compatibility contract. The schema is loader data and can vary by product and release; supported APIs are the contract names and availability functions documented by Microsoft. Dependency inspection tools may decode the schema for diagnostics, but an application should not ship a copied parser as its runtime policy.

Failure handling and release review

Classify startup and runtime failures precisely. A static import missing from the target can prevent process initialization; a delay-loaded import can fail when first called; a dynamic load can succeed while a requested export is absent; an availability query can report that a contract/group is missing; and a supported API can still fail due to ordinary runtime conditions. Return a feature-specific fallback or a clear unsupported result instead of silently substituting a different behavior.

Keep binaries built against the intended SDK and minimum OS target. Include x86, x64, and ARM64 variants in tests when shipped. Analyze the final packaged image, because a transitive library can introduce an import your own source never names. When a dependency references API-set contracts, do not “fix” the report by copying API-set-named files beside the executable: they are not normal deployable DLL dependencies.

API sets make implementation refactoring possible while preserving documented contracts. The robust consumer depends on those contracts, tests optional availability where necessary, and keeps contract mapping distinct from DLL path selection and function-level success.

Related:

Sources:

Comments