Click here to Skip to main content
Click here to Skip to main content

Implementing Callback functions using IJW (avoiding DllImport)

, 13 Jul 2002
Rate this:
Please Sign up or sign in to vote.
Shows how you can call native API functions that require callbacks using IJW, and without the use of DllImport attribute. The technique allows you to pass a delegate as the callback function just as in the MS recommended manner except, I show you how to do this without the ugly DllImport attribute.

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.

//declare our delegate
__delegate bool CallBack(IntPtr hwnd, IntPtr lParam);

...

//ugghhhhhhh!!!! so uglyyyyyy!!!
[DllImport("user32")] 
extern "C" int EnumWindows(CallBack* x, int y); 

...

//create the delegate
CallBack* cb = new CallBack(0, 
    &SomeClass::SomeMatchingMethod);
//call the function
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 //outer class
{
private:
    __nogc class _CEnumWindows //inner class
    {
    private:
        /* This is a native function that follows the */
        /* __stdcall calling convention that's required */
        static  BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM)
        {               
            // We need to get the managed callback
            // up for each instance that our callback 
            // gets called. So we get a pointer to
            // the current instance of the outer class
            // and invoke the delegate that is holding
            // the managed callback method that the
            // callee code has passed to us
            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()
    {   
        //This for the unmanaged class to use
        //when it needs a pointer to the managed class
        return m_pclass;        
    }
    static CEnumWindows* m_pclass=NULL;
    CEnumWindows()
    {
        m_pclass = this;
        m_ew = new _CEnumWindows(); //unmanaged heap
    }
    ~CEnumWindows()
    {
        // we need to delete the object manually
        // as is is on the unmanaged heap
        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)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Nish Sivakumar

United States United States
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Comments and Discussions

 
GeneralCalling managed code from MFC PinmemberLithium0220-Dec-10 14:30 
GeneralNice Trick but Confusing Code !!! PinmemberVivek Ragunathan19-Mar-08 20:17 
GeneralMixed Mode to the rescue! Pinmembervillalvilla5-May-06 1:55 
QuestionWhy? Pinmemberhanifku9-Apr-06 15:02 
AnswerRe: Why? PinmemberPunCha20-Nov-06 16:21 
GeneralConverting the sample to remove the singleton PinmemberNigel de Costa4-Apr-05 6:11 
Following on from my previous post (see earlier thread) where I was attempting to use managed events in preference to the delegate / callback mechanism in the article. I wanted to be able to hold a pointer in the unmanaged inner class to the managed outer class in order to invoke the callbacks. The article has CEnumWindows as a singleton and uses a static member GetClass() to return a pointer to the outer object for the same purpose.
 
The stumbling block is that the compiler won't allow unmanaged classes to contain attributes that are pointers to managed classes.
 
The way around that is actually fairly simple (although not all that obvious). What is required is a helper template class called gcroot:
 
gcroot<CEnumWindows*> _pOuter
 
This wraps the managed pointer and allows it to be used within the unmanaged class (the pointer does not need to be pinned).
 
The steps to convert the sample (taking my previous example as a base) are:
 
Include the following to get gcroot<>...
 
#include <vcclr.h>
 
Add this attribute to _CEnumWindows...
 
gcroot<CEnumWindows*> _pOuter;
 
Initialise this attribute via the constructor for _CEnumWindows...
 
_CEnumWindows(gcroot<CEnumWindows*> pOuter)
{
     _pOuter = pOuter;
}
 
Remove the GetClass() impliementation from CEnumWindows and create it in the NForm object using new.
 
Pass the pointer to the inner class as the state parameter of the call to EnumWindows...
 
::EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc, (LPARAM) this);
 
and cast it back to its original type in the callback...
 
_CEnumWindows* pMe = (_CEnumWindows*) param;
 
then can be used to invoke the callback via the delegate as before...
 
pMe->_pOuter->m_EnumProc->Invoke(hwnd, NULL);
 
There is no great value in doing this for this particular example except as means to demonstrate the mechanism. However I can see a use for it when wrapping unmanaged APIs where a singleton wrapper is inappropriate.
 
Full listing below...
 
#include "stdafx.h"
 
#include <vcclr.h>
 
#using <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>
 
#include <tchar.h>
#include <windows.h>
 
using namespace System;
using namespace System::ComponentModel;
using namespace System::Drawing;
using namespace System::Windows::Forms;
 
__gc class CEnumWindows
{
private:
    
     __nogc class _CEnumWindows
     {
     private:
 
          // pointer to outer class to invoke callbacks
          gcroot<CEnumWindows*> _pOuter;
 
          static BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param)
          {    
               _CEnumWindows* pMe = (_CEnumWindows*) param;
 
               if (pMe->_pOuter!=NULL)
               {
                    pMe->_pOuter->m_EnumProc->Invoke(hwnd, NULL);
               }
 
               return TRUE;
          }         
 
     public:         
 
          // Constructor takes pointer to outer class - use gcroot<CEnumWindows*> as
          // type-safe wrapper to point to managed object
          _CEnumWindows(gcroot<CEnumWindows*> pOuter)
          {
               _pOuter = pOuter;
          }
 
          void StartFinding()
          {
               ::EnumWindows((WNDENUMPROC)_CEnumWindows::EnumWindowsProc, (LPARAM) this);
          }
     };
 
private:
 
     _CEnumWindows* m_ew;
 
     void OnEnumProc(IntPtr hwnd, IntPtr lParam)
     {
          __raise this->EnumProc(hwnd, lParam);
     }
 
public:
 
     __delegate void EnumProcDelegate(IntPtr hwnd, IntPtr lParam);
     __event bool EnumProc(IntPtr hwnd, IntPtr lParam);
 
     CEnumWindows()
     {
          m_ew = new _CEnumWindows(this);
          this->m_EnumProc = new CEnumWindows::EnumProcDelegate(this, &CEnumWindows::OnEnumProc);
     }
 
     ~CEnumWindows()
     {
          delete m_ew;
     }
 
     void StartFinding()
     {         
          m_ew->StartFinding();         
     }
 
     EnumProcDelegate* m_EnumProc;
};
 
