This API is a C module which implements a protocol for communicating with an executing pC++ program. This protocol allows a C program with this API to accomplish (minimally):
This documents describes the usage of these routines. It is broken up into several sections:
There are two sides of the Breezy partnership, 1) the application communicating with the executing parallel program, and 2) the parallel program providing the data. From hereon, I will refer to the former partner as the application or the application side; I will refer to the latter partner as the parallel program. Text enclosed in angle brackets "<>" is information for executing the parallel program. The typical stepwise scenario for using this API is something like this:
The specifics of how to accomplish this are detailed in the sections below.
int initServerConnection(int SocketNum)
The initial connection is trivial. The only necessary prerequisite is to find a socket number to use. <Parallel program note: The parallel program needs to use the same socket number for communication. In the environment that the parallel program is running, you can set the environment variable pcxx_BRKSOCKET to the correct socket number before execution.> The default socket is 6871 (random enough?). The API is implemented using the application side as the server. This means it does not have to have any knowledge of the host with which it is communicating. <The parallel program side then is the client. It must know the correct server to communicate with. It defaults to its own host (localhost). This can be changed by the environment variable pcxx_BRKHOST to the correct host name or IP address.> To initialize the connection then, call the routine initServerConnection. E.g., if you wanted to initialize a connection on socket number 6900:
int mysocket = 6900;
returnedSocketID = initServerConnection(mysocket);
This function returns the socket id that is used for the connection. This can safely be ignored because the brkAccess module keeps track of this for all subsequent communication.
int findIthCollection(char *collName)
There are two main areas that the application may need information about. The first of these is type information. If the application does not already know what type of data it is going to receive, it will need to look at some type description information. The function getTypes() retrieves the types and stores it in another module (typeApi.c). This module has many potential access functions for looking at the type information. There is a brute force way of looking at this data, using the function printTypes(). This functions return a char pointer to a string which contains all the types in a psuedo c-style format (described in the typeApi.c). Note that type information is static, so the application need call getTypes() once at most.
The second area of info is the current collection information, i.e. which collections are currently instantiated in the parallel program at the point it has currently stopped at. To make certain of this information, the application must request a new list of currently instantiated collections at each breakpoint. This is because between breakpoints, new collections could have been constructed, and old collections may have been destroyed. To get this list, use the updateCurrentCollections() function call. This will update an internal list of collections in the brkAccess module. To get at this list there are two functions, printCollections() and findIthCollection(char *name). The first returns a list of collection names. This list is in a specific order. When retrieving parallel data from a collection (discussed in the next section), either the name of a collection, or its number in this list can be used. The main reason for this duplication is because it is very possible that the same variable name can refer to more than one collection. Thus, if you want to retrieve collection by number but you only have the name, a simple search routine findIthCollection(char *collName) returns the first occurence of 'collName' in the collection list.
void callAccessFuncByName(char *collName, int elemNumi, int elemNumj, char *fname)
void callAccessFuncByNum(int collNum, int elemNumi, int elemNumj, char *fname)
void callAccessFuncOverAll(int collnum)
To retrieve parallel data using the automated tactics, there are three functions available. All of these functions require the application to specify the collection of interest. As mentioned in the section above, there are two ways of specifying a collection, either by name or by number. The number is an index into an internally maintained list of current collections (see above section). As there could be duplications of the same name in the collection list, it is necessary to also provide access by number to uniquely identify all collections.
The application may be interested in parallel data from a single element of a collection, or from all the elements of a collection. The first two functions retrieve data from a single element, while the last function retrieves data from all elements of a collection. (To retrieve data from a subset less than the whole of elements, the single element access functions must be called repeatedly for each element in the subset.) First, let me discuss retrieving a single element. The only difference between the first two function calls is the first parameter to the call. This parameter specifies the collection, so in callAccessFuncByName() this parameter is a char pointer, while in callAccessFuncByNum() this parameter is an integer. The next two parameters are the indices that specify which element to retrieve. There are two parameters because pC++ can have 1D or 2D element distributions. (This distribution information is available from the typeApi module after a call to getTypes().) If the collection only has one dimension the second parameter must be a negative number, indicating it is not a valid index. The final parameter indicates what to retrieve from that element. It takes a bit more explanation.
This final parameter is a char pointer to a string. There are several valid values for this string.
The second word in the string acts on the inner class just as the first word acted on the outer class. The second way of accessing data is to call the access functions from all the elements at once. There are only two parameters to this function, an integer specification for the collnum, and a character string that acts like the last parameter in the previous functions. In this way, the user can gather data from all elements at once. The disadvantage to this function is that it requires the user to explicitly receive data from each element. (See section 7 Lower level communication.) That is, the user needs to know how many elements are being access, and perform that number of receives. This is inconvenient. (In the near future, I want to change this to pack all the data into one structure for the application so it does not have to do all the receives.)
char * getUserDefFuncs()
char * getUserDefMethods()
void callUserMemFunc(int collnum, int elemnumi, int elemnumj, char *methodname)
void callUserFunc(char *funcname)
These functions deal with calling specially defined functions and methods within the user program. <To make a function or method accessible in the user program, the programmer simply prepends the string "UserDefined_" onto the name.> Note that only methods that are in collection element classes (or classes that they contain) may be accessed this way.
To get a list of user defined functions, use the getUserDefFuncs() routine. It returns a list of function names <without the "UserDefined_" string prepended> separated by spaces. To call one of these user defined functions, use the callUserFunc() routine, passing the name of the desired function. To get a list of the user defined methods, the routine getUserDefMethods() is provided. The information it returns is necessarily a bit more involved. It must specify the method name and the class it belongs to. It returns a list of items, each of which is in the format, "<classname>::<methodname>". Each item in the list is separated by a newline.
To call a user defined method, use the same parameter meanings as in the callAccessFuncByNum() routine (described in section 3), replacing the last parameter with the method name returned in the user defined methods list (not whole the "<classname>::<methodname>" format, just the methodname!).
void skipNBrkpnts(int numskip)
There are only three routines for controlling execution. The simplest routine, nextBrkpoint(), sends a message to the parallel program to continue to the next breakpoint, and stop again to serve any data request that the application may have. A variation on that is skipNBrkpnts(int numSkip), which skips a specified number of breakpoints before again pausing to answer any data requests. The last routine, terminateProgram(), does just that - terminates the execution of the parallel program.
There is currently only one function that returns other interesting information, getSourceInfo(). This function returns a formatted string containing an id for the function the parallel program is currently paused in, what filename the function is in, and what line number in the filename it has paused on.
int sendData(void *ms, int numbytes)
int sendText(char *ms)
aPackage * receiveData()
There are three slightly lower level routines that the preceding routines are built using. It may be the case that the these routines need to be accessible. In the case of receiving data from elements explicitly when the callAccessFuncOverAll() routine is used for instance. The first routine, sendData(void *ms, int numbytes), send 'numbytes' bytes pointed to by ms over to the parallel program. The next routine, sendText(char *ms), sends the text string 'ms' to the parallel program (must be terminated by NULL byte). The last function is for receiving data back, appropriately, receiveData(). This returns a structure type 'aPackage'. This is a simple structure with two fields, 'data' and 'length'. The 'length' field specifies the number of bytes in the package, and the 'data' field points to the data itself.