.386 .model flatAn important assembler option is case-sensitive external names. All Win32 API names are case-sensitive.
Microsoft: The start address is a PUBLIC symbol. When we invoke the MS linker we specify a /ENTRY: switch to set the start address. If we specify /ENTRY:start we will need to define a PUBLIC symbol called _start (note the prepended underscore).
Borland: The start address is the symbol specified in the END directive. It needn't be PUBLIC.
public _start ; public status ignored by Borland linker .code ; simplified segment directive _start: ; rest of program end _start ; address ignored by MS linker
.data cmd_line_ptr dd 0 extrn GetCommandLine:near .code call GetCommandLine mov [cmd_line_ptr] eax call parse_cmd_line ; our own parserOr so it would seem. That's what all the documents and all the programming books would show. Although the function returns a string pointer there are two kinds of strings: those containing 8-bit ANSI characters and those containing 16-bit Unicode characters. And consequently there are actually two real names associated with this function: GetCommandLineA (A for ANSI) and GetCommandLineW (W for wide Unicode). The C or C++ programmer references an "include" file that redefines (with a macro) GetCommandLine as one of the two real names depending on whether a special (macro) variable has been defined or not. We can use a similar trick in MASM and TASM.
Microsoft: There is a separate .LIB file for each DLL so you need to list each DLL that's accessed. The LIB for KERNEL32.DLL is called KERNEL32.LIB and similarly for all other DLL's. You only need to use one of the two links each .LIB file provides: a link to a JMP stub or a indirect data link. The name of the JMP stub link is a "decorated" version of the API name: prepend an underscore and append "@" + number of argument bytes in decimal. Thus the GetCommandLineA link is called _GetCommandLineA@0. The indirect data link is almost the same: replace the prepended underscore with __imp__. Thus the indirect data link is called __imp__GetCommandLineA@0. The two are used differently:
extrn _GetCommandLineA@0:near call _GetCommandLineA@0 ; direct CALL extrn __imp__GetCommandLineA@0:dword ; must be DWORD !!! call __imp__GetCommandLineA@0 ; indirect CALLBorland: In TASM 4.0 most of the Win32 API is gathered into a single IMPORT32.LIB file. The link name is exactly the same as the API name. There is only one link name the name of a JMP stub and you access it with a direct call.
extrn GetCommandLineA:near call GetCommandLineA ; direct callAs I've mentioned in both linkers the direct call does not jump into the DLL! The JMP stub is an indirect JMP that makes the final leap into the DLL. Despite appearances the Microsoft indirect call eliminates the JMP and consequently is the faster call.
; Don't include vclib.inc if using Borland linker include vclib.inc ; Microsoft (Visual C++) link names include win32hst.inc ; constants structures and dual namesWithin VCLIB.INC are two entries:
GetCommandLineA equ <_GetCommandLineA@0> GetCommandLineW equ <_GetCommandLineW@0>Within WIN32HST.INC is a conditional and two entries:
if UNICODE ; ... GetCommandLine equ GetCommandLineW ; ... else ; ... GetCommandLine equ GetCommandLineA ; ... endif
HANDLE CreateFile( LPCTSTR lpFileName // pointer to name of the file DWORD dwDesiredAccess // access (read-write) mode DWORD dwShareMode // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes // pointer to security descriptor DWORD dwCreationDistribution // how to create DWORD dwFlagsAndAttributes // file attributes HANDLE hTemplateFile // handle to file with attributes to copy );Each of these seven arguments must be put on the stack in reverse order before the function is called. All of the above arguments are 32-bits. The LP and lp prefixes means the argument is a pointer. We need to specify a minimum of four arguments to do any I/O. Unspecified arguments must be zero. (The operator large is needed for TASM 4.0.)
.data source_filename_ptr dd 0 dest_filename_ptr dd 0 source_file_handle dd 0 dest_file_handle dd 0 extrn CreateFile:near .code push large 0 ; template file push large FILE_ATTRIBUTE_NORMAL push large OPEN_EXISTING push large 0 ; security attributes push large 0 ; share mode push large GENERIC_READ push [source_filename_ptr] call CreateFile cmp eax INVALID_HANDLE_VALUE je bad_source mov [source_file_handle] eax push large 0 ; template file push large FILE_ATTRIBUTE_NORMAL push large CREATE_ALWAYS push large 0 ; security attributes push large 0 ; share mode push large GENERIC_WRITE push [dest_filename_ptr] call CreateFile cmp eax INVALID_HANDLE_VALUE je bad_dest mov [dest_file_handle] eaxIf you're familiar with the way C is typically implemented you'll notice that the arguments are not popped off the stack. The Win32 API functions do this for you. This is the stdcall calling convention. The only exceptions are the functions that have a variable number of arguments. The conventional cdecl calling convention generated by C compilers is used by these excepted functions in which case arguments are popped after a return. In the core Win32 API there is only one function that uses the cdecl calling convention -- wsprintf (it has two versions wsprintfA and wsprintfW).
BUFFER_SIZE equ 32768 .data bytes_read dd 0 bytes_written dd 0 .data? temp_buffer db BUFFER_SIZE dup(?) extrn ReadFile:near WriteFile:near .code copy_loop: push large 0 ; ptr to OVERLAPPED structure push offset bytes_read push large BUFFER_SIZE ; maximum bytes to transfer push offset temp_buffer push [source_file_handle] call ReadFile cmp [bytes_read] 0 je end_copy push large 0 ; ptr to OVERLAPPED structure push offset bytes_written push [bytes_read] ; write all bytes that were read push offset temp_buffer push [dest_file_handle] call WriteFile jmp copy_loop end_copy:
extrn CloseHandle:near ExitProcess:near .code push [source_file_handle] call CloseHandle push [dest_file_handle] call CloseHandle push large 0 ; exit code call ExitProcess
.data? cmd_line_2 db 1024 dup(?) ; space for extracted arguments .code parse_cmd_line: mov esi [cmd_line_ptr] ; source mov edi offset cmd_line_2 ; destination call scan_blanks call scan_arg ; skip EXE name call scan_blanks mov [source_filename_ptr] edi call scan_arg call scan_blanks mov [dest_filename_ptr] edi call scan_arg retWe'll first perform the usual leading blank elimination.
tab equ 9 .code scan_blanks_1: inc esi scan_blanks: mov al [esi] cmp al ' ' je scan_blanks_1 cmp al tab je scan_blanks_1 ret ; ESI points to first nonblankFile names in the latest versions of Windows can have embedded spaces which can be signaled by quoting. We'll strip away the quotes. The CreateFile function requires zero (null) terminated strings so we'll add it in.
scan_arg: mov al [esi] cmp al 0 je exit_scan_arg cmp al '"' je scan_quoted scan_unquoted: mov [edi] al inc esi inc edi mov al [esi] cmp al 0 je exit_scan_arg cmp al ' ' je exit_scan_arg cmp al tab je exit_scan_arg cmp al '"' je exit_scan_arg jmp scan_unquoted scan_quoted: inc esi ; skip quote mov al [esi] cmp al 0 je exit_scan_arg cmp al '"' je exit_quoted scan_quoted_1: mov [edi] al inc esi inc edi mov al [esi] cmp al 0 je exit_scan_arg cmp al '"' je exit_quoted jmp scan_quoted_1 exit_quoted: inc esi ; skip quote exit_scan_arg: mov byte ptr [edi] 0 ; terminate destination string inc edi ret ; esi points past argument
.data bad_source_msg db "Can't open source file" 13 10 bad_source_msg_len equ $ - bad_source_msg bad_dest_msg db "Can't open destination file" 13 10 bad_dest_msg_len equ $ - bad_dest_msg extrn GetStdHandle:near .code bad_source: mov esi offset bad_source_msg mov ecx bad_source_msg_len jmp error_exit bad_dest: mov esi offset bad_dest_msg mov ecx bad_dest_msg_len error_exit: push large 0 ; ptr to OVERLAPPED structure push offset bytes_written push ecx ; byte count push esi ; byte buffer push large STD_OUTPUT_HANDLE call GetStdHandle push eax call WriteFile push large 0 ; exit code call ExitProcess
Microsoft: The 32-bit linker has the same name as the
16-bit linker. LINK expects to receive object files in a
UNIX-like COFF format with a default extension of .OBJ. If a
.OBJ file is not a COFF file
the linker (ver. 3.0 does this)
automatically converts the OMF file to COFF. The resulting file
is not retained.
On a side note the latest versions of MASM can create Win32 COFF files directly. NASM is another assembler that can generate Win32 COFF files.
As noted before there will be one .LIB file for each DLL linked in. The API functions we've used are all in KERNEL32.DLL so only one .LIB file needs to be linked in. Win32 views a Console App as a special "subsystem" so we need to specify that (/subsystem:console).
And as noted before an entry point must be specified.
The option names are case-insensitive.
The following assumes that some environment variables have been set up.
link cp kernel32.lib /entry:start /subsystem:consoleBorland: TLINK32 can only handle OMF files. So linking in COFF files (for example the DirectX .LIB files or the .OBJ files created by VC++) is not possible.
tlink32 /Tpe /ap /c cp import32.lib