rddads / X# RDD compatibility
OpenADS’ ace64.dll / ace32.dll is a drop-in replacement for
the Advantage Client Engine. Two third-party RDDs link against it
by name:
- Harbour
contrib/rddads— covered end-to-end since v0.1.0-rc1 (the first Harbour smoke). - X#’s
AXDBFCDXRDD (ADSRDD.prg) — covered locally + over the wire since v1.0.0-rc19 (M12.22 / M12.23).
This page documents the X# RDD work and is the canonical reference for what each of the X#-specific exports actually does.
How X# binds OpenADS
X# ADSRDD.prg performs LoadLibrary("ace32.dll") (or ace64.dll)
and resolves every entry point by name through
GetProcAddress. The X# Advantage RDD calls a much wider surface
than Harbour’s contrib/rddads, including a set of versioned
overloads (AdsCreateTable90, AdsCreateIndex90, etc.) that exist
to absorb the charset / collation / page-size parameters newer ACE
builds added.
OpenADS therefore exports every Ads*NN name X# binds; most
forward to the base signature, a few are accept-and-ignore for
session / statement toggles that don’t apply to a clean-room
implementation, and the genuinely-unimplemented return
AE_FUNCTION_NOT_AVAILABLE so the X# runtime falls back to its own
client path.
M12.22 — versioned ACE overloads
| Export | Behaviour |
|---|---|
AdsConnect26 |
Forwards to AdsConnect60. |
AdsCreateTable71 / AdsCreateTable90 |
Forward to AdsCreateTable (drop charset / collation / page-size). |
AdsOpenTable90 |
Forwards to AdsOpenTable80. |
AdsCreateIndex90 |
Forwards to AdsCreateIndex61 after re-mapping flags. |
AdsDDAddTable90 |
Forwards to AdsDDAddTable. |
AdsDDCreateRefIntegrity62 |
Forwards to AdsDDCreateRefIntegrity. |
AdsFindFirstTable62 / AdsFindNextTable62 |
Forward to base. |
AdsGetDateFormat60 |
Forwards to AdsGetDateFormat. |
AdsGetExact22 |
Forwards to AdsGetExact. |
AdsReindex61 |
Forwards to AdsReindex. |
AdsRestructureTable90 |
Forwards to AdsRestructureTable. |
AdsGetBookmark60 / AdsGotoBookmark60 |
Round-trip the recno as a 4-byte blob. |
AdsCancelUpdate90 / AdsSetProperty90 |
Accepted no-ops. |
AdsFindConnection25 / AdsGetTableHandle25 |
Report not-found — OpenADS keys by handle, not path / name. |
M12.23 — the X# export gap
A live run of X#’s AXDBFCDX against OpenADS’ DLL surfaced ~45
more entry points ADSRDD.prg binds by name. The behaviour:
- Field setters (
AdsSetField,AdsSetEmpty,AdsSetNull,AdsSetShort,AdsSetMoney,AdsSetTime,AdsSetTimeStamp) — all handle the ACE “field name or 1-based ordinal cast to a pointer” idiom (X#’s_FieldSubcallsAdsGetFieldType/Length/Decimalsby ordinal, passing a tiny pointer value the old code dereferenced as a string). - Field readers (
AdsGetDate,AdsGetMemoBlockSize,AdsGetTableOpenOptions,AdsGetBookmark) — real implementations. - Cursor helpers (
AdsCancelUpdate,AdsContinue,AdsEval*Expr) —AdsCancelUpdateis an accept-and-ignore; the others returnAE_FUNCTION_NOT_AVAILABLEso X# falls back. - RI / unique / autoinc enforcement toggles —
accept-and-ignore (the underlying enforcement still happens
through
AdsCreateIndex/ DD). AdsStmt*helpers — returnAE_FUNCTION_NOT_AVAILABLE; X#’s SQL surface routes around them.
Semantics fixes that shipped with M12.23
These looked like “missing exports” from the X# RDD’s point of view, but were actually wrong behaviour in existing entries:
AdsAppendRecordauto-locks the new record. ACE semantics for non-exclusive tables — X#’sGoHotrefuses to write a record it sees as unlocked.AdsIsRecordLocked/AdsLockRecord/AdsUnlockRecordhonourrecno == 0= current record and report the real lock state instead of stubbing0.AdsCreateIndex61/AdsCreateIndex90option-bit fix. The “descending” flag isADS_DESCENDING(0x08), not0x02—0x02isADS_COMPOUND, which X#’s ADSRDD always sets for CDX orders, so the old mask built every X# order descending andDbGoToplanded on the last key.AdsCreateTable/AdsCreateTable90stage an empty.fptnext to the.dbfwhen the field list has anMfield (usingusMemoBlockSize, default 64). Without itConnection::open_tablecan’t attach a memo store and any memo write fails “memo store not attached”.
Remote-server X# (M12.23, rc19)
Three more fixes so X#’s ADSRDD drives openads_serverd over the
wire (AdsConnect60("tcp://host:port/<datadir>",
ADS_REMOTE_SERVER) → AX_SetConnectionHandle → DbUseArea):
remote_field_indexhonours the “field name OR 1-based ordinal cast to a pointer” idiom — same as the local side.- The remote
AdsOpenTablebranch defaults a missing extension to.dbf(X# passes the bare table name for remote tables). AdsGetTableFilenamegained a remote path (returning the opened name) instead of failingAE_INTERNAL_ERROR— X#’sOpencalls it right after_FieldSub.
Test harness
tests/smoke/xsharp/
├── AdsSmoke.prg # local: ace64.dll directly
└── AdsSmoke_remote.prg # remote: openads_serverd over tcp://
Both pass end-to-end against OpenADS:
- Local smoke:
DbCreate(incl. an M field) →DbUseArea→OrdCreate×2 →DbAppend+FieldPut×4 →DbCommit→ NAME-orderGoTop/Skip ±/GoBottom/Eof→DbSeekhit + miss → memo round-trip →DbDelete/DbRecall→ replace a key field + re-read through the CITY order →DbCloseArea. - Remote smoke: opens
customer.dbfon the server and does read / nav (RecCount/GoTop/Skip ±/GoBottom/Eof/FieldGet).
Doctest coverage: tests/abi_versioned_overloads_test.cpp (local)
and tests/abi_remote_overloads_test.cpp (over the wire, gated
on -DOPENADS_TEST_REMOTE=ON).
Follow-ups landed in rc20+ from X# feedback
- rc21 / M12.24 —
AdsGetLastTableUpdatereal signature matches ACE (UNSIGNED8* pucDate, UNSIGNED16* pusLen);AdsSetDateFormatis no longer a no-op;AdsSetAOFreturns success +ADS_OPTIMIZED_NONEon non-optimisable expressions instead of error 7200. - rc22 / M12.25 —
AdsCreateTablestamps today’s date into the DBF header up front (matches ACE), so a fresh create+open reports today instead of1900-00-00until the firstDbAppend.