Creating and Using Notifiers

   

Creating and Using Notifiers

Services such as displaying text in the Message View, building a project, and automating a menu selection are used by our wizard to affect the IDE. What we need next is a way for the IDE to affect our wizard by notifying when the execution of the application has stopped . This is the job of notifiers . The combination of services and notifiers is what allows for the two-way RAD capability provided by Delphi and C++Builder. The Tools API enables us to profit from this RAD capability within the extensions we create. For the MemStat Wizard example that we are enhancing, we need a notifier to signal us through the IDE's debugger when an application process has stopped.

Let's take a look at the various types of notifiers provided by the Tools API as shown in Table 23.5.

Table 23.5. Tools API Service Notifiers

Notifier Interface

Description

IOTABreakpointNotifier

Used to retrieve breakpoint activities such as when the breakpoint is triggered or the user wants to modify the breakpoint.

IOTADebuggerNotifier

Used to retrieve debug notifications such as when the debugger starts debugging a process, finishes debugging a process, or when breakpoints are added or deleted.

IOTAEditLineNotifier

Used to retrieve edit activities such as when the user inserts or deletes lines in a source file.

IOTAEditorNotifier

Used to retrieve activities associated to the source editor such as a modification of text.

IOTAFormNotifier

Used to retrieve activities associated with the form editor such as when a property is altered or when a component on the form is renamed .

IOTAIDENotifier

This is the base class for all IDE notifiers.

IOTAMessageNotifier

Used to retrieve activities associated to the message view such as when a message is added or removed, or a message view tab is added or selected.

IOTAModuleNotifier

Used to retrieve events associated to a particular module such as when a module's source file has been modified.

IOTAProcessModNotifier

Used to notify when a specified module is loaded.

IOTAProcessNotifier

Used to retrieve events associated to a process being debugged such as when the integrated debugger loads or unloads a module, or when a process is created or destroyed .

IOTAThreadNotifier

Used to retrieve events associated to a thread being debugged such as when the integrated debugger loads or unloads a module, or when a thread is created or destroyed.

IOTAToolsFilterNotifier

Used to retrieve activities associated to a Build Tool such as filtering output generated by the Build Tool.

In our example, we want to know when the process representing the application under test is complete. The notifier best suited for this task is the IOTADebuggerNotifier . This notifier has two methods that are useful: ProcessCreated() and ProcessDestroyed() . The ProcessCreated() method is triggered when our application is executed, and the ProcessDestroyed() method is triggered when our application under test is completed.

Defining a Custom Debugger Notifier Class

Let's take a look at Listing 23.10, which illustrates how we can set up the class to represent this notifier for our MemStat Wizard. This code has been added to the wizard_memstatus.h file located in the wizard_part3_notifier folder for this chapter, which can be found on the companion CD-ROM.

