.386
.model flat
An 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 parser
Or 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 CALL
Borland: 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 call
As 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 names
Within 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],eax
If 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
ret
We'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 nonblank
File 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:console
Borland: 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