GroupWise Object API in Visual Basic and C++
Articles and Tips: article
Developer Support Engineer
Novell Americas Developers Support
cmilla@novell.com
01 Aug 2000
This AppNote deals briefly with object-oriented technology as an introduction to programming to the GroupWise Object API. The author looks specifically at programming in the Visual Basic and C++ languages.
Introduction
In order to understand GroupWise Object API, you need to know what object-oriented programming is. Knowing about objects and certain characteristics of the objects will make programming to GroupWise using the Object API much easier. Objects consist of a defined set of data (or properties) and a group of methods. (See David Chappell, Understanding ActiveX and OLE. 1996. Microsoft Press, Redmond Washington, p. 9.) These objects contain three important characteristics: encapsulation, inheritance, and polymorphism. GroupWise Object APIs exhibit these characteristics and allow programmers to create applications that use GroupWise functionality. Now, automation is a very intricate part of GroupWise Object API but will not be discussed in detail unless it pertains to GroupWise. In Visual Basic, automation is well hidden. However, in C++ developers will need to understand interfaces and know about the fundamental interface, IUnknown (a quick discussion of this interface will be given). After a brief summary of the three characteristics of object-oriented technology, an introduction to programming to the GroupWise Object API in both Visual Basic and C++ will be given.
Note: Automation has many different notions and is a broad term. In this article the term automation refers to the programmability or techology used in GroupWise (namely the IUnknown interface and table of pointers called the vtable).
Object Oriented
Object-oriented programming consists of programming Abstract Data Types (ADT). (Object-oriented technology is similar to Component Object Model, or COM, which is actually what GroupWise uses.) Abstract Data Types export a set of operations (interface) that is the only access mechanism to the type's data structure. A programmer is usually asked to take a real life problem and create an application that would solve it. In other words, the programmer abstracts a model from the problem and creates her own set of data representative of the problem at hand. With the groups of methods and data, she organizes the groups into classes. Abstract Data Types are considered classes in object-orientation. Classes then define objects that are instances of the class. Along with having interfaces and creating objects, object-orientation has other essential characteristics such as those named above.
The first characteristic, encapsulation, is hiding data from direct access. In other words, the data is encapsulated and only the methods available in the object are shown. Encapsulation allows the originator of the object to have more control over which data is changeable and which data is not. Thus keeping the essential parts of the object from being corrupted (see Figure 1).
Figure 1: Encapsulation, reading or writing data available only through methods.
Inheritance, the second characteristic, is the idea that allows new objects to have some, or all, the features of an existing object, otherwise known as the parent object. Like a person who inherits eye and hair color from a parent, an object inherits features from its parent object. The parent object is also called a superclass, and a child object is referred to as a subclass. Two types of inheritance are implementation inheritance and interface inheritance, shown in Figure 2. Implementation inheritance occurs when a client of the child object calls one of the child's inherited methods and the code of the parent's method is actually executed. On the other hand, with interface inheritance, the child object must provide the code for handling requests, or the child only inherits the definitions of the parent's methods. One benefit of interface inheritance is that it makes it easier to implement polymorphism. (Definitions taken from Chappell, page 13.)
Figure 2: Implementation inheritance versus interface inheritance.
How does interface inheritance make it easier to implement polymorphism? First of all, what is polymorphism? It is the ability to treat different things as if they were the same. In other words, a user can treat two or more objects the same when, in fact, they are very different. For instance, several objects can have a print method, and each of those objects can implement their print method differently. Now that polymorphism has been explained, let us go back to our original question. How does interface inheritance make it easier to implement polymorphism? The answer lies in flexibility. The child object, having only inherited the definitions of the parent's methods, has to provide the code for handling the client's request. This allows the flexibility to invoke a method in a different manner than another object but everything looks the same to the developer. This flexibility is known as polymorphism.
GroupWise
The GroupWise Object API allows applications to use the same functionality that currently exists in the GroupWise client without having to run the client. Specifically, it lets developers use the GroupWise 5.x address book and document management capabilities to manipulate mail messages, appointments, notes, tasks, and phone messages. (See http://developer.novell.com/ndk/doc/docui/index.htm#../gwobjapi/gwobjenu/data/h7ikbsqg.htm.) Theoretically, any automation technology should be able to make calls to the Object API. However, only three compilers (languages) are officially supported by Novell Developer Support: Delphi, Visual Basic, and C++, specifically Visual C++. Since Delphi and Visual Basic are not significantly different, only Visual Basic and C++ will be mentioned in this article.
We will begin with simply creating the GroupWise session and then logging in to GroupWise. In Visual Basic, you can either use late binding or early binding. Early binding is adding the GroupWare type library in Project->References. It allows Visual Basic to compile the CLSIDs (Class Identifiers) into the current executable, giving it a faster performance. You are then allowed to declare your variables as specific GroupWise types. Late binding is using only the types given by Visual Basic. The following is an example of creating the instance of a GroupWise object in early binding.
Set objGroupWise = New Application
In late binding...
Dim objGroupWise as objectSet objGroupWise = CreateObject("NovellGroupWareSession")
The next step is to log in. The first or base object is the Account object (you will need to declare this object). The following code does so in early binding:
Dim objAccount as Account
Set objGWAccount = objGroupWise.Login(txtUserID, txtCommandLine)
In late binding you are able to use the same login method as above. However, you would just declare objGWAccount as an object (the type given in Visual Basic) instead of Account. TxtUserID and txtCommandLine are optional parameters. However, if they are left out, GroupWise will log in to the previous user.
Visual Basic allows you to add the type libraries, which makes programming in automation much easier. (For more information on automation, refer to Chappell.) However, in C++, automation is not hidden; therefore, it is not as easy. The types are declared in the header file ("gwoapi.h") and pointers will need to be declared and released. Automation (or the creating of instances and classes) is not handled by the compiler as it is for Visual Basic. For instance, here is the code to create a session and log in to GroupWise in C++:
IGWSession* pIGWSession;DIGWAccount* pDIGWAccount;IGWAccount* pIGWAccount;VARIANT vUserId, vCmdLine, vPassword, vWhenToPrompt, vReserved; //Create session if(FAILED(CoCreateInstance(CLSID_GroupWare, NULL, CLSTX_INPROC_SERVER |CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_IGWSession,(void**)pIGWSession))){ printf("Could not create session object\n"); return FALSE; } //Initialize variants VariantInit(vUserId); VariantInit(vCmdLine); VariantInit(vPassword); VariantInit(vWhenToPrompt); VariantInit(vReserved); //Setting variables V_VT(vUserId) = VT_EMPTY; V_VT(vCmdLine) = VT_EMPTY; V_VT(vPassword) = VT_EMPTY; V_VT(vWhenToPrompt) = VT_I2; VariantInit(vReserved) = VT_EMPTY; V_I2(vWhenToPrompt) = 0; //Login if(!SUCCEEDED(pIGWSession-Login(vUserId, vCmdLine, vPassword, vWhenToPrompt,vReserved, pDIGWAccount))) { printf("QueryInterface on IID_IGWAccount failed\n"); return FALSE; } pDIGWAccount-Release(); return TRUE;};
In C++ there is no early binding option. Notice that the initializing and setting of the variants are done by Visual Basic, which is hidden from the developer. However, in C++ you will need to initialize and set the variants yourself. First of all, an instance of the class needs to be created that contains the class ID. From the code above, you can see that the call to CoCreateInstance needs a void pointer to the session created. Afterwards, the variants are initialized and set. In this sample, they are set to empty, similar to the default login method in Visual Basic. The call logs in to GroupWise using the default user (the one previously logged in). The login method only returns DIGWAccount that is then used to call QueryInterface. QueryInterface comes from the fundamental interface called IUnknown which will be discussed next. QueryInterface returns IGWAccount, allowing the developer to access the object's methods and properties as well as have access to the other objects available in GroupWise.
IUnknown
An interface is an interaction between the object and its clients (Chappell, 40). Or in other words, a developer implements the methods and properties of an object correctly and the object promises to support the methods and properties as the developer knows them to be. Where does the QueryInterface call come from (as mentioned above)? GroupWise API supports the fundamental interface called IUnknown. Visual Basic also uses the IUnknown Interface. However, it is hidden from the developer. On the other hand, C++ does not. All C++ developers who would like to make calls to the GroupWise Object API should be familiar with IUnknown. IUnknown has three methods: QueryInterface, Release, and AddRef.
QueryInterface is used to allow new features to be added to existing applications without breaking existing software. It does so by establishing an extra level in accessing the needed object. Notice the pointer to DIGWAccount in the C++ code above, it is received from pIGWSession. DIGWAccount is the initial pointer you use to retrieve the actual object. Next, you call QueryInterface from DIGWAccount and pass in the correct IID. (Interface Identifier. Every interface must have a unique name called a globally unique identifier [GUID]. An interface's GUID is called its inerface identifier [IID] [Chappell, 41].) In our case it would be IID_IGWAccount. If the object supports the pointer given by QueryInterface than it returns a pointer to that interface. Therefore, if IGWAccount is supported by DIGWAccount, pIGWAccount will be a valid pointer (see Figure 3).
Figure 3: QueryInterface.
After calling QueryInterface notice pDIGWAccount->Release. After retrieving IGWAccount, there is no need for DIGWAccount and Release is called to free the pointer. The last method, AddRef, is for reference counting and is used to tell an object when to stop executing. AddRef is not needed nor used for programming with GroupWise Object API in C++.
Account Object
Now that we understand the calls made to IUnknown, we can begin to take a look at the GroupWise objects. As you may have noticed, the account object is retrieved as a result of logging in to GroupWise. Figure 4 shows an interface of the Account object.
Figure 4: An Account object as an interface.
The vtable, as shown in Figure 4, stores a pointer to each method in the interface. The pointer retreived in IGWAccount points to a pointer inside the object. The pointer then points to the vtable. Going back to the code in GroupWise you will see V_VT calls. These calls are to the vtable that contains all the pointers to the methods in the object. The Account object also contains the properties in Figure 5. See http://developer.novell.com/ndk/doc/docui/index.htm#../gwobjapi/gwobjenu/data/h7ikbsqg.htm.
Figure 5: Properties in Account Object.
Properties
|
Access
|
Description
|
AccountRights |
R/O |
AccountRightsCollection. Access rights given to specific users. For the root account (Proxied = FALSE), contains zero or more read/write instances of AccountRightsobjects, each of which grants access rights to a specific user. For a proxied account (Proxied = TRUE), contains exactly one read-only instance of the AccountRights object which specifies the rights a user has to the proxied account. |
AddressBooks |
R/O |
AddressBooks collection. The AddressBooks collection for the root account---even if this account is a proxy account (Proxied = TRUE). |
AllFolders |
R/O |
Folders collection. All folders in this account. Includes shared folders even if they are owned by another user (the folder's Shared property = egwSharedIncoming). |
AllMessages |
R/O |
AllMessages collection. All messages in this account. Does not include messages that belong to other accounts, even if they appear in this account's query folders and shared folders. |
Application |
R/O |
Application. The Application object. |
Archived |
R/O |
Boolean. TRUE if this is an archive account.---GroupWise 5.5 and later |
Cabinet |
R/O |
Folder. The Cabinet Folder. |
Calendar |
R/O |
Folder. Contains the appointments, notes, and tasks stored in this account. Items stored in shared folders are not part of the Calendar folder. Items that have the OnCalendar property set to FALSE are not part of the Calendar folder. |
DefaultAccountRights |
R/O |
AccountRights. Default access rights given to users not listed in the AccountRights property. Only available from the root account. |
DefaultAddressBook |
R/O |
AddressBook. Only available from the root account. |
DefaultDocumentLibrary |
R/O |
DocumentLibrary. The default DocumentLibrary object. Can be Nothing. |
DefaultPathToArchive |
R/O |
String. Returns the path to the archived account, if one exists.----GroupWise 5.5 and later |
DocumentLibraries |
R/O |
DocumentLibraries collection. |
DocumentsFolder |
R/O |
Folder. The folder containing the most recently used documents.---GroupWise 5.5 and later |
FieldDefinitions |
R/O |
FieldDefinitions collection. |
Filters |
R/O |
Filters collection. Represents saved filters. |
FrequentContacts |
R/O |
AddressBook. Only available from the root account. Can be Nothing, even in the root account, if the Novell Address Book is not installed. |
MailBox |
R/O |
Folder. The Mailbox folder. |
ObjType |
R/O |
Enum (egwUser, egwResource). The type of object this account represents |
Owner |
R/O |
Address. The address of the owner of this account. |
Parent |
R/O |
Application. The Application object that owns this object. |
PathToArchive |
R/O |
String. Returns the path to the archived account, if one exists.---GroupWise 5.5 and later |
PathToHost |
R/O |
String. The path-to-host of the GroupWise server that was logged in to, or an empty string ("") if the user logged in through TCP/IP. |
Poxied |
R/O |
Boolean. TRUE if this is a proxy account. |
ProxyAccounts |
R/O |
Accounts collection. The proxy accounts currently being used.---GroupWise 5.5 and later |
Remote |
R/O |
Boolean. TRUE if this is a remote account. |
RootFolder |
R/O |
Folder. The top-level folder that contains all other folders. |
SystemAddressBook |
R/O |
AddressBook. Only available from the root account. |
TCPIPAddress |
R/O |
String. The TCP/IP address of the GroupWise server that was logged in to, or an empty string ("") if the user logged in through a mapped path or a Universal Naming Convention (UNC) path. |
TCPIPPort |
R/O |
Long. The TCP/IP port of the GroupWise server that was logged in to, or zero (0) if the user logged in through a mapped path or a UNC path. |
Trash |
R/O |
Trash. The trash for this account. |
WorkFolder |
R/O |
Folder. The Work In Progress folder. |
In the description column, the type of the property is given. For instance, the property Archived is a boolean value that tells you whether or not the account is archived. The types that are underlined are pointers to other objects accessible through the account object, which means, some propeties are objects. Notice that the parent object, or superclass, is the application object, so the account object inherits all the definitions of the methods of the application object.
As an example, let's access appointments in a calendar folder in the GroupWise client. You need to access the calendar property of the Account object that in turn returns a folder object. Then you look at the documentation given on the folder object and access those properties. To retrieve the appointments in the calendar folder you need to access the messages collection of the folder object. A collection is a special object which contains the entire group of the same object. In other words, the messages collection of the folder object you are looking at contains a collection of messages. You want to obtain all of the messages in that folder (we will cipher through the appointments later). Figure 6 shows the properties of the messages collection. All collections have similar properties. The difference is the parent property.
Figure 6: Property of the messages collection.
Properties
|
Access
|
Descriptions
|
Application |
R/O |
Application. The number of objects in this collection. |
Count |
R/O |
Long. The number of objects in this collection. |
_NewEnum |
R/O |
Enumeration object. Implements IEnumVARIANT. For Windows only. |
Parent |
R/O |
Folder. The Folder object that owns this collection. |
The count property tells how many messages are in the calendar folder. In the documentation you find several add methods along with the find, item, move, and remove methods. Through the find mehtod, you are able to retrieve all the appointments in my calendar folder. The find method requires a condition parameter or a filter expression. (For more information on all the filter expressions available, please refer to the GroupWise documentation.) You can use the UNARY expression of "Appointment." After calling the find method, you receive a messageList object. From the messageList object, which is also a message collection, you are able to call the item method. Each item is an object type message and can be obtained within a loop. Now you are finally able to get the bodytext (or message body), subject, or whatever properties are available and perhaps print these properties to a listbox. Below is code in Visual Basic and describes the steps above after logging in and getting the account object.
Dim myCalendar As FolderDim myMessages As MessagesDim myMessList As MessageListDim searchString As StringDim myAppointment as Message Set myCalendar = objGWAccount.Calendar Set myMessages = myCalendar.Messages searchString = "(APPPOINTMENT)" Set myMessList = myMessages.Find(searchString) If myMessList 0 Then For each myAppointment In myMessList MyListBox.AddItem "Subject: " myAppointment.Subject MyListBox.AddItem "Body: " myAppointment.BodyText Next End If
A short cut...
Dim myMessList As MessageListDim searchString As StringDim myAppointment as Message searchString = "(APPPOINTMENT)" Set myMessList = objGWAccount.Calendar.Messages.Find(searchString) If myMessList 0 Then For each myAppointment In myMessList MyListBox.AddItem "Subject: " myAppointment.Subject MyListBox.AddItem "Body: " myAppointment.BodyText Next End If
Both sets of the code above are essentially the same. However, with the top portion you will be able to see the step by step process of obtaining each object and its properties. So, what does the C++ version of this look like? Every object needs to be obtained by using QueryInterface and released as mentioned above. InVisual Basic, this is all done for you. However, in C++ you will need to access each object and release the necessary pointers. The following is the C++ equivalent of the Visual Basic code above.
DIGWFolder* pDIGWFolder;DIGWMessages* pDIGWMessages;DIGWMessageList* pDIGWMessageList;DIGWMessage* pDIGWMessage;IGWFolder* pIGWFolder;IGWMessages* pIGWMessages;IGWMessageList* pIGWMessageList;IGWMessage* pIGWMessage;BSTR bstr = NULL;char searchString[255+1]; long lCount;VARIANT vIdx;// Get calendar from accountpIGWAccount-get_Calendar(pDIGWFolder);pIGWFolder = NULL;if(pDIGWFolder SUCCEEDED(pDIGWFolder-QueryInterface(IID_IGWFolder,(void**)pIGWFolder))) { pDIGWFolder-Release(); }//Get Messages from folderpIGWFolder-get_Messages(pDIGWMessages);pIGWMessages = NULL;if(pDIGWMessages SUCCEEDED(pDIGWMessages-QueryInterface(IID_IGWMessages,(void**)pIGWMessages))) { pDIGWMessages-Release(); }//Provide Filter syntax or search stringstrcpy(searchString, "APPOINTMENT");lstrcpy(searchString, "(");lstrcat(searchString, ")");bStr = SysAllocString(TO_OLE_STRING(searchString));//Retrieving message list pIGWMessages-Find(bStr, pDIGWMessageList);if(pDIGWMessageList SUCCEEDED(pDIGWMessageList-QueryInterface(IID_IGWMessageList,(void**)pIGWMessageList))) { pDIGWMessageList-Release(); }// Get the number of messages in list.pIGWMessageList-get_Count(lCount);VariantInit(vIdx);V_VT(vIdx) = VT_I4;
//free bStrysFreeString(bStr); //Now get each item for(int i=0; ilCount; i++) { V_I4(vIdx) = i + 1; pIGWMessageList-Item(vIdx, pDIGWMessage); if(pDIGWMessage SUCCEEDED(pDIGWMessage-QueryInterface(IID_IGWMessage, (void**)pIGWMessage))) { pDIGWMessage-Release(); //****** Get subject ****** pIGWMessage-get_Subject(pDIGWFormattedText); if(pDIGWFormattedText SUCCEEDED(pDIGWFormattedText- QueryInterface(IID_IGWFormattedText, (void**)pIGWFormattedText))) { pDIGWFormattedText-Release(); pIGWFormattedText-get_PlainText(bStr); lvItem.pszText = FROM_OLE_STRING(bStr); ListView_InsertItem(hwndLView, lvItem); SysFreeString(bStr); bStr = NULL; pIGWFormattedText-Release(); //****** Get message body****** pIGWMessage-get_BodyText(pDIGWFormattedText); if(pDIGWFormattedText SUCCEEDED(pDIGWFormattedText- QueryInterface(IID_IGWFormattedText, (void**)pIGWFormattedText))) { pDIGWFormattedText-Release(); pIGWFormattedText-get_PlainText(bStr); lvItem.pszText = FROM_OLE_STRING(bStr); ListView_InsertItem(hwndLView, lvItem); SysFreeString(bStr); bStr = NULL;}pIGWFolder-Release();pIGWMessages-Release();pIGWMessageList-Release();pIGWMessage-Release();pIGWAccount-Release();
With C++ you will need to release every pointer that is retrieved as well as obtain more pointers for the actual subject and body text. Every time another object is retrieved from an object, QueryInterface is called to get yet another pointer. Also, the filter expression syntax needs to be declared as a BSTR and memory needs to be allocated and released. Each time you access BSTR you will need to convert the variable using TO_OLE_STRING or FROM_OLE_STRING. The actual conversion code is in Util.cpp taken from the sample codes. (See: http://developer.novell.com/ndk/doc/samplecode/gwobjapi sample/index.htm.)All this is part of handling automation in which Visual Basic handles internally.
Conclusion
The GroupWise Object API is an object-oriented technology. It encapsulates each object by providing methods and properties which are necessary and secure to the developer. It also provides inheritance by allowing other objects to contain definitions of the parent or superclass. Lastly, polymorphism is apparent when considering the find method of the Messages object, MessageList object, and AllMessages object. Each of the find methods of these different objects appear to the developer as one and the same but, in fact, could be quite different.
Visual Basic provides an easier programming environment for the GroupWise Object API. Although C++ coding to the Object API looks quite complicated, the concept is the same. With either environment or language, GroupWise Object API provides a tool that is useful for all applications.
* 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.