Win32-errors- Alternative error handling for Win32 foreign calls

Copyright(c) Michael Steele 2014
Safe HaskellTrustworthy



This module provides functions which can be used as drop-in replacements for Win32 when writing wrappers to foreign imports.

You will likely need to import modules from Win32 as well. To avoid accidentally calling the standard error handling functions it's a good idea to hide a few names:

import qualified System.Win32.Error.Foreign as E
import System.Win32 hiding (failIfFalse_, failIf, failUnlessSuccess, failWith)

Handling error conditions in Windows revolves around a thread-local global variable representing the most recent error condition. Functions indicate that an error occurred in various ways. The C++ programmer will observe that a function failed, and immediately call GetLastError to retrieve details on the possible cause or to get a localized error message which can be relayed to a human in some way.

There are some cases where an error code may mean different things depending on varying context, but in general these codes are globally unique. Microsoft documents which error codes may be expected for any given function.

When working with functions exported by Win32, error conditions are dealt with using the IOError exception type. Most native Win32 functions return an error code which can be used to determine whether something went wrong during its execution. By convention these functions are all named something of the form "c_DoSomething" where DoSomething matches the name given by Microsoft. A haskell wrapper function named "doSomething" will typically, among other things, check this error code. Based on its value the operating system will be queried for additional error information, and a Haskell exception will be thrown.

Consider the createFile function used to open existing files which may or may not actually exist.

createFile "c:\\nofilehere.txt" gENERIC_READ
           fILE_SHARE_NONE Nothing oPEN_EXISTING 0 Nothing

If no file by that name exists the underlying c_CreateFile call will return iNVALID_HANDLE_VALUE. This will result in an IOError exception being thrown with a String value indicating the function and file name. Internally, the IOError will also contain the error code, which will be converted to a general Haskell value.

The Win32-errors package works similarly. A (simplified) wrapper around c_CreateFile could be written as follows. Source code from the Win32 package often provides a good starting point:

createFile name access mode = withTString name $ \ c_name ->
    E.failIf (== E.toDWORD E.InvalidHandle) "CreateFile" $
    c_CreateFile c_name access fILE_SHARE_NONE nullPtr
                 mode 0 nullPtr


failIf :: (a -> Bool) -> Text -> IO a -> IO a Source #

Copied from the Win32 package. Use this to throw a Win32 exception when an action returns a value satisfying the given predicate. The exception thrown will depend on a thead-local global error condition. The supplied Text value should be set to the human-friendly name of the action that triggered the error.

failIfFalse_ :: Text -> IO Bool -> IO () Source #

This function mirrors the Win32 package's failIfFalse_ function.

failIfNull :: Text -> IO (Ptr a) -> IO (Ptr a) Source #

This function mirrors the Win32 package's failIfNull function.

failUnlessSuccess :: Text -> IO DWORD -> IO () Source #

Perform the supplied action, and throw a Win32Exception exception if the return code is anything other than Success. The supplied action returns a DWORD instead of an ErrCode so that foreign imports can be used more conveniently.

failWith :: Text -> ErrCode -> IO a Source #

Throw a Win32Exception exception for the given function name and error code.

errorWin :: Text -> IO a Source #

Windows maintains a thread-local value representing the previously triggered error code. Calling errorWin will look up the value, and throw a Win32Exception exception. The supplied Text argument should be set to the name of the function which triggered the error condition.

Calling this action when no error has occurred (0x00000000 -- ERROR_SUCCESS) will result in an exception being thrown for the Success error code.