Novell is now a part of Micro Focus

Creating a GroupWise 5 Custom Third Party Object (C3PO)Using Delphi

Articles and Tips: article

RUSS LUNDBERG
Support Engineer
GroupWise Division

JAMES KNOWLTON
Technical Writer
GroupWise Division

RON NERDIN
Software Engineer
GroupWise Division

CAROL OERTLI
Technical Editor
GroupWise Division

01 Apr 1997


A C3PO is a COM object that allows developers to customize how GroupWise 5 32-bit client works. You can add menu items, toolbar items, additional dialogs, etc. to the GroupWise 5 32-bit client. You can trap predefined events and add your own events. A C3PO is used to customize the use of the Novell GroupWise 5 32-bit client application.

This article will describe some steps create a C3PO. If you follow the steps in the article you will create a generic C3PO from the ground up. However, to see your C3PO work you will need GroupWise 5 and Borland Delphi 2 installed on your machine. This document will not include all the documentation on how C3PO's work, but will give step by step guidance to create your first C3PO.

Creating a GroupWise 5 Custom Third Party Object (C3PO)Using Delphi

What our C3PO will do is add a menu item to the Tools menu of the GroupWise 5 main client window. The menu will be called "Calculator." When selected it will launch the Windows calculator Calc.exe, which should be contained in the windows directory.

Here we go!

First we will create a C3PO executable application. Start Delphi 2. Select File|New Application from the menus. This will give you a form and a PAS file associated with that form. Next select File|New and then double click on the "Automation Object" icon.

An Automation Object Expert will be displayed that has four fields. Enter the following for each Field:


Class Name:

C3POSample

OLE Class Name:

Project1.C3POSample

Descriptions:

Sample C3PO Project

Instancing:

Multiple Instance

Then press the OK button. This will create PAS file that contains most of the server information that makes your C3PO a unique COM object. Select File|Save All. Save Unit1.pas as EXESRV.PAS. Save Unit2.pas as SERVOBJ.PAS. Save Project1.dpr as PROJECT1.DPR. With the current project saved we are ready to add C3PO specific data to our project.

We will not be making any modifications to the exesrv.pas file. This file should currently contain the following code:

//Beginning of exesrv.pas code

unit exesrv;



interface



uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;



type

  TForm1 = class(TForm)

  private

    { Private declarations }

  public

    { Public declarations }

  end;



var

  Form1: TForm1;



implementation



{$R *.DFM}



end.

//End of exesrv.pas code.

We will be making modifications to servobj.pas unit. The servobj.pas unit should currently contain the following code:

//Beginning of servobj.pas



unit servobj;



interface



uses

  OleAuto;



type

  C3POSample = class(TAutoObject)

  private

    { Private declarations }

  automated

    { Automated declarations }

  end;



implementation



procedure RegisterC3POSample;

const

  AutoClassInfo: TAutoClassInfo = (

    AutoClass: C3POSample;

    ProgID: 'Project1.C3POSample';

    ClassID: '{F0DCA320-8BD9-11D0-9361-00C04FDD2E8B}';

    Description: 'Sample C3PO Project';

    Instancing: acMultiInstance);

begin

  Automation.RegisterClass(AutoClassInfo);

end;



initialization

  RegisterC3POSample;

end.

//end of servobj.pas

Line 21 of the SERVOBJ unit specifies the classname of your COM object. Line 22 includes the Program ID. Line 23 specifies the ClassID which is also known as the GUID (Globally Unique ID). And line 24 contains the description. The class name, program ID, and description were all entered in the Automation Object Expert.

We need to insert additional code for the initialization routine at line 30. Add a begin and end for the initialization routine and then add the following lines after the line that calls RegisterC3POSample:

So initialization should be changed from:

initialization

  RegisterC3POSample;

to look like:

initialization

begin

  RegisterC3POSample;

  g_CommandFactory := TCommandFactory.Create;

   g_EventMonitor := TEventMonitor.Create;

end;

Now we need to add a finalization routine. This routine will be added between the last line of the initialization function "end;" and the last line of code in SERVOBJ unit, "end.". Add the following lines of code after the "end;" in the initialization function:

{Release OleObjects}

finalization

begin

  g_CommandFactory.Release;

  g_EventMonitor.Release;

end;

In the initialization function we added code that uses some undefined variables (g_CommandFactory, and g_EventMonitor). We need to add these as global variables. Insert the following lines of code to at line 17 directly after the "implementation" line:

