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.
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.
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
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.
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.
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.
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.
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 ()
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
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.