Using COM to build an extremely flexible application (framework)
Monday, June 19, 2006 1:26:25 AM
Few years ago, I have written a sample application for my previous employer. It begun as a simple "Internet Explorer hosting" project, but then it's changed. It became a pretty complex, plugin-enabled IE hosting application which we then wanted use as an Internet Kiosk. Company, however, did not want to use the code; "it's impossible for us to manage it", they said. Actually, I believe that it was one of my toughest jobs. It was using private inheritance, private virtual functions, RAII pattern, COM, type discovery and similar stuff."Hey, in .NET, such things are far simple!". Could be, but:
- I don't use .NET
- I don't like .NET
- I don't like to be restricted by a stupid run-time
- With COM and C++, I have got enough run-time support
- My code is far faster
- Can be used on FreeBSD with my UBS library (emulate - not all, but - some COM stuff)
- CLR garbage collection is not compatible enough to deal with COM objects, AFAIK (they may claim it is, but I believe it certainly is not suitable for complex COM scenerios)
Idea
Application consists of some components, which may or may not exist in more than one DLL file. All these components are COM objects. Following is a short list of components used in this idea:
- Application Logic
- UI Manager
- Module Manager
Application Logic (IAppLogic)
This is application business-logic file. In other words, this is the "brain" of the application. It knows everything about application, what to do, when to do and how to do.
UI Manager
Manages user interface stuff. Starting from top-level window, this component is responsible from windows, message loop(s), message handling and providing feedback to IAppLogic, acting as a gateway between user and application logic.
Module Manager
This component is responsible from loading and unloading modules, managing their interactions with each other, helping marshaling (when required), providing various helper functions and provides an export library for some GUIDs (during modules' link time).
Note that this technique does not use well-known CoCreateInstance. Instead, it "implements" CoCreateInstance without registration - modules are not required to be registered.
When application starts, it tries to enumerate DLLs in "modules" folder. After collecting the list, application tries to find a DLL that has got a coclass for "ICustomModuleManager" interface. When one is found, it calls DllGetClassObject and gets the IClassFactory for ICustomModuleManager interface. When IClassFactory is obtained, it instantiates an ICustomModuleManager and immediately calls ICustomModuleManager::Initialize.
Module manager may has a configuration file, in XML, that specifies details about modules. If none exists, module manager does a job much like calling application did (through module manager, anyway) - enumerate DLLs, ask for IAppLogic interface. Rest is a bit complicated. This is why using an XML file is better.
- Ask module task GUID
- Collect objects with matching task GUID
- Instantiate IAppLogic interfaces
- Call IAppLogic::Initialize for each IAppLogic
IAppLogic contains basic information about UI. If IAppLogic requires a UI, it exposes this information through get_UIManager method.
interface IAppLogic : IUnknown
{
// ...
virtual HRESULT STDMETHODCALLTYPE get_UIManager(LPVARIANT lpvaruim) PURE;
// ...
};
This function returns:- VT_BSTR, this could be:
1 - Path of UI manager DLL
2 - CLSID or ProgId of UI manager. - VT_UNKNOWN, if it already has got a pointer to IUIManager interface.
The helper function
HRESULT GENAPI CreateUIManager(LPVARIANT, IUIManager**, HWND);
helps a lot to decode LPVARIANT returned from get_UIManager. In other words, this function takes required action and returns an IUIManager*.
IUIManager::Initialize(HWND)
This method initializes UI manager and creates window for IAppLogic. If hwnd is NULL, UI manager creates a popup window and other windows as required.
IUIManager::Initialize kicks-off a thread which creates window(s). Therefore, it's not the calling thread that owns the window(s). This is an important detail. Other modules or threads - simply, "foreign code", should talk to UI through "thread messages" and not through window messages. Thread handle is stored as a member. Caller may wait for UI event, filter window messages or wait whole UI application to close. IUIManager provides following methods to allow other modules to interact with UI.
IUIManager::WaitForClose
Waits until all popup windows created with IUIManager::Initialize method destroy. After windows are destroyed, this function returns.
IUIManager::WaitForMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, ULONG ulWaitFlags, ULONG ulTimeout)
Waits for particular message, window and arguments. Wait behavior (i.e. for particular message or for particular window, or particular message sent to a particular window) can be defined with ulWaitFlags.
IUIManager::EnumUIElements(IEnumUIElement**)
Returns an enumerator to enumerate sibling UI elements. Note that this enumerator contains only parent windows created with IUIManager. In other words, this enumerator does not return children of windows created by IUIManager. Children are accessible through GetWindow Windows API function.
IUIManager provides few more helper functions. So, basically, what an application does is:
#include <genapi.h>
#pragma comment(lib, "genapi32.lib")
HRESULT LoadModules();
HRESULT MMCreateInstance(LPCTSTR lpszModuleName, ICustomModuleManager**, LPVARIANT lpvargs, ULONG ulVarCount);
int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
MSG msg;
HANDLE* phmodwait;
UINT umodcount;
CoInitialize(NULL);
umodcount = LoadModules();
if (umodcount < 1)
{
CoUninitialize();
return 1;
}
phmodwait = (HANDLE*)LocalAlloc(LPTR, sizeof(HANDLE) * umodcount);
for (UINT umod = 0; umod < umodcount; umod++)
{
// for each module, ask for wait handle and set it in phmodwait.
}
WaitForMultipleObjects(umodcount, phmodwait, TRUE, INFINITE);
LocalFree(phmodwait);
CoUninitialize();
return 0;
}
HRESULT LoadModules()
{
// find all dlls in "modules" sub-folder
for (int i = 0; i < dllcount; i++)
{
// for each module, find ICustomModuleManager
if (HasModuleManager(lpszModuleNames[i])
{
ICustomModuleManager* pcmm = NULL;
if (MMCreateInstance(lpszModuleNames[i], &pcm, NULL, 0))
{
// failed
}
// initialize is called by MMCreateInstance.
// pcmm->put_ConfigFile(L"config.xml"); // if you have one
pcmm->CollectTasks();
// for each task, create IAppLogic, and call IAppLogic::Initialize
}
}
}
Well, what we did want to do was similar to this model, though it's not an exact copy (some portions of the code MAY belong to the company, although they actually do not have it). Well, my intention is not giving you whole source code, but an idea. I can disclose some parts of the code, which I have already had before working on this, at home for self-training. What did we do with this model was more than IE hosting. It allows shell viewing, and it certainly was a better IE. I have added lots of fancy stuff to this logic, such as DirectX stuff. When a page is loading, it's faded to gray and an animation with little transparency plays over the IE window.








