本节提供 C API 内部结构的概述。它在需要对 DLL 代码进行修改时,为开发人员和维护人员提供帮助。
下面的图表显示发生在 C API 逻辑组件之间的通信。调用的应用程序在附录 A 列出的结构中的 API 层发送和接受数据。在处理请求时,API 层与应用程序服务器进行通信,请求可用的代理。该代理向后与 API NetClient 连接,并请求执行操作。在指定操作及其所需数据后,代理在应用程序服务器上处理信息并 返回结果。
C++ 对象由 C API 的内部函数构成。单击此处可显示内部对象之间的关系。
APIManager 是 C API 的主要部件,并起下列作用:
- 维护活动的 APINetClient
- 维护对应用程序服务器的查询
- 创建 AppServerAction 派生类的实例
- 代理对相应操作对象的 API 请求的处理
- 包含内存管理器和 SCIM 缓存。
内存管理器跟踪 C API 内存分配,以防止在使用 API 的应用程序终止时发生内存漏洞。在请求 SCIM 列表时,SCIM 缓存减少网络通信。
当应用程序调用内存管理器函数时,创建一个 APIManager 实例。在不需要调用 DLL 时,通过调用 Disconnect 来释放 APIManager 使用的内存也是应用程序的责任。
APINetClient 类与 ASE NetClient 相似。APINetClient 具有一个新的函数: SyncUserMsg(或 Synchronous User Message),它执行同步调用。像层交互表中显示的一样,APINetClient 必须在处理用户消息前与代理联系。为了启用与该代理的通信,添加了下列函数:
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 使用 CreateLocalSocket 在 API 客户机上创建回调套接字。使用基础类 NetClient 的 UserMsg 来将套接字端口号码发送给应用程序服务器。该端口号码传送给可用的代理,代理使用它在回调套接字上与 APINetClient 连接。
在 CreateConnection 中建立了代理和 APINetClient 之间的连接后,可以在网络上发送用户请求。使用 CopyMessageParms 和 CopyDataType 函数,可将来自用户请求的字段复制到代理提供的 MSGUSERCALL 结构中。然后代理处理请求。
在 AppServerAction 类中定义公共接口处理的 APIManager 对应用程序服务器的呼叫。AppServerAction 类是一种虚拟类,需要任何派生类中的后接虚拟方法的实现。
方法 句法 说明 SendMessage ( const void* pMessageParms )准备并发送消息给应用程序服务器。 pMessageParms 保留一种定义任何要发送的参数的结构。 Prepare Message ( const void* pMessageParms )根据在 pMessageParms 中传入的参数创建要发送给“网络客户机”的 MSGUSERCALL 结构。 GetAction MessageID 无 返回与应用程序服务器要执行的操作相对应的消息标识(号码)。 GetResponse MessageID 无 返回应用程序服务器必须用来响应要完成的操作的消息标识(号码)。
与所有虚拟类一样,不能建立 AppServerAction 类型的对象实例,反之,必须新的类并覆盖虚拟方法。
在需要与应用程序服务器通信时,每种 APIManager 方法创建 AppServerAction 派生的类之一的实例。要向 API 添加新的呼叫,只需创建新的 AppServerAction 派生类并为必需的虚拟方法提供实现。
在 AppServerAction 派生对象上调用 SendMessage 时,完成向服务器的消息传送。该方法使用传送给方法调用的参数构造 APINetClient 使用的 MSGUSERCALL 结构。空闲指针参数 pMessageParms 允许将任何类型的数据传送给方法调用。由于虚拟 AppServerAction 类不具有用于该方法调用的实现,所以派生对象必须能够使用该参数来组装 MSGUSERCALL 结构。通常传送的数据是一种结构。结构传送的一个很好的实例是在 DiagnosticQuery 类中。这个类要求将 REQUEST_CONTEXT 结构移植并传送给 SendMessage 方法。
在 C API 只需要一个或两个参数(而不是一个完整的结构)就可以将 MSGUSERCALL 移植到应用程序服务器时,将向 AppServerAction 派生类添加附加数据成员。然后,添加到派生类的附加方法可能设置这些内部成员。例如,HistoryQuery 类只需要一个长整型数据(请求标识)即可完成其 MSGUSERCALL。在 APIManager 调用 SetRequestID(添加到 HistoryQuery 对象的附加方法)时,设置该必需的参数。通过允许 pMessageParms 参数为 NULL,可以简化对 SendMessage 的调用。
注:如果添加了附加的方法来在派生的类中设置必需的数据成员,则在调用 SendMessage 之前调用它们是很重要的。
以下是某些 Application ServerAction 派生类的列表及其对 pMessageParm 成员的使用:
派生类 对 pMessageParm 的使用 DiagnosticQuery 传送 REQUEST_CONTEXT 结构。 HistoryQuery 传送类专用的变体方法设置的 NULL.MSGUSERCALL 成员。 SCIMQuery 传送 SCIM_KEY 结构。 ProblemQuery 传送 PROBLEM_FILTER 结构。 ProblemSubmission 传送 PROBLEM_CLOSURE 结构。
本节介绍用于处理函数调用的内部 API 代码。还提供了一个图表,介绍各个对象在代码中生成的一些列呼叫。实例代码在前一个 Application.cpp 的实例上构建。
下列代码是从调用 InquireRequests API 函数的文件中取出的。
注:单击此处可查找到显示进程序列的图表。
该实例在以前提供的代码上建立,显示 API 在处理 InquireRequests 函数调用时的内部代码。
{ 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 ); }
tsdcapi.cpp 中的代码将调用传送给 APIManager 对象。从 nAPIHandle 变量获得作为参数的指向 APIManager 对象的指针。
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 ) ); }
APIManager 中的代码显示 AppServerAction 派生对象之一、ProblemQuery 和对 SendMessage 的调用的创建。请注意调用 SendMessage 之前必须调用的方法:
- SetAppServerHandle
- SetRetrievalMaximum
许多 AppServerAction 派生对象都需要这些函数。它们标识处理请求和设置要检索的请求的数目的应用程序服务器。
注:其他 AppServerAction 派生对象可能需要除这些函数之外的附加设置函数。
SetDetailedRecords 是 ProblemQuery 类的函数。它设置一个开关来告知应用程序服务器,它是要显示各个返回的问题的详细信息,还是摘要信息。还要注意的是传送给 ProblemQuery 新运算符的参数。由于所有应用程序服务器消息实际上都由 APINetClient 发送,所以有必要指定 AppServerAction 对象使用的 APINetClient。
在函数部分的开始,对 PROBLEM_FILTER 结构的校验进行了比较。这可以确保在调用 API 函数之前,API 用户对结构进行初始化。否则,结构可能包含会产生错误的未初始化的指针。
APIManager 中的最后一个函数是 AddWatchedBlock。该函数请求 APIManager 类中的内存管理器跟踪分配的内存,以便在消毁 APIManager 对象时可以删除它。
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; }
由于是从 AppSvrAction 类派生来的,ProblemQuery 类包含下面两个 MSGUSERCALL 类成员:
- m_AppServerMessage
- m_AppServerResponse
PrepareMessage 设置 m_AppServerMessage,以便可以将其发送给 NetClient。
注:APINetClient 始终用 MSGUSERCALL 结构来发送响应,因此,存在可以俘获它的 m_AppServerResponse。
该消息的字段可以用于确定 APINetClient 返回的消息。将该响应与从 GetResponseMessageID 获得的期望的响应进行了对比,以确保 APINetClient 执行正确的操作。然后,SendMessage 确定通过对 CalculateRetrievalCount 的调用返回的完成记录的数目。将 m_AppServerMessage 发送给 APINetClient 后,就不再需要它。调用 FreeMessage 来确保清除消息中列出的字符串是很重要的。
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; } }
其他应用程序不能删除一个应用程序占有的内存。对同样是应用程序的 DLL 也一样。由于 C DLL 分配返回调用的应用程序的 PROBLEM_RECORD 列表使用的内存,所以 DLL 必须提供一种让应用程序释放被使用的列表的方法。这通过调用 FreeRequestList 来实现:
FreeRequestList( nAPIHandle, &pProblemList );FreeRequestList 需要 APIHandle 和作为参数的列表地址。
除 FreeRequestList 之外,还有几个清除内存的 C API 函数。其中两个是 FreeSolutionList 和 FreeHistoryList。这些函数分别释放 SOLUTION_RECORD 和 HISTORY_RECORD 结构的列表。
注:作为一种预防,在调用 Disconnect 时,API 释放分配的内存,因此没有必要调用这些函数。
提供释放函数,是为了在执行使用 API 的应用程序而又需要保留内存的情况下提供方便。