var

   g_CommandFactory : TCommandFactory;                 // Create global CommandFactory object

   g_EventMonitor : TEventMonitor;                     // Create global EventMonitor object

  g_C3POManager :Variant;   // This holds the C3PO Manager Object

Now we need to add some functionality to the server portion of our C3PO. On line 9 of SERVOBJ unit there is a class defined as C3poSample which is a class type of TAutoObject. Insert the following function declarations as private functions after the line { Private declarations }:

function GetCmdFact: Variant;

    function GetDescription : string;

    function GetEventMonitor : variant;

    function GetIconFactory : variant;

Add the following lines as automated declarations directly after the line {Automated Declarations}:

property CommandFactory : Variant read GetCmdFact;

    property Description: string read GetDescription;

    property EventMonitor: variant read GetEventMonitor;

    property IconFactory: variant read GetIconFactory;

    function CanShutdown: TOleBool;

    procedure Deinit;

    procedure Init(Manager: variant);

Your C3POSample class should now look like:

type

  C3POSample = class(TAutoObject)

  private

    { Private declarations }

    function GetCmdFact: Variant;

    function GetDescription : string;

    function GetEventMonitor : variant;

    function GetIconFactory : variant;



  automated

    { Automated declarations }

    property CommandFactory : Variant read GetCmdFact;

    property Description: string read GetDescription;

    property EventMonitor: variant read GetEventMonitor;

    property IconFactory: variant read GetIconFactory;

    function CanShutdown: TOleBool;

    procedure Deinit;

    procedure Init(Manager: variant);



  end;

We need to now create a function and procedure for each function and procedure in our C3POSample class. Insert the following lines directly before the procedure RegisterC3poSample:

{C3POSample routines}

function C3poSamp.GetCmdFact : Variant;

begin

     Result := g_CommandFactory.OleObject;            // Return Ole object g_CommandFactory

end;



function C3poSamp.GetDescription : string;

begin

  Result := 'GroupWise Document management';          // Return Customer Tracking string description

end;



function C3poSamp.GetEventMonitor : Variant;

begin

     Result := g_EventMonitor.OleObject;              // return Oleobject g_EventMonitor

end;



function C3poSamp.CanShutdown: TOleBool;

begin

     Result := TRUE;                                  // return it is OK to shutdown

end;



function C3poSamp.GetIconFactory : Variant;

begin

    Result := g_IconFactory.OleObject;                // return OleObject g_IconFactory

end;



procedure C3poSamp.Init(Manager: variant);

begin

  g_C3POManager := Manager;                           // save C3POMonitor object

end;



procedure C3POSample.DeInit;

begin

end;

One last line of code needs to be appended to the Uses list. Add the following units after the OleAuto on line 6 of SERVOBJ: Ole2, Windows, C3poCalc. Make sure you place a comma after OleAuto, and a semicolon after C3poCalc. The new uses list should look like:

uses

  OleAuto, Ole2, Windows, C3poCalc;

We are now done modifying the SERVOBJ unit.

Next we need to add another unit to our project. We will calls this unit the C3poCalc unit. To create a new unit select File|New and then double click on the "Unit" icon. This will create a Unit that will most likely be called Unit2. With the new Unit tab selected, select File|Save and save this unit as C3poCalc.pas. This new unit only has 4 lines of code (unit, interface, implementation and end.). The C3poCalc unit contains the most code and is the work horse for this C3PO. So, there will be lots of typing with very little explanation from here on out. The main functions that you need to be aware of are BuildCommand, CustomizeContextMenu, CustomizeMenu, CustomizeToolBar, Init, and WantCommand. These functions are all part of TCommandFactory class. If you would rather not type these next few hundred lines of code, then you can download this same sample application from the web sight http://devsup.novell.com/sample/areas/groupwises.htm. Download the "DevNotes C3PO Delphi 2.0 Sample."

Well here we go. Once you have this C3PO done and working you will have a template that you can create all other C3POs from. That is one consolation for finishing this off.

Insert the following lines of code in the C3poCalc unit, after interface and before implementation:

uses

  Ole2, OleAuto, Windows, SysUtils, Exesrv;



type

  TGWCommand = class(TAutoObject)

  private

    LongPrmt : string;

    ToolTp : string;

    function GetBaseCmd: Variant;

    function GetLongPrompt : string;

    function GetParameters : integer;

    function GetPersistentID : string;

    function GetToolTip : string;

  public

    m_nCmd : longint;                                 // Command ID information

    BaseCommand:variant;

    procedure SetBaseCmd(Cmd:variant);

    Constructor Create(nCmd: longint);                // Used to create Command Object



  automated

    property BaseCmd: Variant read GetBaseCmd;

    property LongPrompt: string read GetLongPrompt;

    property Parameters: integer read GetParameters;

    property PersistentID: string read GetPersistentID;

    property ToolTip: string read GetToolTip;

    procedure Execute;

    procedure Help;

    procedure Undo;

    function Validate: longint;



  end;



