Introduction
There are lots of hyperlink controls on CodeProject. So why is there any need for this one? Indeed, when I implemented this class couple of years ago, I never thought of publishing it, because it had really nothing special worth for an article. But three weeks ago, I included this class in my ODBCTracer-project (ODBCTracer) and someone on CodeProject was interested in it. Especially, the fact that it was not a CWnd
derivate was the main point of interest.
Why not using the MFC versions?
At this point, I want to avoid any heated discussion about the advantages and disadvantages of MFC in different kinds of projects. Generally, I don't know of any disadvantage about MFC in client-side software systems if you know about the pitfalls. But sometimes, any dependency between your application and a specific MFC version is somewhat annoying. Just think about how things can get messy if you mix different versions of the MFC in different modules used by the same application. In such a case, it is better to rely on Win32 and steer clear of the MFC. This was exactly the case, when I worked on my ODBCTracer project.
Backgrounds
Although the implementation is very straightforward, there is an aspect about it which is worth mentioning. I am using a modified worker object pattern for initialization of the class when the first instance of it is created. Initialization means that the window class of the Win32 control is registered by calling the Win32 API function RegisterClass
and the hand cursor is loaded from a specific module. These two jobs have to be done only once, and normally, to assure this, we are using something like a flag which indicates (when it was set) that the initialization is finished. Let's see how this technique could be implemented:
CHyperlink::CHyperlink()
{
if (!class_was_initialized)
initialize();
.
.
}
The disgusting thing about this technique is that within the constructor there is a check for initialization all the time, and besides the first invocation of the constructor, this check will return false
. By using the modified worker object pattern, you are able to invoke initialization operations before the first instance is created and you can avoid such a check.
Worker object pattern
The worker object pattern is used in situations where you want to execute some piece of code and assure that this is done in any circumstance. Imagine, you have a code fragment where you have to take care about thread safety so you want to assure that only one thread is within this piece of code. You can use a CRITICAL_SECTION
and the Win32 API functions InitializeCriticalSection()
and DeleteCriticalSection()
to ascertain the exclusive access of a single thread. But if you use these functions directly within your code, there are many ways which can lead you to a perfect system deadlock. Let's see how this can happen:
CRITICAL_SECTION lock;
void func1()
{
InitializeCriticalSection(&lock);
DeleteCriticalSection(&lock);
}
The most important point is that you always delete the lock when you leave the critical section. This is done by calling DeleteCriticalSection(&lock)
. But what happens when the code within the critical section throws an exception? It's likely that DeleteCriticalSection(&lock)
won't be called and the next thread which wants to acquire the lock in InitializeCriticalSection(&lock)
will have to wait a very long period of time...
By using a worker object pattern, you can avoid such things. A worker object is an instance of a class which does execute code in its constructor and / or destructor. So, what we need to do is to invoke the functions for acquiring and releasing the lock within the constructor and destructor of a specific class which may be called a mutex.
void func1()
{
{
Mutex lock;
}
}
The modified worker object pattern
Let me first emphasise that I don't think that the idea introduced here is something new. The only fact is that I have never read about it on CodeProject nor in any other community. This doesn't mean that there are no articles on this topic anywhere. It's just I've never come into contact with them...
The modified worker object pattern is based on the worker object pattern. It describes a way of executing some piece of code while the application or the module is loaded. Let's have a look at the class declaration of CHyperlink
.
class CHyperlink
{
class _autoinitializer
{
public:
_autoinitializer();
~_autoinitializer();
protected:
HMODULE hModule;
};
friend class _autoinitializer;
public:
CHyperlink();
virtual ~CHyperlink();
bool create(int resourceid, HWND parent);
bool create(RECT rect, const char *url, HWND parent);
bool create(int x1, int y1, int x2,
int y2, const char *url, HWND parent);
protected:
std::string m_Url;
HWND m_hWnd;
static _autoinitializer __autoinitializer;
static HCURSOR handcursor;
static int WndProc(HWND hwnd,WORD wMsg,WPARAM wParam,LPARAM lParam);
};
The worker object in this class is the static member called __autoinitializer
of the class _autoinitializer
. When the application or module is loaded, the common runtime creates all global or static instances. We can verify that by putting a breakpoint in the constructor of _autoinitializer
to trace back the invocation.
- CHyperlink::_autoinitializer::_autoinitializer() line 43
- $E16() line 75 + 34 bytes
- $E20() + 29 bytes
- _initterm(void (void)* * 0x00429114 $S21, void (void)* * 0x00429228 ___xc_z) line 525
- _cinit() line 192 + 15 bytes
- mainCRTStartup() line 205
- KERNEL32! 77e614c7()
Once the instance of _autoinitializer
is created it initializes the class by registering the window class and loading the hand cursor. When the application is unloaded, the runtime calls the destructor which frees all the resources. The technique of using the runtime to invoke initialization routines even work with DLLs. If you use this class in any kind of DLL, the initialization operation will be invoked when you call LoadLibrary()
for the first time from your application. Once you unload the library, the runtime automatically calls the destructor which in turn handles the deinitialization process. In DLLs, you can also export the DllMain
function to do initialization jobs once the module is attached to the process space. But in this case, initialization and class implementation are scattered through your module which leads to bad design which in turn complicates maintenance.
Pitfalls
You cannot influence the sequence in which modified worker objects will be created! When we put another static member to the declaration of CHyperlink
like the following update, we cannot say which instance will be created first: __autoinitializer1
or __autoinitializer2
.
static _autoinitializer __autoinitializer1;
static _autoinitializer __autoinitializer2;
It is not specified by the ANSI C++ standard how compiler vendors have to generate code for creation of this kind of objects. Be aware of this and refer to Scott Meyers :)
By the way, hope you can use the hyperlink class.