Perforce 2006.1 C/C++ API User's Guide
<< Previous Chapter
Overview
Table of Contents
Index
Perforce on the Web
Next Chapter >>
Public Methods Reference

Chapter 2
Client Programming

Compiling and linking client programs

The following sections tell you how to build your client program on the target platform.

To build samplemain.cc, include clientapi.h, which includes all the necessary header files for the sample client application.

Link order

The link libraries distributed with P4API must be linked explicitly in the following order.

  1. libclient.a

  2. librpc.a

  3. libsupp.a

In the Windows distribution, these files are named libclient.lib, librpc.lib, and libsupp.lib respectively.

Compiler support

UNIX

For all UNIX platforms, you can use the gcc compiler to compile client programs with the Perforce Client API. On Solaris, you can also use the Forte compiler.

Note that clientapi.h includes stdhdrs.h, which might attempt to set platform-specific defines. To ensure these defines are set properly, compile with the -DOS_XXX flag, where XXX is the platform name as specified by Perforce. (Use p4 -V to display the platform name; for example, for LINUX52X86, specify -DOS_LINUX.)

Some platforms require extra link libraries for sockets. For example, Solaris requires the following compiler flags:

Windows

Using Microsoft Visual Studio (VC++), compile your client application with the following flags:

For debugging, compile with the /MTd flag for multithreading. Do not compile with /MD or /MDd, because these flags can cause undefined behavior.

Link with the following libraries:

Macintosh

To create an MPW tool, link with the following libraries:

The compiler option Enums Always Int must be on.

VMS

Link with sys$library:libcxxstd.olb/lib.

Sample Jamfile

The following example shows a Jamfile that can be used to build samplemain.cc, a Perforce client program. (The example that the API is installed in the api subdirectory.)

C++FLAGS = -g -D_GNU_SOURCE ;
LINK = c++ ;
OPTIM = ;
Main samplemain : samplemain.cc ;
ObjectHdrs samplemain : api ;
LinkLibraries samplemain : api/libclient.a api/librpc.a api/libsupp.a ;

For more about jam, see "Building with Jam" on page 18.

Sample Makefile

The following is a gnumake file for building samplemain.cc, a Perforce client program. (The example that the API is installed in the api subdirectory.)

SOURCES = samplemain.cc
INCLUDES = -Iapi
OBJECTS = ${SOURCES:.cc=.o}
LIBRARIES = api/libclient.a api/librpc.a api/libsupp.a
BINARY = samplemain
RM = /bin/rm -f

C++ = c++
C++FLAGS = -c -g -D_GNU_SOURCE
LINK = c++
LINKFLAGS =

.cc.o :
   ${C++} ${C++FLAGS} $< ${INCLUDES}

${BINARY} : ${OBJECTS}
   ${LINK} -o ${BINARY} ${OBJECTS} ${LIBRARIES}

clean :
   - ${RM} ${OBJECTS} ${BINARY}

Building with Jam

Jam is a build tool, similar in its role to the more familiar make. Jamfiles are to jam as makefiles are to make.

Jam is an Open Source project sponsored by Perforce Software. Jam documentation, source code, and links to precompiled binaries are available from the Jam product information page at:

The P4API distribution contains the necessary header files (*.h) and libraries (libclient.a, librpc.a, libsupp.a) required to compile and link a client application. The distribution also includes a sample client program in C++, samplemain.cc.

In general, the process is similar to most APIs: compile your application sources, then link them with the API libraries. The precise steps needed vary somewhat from platform to platform.

The sample client program samplemain.cc is a portable, minimal client application, which we can use as an example. For purposes of this example, assume a Linux system.

Compile and link samplemain.cc as follows:

$ cc  -c -o samplemain.o -D_GNU_SOURCE -O2 -DOS_LINUX -DOS_LINUX24 \
> -DOS_LINUXX86 -DOS_LINUX24X86 -I. -Imsgs -Isupport -Isys samplemain.cc

$ gcc -o samplemain samplemain.o libclient.a librpc.a libsupp.a

The preprocessor definitions (-Ddefinition) vary from platform to platform.

