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.