Daan Leijen "Nov 22 1999": under construction
Registration
The ScriptReg utility (see the example page)
automatically registers (and unregisters) script components. Here is
a list of registry entries, where xxxx is the CLSID of the
component and progid the ProgID of the component:
| Key
| Subkey
| Required
| Default Value
|
HKEY_CLASSES_ROOT\CLSID\{xxxx}
| \InprocServer32
| yes
| Locates the ObjectScript
library. For example "d:\hscript\objectscript.dll"
|
\ScriptServer
| yes
| Contains the ProgID of the script server.
For Haskell scripts, this is normally HugsScript
|
\ScriptSource
| yes
| Locates the source of the script that implements the proxy code, for
example "d:\demos\proxycalculator.hs"
|
\ClassName
| yes
| Contains the name of the component, for example Calculator
|
\ProgID
| no
| Contains the ProgID of the component, for example Haskell.Calculator.
|
\Typelib
| no
| Contains the LIBID of the associated type library. Only required when
there is a type library and the library resides in a different directory
as the source.
|
HKEY_CLASSES_ROOT\progid
| \CLSID
| no
| Contains the CLSID of the component. ProgID's of Haskell components are
normally formed by prefixing the class name with Haskell.,
for example Haskell.Calculator
|
The Protocol
ObjectScript uses the standard ScriptServer interfaces to load the proxy
source and to call the methods. All methods are directly called on the
IScript interface using GetIDsOfNames and
Invoke. If there is a Typelib entry in the
registry, ObjectScript will load the type information. If there is no
entry, ObjectScript will look if there is a type library with the same
name as the proxy module (ie. proxyCalculator.tlb.
in the same directory, if so, it will register
and load the library.
At component creation time, ObjectScript calls
proxyClassNameConstruct, where ClassName is the name
of the class as specified in the ClassName registry entry.
The method should return an IScriptItem interface which represents
the state of the component. This interface is passed as an extra
(last) argument to
every other method called for this component. If the constructor is undefined,
ObjectScript assumes that this is a stateless component and will not add
a state parameter as a last argument to every method call.
When a component is destroyed, ObjectScript calls
proxyClassNameDestruct (passing the state as argument if
this is a statefull component). No error is raised when this method
is undefined.
When QueryInterface is called component,
and ObjectScript can not find the interface in a
type library, it will call proxyClassNameQueryInterface with
the requested IID as argument (and possibly the state as a last argument).
The IID will be passed as a BSTR (string). If the method is defined,
it should return TRUE if the class supports the interface and FALSE if not.
If the method is undefined, it is assumed that the requested
interface is not supported.
For every other method call and propery access, ObjectScript will
call proxyName where Name is the name of the
method or property. ObjectScript will first lookup the name in the script
and check compliance with the type information if available. For a
statefull object, the state is passed as an extra argument.
Proxies in Haskell
When defining a proxy module you'll need to import the ScriptEvents
module which defines the library functions needed.
All proxy methods will have type Export. Values with this
type can be called from outside.
public_0_0 :: state -> IO () -> Export
...
public_1_1 :: Variant a, Variant b => (a -> state -> IO b) -> Export
...
public_n_m :: Variant a1..n, Variant a1..m =>
(a1 -> ... -> an -> state -> IO (a1, ..., am)
-> Export
The public_n_m function is used to define a method proxy.
It takes an IO function, with n arguments and its state and
m results, as argument. The state argument is polymorphic
but should always be the type of state returned by the constructor function.
(Since this is a hole in the type system, proxies should be generated from
IDL descriptions to be type safe). Example (from the Calculator):
proxyDigit :: Export
proxyDigit = public_1_0 (digit :: Int -> Calculator -> IO ())
export_0_0 :: IO () -> Export
...
export_1_1 :: Variant a, Variant b => (a -> IO b) -> Export
...
export_n_m :: Variant a1..n, Variant a1..m =>
(a1 -> ... -> an -> IO (a1, ..., am)
-> Export
The export_n_m function is used to define a method proxy for
a stateless component. The only difference with the public function
is that it doesn't pass a state argument.
export_n_m takes an IO function, with n arguments and
m results, as argument. Example (from ScriptReg):
proxyRegReadValue :: Export
proxyRegReadValue = export_2_1 (readValue :: String -> String -> IO String)
constructor :: IO state -> Export
The constructor function is used to define a constructor proxy.
state should be the type of the component state. If a
constructor is defined, all the methods should use the public_n_m
functions. If it is not defined, this will be a stateless component
and the methods should use export_n_m For example:
proxyCalculatorConstruct :: Export
proxyCalculatorConstruct
= constructor (calculatorConstruct :: IO Calculator)
destructor :: state -> IO () -> Export
The destructor function is used to define a destructor proxy
for statefull components (use export_0_0 for stateless
destructors).
state should be the type of the component state.
For example:
proxyCalculatorDestruct :: Export
proxyCalculatorDestruct
= destructor (calculatorDestruct :: Calculator -> IO ())
queryinterface :: (String -> state -> IO Bool) -> Export
The queryinterface function is used to define
QueryInterface proxy for statefull objects (use export_1_1
for stateless components).
state should be the type of the component state.
The argument function takes the IID as a string argument. If the component
supports this interface, it should return True. Normally,
the component is dynamic or uses a type library. In both cases this
method proxy doesn't have to be defined.
For example:
proxyCalculatorQueryInterface :: Export
proxyCalculatorQueryInterface
= queryInterface calculatorQueryInterface