TEventMonitor = class(TAutoObject)

  private



  public



  automated

    procedure Notify(Context: string; evt: variant);



  end;



TCommandFactory = class(TAutoObject)

  private



  public



  automated

    function BuildCommand( Context: string;

                           PersistentID: string;

                           BaseCommand: variant;

                           Parms: variant): variant;



    procedure CustomizeContextMenu( Context: string;

                                    GWMenu: variant);



    function CustomizeMenu( Context: string;

                            GWMenu: variant): TOleBool;



    function CustomizeToolBar( Context: string;

                               GWToolbar: variant): TOleBool;



    function Init(lcid: longint): longint;



    function WantCommand( Context: string;

                          PersistentID: string): TOleBool;



  end;



 const



      CALC_NEWMENU_SEP = 1;

{Calc Defines}

    CALCc_NEWMENU_SEP = 100;

    CALC_RUNMENU = 101;

    CALC_OPEN = 102;



{GroupWise Context defines}

    eGW_CLIENT = 'GW.CLIENT.WINDOW.BROWSER';



{Command Validate}

    eGW_CMDVAL_ALWAYS = 1;

    eGW_CMDVAL_CHECKED = 2;

    eGW_CMDVAL_DISABLED = 4;



{CommandFactory.Init }

    eGW_CMDINIT_MENUS = 1;

    eGW_CMDINIT_TOOLBARS = 4;

    eGW_CMDINIT_CONTEXT_MENUS = 16;

    eGW_CMDINIT_NO_PREDEFINED = 32;



{Menu positions}

    {Main Menu}

    eGW_MAINMENU_FILEMENU = 1;

    eGW_MAINMENU_EDITMENU = 2;

    eGW_MAINMENU_VIEWMENU = 3;

    eGW_MAINMENU_ACTIONSMENU = 4;

    eGW_MAINMENU_TOOLSMENU = 5;

    eGW_MAINMENU_WINDOWMENU = 6;

    eGW_MAINMENU_HELPMENU = 7;



    {File Menu}

    eGW_FILEMENU_NEWMENU = 1;

    eGW_FILEMENU_OPENARCHIVE = 2;

    eGW_FILEMENU_OPENVIEW = 3;

    eGW_FILEMENU_SAVEAS = 4;

    eGW_FILEMENU_IMPORTDOCUMENTS = 5;

    eGW_FILEMENU_PROXY = 6;

    eGW_FILEMENU_SHARING = 7;

    eGW_FILEMENU_PROPERTIES = 8;

    eGW_FILEMENU_PRINT = 10;

    eGW_FILEMENU_PRINTCALENDAR = 11;

    eGW_FILEMENU_EXIT = 13;



    {New Menu}

    eGW_NEWMENU_MAIL = 1;

    eGW_NEWMENU_APPOINTMENT = 2;

    eGW_NEWMENU_TASK = 3;

    eGW_NEWMENU_NOTE = 4;

    eGW_NEWMENU_DISCUSSION = 5;

    eGW_NEWMENU_PHONEMESSAGE = 6;

    eGW_NEWMENU_DOCUMENT = 8;

    eGW_NEWMENU_DOCUMENTVERSION = 9;

    eGW_NEWMENU_DOCUMENTREFERENCE = 10;

    eGW_NEWMENU_FOLDER = 12;



