Skip to content

Fix Win32 calling convention mismatch in DLL exports - #5

Open
MaxLogic wants to merge 1 commit into
landrix:mainfrom
MaxLogic:fix/win32-cdecl-calling-convention
Open

Fix Win32 calling convention mismatch in DLL exports#5
MaxLogic wants to merge 1 commit into
landrix:mainfrom
MaxLogic:fix/win32-cdecl-calling-convention

Conversation

@MaxLogic

@MaxLogic MaxLogic commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

This changes the Delphi declarations for the unmanaged parse and
isValidNumber exports from stdcall to cdecl, matching the calling
convention used by PhoneNumbersUnmanaged.dll.

It also adds DUnitX interop regression tests that exercise the real Win32 DLL
in Release configuration.

Root cause

PhoneNumbersUnmanaged/PhoneNumbersWrapper.cs exports both functions using
[DllExport] without specifying another calling convention. These exports use
cdecl, while PhoneNumbers.pas declared the corresponding Delphi function
pointers as stdcall.

On Win32, the two conventions disagree about stack cleanup:

  • With cdecl, the caller removes the arguments from the stack.
  • With stdcall, the caller expects the callee to remove them.

The DLL therefore leaves the arguments on the stack, while the Delphi caller
compiled from the stdcall declaration assumes they have already been
removed. This leaves the Win32 stack pointer unbalanced and can cause an access
violation after returning from the DLL.

Why the problem can remain hidden

The mismatch is configuration-dependent and does not necessarily fail during
manual testing.

The supplied sample project uses Win32 Debug settings with optimization
disabled and stack-frame generation enabled. A generated stack-frame epilogue
can restore ESP from EBP when the Delphi method returns, accidentally
repairing the imbalance before it becomes visible.

Win64 is also unaffected in practice because Windows x64 uses a unified calling
convention, so Delphi's cdecl and stdcall declarations do not produce the
same stack-cleanup disagreement as on Win32.

The original declarations produced the following observed results:

Configuration Parse IsValidNumber
Win32 Debug Pass Pass
Win32 Release Pass Access violation
Win64 Release Pass Pass

For Win32 Release, IsValidNumber crashed consistently with exception code
0xC0000005 for both valid and invalid input. Parse happened to survive due
to its different generated call-site and managed return-value handling, but
its declaration was still ABI-incompatible and therefore unsafe.

This explains how both the supplied sample and applications using the wrapper
could previously have appeared to work.

Changes

  • Declare the parse function pointer as cdecl.
  • Declare the isValidNumber function pointer as cdecl.
  • Add Win32 DUnitX interop tests for:
    • parsing a valid German mobile number;
    • validating a valid German mobile number;
    • rejecting an invalid German number.
  • Document that the regression suite must be run as Win32 Release because
    Debug stack frames can mask this class of ABI defect.

Declare the managed DLL exports as cdecl and add real Win32 Release DUnitX coverage for validation and parsing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant