Next Previous Contents

7.3 The Com library

The Com library offers support for performing various basic COM programming tasks, akin to what the Com library that's present on all Win32 boxes offers to C/C++ programmers. The types and functions presented in this section are all exported by the Haskell module Com.

Com exceptions

In COM, methods communicate status back to their caller by returning an HRESULT, a 32-bit value indicating rudimentary information about success or failure of a call. The Haskell Com library allows you to construct HRESULTs and provides an interface between Haskell's IO exceptions and HRESULTs. The programming interface provided is:

type HRESULT = Int32

checkHR    :: HRESULT  -> IO () 
   -- failure => raise IO exception.
returnHR   :: IO ()    -> IO HRESULT 
   -- exception raised => return E_FAIL.
   -- if not, return S_OK.

coFail     :: HRESULT -> IO a
coFailWith :: HRESULT -> String -> IO a
   -- Raise (HRESULT) IO exceptions.

isCoError    :: IOError -> Bool
getCoErrorHR :: IOError -> Maybe HRESULT

coGetErrorString :: IOError -> String

winErrorToHR :: Int32 -> HRESULT
getLastError :: IO Word32

succeeded :: HRESULT -> Bool
failed    :: HRESULT -> Bool

hresultToString :: HRESULT -> IO String

On top of that, the module ComException defines all the HRESULT exception codes that the COM and OLE APIs use.

The GUID type

So-called globally unique identifiers are used throughout by COM, and the following functions are provided for manipulating them in Haskell:

data GUID = ... -- abstract
  -- instance of Eq and Show

mkGUID        :: String -> GUID
guidToString  :: GUID   -> String
stringToGUID  :: String -> IO GUID
nullGUID      :: GUID

marshallGUID   :: GUID -> IO ForeignObj
unmarshallGUID :: Bool -> Ptr GUID -> IO GUID
copyGUID       :: GUID -> IO Addr
writeGUID      :: Ptr GUID -> GUID -> IO ()
readGUID       :: Bool -> Ptr GUID -> IO GUID
sizeofGUID     :: Word32

The CLSID type

One use of GUIDs are as unique identifiers of COM components / classes, so called CLSIDs. To help catch any accidental use of a GUID that doesn't represent a CLSID, we make CLSID a separate Haskell type from a GUID:

data CLSID = ... -- abstract
  -- instance of Eq and Show
  
mkCLSID            :: String -> CLSID
stringToCLSID      :: String -> IO CLSID

clsidToDisplayName :: CLSID  -> String

guidToCLSID        :: GUID   -> CLSID
clsidToGUID        :: CLSID  -> GUID

marshallCLSID      :: CLSID  -> IO ForeignObj
copyCLSID          :: CLSID -> IO Addr
unmarshallCLSID    :: Bool   -> Ptr CLSID -> IO CLSID
writeCLSID         :: Ptr CLSID -> CLSID -> IO ()
readCLSID          :: Bool   -> Ptr CLSID -> IO CLSID
sizeofCLSID        :: Word32

The set of operations are largely identical to what is provided for GUIDs, but with the addition of clsidToDisplayName, which converts a CLSID into the display string expected by the standard class moniker.

The IID type

To identify COM interfaces, the IID type is used; yet another use of GUIDs. The representation of IIDs in Haskell is interesting:

data IID ip = ... -- abstract
  -- instance of Eq and Show

mkIID       :: String -> IID a
stringToIID :: String -> IO (IID a)
guidToIID   :: GUID   -> IID a
iidToGUID   :: IID a  -> GUID

marshallIID      :: IID a -> IO ForeignObj
copyIID          :: IID a -> IO Addr
unmarshallIID    :: Bool   -> Ptr (IID a) -> IO (IID a)
writeIID         :: Ptr (IID a) -> IID a -> IO ()
readIID          :: Bool   -> Ptr (IID a) -> IO (IID a)
sizeofIID        :: Word32

An IID is parameterised over the type of its corresponding interface pointer (see next section for how we represent them in Haskell). By parameterising the unique identifier over its type, we're able to ensure that there's no mismatch between asking for a pointer to a particular interface and the subsequent use of that pointer, i.e., method application is type safe - a nice property to have.

Interface pointers

A COM interface pointer is represented by the Haskell type IUnknown:

data IUnknown_ iface = .. -- abstract
type IUnknown  iface = IUnknown_ iface

ifaceToAddr    :: IUnknown a -> Addr
addrToIPointer :: Bool       -> Addr -> IUnknown a
          -- True => attach finaliser that calls Release().

marshallIUnknown   :: IUnknown a -> IO ForeignObj
unmarshallIUnknown :: Bool -> Addr -> IO (IUnknown a)
    -- True => call AddRef() on the i-pointer.
readIUnknown       :: Bool -> Addr -> IO (IUnknown a)
    -- True => call AddRef() on the i-pointer.
writeIUnknown      :: Bool -> Addr -> IUnknown a -> IO ()
    -- True => call AddRef() on the i-pointer.

The interface pointer is parameterised over the particular interface it is a pointer to. To illustrate, assuming you have got the following two IDL interface declarations:

[...]interface IA : IUnknown { HRESULT m1(); }
[...]interface IB : IA       { HRESULT m2(); }

When fed this as input, HaskellDirect will generate the following definitions for the two interface pointers:

data IA_ a = IA_
type IA  a = IUnknown (IA_ a)

data IB_ a = IB_
type IB  a = IA (IB_ a)

The IA type synonym encodes that IA extends the IUnknown interface. The type argument it takes encodes any possible extensions of IA, as witnessed on the right hand side of the definition of IB.

To state that an interface pointer is of a particular interface, we use the unit type as a `plug':

iidIUnknown :: IID (IUnknown ())

The iidIUnknown represent the IID for just the IUnknown interface; no more, no less.

The extra type parameter is particularly useful when generating method stubs. For example, the above two interface declarations will cause HaskellDirect to generate the following stubs:

m1 :: IA a -> IO ()
m2 :: IB a -> IO ()

A separate stub for invoking method m1 on an IB interface pointer isn't required, you can safely re-use the one generated for IA.

The IUnknown interface

The Com library provides wrappers for the methods of the base COM interface, IUnknown:

queryInterface :: IID (IUnknown b)   -- IID of interface you want to get at.
               -> IUnknown a         -- interface pointer you've got
               -> IO (IUnknown b)    -- the desired interface pointer.

addRef         :: IUnknown a -> IO Word32
release        :: IUnknown a -> IO Word32

iidIUnknown    :: IID (IUnknown ())

interfaceNULL  :: IUnknown a
iidNULL        :: IID ()

The methods that handle the reference counting should not really be used, since interface pointers are finalized by the garbage collector, meaning that when an interface pointer is no longer used by a Haskell program, the garbage collector will call release on the object for you.

The fundamental method of COM is queryInterface, and notice how the type parameters match up between the IID and the interface pointer that's returned, ensuring the type safety of this method.

The BSTR type

Many COM servers and clients use a length-prefixed representation of strings, via the BSTR type. Support the conversion between Haskell strings and BSTR via:

marshallBSTR   :: String -> IO (Ptr BSTR)
unmarshallBSTR :: Ptr BSTR -> IO String
writeBSTR      :: Ptr BSTR -> String -> IO ()
readBSTR       :: Ptr BSTR -> IO String
freeBSTR       :: Ptr BSTR -> IO ()

Creating component instances

To create objects, the Com library mirror what the standard COM library provides:

coCreateInstance :: CLSID
                 -> Maybe (IUnknown b)
                 -> CLSCTX
                 -> IID (IUnknown a)
                 -> IO (IUnknown a)

See COM documentation for the complete story on CoCreateInstance(), but here's a synopsis: First argument is the GUID of the component you wish to create, the second is the interface pointer to the aggregating component. Its use is not relevant in the vast majority of cases, so just pass a Nothing here. The third argument is of type CLSTX, which lets you specify how you want the component created:

data CLSCTX 
 = CLSCTX_INPROC_SERVER 
 | CLSCTX_INPROC_HANDLER
 | CLSCTX_LOCAL_SERVER 
 | CLSCTX_INPROC_SERVER16
 | CLSCTX_REMOTE_SERVER
 | CLSCTX_INPROC_HANDLER16
 | CLSCTX_INPROC_SERVERX86
 | CLSCTX_INPROC_HANDLERX86
 | LocalProcess
 | InProcess
 | ServerProcess
 | AnyProcess

A component can be instantiated inside your process (InProcess), or in a separate one (ServerProcess) plus a couple of nuances inbetween. If you don't care where, use AnyProcess and coCreateInstance will pick the most appropriate one for you.

The fourth argument to coCreateInstance is the IID of the interface you want to initially create the component at.

If the component has been installed and registered on your machine and it can be instantiated, coCreateInstance returns an interface pointer upon (successful) return. To summarise, here's a simple example of its use:


main = do
    ip <- coCreateInstance 
            (mkCLSID "{99B42120-6EC7-11CF-A6C7-00AA00A47DD2}")
            Nothing
            AnyProcess
            (mkIID "{973F77A0-6EC7-11CF-A6C7-00AA00A47DD2}")
    putStrLn "Created component"
    return ()

In addition to coCreateInstance, the Com library also supplies a couple of other component creating actions, which are at times more convenient to use than coCreateInstance:

coCreateObject   :: ProgID 
                 -> IID (IUnknown a)
                 -> IO (IUnknown a)

coGetFileObject  :: String 
                 -> ProgID 
                 -> IID (IUnknown a) 
                 -> IO (IUnknown a)

coGetActiveObject :: ProgID
                  -> IID (IUnknown a)
                  -> IO (IUnknown a)

coGetObject      :: String 
                 -> IID (IUnknown a)
                 -> IO (IUnknown a)

type ProgID = String

progIDFromCLSID  :: CLSID  -> IO ProgID
clsidFromProgID  :: ProgID -> IO CLSID

Utilities

The Com library also provide actions for accessing the COM task allocator,

coAlloc :: Int  -> IO Addr
coFree  :: Addr -> IO ()

which you should use if you exchange (allocated) data with other components.

To emulate OO-style syntax, the Com library defines a pair of operators:

( # )   :: a -> (a -> IO b) -> IO b
( ## )  :: IO a -> (a -> IO b) -> IO b

infixl 1 #
infixl 0 ##

allowing you to invoke methods as follows:

createNew :: IA a -> IO String
createNew ip = do
  x <- ip # m1 "new"
  y <- x  # m2 1 (2.0::Double)
  return y

Most importantly when accessing COM objects is to remember to initialise COM itself before you do so (and `uninitialise' it when you're done). Simplest way of doing this in Haskell is to use coRun:

coRun          :: IO a -> IO ()
coInitialize   :: IO ()
coUnInitialize :: IO ()

You simply wrap this around your Main.main action,

main :: IO ()
main = coRun $ do
   ...as before...

and it takes care of calling coInitialize at the start and coUnInitialize at the end.


Next Previous Contents