{Event Command ID's}

    eGW_CMDEVTID_ONREADY = 'GW#E#0';

    eGW_CMDEVTID_NEWMESSAGE = 'GW#E#1';

    eGW_CMDEVTID_ONSHUTDOWN = 'GW#E#2';



var

   g_C3POManager: variant;                            // Global C3POManger object

   NewCalcCmd : TGWCommand;                           // CTS menu command object

   OpenCalc:TGWCommand;

Wow, that was some typing. Well, here is another typing marathon. Insert the following code after "implementation" and before the "end." statement:

{ TGWCommand routines}



Constructor TGWCommand.Create(nCmd: longint);

var

   tempstr:string;

begin

     tempstr:=inttostr(nCmd);

     inherited Create;

     m_nCmd := nCmd;                                  // save the command id



end;



function TGWCommand.GetBaseCmd : Variant;

begin

     Result := TRUE;

end;



procedure TGWCommand.SetBaseCmd(Cmd:variant);

begin

     BaseCommand:=Cmd;

end;



function TGWCommand.GetLongPrompt : string;

begin

     Result := LongPrmt;                              // return a longprompt

end;



function TGWCommand.GetParameters : integer;

begin

     Result := 0;

end;



function TGWCommand.GetPersistentID : string;

begin

     Result := '';

end;



function TGWCommand.GetToolTip : string;

begin

     Result := ToolTp;

end;



procedure TGWCommand.Execute;

begin

      if(m_nCmd = CALC_RUNMENU) then begin

         WinExec('calc.exe',SW_SHOW);

      end;



end;



procedure TGWCommand.Help;

begin

end;



procedure TGWCommand.Undo;

begin

end;

function TGWCommand.Validate: longint;

begin

     Result := eGW_CMDVAL_ALWAYS;

end;



{TEventMonitor routines}



procedure TEventMonitor.Notify( Context: string;

                                evt: variant);

begin

  if CompareText(evt.PersistentID,eGW_CMDEVTID_ONSHUTDOWN) = 0 then

     Form1.Close;

end;



{TCommandFactory routines}



function TCommandFactory.BuildCommand( Context: string;

                                       PersistentID: string;

                                       BaseCommand: variant;

                                       parms: variant): variant;

begin

end;



procedure TCommandFactory.CustomizeContextMenu( Context: string;

                                                GWMenu: variant);

begin

end;



function TCommandFactory.CustomizeMenu( Context: string;

                                        GWMenu: variant): TOleBool;

var

   CTSSeparator: variant;

   ToolsMenu : variant;

   MainMenuItems : variant;

   Cmd : TGWCommand;

   CalcMenu:variant;

begin

     if (CompareText(Context,eGW_CLIENT)=0) then begin



        MainMenuItems := GWMenu.MenuItems;                                             // get menu items form Menu

        ToolsMenu := MainMenuItems.Item(eGW_MAINMENU_TOOLSMENU);                       // get File menu



        Cmd := TGWCommand.Create(CALC_NEWMENU_SEP);                                    // create command for seperator

        CTSSeparator := ToolsMenu.MenuItems.AddSeparator;

        Cmd.Release;



        NewCalcCmd := TGWCommand.Create(CALC_RUNMENU);                                 // create command for Calculator menu

        CalcMenu := ToolsMenu.MenuItems.Add('Calculator', NewCalcCmd.OleObject);       // add menu item to menu

        NewCalcCmd.LongPrmt := 'Launch the Windows Calculator';                        // set long prompt for menu item

        NewCalcCmd.Release;

        Result:=False;

     end;

   Result:=False;



end;



function TCommandFactory.CustomizeToolBar( Context: string;

                                           GWToolbar: variant): TOleBool;

begin

    Result:=False;

end;



function TCommandFactory.Init(lcid: longint): longint;

begin

     result := eGW_CMDINIT_MENUS;                     // We are modifing the menu's

end;



function TCommandFactory.WantCommand( Context: string;

                                      PersistentID: string): TOleBool;



begin

end;

Great Job!!! Only two things left to do. We need to create the Windows Registration file, and then register the C3PO. Run Notepad and enter the following text as the registration information for our C3PO:

REGEDIT4



[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\GW.CLIENT]



[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\GW.CLIENT\Project1.C3poSample]



[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\GW.CLIENT\Project1.C3poSample\Events]

"OnShutdown"=""



[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\GW.CLIENT\Project1.C3poSample\Objects]

"EventMonitor"=""

"CommandFactory"=""

Then save the file as PROJEXE.REG. This reg file is registering for the GW.CLIENT events and events. It is registering to receive the OnReady, OnShutdown and OnDelivery events. The C3PO has an EventMonitor routine that is called when these events take place.

Now, you need to run the PROJEXE.REG file which will register our C3PO data in the windows registry. You also need to compile the project1 application by selecting Project|Build All from the menus. Once you have done this you can complete the registration process by registering the executable file in the windows registry. You need to run the PROJECT1.EXE files with /regserver as a commandline parameter. Press the windows Start button. Select Run, then type the drive:\path\project1.exe /regserverand then press <Enter<. The C3PO is now registered!!!

Now try running GroupWise 5 and you should see a blank form window display. Then select the Tools menu and you should see your Calculator menu item at the bottom of the list. Select it and it will run the calc.exe found in the Windows directory.

Great Job!!! You did it!!! You have written your first GroupWise 5 C3PO!

* Originally published in Novell AppNotes


Disclaimer

The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.

© Copyright Micro Focus or one of its affiliates