This section provides an overview of the internal structure of the C API. It is intended to aid developers and maintainers when modifications to the DLL code are required.
The following diagram shows the communication that occurs between the logical components of the C API. The calling application sends and receives data from the API layer in structures listed in Appendix A. When processing a request, the API layer communicates with the application server to request an available agent. This agent connects back to the API's NetClient and requests that the operation be performed. After the operation and its required data are specified, the agent processes the information on the application server and returns the results.
C++ objects comprise the internal functions of the C API. The relationship of the internal objects is shown by clicking here.
The APIManager is the main component of the C API and does the following:
- Maintains the active APINetClient
- Maintains queries to the application server
- Creates instances of AppServerAction derived classes
- Delegates processing of API requests to the appropriate action object
- Contains a memory manager and a SCIM cache.
The memory manager tracks C API memory allocation to prevent memory leaks when an application using the API is terminated. The SCIM cache reduces network communication when a SCIM list is requested.
An APIManager instance is created when an application calls the Connect function. It is also the responsibility of the application to free the memory used by the APIManager by calling Disconnect when no more calls to the DLL are required.
The APINetClient class is similar to ASEâ™s NetClient. The APINetClient has a new function, SyncUserMsg (or Synchronous User Message), which performs synchronous calls. As shown in the layer interaction diagram, the APINetClient must contact an agent before a user message is processed. The following functions were added to enable this agent communication:
CreateLocalSocket( SOCKET& callbackSocket );SyncUserMessage( MSGUSERCALL &msgUserCall, ERRORCODE &ec );ReadUserMessage( SOCKET sock, MSGUSERCALL& message );CreateConnection( SOCKET remoteSocket );CopyMessageParms( MSGUSERCALL &destinationMessage, MSGUSERCALL &sourceMessage );CopyDataType( sL_VALUE &destParm, sL_VALUE &sourceParm );
SyncUserMsg creates a callback socket on the API client machine by using CreateLocalSocket. The socket port number is sent to the application server using the base class NetClient's UserMsg. This port number passes to an available agent who uses it to connect back to the APINetClient over the callback socket.
After a connection is established between the agent and APINetClient in CreateConnection, the user request can be sent over the network. Using the CopyMessageParms and CopyDataType functions, fields from the user request are copied to a MSGUSERCALL structure provided by the agent. The agent then processes the request.
Calls made to the application server by APIManager are handled by a common interface defined in the AppServerAction class. The AppServerAction class is a virtual class that requires implementations for the following virtual methods in any derived class.
Method Construction Description SendMessage ( const void* pMessageParms )Prepares and transmits a message to the application server. pMessageParms holds a structure defining any parameters to be sent. Prepare Message ( const void* pMessageParms )Creates a MSGUSERCALL structure to send to the Net Client from the parameters passed in pMessageParms. GetAction MessageID None Returns the message ID (number) corresponding to the action to be done by the application server. GetResponse MessageID None Returns the message ID (number) that the application server must respond with for the action to be completed.
Like all virtual classes, you cannot instantiate objects of type AppServerAction, but instead, must derive a new class and override the virtual methods.
Every APIManager method creates an instance of one of the AppServerAction derived classes when it needs to communicate with the application server. To add a new call to the API you only need to create a new AppServerAction derived class and provide an implementation for the required virtual methods.
The transmission of messages to the application server is accomplished when SendMessage is called on the AppServerAction derived object. This method uses the parameters passed to the method call to construct a MSGUSERCALL structure used by the APINetClient. The void pointer parameter pMessageParms, enables any data type to pass to the method call. Because the virtual AppServerAction class has no implementation for this method call, the derived object must be able to use this parameter to assemble a MSGUSERCALL structure. Usually, the data passed is a structure. An excellent example of structure passing is in the DiagnosticQuery class. This class requires a REQUEST_CONTEXT structure to be populated and passed to the SendMessage method.
When the C API requires only one or two parameters instead of an entire structure to populate a MSGUSERCALL on the application server, additional data members are added to the AppServerAction derived class. These internal members may then be set by additional methods added to the derived class. For example, the HistoryQuery class requires only a long (request_id) to complete its MSGUSERCALL. This required parameter is set when the APIManager makes a call to SetRequestID (an additional method that was added to the HistoryQuery object). This simplifies the call to SendMessage by enabling the pMessageParms parameter to be NULL.
Note: If additional methods have been added to set required data members in a derived class, it is important to call them before calling SendMessage.
The following is a table of some of the Application ServerAction derived classes and their use of the pMessageParm member:
Derived Class Use of pMessageParm DiagnosticQuery Passes a REQUEST_CONTEXT structure. HistoryQuery Passes NULL.MSGUSERCALL members are set by class-specific mutator methods. SCIMQuery Passes a SCIM_KEY structure. ProblemQuery Passes a PROBLEM_FILTER structure. ProblemSubmission Passes a PROBLEM_CLOSURE structure.
This section shows the internal API code used to process a function call. A diagram is also provided that shows the sequence of calls made by each object in the code. The sample code builds on the previous example of Application.cpp.
The following code is taken from files that call the InquireRequests API function.
Note: A diagram showing the process sequence can be found by clicking here.
Building on the code previously presented, this example shows the internal code of the API as it processes the InquireRequests function call.
{ long nAPIHandle; long nErrorCode;Connect( &nAPIHandle, "indy_pp_es1", 8000, "EXAV", NULL, &nErrorCode);PROBLEM_RECORD* pProblemList = NULL; PROBLEM_FILTER filter;InitProblemFilter( nAPIHandle, &filter );filter.system = new char[16]; strcpy( filter.system, "PC APPLICATIONS" ); filter.component = new char[13]; strcpy( filter.component, "WEB BROWSERS" ); filter.item = new char[9]; strcpy( filter.item, "NETSCAPE" );long max_requested = -1; // no restriction on number to retrieve int nResult = InquireRequests( nAPIHandle, &filter, &pProblemList, &max_requested, & nErrorCode );FreeRequestList( nAPIHandle, &pProblemList );Disconnect( nAPIHandle ); }
The code in tsdcapi.cpp passes a call to the APIManager object. The pointer to the APIManager object is obtained from the nAPIHandle variable as a parameter.
int _Export InquireRequests( long nAPIHandle, const PROBLEM_FILTER* pProblemFilter, PROBLEM_RECORD** ppProblem-List, long* pnMaxRequested, long* pnError, const EXTENDED_DATA* pUserDefinedData /*=NULL*/ ){ APIManager *pAPIManager; pAPIManager = (APIManager*)nAPIHandle;return ( pAPIManager->InquireRequests( *pProblemFilter, *ppProblem-List, FALSE, *pnMaxRequested, *pnError, pUserDefinedData ) ); }
The code in the APIManager shows the creation of one of the AppServerAction derived objects, ProblemQuery, and the call to SendMessage. Note the methods that must be called before invoking SendMessage:
- SetAppServerHandle
- SetRetrievalMaximum
These functions are required by many AppServerAction derived objects. They identify the application server that processes the request and sets the number of requests to retrieve.
Note: Other AppServerAction derived objects may require additional setup functions in addition to these.
SetDetailedRecords is a function of the ProblemQuery class. It sets a switch to tell the application server whether it should display details for each returned problem, or summary information. Also of note is the parameter passed to the new operator of ProblemQuery. Because all application server messages are actually sent by the APINetClient, it is necessary to specify an APINetClient that is used by the AppServerAction object.
At the top of the function section there is a comparison on the checksum of the PROBLEM_FILTER structure. This ensures that the structure is initialized by the API user before the API function is called. Otherwise, the structure could contain uninitialized pointers that produce errors.
The final function in the APIManager is AddWatchedBlock. This function requests the memory manager in the APIManager class to track allocated memory so it can be deleted when the APIManager object is destroyed.
int APIManager::InquireRequests( const PROBLEM_FILTER& problem_filter, PROBLEM_RECORD* &pProblemList, BOOL bDetailedRecords, long&max_requested, long& pnError, const EXTENDED_DATA* pUserDefinedData /*=NULL*/ ){ NETHNDL hnet; int requestStatus;if ( strcmp( problem_filter.checksum, GetChecksum() ) != 0 ) { pnError = SAI_CHECKSUM_FAILURE; return SAI_ERROR; } requestStatus = OpenNetConnection( hnet, pnError ); if ( requestStatus != SAI_OK ) { return requestStatus; // pnError should be set by OpenNetConnection }ProblemQuery* pProblemQuery = new ProblemQuery( &m_NetClient ); pProblemQuery->SetApplication ServerHandle( hnet ); pProblemQuery->SetDetailedRecords( bDetailedRecords ); pProblemQuery->SetRetrievalMaximum( max_requested );requestStatus = pProblemQuery->SendMessage( &problem_filter, pUser-DefinedData);if ( requestStatus == SAI_OK ) { pProblemQuery->GetProblemList( pProblemList ); max_requested = pProblemQuery->GetRetrievalCount();if ( pProblemList != NULL ) { // Add the memory to the memory manager for tracking m_MemoryManager.AddWatchedBlock( (long)pProblemList, WatchedMemory:: LIST_OF_PROBLEMS ); } } else { pnError = pProblemQuery->GetLastError(); }delete pProblemQuery;CloseNetConnection( hnet );return requestStatus; }
Because it is derived from the AppSvrAction class, the ProblemQuery class contains the following two MSGUSERCALL class members:
- m_AppServerMessage
- m_AppServerResponse
PrepareMessage sets up the m_AppServerMessage so it can be sent to the NetClient.
Note: The APINetClient always sends responses in a MSGUSERCALL structure so m_AppServerResponse is there to capture it.
The fields of this response can be used to determine the message APINetClient returns. This response is compared with the desired response obtained from GetResponseMessageID to ensure that APINetClient performs the correct action. SendMessage then determines the number of complete records returned through a call to CalculateRetrievalCount. After m_AppServerMessage is sent to APINetClient, it is no longer required. It is important to call FreeMessage to ensure that the string lists in the message are cleared.
int ProblemQuery::SendMessage( const void* pMessageParms ) { if ( m_nHNet == -1 ) { m_nLastError = SAI_INVALID_PARAMETER; return SAI_ERROR; // Unable to process message without a diagnostic type }
PrepareMessage( pMessageParms ); // Prepare the message to be sentSAERRORCODE ec; m_AppServerResponse = m_pNetClient->SyncUserMsg(m_AppServerMessage, ec );FreeMessage(); // Free the used messageif ( ec == SASuccess ) { if ( m_AppServerResponse.pPseudo == NULL ) { m_nRetrievalCount = 0; return SAI_OK; // nothing to process } else if (m_AppServerResponse.pPseudo->message == GetResponseMessageID() ) { m_nLastError = CalculateRetrievalCount(); if ( m_nLastError != SAI_OK ) { return SAI_ERROR; } else { return SAI_OK; } } else // We did not get the appropriate response from the KML app server { m_nLastError = SAI_WRONG_RESPONSE; return SAI_ERROR; } } else // Return the last error code { m_nLastError = ec; return SAI_CLIENT_ERROR; } }
Memory âœownedâ by an application cannot be deleted by another application. The same is true for DLLs which are also applications. Because the C DLL allocates memory used by the PROBLEM_RECORD list that is returned to the calling application, the DLL must provide a way for the application to release the used list. This is done by calling FreeRequestList:
FreeRequestList( nAPIHandle, &pProblemList );FreeRequestList requires the APIHandle, and the address of the list as parameters.
In addition to FreeRequestList, there are several C API functions that clear memory. Two are FreeSolutionList and FreeHistoryList. These functions free lists of SOLUTION_RECORD and HISTORY_RECORD structures respectively.
Note: As a precaution, the API releases allocated memory when Disconnect is called, making it unnecessary to call these functions.
Free functions are provided as a convenience if you need to conserve memory while executing an application using the API.