COM学习笔记(五)

Tue 03 September 2019 / In categories Programming

C++, COM

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_CALLPENDING。如果客户端不需要查询调用的结果,那么可以在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_ETIMEOUT, 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 SOK, 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。

(本篇完)

Load Disqus Comments