1.3 Subscribe and Publish

GroupWise Tokens is comprised of three APIs that subscribe and publish the tokens, as shown below.

Figure 1-2 Subscribe and Publish Tokens

The APIs include:

All three of the APIs use tokens at the core. Tokens are subscribed by or published by one of the token APIs.

1.3.1 Token Subscribing

Subscribing to tokens is the process whereby a third-party application is given a token as it is published before the GroupWise default token handler processes the token. The Token Commander and DDE are ways to publish token events.

Third-Party Handler DLL API

The TPH DLL API allows you to enhance the user’s experience with the GroupWise client by allowing third-party applications to subscribe to internal token events. The TPH DLL API is the mechanism that allows a third-party application DLL to subscribe to token events. A TPH DLL can monitor the token stream listening for tokens that are of interest to the application. TPH DLLs are automatically loaded by the GroupWise client at startup. TPH DLLs are loaded before GroupWise C3PO DLLs are loaded.

The set of all TPH DLLs for a particular workstation are referred to as the third-party DLL chain. The DLLs in the third-party chain have the opportunity to process a token before it is processed by GroupWise. Tokens are always first passed to the third-party chain where any TPH DLL can modify, remove, or ignore any given token. If a TPH DLL modifies or removes a token, all subsequent TPH DLLs in the chain will receive the modified token, or will not receive the deleted token. After all TPH DLLs in the third-party chain have evaluated the token, if it is still in the token stream GroupWise will process the resulting token.

The following figure illustrates the behavior of TPH during initialization, operation, and shutdown of the GroupWise client.

Figure 1-3 TPH Behavior

TPH Interface Requirements

The TPH DLL Interface requires the following:

  1. A Windows Registry entry must be added informing the GroupWise client that the TPH DLL wants to be added to the third-party chain.

    Key
    "HKEY_CURRENT_USER\Software\Novell\GroupWise\Client\Third Party"
    Item Name
    "DLLx" (where x is a number starting at 1)
    Item Type
    STRING
    Item Data
    <DLL path and file name>

    When inserting a TPH DLL entry into the registry, care should be taken to not overwrite any existing TPH DLL entries already registered. Do not always assume your TPH DLL will be DLL1.

    You can also register your TPH DLL under HKEY_LOCAL_MACHINE (in addition to HKEY_CURRENT_USER). The full path and contents of the registry entry are the same for HKEY_LOCAL_MACHINE. This allows a TPH DLL to be registered once per machine (rather than requiring it to be registered for each user on a specific machine).

    DLLS that were registered using HKEY_CURRENT_USER get priority in the call chain over those that were registered with HKEY_LOCAL_MACHINE. For example, if DLL1 and DLL2 were each registered under HKEY_CURRENT_USER, they would both be called before anything registered under HKEY_LOCAL_MACHINE.

    DLLs that were registered using HKEY_LOCAL_MACHINE use the same naming convention for the reg entries (DLL1, DLL2, etc.,) regardless of what might be registered under HKEY_CURRENT_USER. (One DLL might be registered as DLL1 under HKEY_CURRENT_USER and another DLL might be registered as DLL1 under HKEY_LOCAL_MACHINE.)

  2. The following platform specific requirements must be met:

    • For C++ DLLs, the C++ Structure Member Alignment must be set to 1-byte alignment. On some compilers 8-byte alignment is the default. The GroupWise client was written in C++ and was compiled with 1-byte alignment. If the TPH DLL alignment is not 1-byte, you can experience random memory errors in the Kernel32.dll, and the token data structures will not contain discernible data. In C++, make sure the methods are declared with the WINAPI calling convention. For example, the Exit method would be written as: WORD WINAPI Exit(void)

    • In Delphi, make sure the methods are declared with the stdcall calling convention. For example, the Exit method would be written as: Function Exit: SmallInt; export; stdcall;

    • Visual Basic and other programming languages that can create Win32 DLLs, can be used to create TPH DLLs.

The DLL is required to export seven methods in a specific order to allow GroupWise to load the DLL. The TPH DLL entry points are called using their ordinal values.

The methods are to be exported in the following order:

Export Order

Description

1. TPHVersion

Reserved. Even though it is currently not called by GroupWise, it must be included.

2. Compatibility