In order to build the example across a wide variety of platforms, the API distribution also contains two "Jamfiles" (Jamrules and Jamfile), that describe to how to build the sample application on each platform.

Building the sample application

Once you have Jam on your system, you can use it to build the samplemain application. On some platforms, jam needs an extra hint about the operating system version. For instance, on RedHat Linux 7.1, with a 2.4 linux kernel, use OSVER=24:

$ jam
Set OSVER to 42/52 [RedHat M.n], or 22/24 [uname -r M.n]

$ uname -r
2.4.2-2

$ jam -s OSVER=24
...found 121 target(s)...
...updating 2 target(s)...
C++ samplemain.o
Link samplemain
Chmod1 samplemain
...updated 2 target(s)...

$ samplemain info
User name: you
Client name: you:home:sunflower
Client host: sunflower
Client root: /home/you
Current directory: /home/you/tmp/p4api
Client address: 207.46.230.220:35012
Server address: sunflower:1674
Server root: /home/p4/root
Server date: 2002/09/24 12:15:39 PDT
Server version: P4D/LINUX22X86/2002.1/32489 (2002/04/12)
Server license: Your Company 10 users (expires 2003/02/10)

As shown in the example above, jam does not, by default, show the actual commands used in the build (unless one of them fails). To see the exact commands jam generates, use the -o file option. This causes jam to write the updating actions to file, suitable for execution by a shell.

To illustrate; first, invoke jam clean to undo the build:

$ jam -s OSVER=42 clean
  ...found 1 target(s)...
  ...updating 1 target(s)...
  Clean clean
  ...updated 1 target(s)...

Then use jam -o build_sample to create the build file:

$ jam -s OSVER=42 -o build_sample
  ...found 121 target(s)...
  ...updating 2 target(s)...
  C++ samplemain.o
  Link samplemain
  Chmod1 samplemain
  ...updated 2 target(s)...

$  cat build_sample

cc -c -o samplemain.o -O2 -DOS_LINUX -DOS_LINUX42 -DOS_LINUXX86 \
-DOS_LINUX42X86 -I. -Imsgs -Isupport -Isys samplemain.cc
gcc -o samplemain samplemain.o libclient.a librpc.a libsupp.a
chmod 711 samplemain

The generated build_sample can then be executed by a shell:

to produce the executable, which you can test by running samplemain info or most other Perforce commands:

$ samplemain changes -m 1

Change 372 on 2002/09/23 by you@you:home:sunflower 'Building API'

As you can see, samplemain is a usable full-featured command line Perforce client (very similar to the p4 command). The example's functionality comes from the default implementation of the ClientUser class, linked from the libclient.a library and the rest of the library code, for which source code is not included. The source for the default implementation is provided in the P4API distribution as clientuser.cc.

Sending commands to the server

Client programs interact with the Perforce server by:

  1. Initializing a connection.

  2. Sending commands.

  3. Closing the connection.

The Perforce server does not maintain any kind of session identifier. The server identifies the sender of commands by its combination of Perforce user name and client specification name. Different processes that use the same combination of user and client name are not distinguished by the Perforce server. To prevent processes from interfering with each other when submitting changelists, be sure to use separate client specifications for each process. If you need to create large numbers of processes, consider creating a cache of client specifications and serving them to processes as required.

Perforce settings on the client machine

To determine which server and depot are accessed and how files are mapped, the standard classes in the API observe the Perforce settings on the client computer. Assuming the client computer is configured correctly, your client application does not need to provide logic that specifies server, port, client, or user.

To override client computer settings, your client program can call Set methods.

Client computer settings take precedence as follows, highest to lowest:

  1. Values set within a Perforce application

  2. Values in configuration files (P4CONFIG)

  3. Values set as environment variables at the operating system prompt

  4. Variables residing in the registry (set using the p4 set or p4 set -s commands on Windows client machines)

  5. Default values defined by Perforce software or gathered from the system

Connecting to the server

To connect to the Perforce server for which the client computer is configured, your client application must call the client.Init() method; for example:

client.Init( &e );
if ( e.Test() )
   {
      printf("Failed to connect:\n" );
      ErrorLog::Abort(); // Displays the error and exits
   }
printf( "Connected OK\n" );

