Goto Design Pattern 2

(Original version)

Since the discussion on the first edition of this was cut short, I’m posting an update sooner than I might’ve.

The majority of suggested alternatives to using GOTO were based on encapsulating the initialisation code within a class and either breaking out or throwing an exception when one fails, then uninitialising the rest.

My favourite of those posted was Xavier’s, however it still requires having an identical interface for each function which makes it difficult to pass return values around.

To better illustrate the pattern, I’ve written up a procedure for using Windows’ file-mapping functions to map an entire file to memory. (Apologies for the length and the code being in assembly language. Also, the “.if/jmp” construction is an inefficient way of doing it, but it should be easier to read for people used to C-based languages.)

FILEMAP STRUCT
    hFile       DWORD   ?
    hMapping    DWORD   ?
    pBase       DWORD   ?
    dwSize      DWORD   ?
FILEMAP ENDS
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
; This procedure opens and maps the specified file and returns a FILEMAP structure.
 
; IN: spFilename - a pointer to a string containing the path of the file
; OUT: EAX       - a pointer to a FILEMAP structure
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
MapAFile PROC USES esi spFilename:DWORD
    LOCAL       pFileMap:DWORD
 
; --------------------------------------------------------------------------
; allocate space for the FILEMAP structure
 
    ; get the handle of the process heap
    invoke  GetProcessHeap
    .if(eax == 0)
        jmp Failed_GetProcessHeap
    .endif
 
    ; allocate SIZEOF FILEMAP bytes
    invoke  HeapAlloc, eax, 0, SIZEOF FILEMAP
    .if(eax == 0)
        jmp Failed_HeapAlloc
    .endif
    mov     pFileMap, eax
    mov     esi, eax
    ASSUME  esi:PTR FILEMAP     ; [esi] is now treated as a FILEMAP structure
 
; --------------------------------------------------------------------------
; open the file
 
    invoke  CreateFile, spFilename, GENERIC_READ or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0
    .if(eax == INVALID_HANDLE_VALUE)
        jmp Failed_CreateFile
    .endif
    mov     [esi].hFile, eax
 
; --------------------------------------------------------------------------
; create the file mapping object
 
    invoke  CreateFileMapping, [esi].hFile, 0, PAGE_READWRITE, 0, 0, 0
    .if(eax == 0)
        jmp Failed_CreateFileMapping
    .endif
    mov     [esi].hMapping, eax
 
; --------------------------------------------------------------------------
; get the 64-bit size of the file in EDX:EAX
 
    push    0
    invoke  GetFileSize, [esi].hFile, esp
    pop     edx
    .if(eax == -1)
        push    eax
        push    edx
        invoke  GetLastError
        mov     ecx, eax
        pop     edx
        pop     eax
        .if(ecx != 0)
            jmp Failed_GetFileSize
        .endif
    .endif
 
; --------------------------------------------------------------------------
; limit the size to a signed 32-bit value (ie. maximum of 2^31-1)
    .if(edx != 0 || eax > 7FFFFFFFh)
        mov     eax, 7FFFFFFFh
        mov     edx, 0
    .endif
 
    mov     [esi].dwSize, eax
 
; --------------------------------------------------------------------------
; map the entire file (up to the first 2^31-1 bytes)
 
    invoke  MapViewOfFile, [esi].hMapping, FILE_MAP_WRITE, 0, 0, [esi].dwSize
    .if(eax == 0)
        jmp Failed_MapViewOfFile
    .endif
    mov     [esi].pBase, eax
 
; --------------------------------------------------------------------------
; return the FILEMAP structure in EAX
    mov     eax, pFileMap
    ret
 
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
;  FAILURE CODE
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
 
; --------------------------------------------------------------------------
Failed_MapViewOfFile:
    ; nothing has been initialised between the view and getting the file size
 
; --------------------------------------------------------------------------
Failed_GetFileSize:
    ; we need to close the file mapping object
    invoke  CloseHandle, [esi].hMapping
 
; --------------------------------------------------------------------------
Failed_CreateFileMapping:
    ; we need to close the file handle
    invoke  CloseHandle, [esi].hFile
 
; --------------------------------------------------------------------------
Failed_CreateFile:
    ; we need to free the pFileMap memory
    invoke  GetProcessHeap
    .if(eax == 0)
        jmp Failed_GetProcessHeap
    .endif
 
    invoke  HeapFree, eax, 0, pFileMap
 
; --------------------------------------------------------------------------
Failed_HeapAlloc:
    ; the process heap doesn't need to be closed, so we don't do anything here
 
; --------------------------------------------------------------------------
Failed_GetProcessHeap:
    xor     eax, eax    ; return null-pointer
    ret
 
    ASSUME esi:NOTHING  ; this is only an assembler directive, so it can go anywhere
MapAFile ENDP

Hopefully this example will clarify what I meant by passing parameters and the futility of encapsulating each function in a parameter-less function. Maybe I’ll convert this to work with Xavier’s model and see how that looks.

Also, for those who mentioned using exceptions, Raymond Chen posted his view a few years ago on Cleaner, more elegant, and wrong with a follow-up including garbage collection and some very telling comments here.