Next Previous Contents

7.6 The StdDispatch library

The ComServ provides one way of creating components supporting the IDispatch interface, using a type library and the COM library provided type library marshaller to do the bulk of the work.

The StdDispatch library offers a lighter-weight alternative, providing a Haskell implementation of IDispatch. The methods that your IDispatch interface supports is specified directly in Haskell, no need for type libraries. You create an instance using one of the following actions:

createStdDispatch :: objState
                  -> [DispMethod objState]
                  -> IID iid
                  -> IO (IUnknown iid)

data MethodKind 
  = Method
  | PropertyGet
  | PropertyPut

type DISPIDLookupFun = String -> Maybe DISPID

createStdDispatchVTBL
      :: DISPIDLookupFun
      -> InvokeAction
      -> IO (ComVTable (IDispatch iid) objState)

createStdDispatchVTBL2
      :: [DispMethod objState]
      -> IO (ComVTable (IDispatch iid) objState)

createStdDispatchVTBL is the most primitive of these, creating a IDispatch method table for you, leaving you 'just' with the task of specifying the mapping between method names and DISPIDs, and, most importantly, the IO action which implements the functionality of the different methods you support. The supplied invocation action has the following form,

type InvokeAction
   = DISPID
  -> MethodKind
  -> [VARIANT]
  -> objState
  -> IO (Maybe VARIANT)

the arguments it expects are,

The InvokeAction possibly returns a VARIANT as result - it should do in case it was invoked as a property 'getter' or the method's last argument had retval as one of its attributes.

The two actions you supply to createStdDispatchVTBL lets you implement the methods of your IDispatch component support at a reasonable level of abstraction. You can layer higher-level abstractions on top of it though to suit your programming taste, which is what createStdDispatchVTBL2 does with its list of DispMethods:

createStdDispatchVTBL2
      :: [DispMethod objState]
      -> IO (ComVTable (IDispatch iid) objState)

data DispMethod objState
  = DispMethod {
       disp_method_name :: String,     -- method name
       disp_method_id   :: DISPID,     -- its ...
       disp_method_kind :: MethodKind,
       disp_method_act  :: ([VARIANT] -> objState -> IO (Maybe VARIANT))
    }

each method is specified by giving its name, DISPID and the Haskell IO action which implements it.

To help with the conversion between VARIANT and Haskell values, we make good use of the Variant type class defined by the Automation library, and provide the following helpers:

inArg :: ( Variant a )
      => (a -> [VARIANT] -> objState -> IO b)
      -> [VARIANT]
      -> objState
      -> IO b

inoutArg :: ( Variant a )
         => ( a -> (a -> IO ()) -> [VARIANT] -> objState -> IO b)
         -> [VARIANT]
         -> objState
         -> IO b

outArg :: ( Variant a )
       => ((a -> IO ()) -> [VARIANT] -> objState -> IO b)
       -> [VARIANT]
       -> objState
       -> IO b

retVal :: ( Variant a )
        => ((a -> IO ()) -> [VARIANT] -> objState -> IO (Maybe VARIANT))
        -> [VARIANT]
        -> objState
        -> IO (Maybe VARIANT)

apply_0 :: (objState -> IO ())
        -> [VARIANT]
        -> objState
        -> IO (Maybe VARIANT)

apply_1 :: (Variant a)
        => (objState -> IO a)
        -> (a -> IO ())
        -> [VARIANT]
        -> objState
        -> IO (Maybe VARIANT)

apply_2 :: (Variant a0, Variant a1)
        => (objState -> IO (a0,a1))
        -> (a0 -> IO ())
        -> (a1 -> IO ())
        -> [VARIANT]
        -> objState
        -> IO (Maybe VARIANT)
..etc..

To demonstrate their use, suppose we want to supply a Haskell implementation for the following method:

  [id(2)]HRESULT RegisterTask([in]BSTR s,[out,retval]int* i);

The Haskell method will have the type signature

registerTask :: String -> SomeObjState -> IO Int

to wrap this up in a form that can be included in a DispMethod, we define a little wrapper:

wrap_registerTask =
  inArg   $ \ s      ->
  retVal  $ \ ret_i  ->
  apply_1 (registerTask s) ret_i

The wrapping up of methods inside DispMethod values can be simplified further by providing a family of (overloaded) wrappers, grouped by the number of [in] and [out] arguments their underlying methods expect:

dispmethod_0_0 :: String -> DISPID -> (objState -> IO ()) -> DispMethod objState
dispmethod_0_0 name id f = DispMethod name id Method (apply_0 f)

dispmethod_1_0 :: (Variant a)
               => String
               -> DISPID
               -> (a -> objState -> IO ())
               -> DispMethod objState
...
dispmethod_1_2 :: (Variant a0,Variant a1, Variant a2)
               => String
               -> DISPID
               -> (a0 -> objState -> IO (a1,a2))
               -> DispMethod objState
...

As a convenience function, StdDispatch also exports createStdDispatch which calls createStdDispatchVTBL2 to create the method table and then just creates an interface pointer which supports a singular interface (in addition to IDispatch and IUnknown, of course):

createStdDispatch :: objState
                  -> [DispMethod objState]
                  -> IID iid
                  -> IO (IUnknown iid)

Useful when you want to dynamically create and dish out interface pointers, e.g., when creating event sinks.


Next Previous Contents