Your program only needs to connect once. After connecting, the application can issue as many Perforce commands as required. If you intend to use tagged output, your program must call client.SetProtocol() before calling client.Init(). For details about using tagged output, refer to "Tagged data" on page 24.

Displaying Perforce forms

Perforce client commands that collect a large amount of input from the user (such as p4 branch, p4 change, p4 label) use ASCII forms. To interact with your end user, your client application program can display Perforce ASCII forms such as changelists, client specification, and so on. To display a form and collect user input, call ClientUser::Edit(), which puts the form into a temporary file and invokes the text editor that is configured for the client machine.

All form-related commands accept the batch mode flags -o and -i:

These flags allow changes to the form to occur between separate invocations of the p4 client program, rather than during a single invocation. (For details about the -o and -i global options, see the Command Reference.)

All form-related commands can return a form descriptor. Your client program can use this descriptor to parse forms into constituent variables and to format them from their constituent variables. The specstring protocol variable enables this support in the server. Form descriptors are best used with the tag protocol variable, which causes the form data to appear using ClientUser::OutputStat() rather than OutputInfo().

Select the protocol with ClientApi::SetProtocol() as follows:

client.SetProtocol( "specstring", "" );
client.SetProtocol( "tag", "" );

To obtain the descriptor containing the results of the method call, your client program must pass a StrDict object to ClientUser::OutputStat(). Your client program can override the OutputStat() method in a class derived from ClientUser. The Perforce Client API calls this derived method, passing it the output from the command.

Sending commands

The following example illustrates how you set up arguments and execute the p4 fstat command on a file named Jam.html.

char file[] = "Jam.html" ;
char *filep = &file[0];
client.SetArgv( 1, &filep );
client.Run( "fstat", &ui );

For commands with more arguments, use an approach like the following:

char    *argv[] = { "-C", "-l", 0, 0 };
int      argc = 2;
char    *file = "Jam.html";
argv[ argc++ ] = file;
client.SetArgv( argc, argv );
client.Run( "fstat", &ui );

Processing data from the server

The Perforce server (release 99.2 and higher) can return tagged data (name-value pairs) for some commands. The following sections tell you how to handle tagged and untagged data.

Tagged data

The following example shows data returned in tagged format by p4 -Ztag clients command. (The -Z flag specifies that tagged data is to be returned; this flag is unsupported and intended for debugging use.)

...client xyzzy
...Update 972354556
...Access 970066832
...Owner gerry
...Host xyzzy
...Description Created by gerry

To enable the Perforce server to return tagged data, your client program must call SetProtocol("tag", "") before connecting to the server. To extract values from tagged data, use the GetVars method.

The following Perforce commands can return tagged output. A release number, when present, indicates the first Perforce server release that supports tagged output for the command.

p4 add (2005.2)

p4 fixes (2000.1)

p4 protect -o

p4 branch -o

p4 group -o

p4 reviews (2005.2)

p4 branches

p4 groups (2004.2)

p4 reopen (2005.2)

p4 change -o (2005.2)

p4 have (2005.2)

p4 resolve (2005.2)

p4 changes

p4 info (2003.2)

p4 revert (2005.2)

p4 client -o

p4 integrate (2005.2)

p4 review (2005.2)

p4 clients

p4 job -o

p4 submit (2005.2)

p4 counter (2005.2)

p4 jobs

p4 sync (2005.2)

p4 counters (2000.2)

p4 jobspec -o

p4 trigger -o

p4 delete (2005.2)

p4 label -o

p4 typemap -o (2000.1)

p4 describe

p4 labels

p4 unlock (2005.2)

p4 depots (2005.2)

p4 labelsync (2005.2)

p4 user -o

p4 diff (2005.2)

p4 lock (2005.2)

p4 users

p4 diff2 (2004.2)

p4 logger (2000.2)

p4 verify (2005.2)

p4 edit (2005.2)

p4 monitor (2005.2)

p4 where (2004.2)

p4 filelog

p4 obliterate (2005.2)

p4 fix (2005.2)

p4 opened

The tagged output of some commands may have changed since the command's first appearance in this table. The output of p4 resolve and p4 diff are not fully tagged. For complete details, see the release notes:

To obtain output in the form used by earlier revisions of Perforce, set the api variable according to the notes for SetProtocol().

Untagged Data

To handle untagged data, create a subclass of ClientUser for every type of data required and provide alternate implementations of ClientUser::OutputInfo(), OutputBinary(), OutputText(), and OutputStat().

Disconnecting from the server

After your client program is finished interacting with the Perforce server, it must disconnect as illustrated below:

client.Final( &e );
e.Abort();

To ensure the client program can exit successfully, make sure your client program calls ClientApi::Final() before calling the destructor.

Performing file I/O

The default client file I/O implementation returns a FileSys object, which is described in filesys.h. To intercept client workspace file I/O, replace the FileSys *ClientUser::File() method by subclassing ClientUser.

The following example illustrates how you can override FileSys.

#include "clientapi.h"
class MyFileSys : public FileSys {
    public:

   MyFileSys();
   ~MyFileSys();

   virtual void    Open( FileOpenMode mode, Error *e );
   virtual void    Write( const char *buf, int len, Error *e );
   virtual int     Read( char *buf, int len, Error *e );
   virtual int     ReadLine( StrBuf *buf, Error *e );
   virtual void    Close( Error *e );
   virtual int     Stat();
   virtual int     StatModTime();
   virtual void    Truncate( Error *e );
   virtual void    Unlink( Error *e = 0 );
   virtual void    Rename( FileSys *target, Error *e );
   virtual void    Chmod( FilePerm perms, Error *e );

   protected:
   int nchars;
} ;

MyFileSys::MyFileSys()
{
   nchars = 0;
}

MyFileSys::~MyFileSys()
{
   printf( "Number of characters transferred = %d\n", nchars );
}

void MyFileSys::Open( FileOpenMode mode, Error *e )
{
   printf( "In MyFileSys::Open()\n" );
}

void MyFileSys::Write( const char *buf, int len, Error *e )
{
   printf( "In MyFileSys::Write()\n" );
   printf( "%s", buf );
   nchars = nchars + len;
}


int MyFileSys::Read( char *buf, int len, Error *e )
{
   printf( "In MyFileSys::Read()\n" );
   return 0;
}

int MyFileSys::ReadLine( StrBuf *buf, Error *e )
{
   printf( "In MyFileSys::ReadLine()\n" );
   return 0;
}

void MyFileSys::Close( Error *e )
{
   printf( "In MyFileSys::Close()\n" );
}

int MyFileSys::Stat()
{
   printf( "In MyFileSys::Stat()\n" );
   return 0;
}

int MyFileSys::StatModTime()
{
   printf( "In MyFileSys::StatModTime()\n" );
   return 0;
}

void MyFileSys::Truncate( Error *e )
{
   printf( "In MyFileSys::Truncate()\n" );
}

void MyFileSys::Unlink( Error *e = 0 )
{
   printf( "In MyFileSys::Unlink()\n" );
}


void MyFileSys::Rename( FileSys *target, Error *e )
{
   printf( "In MyFileSys::Rename()\n" );
}

void MyFileSys::Chmod( FilePerm perms, Error *e )
{
   printf( "In MyFileSys::Chmod()\n" );
}

class ClientUserSubclass : public ClientUser {

    public:
   virtual FileSys *File( FileSysType type );
} ;

FileSys *ClientUserSubclass::File( FileSysType type )
{
   return new MyFileSys;
}

int main( int argc, char **argv )
{
   ClientUserSubclass ui;
   ClientApi client;
   Error e;

   char force[] = "-f";
   char file[] = "hello.c";
   char *args[2] = { &force[0], &file[0] };

   // Connect to server

   client.Init( &e );
   e.Abort();

   // Run the command "sync -f hello.c"

   client.SetArgv( 2, &args[0] );
   client.Run( "sync", &ui );

   // Close connection

   client.Final( &e );
   e.Abort();
   return 0;
}

The preceding program produces the following output when you run it.

% ls -l hello.c
-r--r--r--    1 member   team           41 Jul 30 16:57 hello.c
% cat hello.c
main()
{
  printf( "Hello World!\n" );
}
% samplefilesys
//depot/main/hello.c#1 - refreshing /work/main/hello.c
In MyFileSys::Stat()
In MyFileSys::Open()
In MyFileSys::Write()
main()
{
   printf( "Hello World!\n" );
}
In MyFileSys::Close()
Number of characters transferred = 41

Handling errors

To encapsulate error handling in a maintainable way, subclass ClientUser at least once for every command you want to run and handle errors in the HandleError() method of the derived class.

To best handle the formatting of error text, parse the error text, looking for substrings of anticipated errors, and display the rest. For example:

void P4CmdFstat::HandleError(Error *e)
{
   StrBuf  m;
   e->Fmt( &m );
   if ( strstr( m.Text(), "file(s) not in client view." ) )
      e->Clear();
   else if ( strstr( m.Text(), "no such file(s)" ) )
      e->Clear();
   else if ( strstr( m.Text(), "access denied" ) )
      e->Clear();
   else
      this->e = *e;
}

Connection errors

If any error occurs when attempting to connect with the Perforce server, the ClientApi::Init() method returns an error code in its Error parameter.

Server errors

The ClientApi::Final() method returns any I/O errors that occurred during ClientApi::Run() in its Error parameter. ClientApi::Final() returns a non-zero value if any I/O errors occurred or if ClientUser::OutputError() was called (reporting server errors) during the command run.

To report errors generated by the server during an operation, your application can call the ClientUser::HandleError() method. The default implementation of HandleError() is to format the error message and call ClientUser::OutputError(), which, by default, writes the message to standard output. HandleError() has access to the raw Error object, which can be examined with the methods defined in error.h. Prior to release 99.1, Perforce servers invoked OutputError() directly with formatted error text.

Class overviews

The following classes comprise the Perforce API. Public methods for these classes are documented in "Public Methods Reference" on page 35.

ClientApi - Perforce server connections and commands

The ClientApi class represents a connection with the Perforce server.

Member functions in this class are used to establish and terminate the connection with the server, establish the settings and protocols to use while running commands, and run Perforce commands over the connection.

I/O is handled by a ClientUser object, and errors are captured in an Error object. A ClientApi object maintains information about client-side settings (P4PORT, etc.) and protocol information, such as the server version, and whether "tagged" output is enabled.

ClientApi does not include any virtual functions, and typically does not need to be subclassed.

Any Perforce command that is executed must be invoked through ClientApi::Run() after first opening a connection using ClientApi::Init(). A single connection can be used to invoke multiple commands by calling Run() multiple times after a single Init(); this approach provides faster performance than using multiple connections.

ClientUser - I/O for Perforce commands

The ClientUser class is used for all client-side input and output. This class implements methods that return output from the server to the user after a command is invoked, and gather input from the user when needed.

Member functions in this class are used to format and display server output, invoke external programs (such as text editors, diff tools, and merge tools), gather input for processing by the server, and to handle errors.

Customized functionality in a Perforce client application is most typically implemented by subclassing ClientUser. In order to enable such customization, nearly all of ClientUser's methods are virtual. The default implementations are used in the p4 command-line client.

Error - collect and report layered errors

Member functions in this class are used to store error messages, along with information about generic type and severity, format error messages into a form suitable for display to an end user, or marshal them into a form suitable for transferring over a network.

Error objects are used to collect information about errors that occur while running a Perforce command.

When a connection is opened with ClientApi::Init(), a reference to an Error object is passed as an argument to Init(). This Error object then accumulates any errors that occur; a single Error object can hold information about multiple errors. The Error can then be checked, and its contents reported if necessary.

Although Error itself does not provide any virtual methods that can be re-implemented, the manner in which errors are handled can be changed by re-implementing ClientUser::HandleError(). The default behavior for handling errors typically consists of simply formatting and displaying the messages, but Error objects maintain additional information, such as severity levels, which can be used to handle errors more intelligently.

ErrorLog - output error messages

The ErrorLog class is used to report layered errors, either by displaying error messages to stderr, or by redirecting them to logfiles. On UNIX systems, error messages can also be directed to the syslog daemon.

FileSys - Perforce file I/O

The FileSys class provides a platform-independent set of methods used to create, read and write files to disk.

You can intercept the file I/O and implement your own client workspace file access routines by replacing FileSys *ClientUser::File() in a ClientUser subclass, .

Note

Replacing the existing I/O routines is non-trivial. Your replacement routines must handle all special cases, including cross-platform file issues.

Unless your application has highly specialized requirements, (for instance, performing all file I/O in memory rather than on disk), this approach is not recommended.

If you intend to replace File(), all of the virtual methods documented are required. The non virtual methods are not required and not documented.

KeepAlive - support for client-side disconnection

The KeepAlive class has only one method, KeepAlive::IsAlive(). The method is used by client programs to support client-side command termination.

Options - parse and store command line options

The Options class encapsulates functions useful for parsing command line flags, and also provides a means of storing flag values.

Sample code is provided to illustrate how Options::GetValue() and Options::Parse() work together to parse command line options.

Signaler - interrupt handling

The Signaler class enables the API programmer to register functions that are to be called when the client application receives an interrupt signal. The Signaler class maintains a list of registered functions and calls each one in turn.

By default, after all of the registered functions have been executed, the process exits, returning -1 to the operating system.

StrBuf - string manipulation

The StrBuf class is the preferred general string manipulation class. This class manages the memory associated with a string, including allocating new memory or freeing old memory as required.

The StrBuf class is derived from the StrPtr class, and makes heavy use of the buffer and length members inherited from the StrPtr class. The buffer member of a StrBuf instance is a pointer to the first byte in the string. The length member of a StrBuf instance is the length of the string.

Most member functions maintain the string pointed to by the buffer member of a StrBuf as a null-terminated string. However, the Clear member function does not set the first byte of the string to a null byte, nor does the Extend member function append a null byte to an extended string. If you need to maintain a string as null-terminated when using the Clear() and Extend() member functions, follow the calls to Clear() and Extend() with calls to Terminate().

A number of member functions move the string pointed to by a StrBuf's buffer, and change the buffer member to point to the new location. For this reason, do not cache the pointer. Use StrPtr::Text() whenever the pointer a StrBuf's buffer is required.

StrDict - field/value manipulation

The StrDict class provides a dictionary object of StrPtrs with a simple Get/Put interface. This class contains abstract methods and therefore cannot be instantiated, but its subclasses adhere to the basic interface documented here.

ClientApi is a descendant of StrDict; most notably, the StrDict::SetArgv() method is used to set the arguments to a Perforce command before executing it with ClientApi::Run().

The ClientUser::OutputStat() method takes a StrDict as an argument; the StrDict methods are therefore necessary to process data with OutputStat(). Note that pulling information from a StrDict is typically easier than trying to parse the text given to OutputInfo().

StrNum - small numeric strings

The StrNum class, derived from StrPtr, is designed to hold a small string representing a number. Like a StrBuf, it handles its own memory. Unlike a StrBuf, it does not dynamically resize itself, and is limited to 24 characters, meaning that the largest number that can be represented by a StrNum is 999999999999999999999999.

StrOps - string operations

StrOps is a memberless class containing static methods for performing operations on strings.

StrPtr - text operations

The StrPtr class is a very basic pointer/length pair used to represent text.

This class provides a number of methods for comparison and reporting, but it is not in itself very useful for storing data; the StrBuf child class is a more practical means of storing data, as it manages its own memory.

StrRef - refer to existing strings

The StrRef class is a simple pointer/length pair representing a string. The StrRef class is is derived from StrPtr and does not add a great deal of new functionality to that class, with the exception of methods that make the pointer mutable (and therefore usable), whereas a base StrPtr is read-only.

As its name suggests, a StrRef serves as a reference to existing data, as the class does not perform its own memory allocation. The StrBuf class is most useful when storing and manipulating existing strings.


Perforce 2006.1 C/C++ API User's Guide
<< Previous Chapter
Overview
Table of Contents
Index
Perforce on the Web
Next Chapter >>
Public Methods Reference
Please send comments and questions about this manual to [email protected].
Copyright 2002-2006 Perforce Software. All rights reserved.
Last updated: 06/23/06