public __gc class NForm : public Form
{
public:
         
     bool EWHandler(IntPtr hwnd, IntPtr lParam)
     {
          char buff[512];    
          if(GetWindowText((HWND)hwnd.ToInt32(),buff,511))
          {
               String* s = String::Format("{0}{1}",
                    __box(hwnd.ToInt32())->ToString("X8")->PadRight(30),
                    Convert::ToString(buff));              
               lbox->Items->Add(s);
          }
          return false;
     }
 
     NForm()
     {
          StartPosition = FormStartPosition::CenterScreen;
            Text = "Callbacks with IJW - Nish for CodeProject - Avoid DllImport";
            Size = Drawing::Size(750,550);
            FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
          MaximizeBox = false;
 
          lbox = new ListBox();         
            lbox->Location=Point(5,5);
            lbox->Size=Drawing::Size(730,500);                   
          lbox->Sorted = true;
 
            Controls->Add(lbox);
 
          CEnumWindows* p = new CEnumWindows();
          __hook(&CEnumWindows::EnumProc, p, &NForm::EWHandler);
 
          p->StartFinding();
         
     }
     ListBox* lbox;
 
};
 
int __stdcall WinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{    
     Application::Run(new NForm());
      return 0;
}
 

GeneralConverting the sample to use managed events... PinmemberNigel de Costa3-Apr-05 23:48 
GeneralLNK2001: unresolved external symbol &quot;void * __cdecl operator new(unsigned int)&quot; (??2@$$FYAPAXI@Z) Pinmembermarkbanderson29-Jan-04 11:55 
GeneralRe: LNK2001: unresolved external symbol &quot;void * __cdecl operator new(unsigned int)&quot; (??2@$$FYAPAXI@Z) Pinmembermarkbanderson30-Jan-04 1:11 
GeneralRe: LNK2001: unresolved external symbol &quot;void * __cdecl operator new(unsigned int)&quot; (??2@$$FYAPAXI@Z) PinsussSudesh Sawant15-Mar-05 19:34 
GeneralRe: LNK2001: unresolved external symbol &quot;void * __cdecl operator new(unsigned int)&quot; (??2@$$FYAPAXI@Z) PinsussDenis Delarze7-May-04 7:12 
Generalpassing pointers from unmanaged to managed Pinmembersivo8-Oct-03 7:44 
GeneralConfused Pinmemberigor196023-Sep-03 8:58 
QuestionHow to invoke the delegate asynchronously ? PinmemberYoni Rabinovitch11-Sep-03 3:27 
AnswerRe: How to invoke the delegate asynchronously ? PinmemberBumblebeeFromNewYork3-Nov-03 7:31 
GeneralRe: How to invoke the delegate asynchronously ? PinmemberYoni Rabinovitch3-Nov-03 19:44 
GeneralRight way PinmemberRama Krishna27-Jul-02 4:45 
GeneralRe: Right way PinmemberAlex Farber14-Apr-03 23:39 
GeneralAnother solution PinmemberDaniel Lohmann15-Jul-02 8:15 
GeneralRe: Another solution PinsubeditorNishant S15-Jul-02 14:54 
GeneralRe: Another solution PinmemberPVL23-Jun-03 9:16 
GeneralGood work PinmemberKannan Kalyanaraman14-Jul-02 22:52 
GeneralRe: Good work PinsubeditorNishant S15-Jul-02 14:51 
GeneralNot thread safe PinmemberRama Krishna14-Jul-02 3:27 
GeneralRe: Not intended to be so PinsubeditorNishant S14-Jul-02 4:32 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 14 Jul 2002
Article Copyright 2002 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid