Creating a GroupWise 5 Custom Third Party Object (C3PO)Using Delphi
Articles and Tips: article
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.