On an MS Windows system, if you write a program that uses a library function
fun()
from the library fickle.lib
, and you
then download and install a new version of
fickle.lib
which does not implement
fun()
and try to build your old program. What will
happen? (HINT: its a .lib).
- The linker will complain that the function is not defined.
- The compiler will complain that the the function is not defined.
- An error will result when you try to run the program.
- The build will fail because it runs out of memory.
- Everything will work fine.
Since it is a statically linked library, the linker will complain when it cannot resolve
external references.
On an MS Windows system, if you are writing a program that uses
some other DLL file, in order for you to to link with it, it must
- Have a module definition file (.def).
- Be registered with the Registry.
- Be loaded by the linker and linked into the resulting .exe.
- Implement one of the standard interfaces.
- Be placed under the Windows directory.
Say you implement a class CVcr
and export it as a .DLL. This DLL is shipped
to your customers who link it with their programs. Later on you add a private data member to
this class and ship the new version of your DLL. What will happen when your customers
try to do something like CVcr vcr;
in their programs?
- It will work but not enough memory will be allocated, leading to almost
certain memory corruption.
- They will get an error when they try to link.
- They will get a DLL error when they try to execute their program.
- Everything will work fine.
- The will get an error when they try to compile.
If you run the following code:
class IFoo{
public:
virtual int _stdcall GetFirst();
private:
int m_nFirst;
};
Ifoo * i = ...(get an object)
Ifoo * j = ...(get a different object)
How many vptr and vtbl will be created for this code?
- Two vptr and one vtbl.
- One vptr and two vtbl.
- One vptr and two vtbl.
- Two vptr and two vtbl.
- Three vtbl and one vptr.
Its one vptr per instance, then one vtpl per class.
Using standard C++, what is one technique that we can use for
making available an interface as a DLL while still retaining the
ability to make any changes to our implementation without
breaking any existing programs that link with our DLL. That is, how
can we achieve compiler independence in C++ without using COM?
- Use an adaptor class to expose our interface.
- Build our class so that it can be aggregated by others.
- It cannot be done. This is why COM was invented.
- Use only static member functions.
- Use only static data member.
See the Adapter pattern.
It works because the adapter does not have any data member that can change size (only a pointer to the real object).
A COM Component is usually:
- A binary module (.dll or .exe) which hosts one or more COM objects.
- A runtime instance of a COM class.
- An interface implemented by every COM object.
- A graphical COM object.
- An object to be used by Visual Basic.
A COM Object is
- A runtime instance of a COM class, whose source is linked to a COM component.
- A binary module (.dll or .exe) which hosts one or more COM components.
- An object which implements the COM Component interface.
- An object with a graphical representation.
- An abstract base class.
COM is a
- Fully-specified binary standard.
- Set of C++ Libraries.
- Protocol for remote object invocation.
- Proprietary technology for component programming.
- An extension of CORBA.
Not, it is not proprietary. In fact, there do exist some non-Microsoft implementations of COM.
In COM the IUnknown interface supports
- Dynamic discoverability, reference counting, and object identity.
- Distributed object invocation.
- Marshalling.
- Reference counting and garbage collection.
- Multiple inheritance.
The QueryInterface
function is implemented by
- Every COM object.
- The IUnknown interface.
- The IDispatch interface.
- Every COM Component.
- The COM libraries.
An interface does not implement anything, it simply declares.
Which one of these is something that every member function of a COM object must do.
- Return an HRESULT.
- Call
AddRef
.
- Call the constructor.
- Check if the object is in an STA or an MTA.
- Never set an [out] argument to be a pointer to locally allocated memory.
You can only allocate memory locally!
The QueryInterface()
function in COM allows you to
- Get a handle on any other interface the COM object might implement.
- Get an interface from a COM component.
- Ask the Interface a question.
- Determine if the current object is running locally.
- Determine if the Interface is stored in a DLL.
The QueryInterface()
is often implemented by
- Returning a reference to
this
which has been typecast to the appropriate interface
type.
- Returning the appropriate member variable reference.
- Returning a pointer to
IUnknown
.
- Accessing the COM Component's interface repository and returning the appropriate interface pointer, after calling
AddRef
on it.
- Querying the Registry for the appropriate interface and returning it.
The MIDL language is most similar to
- A C++ header file.
- A Java program.
- A CORBA IDL file.
- A C program.
- A Visual Basic program..
In a MIDL file, an attribute is
- Always enclosed in brackets.
- One of the reserved types.
- A comment to an argument.
- Always one of "in", "out", or "in, out".
- A directive to include another file.
In MIDL, the attribute that tells us that this is a COM interface is
- object
- COMInterface
- IUnknown
- com
- COM
Version control is automatically "handled" in COM because
- Every interface and component is given a unique id.
- Older versions of COM objects are always preserved by the implementation repository.
- They implement the
IVersion
interface.
- All COM objects are always backwards-compatible.
- It is part of the specification.
That would be something. "You must support version control on your implementations". Yeah, that'll fly.
Say you generate a program that implements the MIDL function
HRESULT foo([in,ref] wchar_t * a, [in,ref] wchar_t *b );
and is then called be the client with:
p->foo(x,x);
inside the function foo(), a and b are:
- Pointers to two different instances of the same character.
- Pointers to the same instance of a character.
- Pointers to different strings.
- Exactly the same.
- Pointers to different instances of different characters.
If you want to pass a pointer to a function, and you want this
pointer to behave just like a C++ pointer, and you are writing a
MIDL interface file, you must:
- Give that parameter the
ptr
attribute.
- Give that parameter the
ref
attribute.
- Give that parameter the
unique
attribute.
- Give that parameter the
pointer_default
attribute.
- Give that parameter the
stdcall
attribute.
In order to pass an array as an argument in a MIDL declaration you must
- Use the
size_is
attribute with a length as the parameter.
- Make sure the array is null-terminated.
- Simply pass the pointer, COM takes care of the rest.
- Make sure to use the
VALARRAY
data type.
- Declare the size of the array on the MIDL file.
The library
keyword in a MIDL interface file tells the MIDL compiler to
- Create a type library for a given component.
- Link with the given library.
- Create a DLL.
- Use only standard types.
- Use only automation types.
The IDispatch
interface in COM supports
- Dynamic invocation and activation.
- Event-handling.
- Aggregation.
- Composition.
- Reference counting.
To specify that a COM interface supports dynamic invocation you must
- Derive it from
IDispatch
or use the dispinterface
keyword.
- Derive it from
IDispatch
- Implement
IUnknown
correctly.
- Set the appropriate Registry keys.
- Use the MIDL compiler
If you have two COM interface pointers and you want to determine
if they belong to the same COM component, you can do this by
- Asking both of them for the
IUnknown
interface pointer and comparing these two pointers.
- Typecasting both of them to the type of the COM object and comparing these pointers.
- Typecasting both of them to
IUnknown
and comparing these pointers.
- Using the
CMembership()
function.
- It cannot be done because of the interface abstraction that COM provides.
The IUnknown::QueryInterface
COM rules state that this function must be
- Consistent, reflexive, symmetric, and transitive.
- Consistent, reflexive, symmetric, transitive, and modular.
- Symmetric and transitive.
- Consistent.
- Reflexive and modular.
The problem with using nested classes to implement a COM object that has multiple interfaces is that:
- We have to keep repeating the code for the
IUnknown
functions.
- C++ does not support nested classes.
- We have to write everything in the header file.
- Interfaces are harder to get a hold of.
- It does not allow us to use as many interfaces.
A COM standard class factory can be created by
- Implementing the
IClassFactory
interface.
- Implementing the
IStdClassFactory
interface.
- Using the
stdfactory
keyword.
- Just creating a class that returns a COM object.
- Implementing a message pump.
A COM component can run as (pick best).
- An in-process, an out-of-process remote server, an out-of-process local server.
- An in-process local server, an in-process non-local server, an out-of-process remote server, an out-of-process local server.
- A local .DLL or .EXE
- An in-process server.
- A local process.
Every thread (client or server) that uses COM must initialize it by calling
CoInitializeEx
or CoInitialize
InitializeCOMEx
or InitializeCOM
QueryInterface
AddRef
on the COM object.
IUnknown
If you have implemented an out-of-process COM server and you want users to dynamically find it, you must
- Register your factories with the SCM.
- Register all your objects with the Registry.
- Store it on the component repository.
- Given them well-known UUIDs.
- Implement
IDynamic
.
COM components keep their own reference count by
- Calling
ComponentAddRef
- Calling
IUnknown::AddRef
- Incrementing a member variable with ++.
- Calling
AddRef
on their default object.
- Components do not keep a reference count, objects do.
An out-of-process COM component knows that it is time to kill itself when
- It receives the appropriate win32 event.
- It receives a call to
ComponentRelease
.
- All its objects are dead.
- Someone calls
IUnknown:Release
.
- Someone calls
Release
.
ComponentRelease
makes the signal only when the reference count reaches zero.
An in-process COM component knows that it is time to kill itself when
- It never kills itself.
- It receives the appropriate win32 event.
- It receives a call to
ComponentRelease
.
- All its object references are dead.
- Someone calls
Release
.
In order to facilitate the registration, every out-of-process COM component must
- Implement the -RegServer and -UnRegserver command-line arguments.
- Implement
IRegister
.
- Automatically register itself every time its run.
- Provide access to its Registry keys.
- Enable registration.
In order to register an in-process COM component you can
- Use the regsvr32 program.
- Use the -RegServer command-line argument.
- Implement the
IRegisterMe
interface.
- You do not need to do that.
- Have each client register it.
The COM call to create a COM object is
CoCreateInstance
Create
CreateObject
GetNewObject
GetNewCoObject
When a client is done using COM interface it should
- Call
Release
on it.
- Call
Release
on its object
- Call
InterfaceRelease
on it.
- Call
delete
- Call
delete
but only if the reference count is zero.
When implementing a COM object that inherits from another COM object you must
- COM does not support inheritance.
- Make sure object supports containment.
- Make sure object supports aggregation.
- Implement an implicit
IUnknown
- Implement the
IUnknown
for both.
Objects in a COM apartment share
- The same concurrency semantics.
- The same clients.
- The same thread.
- A common naming scheme.
- The same DLL.
A Single Threaded Apartment implementation must
- Implement a message pump.
- Run more than one thread.
- Make sure there the program is thread-safe.
- Marshall all invocations.
- Replace default COM behavior.