Making and Processing Asynchronous Calls
COM对象支持异步调用。当客户端发起调用之后,服务端可以立刻返回,然后客户端可以继续执行其他工作。等需要结果的时候再向COM接口索要。或者等到任务完成的时候,再向服务端要,就不会被阻塞。
客户端并不直接在COM对象上直接进行异步操作。而是客户端获取一个调用对象(call object),由这个对象在同步的接口上实现异步操作。然后一部异步操作的命名通常是以Async开头的,比如AsyncInterfaceName。如果一个COM对象提供的同步接口是IMyInterface,那么这个调用对象就是AsyncIMyInterface。
从IDispatch派生出来的对象不支持异步操作。
能够支持异步的COM对象必须实现ICallFactory接口。这个接口只有一个方法用来创建调用对象,那就是CreateCall。
对于一个接口的异步版本,每个方法都在异步版本中对应两个方法,以Begin_和Finish_开头。服务端不必为每一个接口提供调用对象。如果服务端实现了ICallFactory接口,并且使用standard marshaling,那么客户端的代理会自动生成相应的调用对象。这时候Begin_ 方法会是一个同步方法,然后Finish_ 方法可以返回其结果。
如果客户端和服务端在一个apartment里面,没有代理和驻桩,那么就没有办法对不支持异步调用的接口模拟异步调用,会导致ICallFactory::CreateCall失败。只能调用同步方法。
Making an Asynchronous Call
如果开始一个异步调用:
- Query the server object for the ICallFactory interface. If QueryInterface returns E_NOINTERFACE, the server object does not support asynchronous calling.
- Call ICallFactory::CreateCall to create a call object corresponding to the interface you want, and then release the pointer to ICallFactory.
- If you did not request a pointer to the asynchronous interface from the call to CreateCall, query the call object for the asynchronous interface.
- Call the appropriate Begin_ method.
一个调用对象只能支持一个异步调用,在一个异步调用还没有结束的时候调用Begin_方法会返回RPC_E_CALL_PENDING。如果客户端不需要查询调用的结果,那么可以在Begin_ 之后释放调用对象。Finish_ 不会得到调用。调用对象的ISynchronize可以用来设置调用对象的完成状态,可以把调用设置为已完成。ISynchronize::Signal会设置其对象内部所包含的事件句柄。
如果服务端过早调用ISynchronize::Signal来标识任务已经完成,但是Begin_方法还没有返回,那么服务端再也无法继续代表客户端进行操作。如果不调用ISynchronize::Signal,那么服务端可以在线程上保持客户端令牌,直到服务端调用IServerSecurity::RevertToSelf,或者Begin_ 返回。
完成一个异步调用:
- Query the call object for the ISynchronize interface.
- Call ISynchronize::Wait.
- If Wait returns RPC_E_TIMEOUT, the Begin_ method is not finished processing. The client can continue with other work and call Wait again later. It cannot call the Finish_ method until Wait returns S_OK.
- If Wait returns S_OK, the Begin_ method has returned. Call the appropriate Finish_ method.
调用对象在一个异步调用结束以后还可以继续进行其他调用。如果没有正在进行的异步调用Finish_ 方法会返回RPC_E_CALL_COMPLETE。
MIDL创建的代理经办会对所有使用 standard marshaling的对象实现IClientSecurity,可以用来管理异步调用的安全问题。
可以通过调用对象的ICancelMethodCalls接口来中止一个异步调用。如果采用standard marshaling,那么ICancelMethodCalls默认提供。客户端从调用Begin_ 到Finish_ 返回的过程中,都可以中止异步调用。如果在Finish_ 之前中止,那么需要调用Finish_ 方法来清理残留资源。如果不这么做,则下一次的Begin_ 方法会返回RPC_E_CALL_PENDING。
所以,中止异步操作,通过下面几步:
- Query the call object for ICancelMethodCalls.
- Call ICancelMethodCalls::Cancel, and then call Release to release the pointer obtained by the QueryInterface call in step 1.
- If the client has not called the Finish_ method already, call it now.
就算发出了中止请求,有时候服务端也无法立刻中止。在必要的时候,客户端需要查询服务端的状态,然后进行下一步操作。
对于单线程的Apartment,可以使用 IMessageFilter来中止。
COM调用的同步机制大致可以分为三种:
- 同步调用。在同步调用的时候,COM会在模态循环里面等待调用返回结果。
- 异步调用。调用结果通过而外的消息返回,比如使用PostMessage 或者其他事件。IAdviseSink定义了五个接口:
- OnDataChange
- OnViewChange
- OnRename
- OnSave
- OnClose
- 输入同步的调用。在这种情况下,COM必须在调用结束后才能返回。这用来保证用户输入以及焦点的正常运作。这些调用是COM通过SendMessage发起,并不经过模态事件循环。当处理此类需要同步输入的调用时,不能待用任何会导致控制转移的操作。下列方法需要输入同步:
- IOleWindow::GetWindow
- IOleInPlaceActiveObject::OnFrameWindowActivate
- IOleInPlaceActiveObject::OnDocWindowActivate
- IOleInPlaceActiveObject::ResizeBorder
- IOleInPlaceUIWindow::GetBorder
- IOleInPlaceUIWindow::RequestBorderSpace
- IOleInPlaceUIWindow::SetBorderSpace
- IOleInPlaceFrame::SetMenu
- IOleInPlaceFrame::SetStatusText
- IOleInPlaceObject::SetObjectRects
不能把同步调用混杂在异步调用中。比如OnDataChange中必能包含同步的IPersistStorage::Save。
COM通过逻辑线程id来管理调用。当一次调用发生的时候会分配一个逻辑线程id,后续相关的调用会使用相同的逻辑线程id。
(本篇完)