Cogs and Levers A blog full of technical stuff

Using wcc386 and tasm together

It’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.