Called during the initialization process. It passes the GroupWise client version to the TPH via this method. You can determine the version by evaluating the high and low order bytes of the AppVersion parameter.

3. TimeStamp

Reserved. Even though it is currently not called by GroupWise, it must be included.

4. Entry

Used to initialize your TPH specific data. The GroupWise client language is also passed as a parameter to the TPH via this method. You can determine the language by evaluating the high and low order bytes of the wLanguage parameter.

5. Exit

Called during the shut down process. It is where you can clean up any memory you allocated.

6. HandleToken

Called whenever a token is published by the GroupWise client. The token is then passed as a parameter.This is the heart of the TPH process. By evaluating the token parameter, you can determine what token was published as well as any token related data in the token structure.

7. ValidateToken

Reserved. Even though it is currently not called by GroupWise, it must be included.

1.3.2 Token Publishing

Publishing tokens is the process whereby a token is passed to the GroupWise client to cause it to act upon the token. The TPH DLL API is where an application subscribes to token events.

The Token Commander API

The Token Commander API allows you to publish tokens to the GroupWise client. This API is COM-based and replaces the older DDE interface for publishing tokens. The DDE interface for tokens is still supported in both the 16-bit and 32-bit GroupWise 5 clients. See DDE for more information.

The Token Commander interface is simple and contains only one method: Execute(). The syntax for this method uses the text version of the token. For example, to open a specific message in the client, the application publishes the ItemOpen token similar to this Execute("ItemOpen(\"39255B499091137016\")")

GroupWise then takes the token and passes it through the third-party token chain for any TPH DLL that wanted to subscribe to the token to process it.

Other examples include publishing tokens to manipulate message text with the ItemSetText() (AFTKN_ITEM_SET_TEXT) token, change client settings with the PrefEnvironment (AFTKN_SET_ENV) token, display client dialog boxes with the FilterDlg (DTKN_FILTER_ITEMLIST) token, and automatically trigger events such as archive a message with the ItemArchive (AFTKN_ITEM_ARCHIVE) token.

DDE

Simply put, Dynamic Data Exchange (DDE) helps Windows applications communicate. Applications establish a DDE conversation and send messages to each other. A DDE conversation involves server and client applications. The server provides resources to the client. The client accepts processing or information from the server.

Your GroupWise solution can:

  • Share GroupWise resources using DDE Execute.

  • Obtain GroupWise processing results using DDE Request.

DDE Management Library (DDEML) The DDE Management Library (DDEML) simplifies DDE management by providing a high-level interface. This document discusses DDEML and not lower-level DDE calls. Use DdeInitialize() to register an application with DDEML.

DdeConnect() Use DdeConnect() to establish a conversation. A service parameter specifies an application to establish a conversation with. A topic parameter specifies the conversation topic. See Conversation with GroupWise.

DdeClientTransaction() Use DdeClientTransaction() to send service and information requests between conversing applications.

DdeDisconnect() Use DdeDisconnect() to end a conversation.

DdeUninitialize() Use DdeUninitialize() to clear application registration.

Conversation with GroupWise

A DDEML-registered application can establish a conversation with GroupWise using DdeConnect() including a service and topic name pair from the table below. The first pair is generally recommended:

Service Name

Topic Name

Comments

GroupWise

Command

Recommended. A conversation using this pair can send any supported command to GroupWise.

Foreign

Command

Operates like GroupWise/Command but makes GroupWise compatible with applications that launch a server based on the service name, in this case, OFWIN.EXE.

For example:

const char lpszService[ ] = "OFWIN"; 
const char lpszTopic[ ] = "COMMAND"; 
 
HDDEDATA FAR PASCAL __export DdeCallback( WORD, WORD, HCONV, HSZ, HSZ, 
HDDEDATA, DWORD, DWORD ); 
 
DWORD dwDDEInst; 
HSZ   hszService; 
HSZ   hszTopic; 
HCONV hConv; 
 
/** Register with DDEML **/ 
DdeInitialize(&dwDDEInst,(PFNCALLBACK)DdeCallback, APPCLASS_STANDARD | 
   APPCMD_CLIENTONLY, 0L); 
 
/** Create our String Handles **/ 
hszService = DdeCreateStringHandle(dwDDEInst, lpszService, 0); 
hszTopic = DdeCreateStringHandle(dwDDEInst, lpszTopic, 0); 
 
/** Attempt to establish connection **/ 
hConv = DdeConnect(dwDDEInst, hszService, hszTopic, NULL); 
 
/** Free the string handles **/ 
DdeFreeStringHandle(dwDDEInst, hszService); 
DdeFreeStringHandle(dwDDEInst, hszTopic);

Sending GroupWise Commands

Your solution can send GroupWise token in a DDE Execute parameter.

When GroupWise cannot process a command, it stores user-accessible error information. See Using DDE to Get Error Information.

DDE Execute String Format

DDE Execute strings are null-terminated ANSISTRINGs. Each string consists of supported tokens. You can space-delimit multiple tokens in a string. For more information, see the GroupWise 4.1 Macros Manual in the GroupWise 4.1 Developer Components download at http://developer.novell.com/ndk/unsupported.htm.

For example:

"ViewOpenDlg( )" 
"ViewOpen( Appointment!; Yes! )" 
"ViewOpen( Appointment!; Yes!) FocusSet( Message! ) EditPaste( )" 
"ViewOpen(Appointment!;Yes!)FocusSet(Message!)EditPaste()" 
"ViewOpen(Appointment!;Yes!) FocusSet(Message!) EditPaste( ) "

DDE ignores the remainder of a transaction containing an invalid command or syntax. For more information, see the GroupWise 4.1 Macros Manual in the GroupWise 4.1 Developer Components download at http://developer.novell.com/ndk/unsupported.htm.

Send GroupWise a token string as the DDE data block in a DDE Execute transaction. This is the Windows DDEClientTransaction(), lpvData parameter.

For example:

/** lpszCmd is a pointer to an ANSISTRING **/ 
/** containing the token(s) we wish to **/ 
/** send to GroupWise **/ 
DdeClientTransaction( 
   (LPBYTE)lpszCmd, 
   (DWORD)_fstrlen(lpszCmd)+1, 
   hConv, 
   NULL, 
   CF_TEXT, 
   XTYP_EXECUTE, 
   (DWORD)TIMEOUT_ASYNC, 
   NULL );

Using DDE to Get Error Information

Your solution can use DDE to request GroupWise error information. DDEGetLastError() returns information about the cause of the last error when DDEClientTransaction() returns FALSE.

A DMLERR_NOTPROCESSED return value indicates GroupWise has received a token but cannot process it.

Send an EnvLastError() DDE Request to GroupWise to return an error information string. Successful GroupWise tokens empty the error value so that EnvLastError() returns an empty string. The following table lists and describes error values:

Value

Description

10E1

Bad Parameter x, where x is the index of the bad parameter.

10E2

Failed.

10E3

Command is invalid given current state of GroupWise.

10E4

Not found.

10E5

Syntax error.

10E7

BC (Blind Copy) does not apply to personal items.

10E8

Not supported for encapsulated item attachments.

10E9

Not supported for OLE attachments.

10EA

The message ID references an Out Box message that does not exist yet.

10EB

You cannot set an alarm for a time that has already expired.

10EC

The source attributes of the In Box filter cannot be changed.

10ED

The source attributes of the Out Box filter cannot be changed.

XXXX

Other GroupWise error.

For example:

HSZ hCmd; 
 
/** Create a valid string handle for GroupWise Token **/ 
hCmd = DdeCreateStringHandle (dwDDEInst, "EnvLastError()", CP_WINANSI); 
 
/** Request the information **/ 
if ( 0 != (hReqData = DdeClientTransaction( NULL, (DWORD)0L, hConv, 
   hCmd, CF_TEXT, XTYP_REQUEST, (DWORD)2000, NULL ))) 
{ 
 
   /** Free our string handle **/ 
   DdeFreeStringHandle (dwDDEInst, hCmd); 
 
   /** Get a valid pointer to the data that we can access **/ 
   lpDataByte = DdeAccessData (hReqData, &cbDataLen); 
 
   /** Copy the data into the return buffer */ 
   lstrcpy (lpszBuf, (LPSTR)lpDataByte); 
 
   /** THE ERROR MESSAGE IS NOW STORED IN lpszBuf **/ 
 
   /** Free up the pointer we locked down **/ 
   DdeUnaccessData (hReqData); 
   return TRUE; 
}

Requesting GroupWise Information

To obtain GroupWise return value information, use:

  • EnvLastCmdResult token

  • DDE Request

  • DDE Advise Loop

GroupWise returns information in ANSISTRINGs. You can convert returned ANSISTRING data to its native data type. For example, GroupWise returns a numeric value 96 as the ANSISTRING 96. Native data types and corresponding GroupWise ANSISTRINGs include:

Native Data Type

ANSISTRING Returned by GroupWise

Numeric

Numeric characters

Boolean

"TRUE", "FALSE"

ANSISTRING

ANSISTRING

EnvLastCmdResult You can request GroupWise DDE Execute return information using DDE Request including EnvLastCmdResult(). For example:

HSZ hCmd; 
 
/** Create a valid string handle for our command string **/ 
hCmd = DdeCreateStringHandle (dwDDEInst, "EnvLastCmdResult()", 
CP_WINANSI); 
 
/** Request the information **/ 
if ( 0 != (hReqData = DdeClientTransaction( NULL, (DWORD)0L, hConv, 
hCmd, CF_TEXT, XTYP_REQUEST, (DWORD)2000, NULL))) 
{ 
   /** Free our string handle **/ 
   DdeFreeStringHandle (dwDDEInst, hCmd); 
 
   /** Get a valid pointer to the data that we can access **/ 
   lpDataByte = DdeAccessData (hReqData, &cbDataLen); 
 
   /** Copy the data into the return buffer */ 
   lstrcpy (lpszBuf, (LPSTR)lpDataByte); 
 
   /** THE COMMAND RESULT DATA IS NOW STORED IN lpszBuf **/ 
 
   /** Free up the pointer we locked down **/ 
   DdeUnaccessData (hReqData); 
   return TRUE; 
}

DDE Request DDE Request substitutes DDE Execute command strings for DDE Item names and obtains return values. DDE Request returns a DDE data handle, then lets your solution have it.

For example:

HSZ hCmd; 
/** Create a valid string handle for our command string **/ 
hCmd = DdeCreateStringHandle (dwDDEInst, "AddressBookGetField( 
ABField: 
   USERSNETID! )", CP_WINANSI); 
/** Request the information **/ 
if 
   ( 0 != (hReqData = DdeClientTransaction( NULL, (DWORD)0L, hConv,hCmd, 
   CF_TEXT, XTYP_REQUEST, (DWORD)2000, NULL))) 
{ 
   /** Free our string handle **/ 
   DdeFreeStringHandle (dwDDEInst, hCmd); 
 
   /** Get a valid pointer to the data that we can access **/ 
   lpDataByte = DdeAccessData (hReqData, &cbDataLen); 
 
   /** Copy the data into the return buffer */ 
   lstrcpy (lpszBuf, (LPSTR)lpDataByte); 
 
   /** THE COMMAND RESULT DATA IS NOW STORED IN lpszBuf **/ 
 
   /** Free up the pointer we locked down **/ 
   DdeUnaccessData (hReqData); 
 
   return TRUE; 
}

DDE Advise Loop You can establish a DDE Advise loop instead of using EnvLastCmdResult(). A DDE Advise loop returns DDE Execute values to DDE Advise Data, then GroupWise, notifies your solution. DDE notifies your solution only of DDE Advise Data values for commands that return values and commands sent to GroupWise not using DDE Request.

HSZ hItem; 
HCONV hConv; // These two variables are 
DWORD 
dwDDEInst; // assigned valid values 
// during DDE initialization 
 
/** Create a valid string handle for our command string **/ 
hItem =    
DdeCreateStringHandle (dwDDEInst, "CommandReturn", CP_WINANSI); 
 
/** Start the Advise Loop **/ 
DdeClientTransaction( (LPBYTE)NULL, 0, hConv, hItem, CF_TEXT, 
   XTYP_ADVSTART, (DWORD)TIMEOUT_ASYNC, &dwTransResult 
);

Simultaneous Commands

DDE does not let your solution send DDE Execute during another same-conversation command. Make sure your solution does not send simultaneous multiple DDE Execute or DDE Request commands. Otherwise, a DDE Reentrancy error results and your commands may not execute.