// IExampleExe.c demonstrates how to put a COM component in its own
// EXE. In that way, our object runs in a different process (address
// space) as someone who uses our COM component. This allows an extra
// bit of crash protection to that client application.
#include <windows.h>
#include <objbase.h>
#include "IExampleExe.h"
// Comment this out if you want this COM component to continue running
// even after all apps are finished using it
#define ALLOW_UNLOADING
// A count of how many objects our EXE has created (by some
// app calling our IClassFactory object's CreateInstance())
// which have not yet been Release()'d by the app
static DWORD OutstandingObjects;
// A count of how many apps have locked our EXE via calling our
// IClassFactory object's LockServer()
static DWORD LockCount;
// Where I store a pointer to my type library's TYPEINFO
static ITypeInfo *MyTypeInfo;
// The IExampleExe object ////////////////////////////////////////////////////////////
// In our .H file, we use a macro which defines our IExampleExe struct
// as so:
//
// typedef struct {
// IExampleExeVtbl *lpVtbl;
// } IExampleExe;
//
// In other words, the .H file defines our IExampleExe to have nothing
// but a pointer to its VTable. And of course, every COM object must
// start with a pointer to its VTable.
//
// But we actually want to add some more members to our IExampleExe.
// We just don't want any app to be able to know about, and directly
// access, those members. So here we'll define a MyRealIExampleExe that
// contains those extra members. The app doesn't know that we're
// really allocating and giving it a MyRealIExampleExe object. We'll
// lie and tell it we're giving a plain old IExampleExe. That's ok
// because a MyRealIExampleExe starts with the same VTable pointer.
//
// We add a DWORD reference count so that this IExampleExe
// can be allocated (which we do in our IClassFactory object's
// CreateInstance()) and later freed. And, we have an extra
// BSTR (pointer) string, which is used by some of the functions we'll
// add to IExampleExe
typedef struct {
IExampleExeVtbl *lpVtbl;
DWORD count;
BSTR string;
} MyRealIExampleExe;
// Here are IExampleExe's functions.
//
// Every COM object's interface must have the 3 functions QueryInterface(),
// AddRef(), and Release().
//
// I also chose to add 2, extra functions to IExampleExe, which a program
// will call with the names GetString and SetString.
// IExampleExe's QueryInterface()
static HRESULT STDMETHODCALLTYPE QueryInterface(IExampleExe *this, REFIID vTableGuid, void **ppv)
{
// Check if the GUID matches IExampleExe VTable's GUID. We gave the C variable name
// IID_IExampleExe to our VTable GUID. We can use an OLE function called
// IsEqualIID to do the comparison for us. Also, if the caller passed a
// IUnknown GUID, then we'll likewise return the IExampleExe, since it can
// masquerade as an IUnknown object too. Finally, if the called passed a
// IDispatch GUID, then we'll return the IExampleExe, since it can masquerade
// as an IDispatch too
if (!IsEqualIID(vTableGuid, &IID_IUnknown) && !IsEqualIID(vTableGuid, &IID_IExampleExe) && !IsEqualIID(vTableGuid, &IID_IDispatch))
{
// We don't recognize the GUID passed to us. Let the caller know this,
// by clearing his handle, and returning E_NOINTERFACE.
*ppv = 0;
return(E_NOINTERFACE);
}
// Fill in the caller's handle
*ppv = this;
// Increment the count of callers who have an outstanding pointer to this object
this->lpVtbl->AddRef(this);
return(NOERROR);
}
// IExample's AddRef()
static ULONG STDMETHODCALLTYPE AddRef(IExampleExe *this)
{
// Increment IExampleExe's reference count, and return the updated value.
// NOTE: We have to typecast to gain access to any data members. These
// members are not defined in our .H file (so that an app can't directly
// access them). Rather they are defined only above in our MyRealIExampleExe
// struct. So typecast to that in order to access those data members
return(++((MyRealIExampleExe *)this)->count);
}
// IExample's Release()
static ULONG STDMETHODCALLTYPE Release(IExampleExe *this)
{
// Decrement IExampleExe's reference count. If 0, then we can safely free
// this IExampleExe now
if (--((MyRealIExampleExe *)this)->count == 0)
{
if (((MyRealIExampleExe *)this)->string) SysFreeString(((MyRealIExampleExe *)this)->string);
GlobalFree(this);
// Decrement the count of outstanding objects
InterlockedDecrement(&OutstandingObjects);
// If we can unload this EXE now, then post a WM_QUIT message so WinMain
// will drop out of the loop and this EXE terminates
if (!DllCanUnloadNow()) PostMessage(0, WM_QUIT, 0, 0);
return(0);
}
return(((MyRealIExampleExe *)this)->count);
}
// ================== The standard IDispatch functions
// This is just a helper function for the IDispatch functions below
static HRESULT loadMyTypeInfo(void)
{
register HRESULT hr;
LPTYPELIB pTypeLib;
// Load our type library and get a ptr to its TYPELIB. Note: This does an
// implicit pTypeLib->lpVtbl->AddRef(pTypeLib)
if (!(hr = LoadRegTypeLib(&CLSID_TypeLib, 1, 0, 0, &pTypeLib)))
{
// Get Microsoft's generic ITypeInfo, giving it our loaded type library. We only
// need one of these, and we'll store it in a global Tell Microsoft this is for
// our IExampleExe's VTable, by passing that VTable's GUID
if (!(hr = pTypeLib->lpVtbl->GetTypeInfoOfGuid(pTypeLib, &IID_IExampleExe, &MyTypeInfo)))
{
// We no longer need the ptr to the TYPELIB now that we've given it
// to Microsoft's generic ITypeInfo. Note: The generic ITypeInfo has done
// a pTypeLib->lpVtbl->AddRef(pTypeLib), so this TYPELIB ain't going away
// until the generic ITypeInfo does a pTypeLib->lpVtbl->Release too
pTypeLib->lpVtbl->Release(pTypeLib);
// Since caller wants us to return our ITypeInfo pointer,
// we need to increment its reference count. Caller is
// expected to Release() it when done
MyTypeInfo->lpVtbl->AddRef(MyTypeInfo);
}
}
return(hr);
}
// IExampleExe's GetTypeInfoCount()
static ULONG STDMETHODCALLTYPE GetTypeInfoCount(IExampleExe *this, UINT *pCount)
{
*pCount = 1;
return(S_OK);
}
// IExampleExe's GetTypeInfo()
static ULONG STDMETHODCALLTYPE GetTypeInfo(IExampleExe *this, UINT itinfo, LCID lcid, ITypeInfo **pTypeInfo)
{
register HRESULT hr;
// Assume an error
*pTypeInfo = 0;
if (itinfo)
hr = ResultFromScode(DISP_E_BADINDEX);
// If our ITypeInfo is already created, just increment its ref count. NOTE: We really should
// store the LCID of the currently created TYPEINFO and compare it to what the caller wants.
// If no match, unloaded the currently created TYPEINFO, and create the correct one. But since
// we support only one language in our IDL file anyway, we'll ignore this
else if (MyTypeInfo)
{
MyTypeInfo->lpVtbl->AddRef(MyTypeInfo);
hr = 0;
}
else
{
// Load our type library and get Microsoft's generic ITypeInfo object. NOTE: We really
// should pass the LCID to match, but since we support only one language in our IDL
// file anyway, we'll ignore this
hr = loadMyTypeInfo();
}
if (!hr) *pTypeInfo = MyTypeInfo;
return(hr);
}
// IExampleExe's GetIDsOfNames()
static ULONG STDMETHODCALLTYPE GetIDsOfNames(IExampleExe *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
if (!MyTypeInfo)
{
register HRESULT hr;
if ((hr = loadMyTypeInfo())) return(hr);
}
// Let OLE32.DLL's DispGetIDsOfNames() do all the real work of using our type
// library to look up the DISPID of the requested function in our object
return(DispGetIDsOfNames(MyTypeInfo, rgszNames, cNames, rgdispid));
}
// IExampleExe's Invoke()
static ULONG STDMETHODCALLTYPE Invoke(IExampleExe *this, DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *params, VARIANT *result, EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
// We implement only a "default" interface
if (!IsEqualIID(riid, &IID_NULL))
return(DISP_E_UNKNOWNINTERFACE);
// We need our type lib's TYPEINFO (to pass to DispInvoke)
if (!MyTypeInfo)
{
register HRESULT hr;
if ((hr = loadMyTypeInfo())) return(hr);
}
// Let OLE32.DLL's DispInvoke() do all the real work of calling the appropriate
// function in our object, and massaging the passed args into the correct format
return(DispInvoke(this, MyTypeInfo, dispid, wFlags, params, result, pexcepinfo, puArgErr));
}
// ================== The following are my own extra functions added to IExample
// IExampleExe's SetString(). This copies the passed string to IExampleExe's
// string[]
static HRESULT STDMETHODCALLTYPE SetString(IExampleExe *this, BSTR str)
{
// Make sure that caller passed a buffer
if (!str) return(E_POINTER);
// First, free any old BSTR we allocated
if (((MyRealIExampleExe *)this)->string) SysFreeString(((MyRealIExampleExe *)this)->string);
// Make a copy of the caller's string and save this BSTR
if (!(((MyRealIExampleExe *)this)->string = SysAllocStringLen(str, SysStringLen(str))))
return(E_OUTOFMEMORY);
return(NOERROR);
}
// IExampleExe's GetString(). This retrieves IExampleExe's string[],
// and stores its contents in a buffer passed by the caller
static HRESULT STDMETHODCALLTYPE GetString(IExampleExe *this, BSTR *buffer)
{
// Make sure that caller passed a handle
if (!buffer) return(E_POINTER);
// Create a copy of our buffer. The caller is responsible for freeing it
if (!(*buffer = SysAllocString(((MyRealIExampleExe *)this)->string)))
return(E_OUTOFMEMORY);
return(NOERROR);
}
// Here's IExampleExe's VTable. It never changes so we can declare it
// static
static const IExampleExeVtbl IExampleExe_Vtbl = {QueryInterface,
AddRef,
Release,
GetTypeInfoCount,
GetTypeInfo,
GetIDsOfNames,
Invoke,
SetString,
GetString};
// The IClassFactory object ///////////////////////////////////////////////////////
// Since we only ever need one IClassFactory object, we declare
// it static. The only requirement is that we ensure any
// access to its members is thread-safe
static IClassFactory MyIClassFactoryObj;
// IClassFactory's AddRef()
static ULONG STDMETHODCALLTYPE classAddRef(IClassFactory *this)
{
// Someone is obtaining my IClassFactory, so inc the count of
// pointers that I've returned which some app needs to Release()
InterlockedIncrement(&OutstandingObjects);
// Since we never actually allocate/free an IClassFactory (ie, we
// use just 1 static one), we don't need to maintain a separate
// reference count for our IClassFactory. We'll just tell the caller
// that there's at least one of our IClassFactory objects in existance
return(1);
}
// IClassFactory's QueryInterface()
static HRESULT STDMETHODCALLTYPE classQueryInterface(IClassFactory *this, REFIID factoryGuid, void **ppv)
{
// Make sure the caller wants either an IUnknown or an IClassFactory.
// In either case, we return the same IClassFactory pointer passed to
// us since it can also masquerade as an IUnknown
if (IsEqualIID(factoryGuid, &IID_IUnknown) || IsEqualIID(factoryGuid, &IID_IClassFactory))
{
// Call my IClassFactory's AddRef
this->lpVtbl->AddRef(this);
// Return (to the caller) a ptr to my IClassFactory
*ppv = this;
return(NOERROR);
}
// We don't know about any other GUIDs
*ppv = 0;
return(E_NOINTERFACE);
}
// IClassFactory's Release()
static ULONG STDMETHODCALLTYPE classRelease(IClassFactory *this)
{
ULONG count;
// One less object that an app has not yet Release()'ed
count = InterlockedDecrement(&OutstandingObjects);
// If we can unload this EXE now, then post a WM_QUIT message so WinMain
// will drop out of the loop and this EXE terminates
if (!DllCanUnloadNow()) PostMessage(0, WM_QUIT, 0, 0);
return(count);
}
// IClassFactory's CreateInstance() function. It is called by
// someone who has a pointer to our IClassFactory object and now
// wants to create and retrieve a pointer to our IExampleExe
static HRESULT STDMETHODCALLTYPE classCreateInstance(IClassFactory *this, IUnknown *punkOuter, REFIID vTableGuid, void **objHandle)
{
HRESULT hr;
register IExampleExe *thisobj;
// Assume an error by clearing caller's handle
*objHandle = 0;
// We don't support aggregation in this example
if (punkOuter)
hr = CLASS_E_NOAGGREGATION;
else
{
// Allocate our IExampleExe object (actually a MyRealIExampleExe)
if (!(thisobj = (IExampleExe *)GlobalAlloc(GMEM_FIXED, sizeof(MyRealIExampleExe))))
hr = E_OUTOFMEMORY;
else
{
// Store IExampleExe's VTable in the object
thisobj->lpVtbl = (IExampleExeVtbl *)&IExampleExe_Vtbl;
// Increment the reference count so we can call Release() below and
// it will deallocate only if there is an error with QueryInterface()
((MyRealIExampleExe *)thisobj)->count = 1;
// Initialize any other members we added to the IExampleExe. We added
// a string member
((MyRealIExampleExe *)thisobj)->string = 0;
// Fill in the caller's handle with a pointer to the IExample we just
// allocated above. We'll let IExampleExe's QueryInterface do that, because
// it also checks the GUID the caller passed, and also increments the
// reference count (to 2) if all goes well
hr = IExampleExe_Vtbl.QueryInterface(thisobj, vTableGuid, objHandle);
// Decrement reference count. NOTE: If there was an error in QueryInterface()
// then Release() will be decrementing the count back to 0 and will free the
// IExample for us. One error that may occur is that the caller is asking for
// some sort of object that we don't support (ie, it's a GUID we don't recognize)
IExampleExe_Vtbl.Release(thisobj);
// If success, inc static object count to keep this EXE loaded
if (!hr) InterlockedIncrement(&OutstandingObjects);
}
}
return(hr);
}
// IClassFactory's LockServer(). It is called by someone
// who wants to lock this EXE in memory
static HRESULT STDMETHODCALLTYPE classLockServer(IClassFactory *this, BOOL flock)
{
if (flock) InterlockedIncrement(&LockCount);
else InterlockedDecrement(&LockCount);
return(NOERROR);
}
// IClassFactory's VTable
static const IClassFactoryVtbl IClassFactory_Vtbl = {classQueryInterface,
classAddRef,
classRelease,
classCreateInstance,
classLockServer};
// Miscellaneous functions ///////////////////////////////////////////////////////
/************************ DllGetClassObject() ***********************
* This is called by the OLE functions CoGetClassObject() or
* CoCreateInstance() in order to get our EXE's IClassFactory object
* (and return it to someone who wants to use it to get ahold of one
* of our IExampleExe objects). Our IClassFactory's CreateInstance() can
* be used to allocate/retrieve our IExampleExe object.
*
* NOTE: After we return the pointer to our IClassFactory, the caller
* will typically call its CreateInstance() function.
*/
HRESULT PASCAL DllGetClassObject(REFCLSID objGuid, REFIID factoryGuid, void **factoryHandle)
{
register HRESULT hr;
// Check that the caller is passing our IExampleExe GUID. That's the
// only object our EXE implements
if (IsEqualCLSID(objGuid, &CLSID_IExampleExe))
{
// Fill in the caller's handle with a pointer to our IClassFactory object.
// We'll let our IClassFactory's QueryInterface do that, because it also
// checks the IClassFactory GUID and does other book-keeping
hr = classQueryInterface(&MyIClassFactoryObj, factoryGuid, factoryHandle);
}
else
{
// We don't understand this GUID. It's obviously not for our EXE.
// Let the caller know this by clearing his handle and returning
// CLASS_E_CLASSNOTAVAILABLE
*factoryHandle = 0;
hr = CLASS_E_CLASSNOTAVAILABLE;
}
return(hr);
}
/************************ DllCanUnloadNow() ***********************
* This is called by some OLE function in order to determine
* whether it is safe to unload our EXE from memory.
*
* RETURNS: S_OK if safe to unload, or S_FALSE if not.
*/
HRESULT PASCAL DllCanUnloadNow(void)
{
// If someone has retrieved pointers to any of our objects, and
// not yet Release()'ed them, then we return S_FALSE to indicate
// not to unload this EXE. Also, if someone has us locked, return
// S_FALSE
return((OutstandingObjects | LockCount) ? S_FALSE : S_OK);
}
/************************ WinMain() **************************
* Our EXE's entry point. This is called by Windows when our
* EXE first starts up.
*/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
DWORD nToken;
// No TypeInfo yet loaded
MyTypeInfo = 0;
// Clear global lock count
LockCount = 0;
// Initialize my IClassFactory with the pointer to its vtable
MyIClassFactoryObj.lpVtbl = (IClassFactoryVtbl *)&IClassFactory_Vtbl;
// Initialize COM
CoInitialize(0);
{
HRESULT hr;
#define ALLOW_UNLOADING
// Because CoRegisterClassObject will call our IClassFactory AddRef() and
// then not call its Release until we do CoRevokeClassObject, let's initialize
// OutstandingObjects to -1 so that we don't really count this first AddRef
// on our IClassFactory. That way OutstandingObjects represents only those
// AddRef's caused by an app using this EXE
OutstandingObjects = (DWORD)-1;
#else
OutstandingObjects = 0;
#endif
// Add this EXE to COM's Running Task table. We pass our IExampleExe object's GUID, and a pointer to
// our IClassFactory object (we use only 1). Save the token that CoRegisterClassObject returns so we
// can later remove this EXE from COM's task table
hr = CoRegisterClassObject(&CLSID_IExampleExe, (IUnknown *)&MyIClassFactoryObj, CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &nToken);
if (hr)
{
MessageBox(0, "Can't add this EXE to COM's running task list!", "Error", MB_OK|MB_ICONEXCLAMATION);
return(-1);
}
}
// Do a message until WM_QUIT message is received. Our objects post this when they see that all of
// our objects have been released
{
MSG msg;
while (GetMessage(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Release any ITypeInfo we got
if (MyTypeInfo) MyTypeInfo->lpVtbl->Release(MyTypeInfo);
// Remove this EXE from COM's Running task table
CoRevokeClassObject(nToken);
// Free up COM
CoUninitialize();
return 0;
}