Introduction
3 years ago, I had written an article titled
Implementing Callback functions using IJW (avoiding DllImport) that
described a technique by which you could call API functions requiring callbacks
without using the DllImport mechanism. The technique I used involved declaring
an __gc
class with an inner __nogc
class with the
__gc
class wrapping the inner class and exposing a managed
interface to the outer world. While it was rather contorted a technique, it was
the only option at that time. But today, with the release of Whidbey around the
corner, things have got a whole lot better, with the introduction of two new
methods in the Marshal
class - GetFunctionPointerForDelegate
and GetDelegateForFunctionPointer
. Now you can take a function
pointer and wrap a delegate around it, or the reverse - take a delegate and use
it as a function pointer. This article demonstrates both techniques using the
almost proverbial EnumWindows
example everyone uses when they want
to demonstrate something to do with callback functions.
Sample usage
delegate BOOL EnumWindowsDelegateProc(HWND hwnd,LPARAM lParam);
I've declared the delegate type that'll be exposed to the .NET world (or at
least to the C++ .NET world given that I've used some native types). The
delegate type matches the callback prototype for the EnumWindows
function
ref class WindowEnumerator
{
private:
EnumWindowsDelegateProc^ _WindowFound;
public:
WindowEnumerator()
{
_WindowFound = nullptr;
}
event EnumWindowsDelegateProc^ WindowFound
{
public:
void add(EnumWindowsDelegateProc^ d)
{
if(_WindowFound == nullptr)
_WindowFound = d;
}
void remove(EnumWindowsDelegateProc^)
{
_WindowFound = nullptr;
}
}
void Init()
{
pin_ptr<EnumWindowsDelegateProc^> tmp = &_WindowFound;
EnumWindows((WNDENUMPROC)Marshal::GetFunctionPointerForDelegate(
_WindowFound).ToPointer(), 0);
}
};
I have a non-trivial event (WindowFound
) that's of the delegate
type declared earlier - the reason I had to use a non-trivial event is that C++/CLI
does not treat an event as a regular class member and won't allow operations
like &
(address of) on an event member. Notice how I check for
nullptr
before assigning the event handler - this is done to ensure
that there is only one delegate associated with the event. The crux of the
code is in the Init
method where I've called EnumWindows
directly and have used GetFunctionPointerForDelegate
to convert the
delegate object into a function pointer. If you look at the mscorlib
source using Reflector or ILDasm, you'll see that
GetFunctionPointerForDelegate
does a nullptr
check and then calls an extern
function
GetFunctionPointerForDelegateInternal
.
GetFunctionPointerForDelegateInternal
creates a native callable stub
around the delegate that has been passed in and my best guess as to where it's
defined in would be mscorwks.dll (just a guess, someone who works in the
CLR team will know better). Notice how I pin the delegate object using a
temporary pin_ptr
variable, this is because I do not want the
delegate object to be moved around on the CLR heap while I am using the function
pointer in native code.
delegate int DispProc(String^, String^);
Here, I've declared my second delegate (which I'll use to wrap the
printf
function provided in the CRT library).
ref class MyClass
{
public:
static BOOL HandleFoundWindow(HWND hwnd,LPARAM lParam)
{
char buff[512];
GetWindowText(hwnd, buff, 511);
if(IsWindowVisible(hwnd) && pDispProc && strlen(buff))
pDispProc("%s\r\n",gcnew String(buff));
return TRUE;
}
static DispProc^ pDispProc = nullptr;
};
This class simply defines a static
HandleFoundWindow
method that matches the delegate prototype expected by the
WindowEnumerator
class. Notice that I could have used
Console::WriteLine
here, but I wanted to use a delegate that wrapped a
function pointer (so I could demonstrate the use of the
GetDelegateForFunctionPointer
method. After having
written so many articles and a technical book, I've lost any traces of shame
when I invent unusually contorted examples to demonstrate coding techniques.
Many others like me who do technical authoring are notorious for creating the
most beautiful pieces of code that'll never ever serve any purpose in a
real-world production environment. So, kindly have mercy on me guys.
int main(array<System::String ^> ^args)
{
WindowEnumerator wenum;
EnumWindowsDelegateProc^ d1 = gcnew EnumWindowsDelegateProc(
MyClass::HandleFoundWindow);
wenum.WindowFound += d1;
HMODULE h1 = LoadLibrary("msvcrt.dll");
if(h1)
{
typedef int (*FUNC_PTR)(const char *, ...);
FUNC_PTR pfn = reinterpret_cast<FUNC_PTR>(GetProcAddress(h1, "printf"));
if(pfn)
{
DispProc^ d2 = (DispProc^)Marshal::GetDelegateForFunctionPointer(
(IntPtr)pfn,DispProc::typeid);
MyClass::pDispProc = d2;
wenum.Init();
}
FreeLibrary(h1);
}
return 0;
}
I get a pointer to the printf
function using LoadLibrary
/GetProcAddress
and call GetDelegateForFunctionPointer
to create a delegate that
wraps this function pointer. If you look at the implementation of
GetDelegateForFunctionPointer
, you'll see that it does some nullptr
checks, then checks to see that you've actually passed in a Delegate
type and then calls an extern
function
GetDelegateForFunctionPointerInternal
.
GetDelegateForFunctionPointerInternal
creates a new delegate that wraps a
native function pointer (similar to the P/Invoke mechanism) and is defined in an
undocumented DLL - my best guess, as I mentioned earlier is mscorwks.dll,
but I might be wrong there. MSDN says that the function pointer that's passed
must be a pure unmanaged function pointer - means you cannot safely pass in a
native pointer in a mixed-mode module. Though when I tested it with a local
function in a mixed-mode project, things worked pretty smooth - but it's safer
to follow MSDN guidelines - you never know when something'll break otherwise.
Performance improvement
I did a little speed test to see which is more performant. I wrote two
classes - both of them enumerating windows using the EnumWindows
API, one of which used P/Invoke while the other used the
Marshal::GetFunctionPointerForDelegate
method to manually do the delegate
to function pointer conversion. Just as I expected, the latter method was more
efficient and this efficiency improvement was seen to be pretty consistent as I increased the
iterations.
Here are the two classes :-
delegate int DEnumWindowsProc(IntPtr hWnd, IntPtr lParam);
ref class Base abstract
{
public:
static int EnumWindowsProc(IntPtr hWnd, IntPtr lParam)
{
return TRUE;
}
virtual void Init() abstract;
};
ref class PIClass : Base
{
public:
[DllImport("user32.dll")]
static bool EnumWindows(DEnumWindowsProc^ lpEnumFunc,
IntPtr lParam);
virtual void Init() override
{
EnumWindows(gcnew DEnumWindowsProc(EnumWindowsProc),
IntPtr::Zero);
}
};
ref class FuncPtrClass : Base
{
public:
static DEnumWindowsProc^ dg = gcnew DEnumWindowsProc(EnumWindowsProc);
virtual void Init() override
{
pin_ptr<DEnumWindowsProc^> tmp = &dg;
::EnumWindows((WNDENUMPROC)
Marshal::GetFunctionPointerForDelegate(dg).ToPointer(), 0);
}
};
And here's my test code :-
generic<typename T> where T : Base int CalcSpeed(int count)
{
Console::WriteLine(
"Checking class : {0} with {1} iterations", T::typeid, count);
T t = Activator::CreateInstance<T>();
DWORD start = GetTickCount();
while(count--)
t->Init();
DWORD stop = GetTickCount();
Console::WriteLine("{0} milliseconds", stop - start);
return stop - start;
}
void DoCalc(int count)
{
int t1 = CalcSpeed<PIClass^>(count);
int t2 = CalcSpeed<FuncPtrClass^>(count);
float pc = (float)(t1-t2)/t1 * 100;
int pcrounded = (int)pc;
Console::WriteLine(
"{0}% improvement for {1} iterations\r\n", pc, count);
}
int main(array<System::String ^> ^args)
{
DoCalc(10000);
DoCalc(50000);
DoCalc(100000);
DoCalc(500000);
return 0;
}
Here's the output I got (you'll get slightly varying results obviously) :-
History
- June 17, 2005 : Article first published.