Fix “the ordinal NNN could not be located in ace64.dll”

Symptom

After dropping OpenADS’ ace64.dll (or ace32.dll) onto an existing application’s PATH, Windows pops:

The procedure entry point at ordinal 328 could not be located in the dynamic link library ace64.dll.

The number changes (328, 415, …). The application aborts.

Root cause

The application or its rddads.lib was linked against an ace32.lib / ace64.lib import library that records imports by ordinal, not by name. Each Ads* entry point lives at a specific ordinal (1, 2, 3, …, 328, …) inside the SAP-shipped DLL. The Windows loader looks up that ordinal in the new DLL — and fails, because OpenADS’ upstream .def file only declares exports by name, leaving ordinals to be auto-assigned in source-file order. The numbers don’t line up with SAP’s.

OpenADS ships a clean-room implementation of every Ads* function — the names are public (Harbour’s contrib/rddads calls them by name). What’s missing for ordinal-linked binaries is just the numeric mapping from ordinal → name. That mapping lives in the SAP-shipped DLL the user already legally possesses; we never read SAP-owned source. A small helper extracts the mapping locally on the user’s machine.

Fix — one-time, on the same Windows box that has SAP’s DLL

1. Dump SAP’s export table

Open a Developer Command Prompt and run:

dumpbin /exports ace64.dll > ace64-exports.txt

The output starts like:

        ordinal hint RVA      name
              1    0 00001234 AdsConnect60 = AdsConnect60
              2    1 0000125A AdsDisconnect = AdsDisconnect
            ...
            328  ... ........ AdsXyzWhatever = AdsXyzWhatever

2. Convert it into an OpenADS .def file

python tools\scripts\sap_ordinals_to_def.py ace64-exports.txt > openads_ace_ordinals.def

The script reads only the <ordinal> <hint> <RVA> <name> columns. The names round-trip back into the OpenADS source — those are the same public Harbour-callable names. The ordinals are user-supplied metadata. No SAP code is read or copied.

3. Rebuild OpenADS with the custom .def

cmake -S . -B build\ord -DOPENADS_ACE_DEF=%CD%\openads_ace_ordinals.def
cmake --build build\ord --target openads_ace --config Release

The resulting build\ord\src\Release\ace64.dll keeps every function name we already implement AND assigns each one the ordinal SAP picked. Drop it on the application’s PATH ahead of the original DLL — the loader’s ordinal lookups now hit the right symbol.

Why we don’t ship ace64-exports.txt upstream

The export table is metadata about a binary OpenADS does not ship and does not own. Re-publishing it inside this repo would mean redistributing data extracted from a SAP-owned binary, which conflicts with the project’s clean-room policy. Each user generates their own copy locally from a binary they already have a legal right to inspect.

Won’t there be ordinal drift across SAP versions?

Yes. The mapping you extract from ace64.dll 11.10 may differ from 11.30 or 12.0. Re-run the two-step extract whenever the target environment upgrades.

If the consuming application can be re-linked, don’t bother with the ordinal stunt. Generate an import library from OpenADS’ own DLL (which uses by-name exports):

lib /def:openads_ace.def /machine:x64 /out:ace64.lib

…and re-link rddads and the application against that ace64.lib. The result links by name, no ordinal coupling at all. This is the only future-proof approach.