Click here to Skip to main content
13,191,400 members (78,567 online)
Click here to Skip to main content
Add your own
alternative version

Stats

127.3K views
44 bookmarked
Posted 17 Jun 2005

Function pointers and Delegates - Closing the gap!

, 17 Jun 2005
Rate this:
Please Sign up or sign in to vote.
Explains the usage of the Marshal class methods GetFunctionPointerForDelegate and GetDelegateForFunctionPointer, and compares their performance with the P/Invoke mechanism.

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 <code lang=mc++>__gc class with an inner <code lang=mc++>__nogc class with the <code lang=mc++>__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 <code lang=mc++>Marshal class - <code lang=mc++>GetFunctionPointerForDelegate and <code lang=mc++>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 <code lang=mc++>EnumWindows example everyone uses when they want to demonstrate something to do with callback functions.

Sample usage

<pre lang=mc++>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 <code lang=mc++>EnumWindows function

<pre lang=mc++>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 (<code lang=mc++>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 <code lang=mc++>& (address of) on an event member. Notice how I check for <code lang=mc++>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 <code lang=mc++>Init method where I've called <code lang=mc++>EnumWindows directly and have used <code lang=mc++>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 <code lang=mc++> GetFunctionPointerForDelegate does a <code lang=mc++>nullptr check and then calls an <code lang=mc++>extern function <code lang=mc++> GetFunctionPointerForDelegateInternal. <code lang=mc++> 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 <code lang=mc++>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.

<pre lang=mc++>delegate int DispProc(String^, String^);

Here, I've declared my second delegate (which I'll use to wrap the <code lang=mc++> printf function provided in the CRT library).

<pre lang=mc++>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 <code lang=mc++>static <code lang=mc++>HandleFoundWindow method that matches the delegate prototype expected by the <code lang=mc++> WindowEnumerator class. Notice that I could have used <code lang=mc++> Console::WriteLine here, but I wanted to use a delegate that wrapped a function pointer (so I could demonstrate the use of the <code lang=mc++> 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.

<pre lang=mc++>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 <code lang=mc++>printf function using <code lang=mc++>LoadLibrary/<code lang=mc++>GetProcAddress and call <code lang=mc++>GetDelegateForFunctionPointer to create a delegate that wraps this function pointer. If you look at the implementation of <code lang=mc++> GetDelegateForFunctionPointer, you'll see that it does some <code lang=mc++>nullptr checks, then checks to see that you've actually passed in a <code lang=mc++>Delegate type and then calls an <code lang=mc++>extern function <code lang=mc++> GetDelegateForFunctionPointerInternal. <code lang=mc++> 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 <code lang=mc++>EnumWindows API, one of which used P/Invoke while the other used the <code lang=mc++> 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 :-

<pre lang=mc++>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 :-

<pre lang=mc++>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.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Nish Nishant
United States United States
Nish Nishant is the Principal Software Architect/Consultant for Ganymede Software Solutions LLC, and is based out of Columbus, Ohio. He has over 17 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish was a Microsoft Visual C++ MVP between 2002 and 2015.

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored C++/CLI in Action for Manning Publications in 2005, and had previously co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.

Contact Nish : If you are interested in hiring Nish as a consultant, you can reach him via his google email id voidnish.

Company Website : www.ganymedesoftwaresolutions.com

You may also be interested in...

Comments and Discussions

 
News[My vote of 1] how do I pass pass a method adress from win32 application to c# library method which has Action as a parameter Pin
a ahole26-Jun-11 5:37
membera ahole26-Jun-11 5:37 
Questionhow do I pass pass a method adress from win32 application to c# library method which has Action as a parameter Pin
a ahole26-Jun-11 5:19
membera ahole26-Jun-11 5:19 
GeneralMore comment: can't cast IntPtr directly to function pointer Pin
dtr2228-Oct-08 1:53
memberdtr2228-Oct-08 1:53 
need to go through (void*), or better, call first IntPtr.ToPointer(), and only then to the cast to pointer-to-function.
(maybe its a new behaviour of VS2008)
QuestionHow about translating it to the managed world? Pin
marsboee18-Oct-06 8:38
membermarsboee18-Oct-06 8:38 
GeneralTrivial Event woes Pin
tonyt28-Sep-06 4:19
membertonyt28-Sep-06 4:19 
GeneralPinning unnecessart (I think...) Pin
A Debaene30-Aug-06 1:42
memberA Debaene30-Aug-06 1:42 
GeneralRe: Pinning unnecessart (I think...) Pin
Nishant Sivakumar30-Aug-06 2:00
staffNishant Sivakumar30-Aug-06 2:00 
Generalpin_ptr Pin
philipp__p10-Jul-06 5:12
memberphilipp__p10-Jul-06 5:12 
GeneralFunction pointers Pin
Parag Gadkari23-May-06 5:57
memberParag Gadkari23-May-06 5:57 
Questionpointer to member functions?? Pin
TheLion4-May-06 9:51
memberTheLion4-May-06 9:51 
AnswerRe: pointer to member functions?? Pin
Sameer Khan29-Oct-07 7:32
memberSameer Khan29-Oct-07 7:32 
GeneralRe: pointer to member functions?? Pin
TheLion29-Oct-07 8:01
memberTheLion29-Oct-07 8:01 
GeneralRe: pointer to member functions?? Pin
Sameer Khan30-Oct-07 4:47
memberSameer Khan30-Oct-07 4:47 
QuestionC# example? Pin
Calibus_22-Feb-06 3:40
memberCalibus_22-Feb-06 3:40 
AnswerRe: C# example? Pin
Nishant Sivakumar22-Feb-06 3:51
staffNishant Sivakumar22-Feb-06 3:51 
GeneralRe: C# example? Pin
Calibus_22-Feb-06 5:09
memberCalibus_22-Feb-06 5:09 
GeneralRe: C# example? Pin
M i s t e r L i s t e r24-Mar-06 8:05
memberM i s t e r L i s t e r24-Mar-06 8:05 
QuestionVS 2005 solution? Pin
ericwilliamg8-Nov-05 11:39
memberericwilliamg8-Nov-05 11:39 
AnswerRe: VS 2005 solution? Pin
ericwilliamg8-Nov-05 11:54
memberericwilliamg8-Nov-05 11:54 
Question^ accent usage ? Pin
toxcct23-Jun-05 1:50
membertoxcct23-Jun-05 1:50 
AnswerRe: ^ accent usage ? Pin
Nishant Sivakumar23-Jun-05 1:56
staffNishant Sivakumar23-Jun-05 1:56 
GeneralRe: ^ accent usage ? Pin
toxcct23-Jun-05 5:20
membertoxcct23-Jun-05 5:20 
GeneralRe: ^ accent usage ? Pin
Nishant Sivakumar24-Jun-05 2:04
staffNishant Sivakumar24-Jun-05 2:04 
GeneralInitialization lists Pin
Nemanja Trifunovic17-Jun-05 9:54
memberNemanja Trifunovic17-Jun-05 9:54 
GeneralRe: Initialization lists Pin
Nishant Sivakumar17-Jun-05 18:11
staffNishant Sivakumar17-Jun-05 18:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.171017.1 | Last Updated 17 Jun 2005
Article Copyright 2005 by Nish Nishant
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid