Dynamic Interface Invocation
- Automation focuses on run-time type checking at the expense of
speed and compile-time type checking.
- Automation replaces code generated by the compiler with code
written by developer.
- Automation Server is a COM component that implements the IDispatch
interface.
- Automation Controller is a COM client that communicates with the
Automation Server through its IDispatch interface.
- Automation Controller uses member functions in the IDispatch
interface to indirectly call functions in the Automation server.
- IDispatch takes the name of a function and executes it.
- GetIDsOfNames : accepts the name of a function and returns its
dispatch ID(DISPID) which identifies a function.
- Invoke() : DISPID informs to invoke which function.
- The set of functions implemented by an IDispatch::Invoke()
implementation is called a dispatch interface(dispinterface).
- The implementation part requires to implement a table for function
name-to-DISPID and DISPID-to-Function pointer transformations.
For small number of functions, a vector can be used while for
large number of functions hashtable will be preferable.
- Dual Interface
IUnknown -> Foo will be
IUnknown -> IDispatch -> Foo.
this convention allows clients to access Foo's methods through
vtbl or through Invoke() interface, therefore they are called dual
interfaces.
- Dual interfaces are preferred way of implementation strategy since
it allows C++ clients directly use functions through vbtl while
automation clients use dispinterface.
- This functionality makes Visual Basic Developer's life very easy
while C++ Developer's life turns out to be a suffering.
- For a C++ Programmer Client:
- needs to get ProgID
- gets CLSID by using ProgID(::CLSIDFromProgID())
- gets a pointer to IDispatch interface(::CoCreateInstance())
- gets DISPID of function(GetIDsOfNames())
- prepares parameters in DISPPARAMS structure
- executes Invoke() method
- handles return values, exceptions, and possible error codes.
- Automation controller does not need the interface definition.
- The types of parameters that Invoke() can pass to a function in a
dispinterface are limited in number(VARIANTS).
- Giving a which function, client wants to call is a problem becasue
each name there are four possible cases:
- a normal function(DISPATCH_METHOD)
- a function to put a property (DISPATCH_PROPERTYPUT)
- a function to put a property by reference
(DISPATCH_PROPERTYPUTREF)
- a function to get a property (DISPATCH_PROPERTYGET)
These functions come from the MIDL definition for providing easy
method definitions for accessor methods(get_XX,put_XXX).
Therefore, Visual Basic User uses only names in the program and
interpreter executes an appropriate method on an interface.
- Dispatch parameter structure supports Name-value paired parameter
list. For C++ programmer, name values makes no sense, providing
parameter values is sufficient.
- Results are obtained as a VARIANT array.
- Exceptions are obtained as an EXCEPTINFO structure.
Type Libraries
Type libraries provide type information about components interfaces,
methods, properties, argumenmts and structures. A type library is compiled
version(binary) of an IDL file that can be accessed programmatically.
Automation library provides standard components for creating and reading
this binary file. This feature allows Visual Basic clients to access
functions through vtbl (which is faster and type safe).
Type libraries may contain help strings for all the components, interfaces,
functions inside the library and this makes easier for object browsers
(such as Visual Basic) to provide help for those components.
Type libraries(.TLP) can be build from IDL by using library keyword
in the definition. IDL compiler produces a type library files for this
components and all interfaces that are contained by this component.
- Implementation inheritance: a class inherits its code or
implementation from its base class. (COM does not support)
- Interface inheritance: a class inherits the type or interfaces of
its base class.
- COM does not support implementation inheritance because
implementation inheritance binds one object tightly to the
implementation of another object. If the implementation of a
base object changes, the derived object breaks and must be changed.
- Experts on writing large applications in C++ strongly recommend
building large applications using a foundation of abstract base
classes.
- Containment and Aggregation is a way of specializing components.
- A component keeps a pointer to other components and implements all
their interfaces by directly calling the same functions or
addition of new codes before/after the function call. Containment
based approach never let outsider see the contained components.
- Aggregation based specialization allows client to access the
contained component and invoke the function call directly on them.
- A smart pointer is a class that overwrites operator->
The smart pointer class contains a pointer to another object.
Similarly, a smart interface pointer is a smart pointer that
contains a pointer to an interface. Smart interface pointers
are usefull especially dealing with reference counting problems
in COM component development.
- 32 bit HRESULT is used for error codes. Users can defined their
return codes by using 16 bit but propagating the error code
from inner components to outside is a very complicated task
therefore, suggested practice is to use extra out parameters
to propagate the error.
- Global Unique Identifier defines a 128-bit(16 byte) value wich is
guaranteed to be unique without consulting a general authority.
These IDDs are used to distinguish interfaces(IID), components(CLSID).
- WINDOWS registry keeps CLSID->DLL mappings. These mappings go to
HKEY_CLASSES_ROOT\CLSID key.
Aggregation
- A simply returning the pointer of included component is not enough
since this reference does not directly with the outer component's
interface. Therefore, a component should be returning the outer
components IUnknown interface if it is aggregated in an outer
component.
- This requires two version of CoCreateInstance functions one for
non-aggrageted and one for aggregated version.
- Delegating unknown forwards IUnknown functions calls to either the
outher IUnknnown or the non-delegating IUnknown interface.
- Outer component controls the inner component with nondelegating
IUnknown interface.
- Client of the aggregated component never gets a pointer to
nondelegating IUnknown interface.
- NondelegatingIUnknown interface's NondelegatingQueryInterface
method returns a pointer this interface.
- DelegatingIUnknown interfcae keeps a pointer to the outer IUnknown
interface. If component is not afggregated within another component,
this pointer references the NondelegatingIUnknown interface so that
it can work without any problem. DelegatingIUnknown interface
forwards all the calls to this referenced interface. This will
also require some changes in the CreateInstance function of
IClassFactory interface and the constructor of the aggregated
interface to take care of possible aggregated use.
Registry
- Registry is a hierarchy of elements whose each element is called a
key. A key can include a set of subkeys, a set of named values,
and/or one unnamed value. Subkeys can have other subkeys and values.
- COM uses HKEY_CLASSES_ROOT branch of Registry tree.
- CLSID Key contains an externalized GUIO of registered classes.
Under this representation, it might have an InprocServer32 key
with an appropriate DLL file name.
- ProgID key is used to map a programmer friendly string to a CLSID
so that users of these components can access components through
ProgID values. The default value of ProgID denotes the program name,
component name and version number. This should be a key in
HKEY_CLASSES_ROOT tree, including CLSID of this component. ProgID
might contain a key named VersionIndepenedentProgID whose value
again a key in HKEY_CLASSES_ROOT tree.
Communication between process
- Dynamic Data Exchange (DDE)
- Named pipes
- shared memory
- LPC is local procedure call based on OSF DCE RPC.
- DLL is in-process
- EXE is out-of-process
- EXE is local-server
- Proxy/Stub DLL
- MIDL
- object : means that this interface is a COM interface.
- uuid : specifies the IID for thise interface.
- helpstring
- pointer_default
- MIDL compiler produces necessary header and DLL files which
implements Proxy/Stub pairs.
- Proxy/Stub DLLs should be registered. In the registry:
HKEY_CLASSES_ROOT\Interface
GUID of interface
ProxyStubClsid32 contains the CLSID for the proxy/stub DLL. Note
that this should be already registered at HKEY_CLASSES_ROOT\CLSID.
In this registration, "InprocServer32" key points to the location
of DLL file related to this class.
- Serving component from an EXE is different from serving components
from DLL.
- DEF file tells which function will be in DLL interface. LIBRARY
keyword gives the name of DLL file and EXPORTS section lists
the name of the functions and their function indices. Note that
those functions should be decorated with "extern 'C' " if they are
written in C++.
- LoadLibrary(WIN32) loads the DLL.
- DLLs share the same address space as the application they are
linked to.
- CoCreateInstance takes CLSID, creates an instance of the
corresponding component and returns an interface pointer for
this instance of the component.
- Third parameter in the CoCreateInstance call specifies where the
component resides:
- CLSCTX_INPROC_SERVER :
Component must be implemented in DLLs to run in the
client's process.
- CLSCTX_INPROC_HANDLER :
An in-proc handler is an in-process component that
implements only a part of a component. The other parts
of the component are implemented by out-of-process
component on a local or remote server.
- CLSCTX_LOCAL_SERVER :
Local servers are implemented as EXEs and they have
different process that the client has.
- CLSCTX_REMOTE_SERVER :
On remote machine and needs DCOM to work.
- A class factory is a component to create other components. It
supports IClassFactory interface.
- CoGetClassObject call returns the class factory object related to
given CLSID.
- IClassFactory has two functions:
- CreateInstance: It allows to return the given interface
together.
- Advantages of using IClassFactory
- When client needs to use a creation interface other than
IClassFactory such as IClassFactory2(which is the new
interface allowing licensing/permissions to IClassFactory
interfaces.)
- When client needs to create a bunch of components all at one
time, it is faster than CoCreateInstance since CoCreateInstance
obtains and releases the IClassFactory interface for each
invocation.
- Class factory component is provided the developer of DLL.
- DllCanUnloadNow():
is being called by COM to ask the DLL to free it or not. DLLs keep
track of their supporting components so that if it drops to zero
and COM invokes this method then DLL tells whether it can be removed
from memory or not. Note that COM periodically runs
CoFreeUnusedLibraries() during its idle time and this call actually
invokes DllCanUnloadNow() method on each DLL.
- LockServer():
If DLL has class factories and those objects might be in use so
that LockServer interface allows client to the similar thing to
force DLL not to leave the memory especially client needs to use
the IClassFactory interface.
- DllRegisterServer():
- DllUnregisterServer():
- DllMain():
- AddRef and Release implement a memory-management technique called
reference counting.
- A COM component maintains a number called the reference count.
- When a client gets an interface from a component the reference
count is incremented. When that client is finished using an
interface, the reference count is decremented. The client also
increments the reference count when it creates another reference
on an existing interface(for example as a result of copying).
- Functions that return interfaces should always call AddRef on the
pointer before returning such as QueryInterface method.
- When client is finished with an interface, client should call
Release on that interface.
- Whenever client creates another reference to an interface,
client should call AddRef.
- Component requires reference counting for each interface because:
- easy debugging.
- support on-demand resources.
- In the implementation of AddRef and Release methods developer
basically increments and decrements a counter for particular interface.
For thread safety InterlockedIncrement() and
InterlockedDecrement() functions should be used.
- In Release() method, developer deletes object whenever reference
count drops zero.
- Optimization in reference counting can be obtained when it is
guaranteed that the life-cycle of the copied refence is nested in the
life-cycle of the source of this copy operation.
- When interface pointers are used as a parameter to function calls,
the following rules should be followed:
- Out parameter : function should execute AddRef() before
returning such as QueryInterface() implementetation.
- In parameter : Since function can not change the
pointer's value then there is no need to call AddRef
and Release() functions on the pointer.
- In-Out parameter : Release() call should be made on
pointer before assigning a new value to the pointer.
AddRef() on this pointer should be called before
returning from the function.
Updated on : October 13, 1998