.Trust our experience in High-Power Network Tools and Communication Tools.

Calling 64-bit assembly language functions lodged inside the Delphi source code

This article applies to Delphi XE2 and above.

I will explore here  the 64-bit BASM (Borland Assembly Language) facility. I started as an Assembly Language addicted in my old days and still can not resist to experiment a bit when the opportunity arises.

With BASM we can mix Assembly Language with Delphi code in the same source file (even inline within the same function for 32-bit BASM). Microsoft Visual Studio allows as well the mix of C/C++ with Assembly Language in the same source file, but only in 32-bit code. So, Delphi wins here because it supports both 32-bit and 64-bit in the same source file. Really amazing.

I know there are limitations in BASM, but there are also many limitations with the VS Microsoft inline Assembly Language. So, for very complex Assembly Language parts it is still better to compile the ASM in a separate file and then link everything together.

I have prepared a small demonstration program where BASM is called from Delphi. It will compile both in 32-bit and 64-bit. The program is simple, although more advanced than most samples you can find in the internet. The program shows as well how you can return a large amount of information from the call, in this case an array.

 

The Source Code

One thing you will notice immediately by looking at the code below is that 32-bit Assembly Language is totally different from 64-bit Assembly language. So, if you want to port 32-bit routines to 64-bit, forget about that and start new ones from scratch.

Here is the source code (you can download the demo program from the link at the bottom of this page).

unit basmTestUnit;

interface

uses
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
    Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
    TBasmTest = class(TForm)
    GroupBox1: TGroupBox;
    lblCaption: TLabel;
    edCaption: TEdit;
    lblInput: TLabel;
    edInput: TEdit;
    btGo: TButton;

procedure btGoClick(Sender: TObject);
private

public
    { Public declarations }
end;

var
    BasmTest: TBasmTest;

implementation

{$R *.dfm}

var
    myReturnString : string = 'I am back in Delphi!';
    charCount : integer;

type
    bigarray = array[0..127] of char;

function assemblyTestFunction(myCaption: string; myText : string): bigarray;
{$IFDEF CPUX86}
asm
    // This is the 32-bit case with the default Register (Borland Fastcall) calling convention
    // where the Result is an extra var parameter.
    // Arguments are passed in EAX, EDX and ECX, left to right. So, the Result (i.e, the
    // return value pointer) will come to the function in ECX.
    push edi
    push esi
    push dword ptr 0
    push dword ptr myCaption
    push dword ptr myText
    push dword ptr 0
    mov edi, ecx; // Save the return pointer to EDI because we need ECX as a counter and
                  // EDI is the destination pointer in a rep movsw instruction
    call MessageBox
    mov esi, dword ptr myReturnString
    mov ecx, [charCount]
    rep movsw
    pop esi
    pop edi
    ret
end;

{$ELSE}
{$IFDEF CPUX64}
    // This is the 64-bit case
    // General rule: Integer and pointer arguments are passed left to right in RCX, RDX, R8
    // and R9. HOWEVER, when there is a large return value, this is the case, RCX contains
    // a pointer to the return space when the callee is called and all Registers usage are
    // pushed one to the right
    //
asm
    push rsi
    push rdi
    push rbx
    push rbp // Yes, it is a kludge. No need to push rbp, it is just to align the stack.
             // The alternative is 'and rsp,-16'
    sub rsp, 28h

    xor r9, r9
    mov rbx, rcx
    mov rax, rdx
    mov rdx, r8
    mov r8, rax
    xor rcx, rcx
    call MessageBox

    mov rdi, rbx
    mov rsi, qword ptr myReturnString
    mov ecx, [charCount]
    rep movsw
    add rsp, 28h
    pop rbp
    pop rbx
    pop rdi
    pop rsi
    ret
end;
{$ENDIF CPUX64}
{$ENDIF}

procedure TBasmTest.btGoClick(Sender: TObject);
var
    retValue : bigArray;
begin
    fillchar(retValue, sizeof(bigArray),0);
    charCount := length(myReturnString);
    retValue := assemblyTestFunction(edCaption.Text, edInput.Text);
    showMessage(retValue);
end;

end.

 Download the Delphi BASM 32-bit/64-bit demo program source code Now!