Listing 23.10 DebugNotifier Class Declaration
 class DebugNotifier: public TNotifierObject, public IOTADebuggerNotifier  {    typedef TNotifierObject inherited;  public:    __fastcall DebugNotifier(const _di_IOTADebuggerServices debugger,                             const MemStatusWizard*   wizard);    __fastcall ~DebugNotifier();    // IOTADebuggerNotifer    virtual void __fastcall BreakpointAdded(_di_IOTABreakpoint Breakpoint);    virtual void __fastcall BreakpointDeleted(_di_IOTABreakpoint Breakpoint);    virtual void __fastcall ProcessCreated(_di_IOTAProcess Process);    virtual void __fastcall ProcessDestroyed(_di_IOTAProcess Process);    // override NotifierObjectObject  methods    void __fastcall AfterSave();    void __fastcall BeforeSave();    void __fastcall Destroyed();  // implement this    void __fastcall Modified();  protected:    // override IInterface  methods    virtual HRESULT __stdcall QueryInterface(const GUID&, void**);    virtual ULONG   __stdcall AddRef();    virtual ULONG   __stdcall Release();  private:    const MemStatusWizard*   wizard; // keep track of the wizard that owns this notifier    _di_IOTADebuggerServices debugger; // keep track of debuggerservice that added me  as a notifier    AnsiString name; // remember the debugger's old name    int index; // Notifier index  };  

We create a notifier by defining a class that implements a specific notifier interface. Note that this is very similar to how we created our custom wizard. In fact, our Wizard class is also a descendent of a notifier (remember all that discussion on IOTANotifier earlier).

In our case the specific notifier interface we need is IOTADebuggerNotifier . Like the wizard, we also inherit TNotifierObject . The constructor method of the DebugNotifier class will provide the processing for assigning a notifier to an interface. That is why the first parameter of the constructor method requires a _di_IOTADebuggerServices service interface. The second parameter will provide a pointer to our wizard class, MemStatusWizard , so we have a way to directly notify the wizard of IDE changes. This, incidentally, requires a few new methods and properties to be added in the public section of our custom wizard class within the wizard_memstatus.h file so that the notifier can provide the anticipated notification. This is shown in Listing 23.11.

Listing 23.11 MemStatWizard Class ”Public Declarations
 class PACKAGE MemStatusWizard : public TNotifierObject, public IOTAMenuWizard  {    typedef TNotifierObject inherited;  public:    __fastcall MemStatusWizard();    __fastcall ~MemStatusWizard();  void __fastcall SetProcessActive(bool value);  // used by debug notifier   // expose these properties (used mainly for debugmessaging by the debug notifier)   _di_IOTAMessageServices  MessageServices;   _di_IOTAMessageGroup     MessageGroup;  };  

The DebugNotifier class will use the properties and methods that are shown in bold. Let's now look at the methods created for DebugNotifier that have been added to the wizard_memstatus.cpp file as shown in Listing 23.12.

Listing 23.12 DebugNotifier Class Methods
[View full width]
 __fastcall DebugNotifier::DebugNotifier(const _di_IOTADebuggerServices debugger,                             const MemStatusWizard*   wizard)  : index(-1), debugger(debugger), wizard(wizard) {      // register the notifier      index =  debugger->AddNotifier(this);      #ifdef DebugMessages      char value[MAX_PATH];      sprintf(value," - [Debug] - DebugNotifer has been created.  Index = %d",index);      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + value),wizard->MessageGroup);      #endif  }  //                                      - __fastcall DebugNotifier::~DebugNotifier()  {     //unregister the notifier if that hasn't happend yet     if (index >= 0)     {      #ifdef DebugMessages      char value[MAX_PATH];      sprintf(value," - [Debug] - About to call RemoveNotifer - inside ~Debug Notifier() - graphics/ccc.gif Index = %d",index);      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + value),wizard->MessageGroup);      #endif      debugger->RemoveNotifier(index);     }     debugger = 0;  }  //                                      - ULONG   __stdcall DebugNotifier::AddRef()  { return inherited::AddRef(); }  ULONG   __stdcall DebugNotifier::Release() { return inherited::Release(); }  HRESULT __stdcall DebugNotifier::QueryInterface(const GUID& iid, void** obj)  {    QUERY_INTERFACE(IOTADebuggerNotifier, iid, obj);    return inherited::QueryInterface(iid, obj);  }  void __fastcall DebugNotifier::AfterSave()  {}  void __fastcall DebugNotifier::BeforeSave() {}  void __fastcall DebugNotifier::Destroyed()  {     //unregister the notifier if that hasn't happend yet     if (index >= 0)     {      #ifdef DebugMessages      char value[MAX_PATH];      sprintf(value," - [Debug] - About to call RemoveNotifer "                   "- inside Destroyed() - Index = %d",index);      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + value),wizard->MessageGroup);      #endif      debugger->RemoveNotifier(index);      index = -1;     }     debugger = 0;  }  void __fastcall DebugNotifier::Modified()   {}  void __fastcall DebugNotifier::BreakpointAdded(_di_IOTABreakpoint Breakpoint)  {      #ifdef DebugMessages      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + " - Breakpoint Added..."),                        wizard->MessageGroup);      #endif  }  //                                      - void __fastcall DebugNotifier::BreakpointDeleted(_di_IOTABreakpoint Breakpoint)  {      #ifdef DebugMessages      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + " - Breakpoint Deleted..."),                        wizard->MessageGroup);      #endif  }  //                                      - void __fastcall DebugNotifier::ProcessCreated(_di_IOTAProcess Process)  {      wizard->SetProcessActive(true);      #ifdef DebugMessages      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + " - Process Created - Process ID = " +  AnsiString((int)Process->ProcessId)),wizard->MessageGroup);      #endif  }  //                                      - void __fastcall DebugNotifier::ProcessDestroyed(_di_IOTAProcess Process)  {      wizard->SetProcessActive(false);      #ifdef DebugMessages      wizard->MessageServices->AddTitleMessage(AnsiString(wizard->GetName() + " - Process Destroyed - Process ID = " +  AnsiString((int)Process->ProcessId)),wizard->MessageGroup);      #endif  }  

Again, the constructor for the DebugNotifier class self registers as a notifier by using the AddNotifier() method associated to the _di_IOTADebuggerServices object, which is passed in as the parameter. The class's destructor and the Destroyed() method both provide a way to clean up the notifier, which needs to be done or we'll end up having IDE woes.

The two key methods we need to implement from this notifier interface are ProcessCreated() and ProcessDestroyed() . Both of these methods call the SetProcessActive() method, available from the wizard, to indicate if a process is active or not. Let's look at this new wizard method in Listing 23.13, which has been added to the wizard_memstatus.cpp file.

Listing 23.13 MemStatusWizard Class ” SetProcessActive() Method
 void __fastcall MemStatusWizard::SetProcessActive(bool value)  {    ProcessActive = value;  } 

There's not a lot of complexity in SetProcessActive() , it just simply resets the ProcessActive property associated to our custom wizard. As we will see shortly, the Execute() method of our wizard checks on this variable to provide the final automation of the memory analysis process.

Utilizing Our Debugger Notifier

Now, let's take a look at how we set up and utilize this debug notifier in the Execute() method for our MemStat Wizard located within the wizard_memstatus.cpp file. Depicted in bold within Listing 23.14 are all the new additions to the Execute() method since our last update.

Listing 23.14 MemStatusWizard Class ” Execute() Method
 void __fastcall MemStatusWizard::Execute()  {    Application->ProcessMessages();  // let menu processing complete    bool launch = true;  // unless instruected otherwise, run the application    TFormMemStat*  FormMemStat = NULL;    SetupMessageViewAccess();  _di_IOTADebuggerServices DebuggerServices;  // we'll use these a little later   DebugNotifier* debugnotifier;        // keep track of debugnotifier  _di_IOTAModuleServices ModServices;    BorlandIDEServices->Supports(ModServices); // get access to Modules)    _di_IOTAProject project = FindCurrentProject(ModServices);    if (project)    {        _di_IOTAProjectBuilder projectbuilder = project->ProjectBuilder;       if (projectbuilder->ShouldBuild)       {            MessageServices->AddTitleMessage(AnsiString(GetName() + " - Project needs to be built first"),                  MessageGroup);            AnsiString filename = ExtractFileName(project->FileName);            AnsiString message = "MemStatus Wizard detected that the " +                                 filename + " project needs to be built first.\n"                                 "Do you wish to continue?";            int result = MessageBox(NULL,message.c_str(),                                "MemStat Wizard - Build Project?",                                MB_YESNO);            if (result == IDYES)            {                launch = projectbuilder->BuildProject(cmOTAMake,true,true);            }            else launch = false; // user doesn't want to run now       }   }  else  // could not find project    {        AnsiString message = GetName() + " - Project not loaded. Unable to run.";        MessageServices->AddTitleMessage(message,MessageGroup);        MessageBox(NULL,message.c_str(),                "MemStat Wizard - Build Project?", MB_OK);        launch = false;    }    if (launch)  // if launch is still a go    {      MessageServices->AddTitleMessage(AnsiString(GetName() + " - Project is built and ready to run"),                MessageGroup);      _di_INTAServices NativeServices;      BorlandIDEServices->Supports(NativeServices); // get access to IDE (menu bar)      // let's find the Run top menu item...      TMenuItem* MenuItem =          FindMenuItemCaption(NativeServices->MainMenu->Items,"Run");      if (MenuItem)      {          // now let's find the Run (F9) menu item          TMenuItem* temp = FindMenuItemCaption(MenuItem,"Run");          MenuItem = temp;          if (MenuItem)          {              if (MenuItem->Enabled)              {                  int result = MessageBox(NULL,                       "MemStat Wizard is ready to launch active project and "\                       "measure memory performance. \n\n" \                       "Although not required, it's recommended that you "\                       "close all other applications with the exception of "\                       "C++Builder before running the memory test. This will "\                       "allow the MemStat Wizard to more precisely measure "\                       "memory performance of the application under test.\n\n"\                       "Do you wish to continue?",                       "MemStat Wizard - Ready to Run",MB_YESNO);                  if (result == IDYES)                  {                      FormMemStat = new TFormMemStat(0);//instantiate MemStat                      FormMemStat->Show();  // show the wizard                      MessageServices->AddTitleMessage(AnsiString(GetName() + " - " +                          FormMemStat->GetMemoryTotal()),MessageGroup);    // get interace to debuggerservices and a notifier   BorlandIDEServices->Supports(DebuggerServices);   debugnotifier = new DebugNotifier(DebuggerServices,this);  Application->ProcessMessages(); //let FormMemStat complete                      FormMemStat->SpeedButtonStartClick(0); //measure memory                      MessageServices->AddTitleMessage(AnsiString(GetName() + " - " +                          FormMemStat->GetMemoryStartFree()),MessageGroup);                      MenuItem->OnClick(0);  // run the app through the menu item  // here's how we could run the app from the debugger service   #ifdef RunFromDebugger   _di_IOTAProcess Process;   // figure out exe name   AnsiString exename = ExtractFileNameNoExt(   project->FileName,true) + ".exe ";   if (FileExists(exename))   {   MessageServices->AddTitleMessage(   AnsiString(GetName() + " - " +   "Create Process = " + exename),MessageGroup);   DebuggerServices->CreateProcess(exename, "", "");   Process = DebuggerServices->CurrentProcess;   if (Process)   {   MessageServices->AddTitleMessage(   AnsiString(GetName() + " - " +"Process = " +   AnsiString(Process->ProcessId)),MessageGroup);   //Process->Pause(); // Pause   //Process->Run(ormRun);   // Run Normally   }   }   #endif // RunFromDebugger  }                  else                  {                      MessageServices->AddTitleMessage(AnsiString(GetName() +                          " - User aborted run."),MessageGroup);                  }              }          }      }      if (!MenuItem)      {                  MessageServices->AddTitleMessage(AnsiString(GetName() + " - Unable to run application. " \                     "Project not loaded."),MessageGroup);      }    }    // little loop processing here - but don't tie up system (check and get out)    if (FormMemStat)    {        while (FormMemStat->Visible)  // check        {  if (!ProcessActive)  // process is no longer active / shut it down   {   FormMemStat->SpeedButtonStopClick(0); // final measurement   FormMemStat->Close();   }  Application->ProcessMessages(); //get out (process current messages)        }    if (ProcessActive) // wizard has been shut down, but app remains   {   int result = MessageBox(NULL,   "MemStat Wizard has been shut down, but the application "\   "under test is still active. \n\n" \   "Do you wish to close application under test as well?",   "MemStat Wizard - Terminated",MB_YESNO);   if (result == IDYES)   {   _di_IOTAProcess Process;   Process = DebuggerServices->CurrentProcess;   if (Process)   Process->Terminate();   }   }  MessageServices->AddTitleMessage(AnsiString(GetName() + " - " +            FormMemStat->GetMemoryStopFree()),MessageGroup);        MessageServices->AddTitleMessage(AnsiString(GetName() + " - " +            FormMemStat->GetMemoryFinalUsage()),MessageGroup);        MessageServices->AddTitleMessage(AnsiString(GetName() + " - " +            FormMemStat->GetMemoryPeakUsage()),MessageGroup);        delete FormMemStat;    }  if (debugnotifier)  // little clean-up   {   debugnotifier->Destroyed();   debugnotifier =  NULL;   }  MessageServices->AddTitleMessage(GetName() + " - Completed",MessageGroup);  }   

To get a notifier we need a service interface that can provide it. In this case it is the _di_IOTADebuggerServices interface. This interface provides a method called AddNotifier() that we will use in the constructor for our DebugNotifer . Earlier, we used the RegisterPackageWizard() method for registering a custom wizard. For notifiers, we use the AddNotifier() method. Later, we will use the RemoveNotifer() within our notifier's destructor and Destroyed() method for proper clean up (release) of the notifier.

After we instantiate a debug notifier by passing the DebuggerServices interface to the DebugNotifier constructor, the notifier is ready to receive notification from the IDE of debugger activities.

After the application under test is launched, and the form for MemStatus Wizard is activated, we check within our looping process to see if the DebugNotifier object has set our wizard's ProcessActive flag to false . This flag identifies if the application under test is active or not. If it is not active, we mark the memory, close the MemStatus Wizard form, and report the memory status within our message group window. If the form for the wizard is shutdown while the application under test is still active, we query the user and, based on his response, we terminate the process. This is accomplished by attaining the current process through the CurrentProcess() property for the DebuggerServices interface. After we have the current process we can call its Terminate() method.

Finally, we clean up by calling the Destroyed() method of our debug notifier.

With this code in place, our wizard is fully automated. Originally, we started off with just a form that came into view requiring full user intervention. Then, we figured out when to start the memory analysis after the application was launched by using interface services. This time we figured out how to precisely know when to stop the memory analysis, and how to clean up using notifiers. But the capabilities that we can add don't just have to stop there.


   
Top


C++ Builder Developers Guide
C++Builder 5 Developers Guide
ISBN: 0672319721
EAN: 2147483647
Year: 2002
Pages: 253

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net