Using wcc386 and tasm together
08 Oct 2015It’s possible to use the Watcom compiler to mix your code with modules compiled (or in this article’s case, assembled) with other tools. In today’s post, I’ll take you through the simple process of creating a module using Borland’s Turbo Assembler and linking it with a simple C program.
Creating a test
First thing to do, is to create an assembly module that we can integrate with. For this module, we’re going to take two numbers; add them together and send out the result.
; adder.asm
;
; Assembly module to add two numbers
.386p
.MODEL FLAT
_TEXT SEGMENT DWORD PUBLIC 'CODE'
ASSUME CS:_TEXT
PUBLIC add_numbers
add_numbers PROC NEAR
push ebp
mov ebp, esp
ARG A:DWORD, B:DWORD
mov eax, [A]
mov ecx, [B]
add eax, ecx
mov esp, ebp
pop ebp
ret
add_numbers ENDP
_TEXT ENDS
END
This is a basic module, with most of the stack-balancing work being handled for us by the ARG directive. From the documentation:
ARG is used inside a PROC/ENDP pair to help you link to high level languages. Using ARG, you can access arguments pushed onto the stack.
Also from the documentation:
In the code that follows, you can now refer to PAR1 or PAR2, and the correct [BP + n] expression will be substituted automatically by the assembler.
Of course, we could have just as easily used the following without needing the ARG
directive:
mov eax, [ebp + 12]
mov ecx, [ebp + 8]
In accordance with the 32bit ABI, we put the result in EAX at the end of execution. Producing an object file from this assembly source is relatively easy:
C:\SRC> tasm /mx /zi /os adder.asm adder.obj
Integrating with the module
Now that we’ve got an object file with our function in it, we’ll create a very small, simple C program that will use this function. In order to do so though, we need to declare the function as an extern; as it is implemented externally to our C code:
/* test.c
*
* Assembly module usage
*/
#include <stdio.h>
/* Here's our externally implemented function */
int add_numbers(int a, int b);
int main(int argc, char *argv[]) {
printf("2 + 3 = %d\n", add_numbers(2, 3));
return 0;
}
Because we’re using C, there’s no need to really decorate the function prototype of add_numbers
. Had we been compiling a C++ module, this declaration would need to change slightly to attribute the calling convention:
extern "C" {
int add_numbers(int a, int b);
}
This module is now ready to be compiled itself and linked to the assembly implementation. We can achieve this with wcc386
and wlink
to tie it all together for us.
C:\SRC> wcc386 /d2 /3s test.c
C:\SRC> wlink system dos4g file test,adder name test
From there, we have a linked and read to run executable test.exe
.