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,
DISPID identifying what particular method that's being called.InvokeKind, or some such),
which lets you distinguish (like IDispatch does) between 'normal'
method invocation and property accessors.VARIANT arguments. The createStdDispatchVTBL implementation
does not try to unmarshal any of the arguments for you, leaving that up to you.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.