Scenario x64_2: Creating static libraries. Integer to text conversion function.

Prerequisites
The software environment is installed and configured.

Scenario
Create the conversion library linked in a static way. Write the main program to call the library function. Display the result in a console.

Result
The integer number converted to its string representation is displayed in a console.

Start
Create the library source file.

Step 1
To create the static library, the assembler module shouldn't have the main procedure defined. All other procedures will be made available for other programs by default. If there is a need to hide a procedure from visibility, it is possible to mark it as PRIVATE. The first step is to assemble the source file with MASM.

ml64 /c source.asm

The second step is to create the lib file with the lib tool.

lib source.obj

This will create the source.lib file, which can be imported into the program, where we can use all available procedures.

The example for the library will be the program containing the function “int_to_ascii”, which converts the integer number into a text representation. Let's begin with the function itself. The function accepts two arguments: the number to be converted passed by RCX and the pointer to the buffer for the resulting text passed by RDX. It converts a signed 64-bit number and returns the updated pointer in RDX and the length of the resulting string in RAX. We can use the results in the WinAPI function WriteConsoleA to display the ASCII representation of a number in the console.

Please note that the code of the library module does not have the “main” function, which in an executable program file serves as an entry point.
option casemap:none
 
.code
; ----------------------------------------
; int_to_ascii
; input:   RCX = signed 64-bit number
; output:  updated string at address in RDX
;          RAX = length of the resulting string
; ----------------------------------------
int_to_ascii PROC
    push rbx              ; rbx is nonvolatile
    push rdi              ; rdi is nonvolatile
    sub rsp, 24           ; shadow space
    mov [rsp+8],  rcx
    mov [rsp+16], rdx
    mov rax, rcx          ; mov imput number to rax
 
; point rdi into the buffer end
    mov rdi, rdx          ; pointer to a string
    add rdi, 31
    mov byte ptr [rdi], 0 ; mark string end with terminator
 
    mov rbx, 10
 
; test if the numer is positive or negative
    xor r8d, r8d	  ; r8 = 0 → positive flag
    test rax, rax	  ; test the sign
    jge convert		  ; jump if rax positive
 
    neg rax	          ; change the sign of rax
    mov r8d, 1		  ; r8 = 1 → negative flag
 
; conversion loop
convert:
    dec rdi		  ; starting from the end of the text (least significant digit)
    xor rdx, rdx	  ; prepare to divide rdx:rax by rbx
 
    div rbx		  ; rax / 10 → remainder in rdx
    add dl, "0"		  ; convert remainder into ASCII
    mov [rdi], dl	  ; write character of a digit to buffer
    test rax, rax	  ; test if there is still a value for conversion
    jne convert
 
; add minus if needed
    cmp r8d, 0            ; r8 = 1 → negative flag
    je write
    dec rdi               ; add minus character
    mov byte ptr [rdi], '-'
 
write:
; calculate length of the text (end – rdi)
    mov rax, [rsp+16]     ; get pointer to an original buffer
    add rax, 31
    sub rax, rdi          ; resulting number length in rax
    mov rdx, rdi          ; adjusted pointer to string in a buffer
 
    add rsp, 24           ; restore stack pointer 
    pop rdi
    pop rbx
    ret
int_to_ascii ENDP
 
END

Step 2
This library can be imported into the assembler program or a program written in another programming language. Assembly program can look as follows:

option casemap:none
 
; include the system library and our convert library
includelib kernel32.lib
includelib convert.lib
 
; declare function we use in our program
EXTERN GetStdHandle:PROC
EXTERN WriteConsoleA:PROC
EXTERN ExitProcess:PROC
EXTERN int_to_ascii:PROC
 
; costant required by GetStdHandle system function
STD_OUTPUT_HANDLE equ -11
 
; data section
.data
    buffer db 32 dup(0) ; buffer for a string
    hOut   dq ?         ; placeholder for console handle
    dummy  dq ?         ; place for dummy parameter
 
;code section
.code
 
; -------------------------------------------
; main function of the program - entry point
; -------------------------------------------
main PROC
; shadow space
    sub rsp, 40
 
; get the handle of stdout
    mov ecx, STD_OUTPUT_HANDLE ; console output
    call GetStdHandle
    mov hOut, rax       ; store the handle
 
; call conversion function
    mov rcx, 33550336   ; number for displaying
    lea rdx, buffer     ; pointer to a buffer
    call int_to_ascii
 
; prepare agruments for WriteConsoleA(hOut, rdi, len, ...)
    mov rcx, hOut       ; console handle 
                        ; pointer to the beginning of a string is in rdx
    mov r8, rax         ; nNumberOfCharsToWrite is in rax  
    lea r9, dummy       ; dummy for lpNumberOfCharsWritten
    mov qword ptr [rsp+20h], 0  ; lpReserved (must be NULL)
 
    call WriteConsoleA  ; displaying function
 
    xor ecx, ecx        ; return value of a program
    call ExitProcess    ; go back to Windows OS
main ENDP
 
END

Result validation
The code should print the converted integer number as text in a console window.

Fun fact. The number chosen for the example is not random. It is the fifth perfect number. Such a number is equal to the sum of its positive proper divisors.

If it does not assemble?
Observe the messages passed by the assembler and linker programs. Refer to the documentation and instruction set. Make sure that all source files are available to MASM, and the resulting object and library files are available to the linker program.