Goto Design Pattern 2
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.
