Introduction
This whole business started one day when there was a post in the Microsoft
dotnet.languages.vc newsgroup where someone was complaining that he was having
trouble using EnumWindows
from Managed C++. He stated very firmly that he did
not want to use the DllImport
attribute. This got me interested naturally, and I
thought I could try and help him out. To my utter disappointment I found that I
was having trouble too. The issue was that EnumWindows
took as it's first
argument a callback function. All my searches on MSDN and google took me to
solutions that showed how to do this using the DllImport
attribute.
The technique suggested was simple. We are to declare a __delegate
object identical to the callback function. Now we are to use DllImport
to define EnumWindows
so that it takes as first argument our
__delegate
type. Now we can simply write our callback function as a
member of a managed class and pass this function to EnumWindows
.
__delegate bool CallBack(IntPtr hwnd, IntPtr lParam);
...
[DllImport("user32")]
extern "C" int EnumWindows(CallBack* x, int y);
...
CallBack* cb = new CallBack(0,
&SomeClass::SomeMatchingMethod);
EnumWindows(cb, 0);
The problem
All this is well and good, but it was beginning to get annoying. My problem was that whatever I did I couldn't get the callback function to
work. Obviously I couldn't pass a delegate directly because when we use IJW, the
native API functions expect native arguments and not managed arguments. I even
tried something as silly as casting a delegate object to a WNDENUMPROC
and as you might have guessed failed thoroughly. I also tried passing both
static and instance members of managed classes as the callback function, but I
kept getting run time exceptions about NULL
references and objects.
This was really disappointing to say the least.
That's when I got a huge boost from Richard Grimes who is a Microsoft MVP,
and who has written several quality books on Microsoft programming technologies.
His latest book is on using the managed extensions to program with VC++ .NET. In
reply to my query about calling EnumWindows
using IJW, he replied
to me and the reply included a sample code snippet from his latest book, but
unfortunately he used DllImport
. I replied back saying that I
wasn't looking for DllImport
and I must say my exasperation must
have reflected poorly in my reply. Because Richard's answer was a little crispy too to
begin with. But he gave me my first clue as to why I was going the wrong
direction. He explained to me how managed class members use the __clrcall
calling convention and how unmanaged callback functions use the __stdcall
calling convention In fact when I took a closer look at the compiler
warnings, I was shocked to find a message that said that I was trying to attempt
a redefinition of calling convention from __clrcall
to __stdcall
which is not possible and was therefore being ignored. That's when I realized
that I simply had to give up trying to use a managed class member method as my
callback.
The solution
Richard's final answer was an emphatic NO. But I badly wanted to figure out a way by which a managed class can pass a
delegate as the callback function. That's when this idea hit me out of the blue.
Inner classes. We could use inner classes, see! All we had to do was to have an
__gc
class with an inner __nogc
class and the outside
managed class will wrap the inner unmanaged class and expose it to the outside
world. The outer class has a delegate that acts as the managed callback. The
inner __nogc
class has a native __stdcall
method as the callback function. This
callback function will invoke the managed delegate each time it gets called.
Thus we simulate a managed callback mechanism here. I have commented the code in
vital areas so that you can understand this better.
__gc class CEnumWindows
{
private:
__nogc class _CEnumWindows
{
private:
static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM)
{
CEnumWindows* pew = CEnumWindows::GetClass();
pew->m_EnumProc->Invoke(hwnd, NULL);
return TRUE;
}
public:
void StartFinding()
{
EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc,NULL);
}
};
private:
_CEnumWindows* m_ew;
public:
__delegate bool EnumProc(IntPtr hwnd, IntPtr lParam);
static CEnumWindows* GetClass()
{
return m_pclass;
}
static CEnumWindows* m_pclass=NULL;
CEnumWindows()
{
m_pclass = this;
m_ew = new _CEnumWindows();
}
~CEnumWindows()
{
delete m_ew;
}
void StartFinding()
{
m_ew->StartFinding();
}
EnumProc* m_EnumProc;
};
Now we can use this from any managed class and pass any managed class member
function as the callback function. In the example below, I create a new instance
of CEnumWindows
which is the outer class. Then I associate a managed function
from one of my classes to the delegate member of the CEnumWindows
object. Alright,
alright,
I know that using a public delegate member is not a proper way to do this, but I
am only trying to demonstrate how this is done. Put this in a property if you
want to, or write a function that'll do this for you.
CEnumWindows* p = new CEnumWindows();
p->m_EnumProc = new CEnumWindows::EnumProc(this,&NForm::EWHandler);
p->StartFinding();
Conclusion
For my own whimsical reasons I am a big fan of using IJW which I feel is a
lot more natural for a C++ programmer than the use of weird looking attributes
that makes your code look like C# or VB .NET. I don't have anything against other
languages but I prefer my C++ code too look like C++ and not like some kind of
ugly mutation of other subjectively inferior languages. Anyway thanks goes to
Richard Grimes for pointing me in the correct direction. Those of you who are
interested in his new book on using the managed extensions can go to this link.
Programming
with Managed Extensions for Microsoft� Visual C++� .NET (Microsoft Press)