Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

05 Apr 2020

读oldnewthing笔记(二):COM Context相关

三人行,必有我师矣!读oldnewthing笔记。

C++ coroutines: Getting started with awaitable objects

Give an example of resuming co-routine on background

C++ coroutines: The problem of the synchronous apartment-changing callback

IContext­Callback是一个同步调用,一个线程使用IContext­Callback在另一个线程上执行操作的时候会挂起等待。

Using contexts to return to a COM apartment later

使用Co­Get­Object­Context可以获取当前COM的套间(apartment)

auto CaptureCurrentApartmentContext()
{
  winrt::com_ptr<IContextCallback> context;
  check_hresult(CoGetObjectContext(IID_PPV_ARGS(context.put())));
  return context;
}

使用IContext­Callback::Context­Callback 可以在保存的COM套间运行代码:

 [saveComplete = std::move(saveComplete),
     context = CaptureCurrentApartmentContext()](bool result)
  {
    InvokeInContext(context.Get(), [&]()
    {
      saveComplete(result);
    });
  }

When should I use delayed-marshaling when creating an agile reference?

使用Ro­Get­Agile­Reference创建机动引用的时候,可以选择早列集或者晚列集:

  • AGILE­REFERENCE_DEFAULT Eager marshaling
  • AGILE­REFERENCE_DELAYED­MARSHAL Lazy marshaling

早列集在创建引用的时候就列集必要信息,当这个引用在另一个线程中使用的时候,就有足够的信息创建代理对象。而晚列集则是将信息列集推迟到使用的时候,再到原线程收集。如果你的机动引用是要到其他线程使用的,那么可以使用早列集,避免晚列集导致的线程回切。

How do you get into a context via IContext­Callback::Context­Callback?

这篇文章是在说IContext­Callback::Context­Callback, 但是看得不是太懂。

IContext­Callback::Context­Callback似乎涉及了COM的底层汇集(marshal)机制。IContext­Callback::Context­Callback把传入的回调函数,回调函数的参数(ComCallData.pUserDefined)以及IID,IID方法索引等待传给COM。COM则要假装把回调当初索引指示的IID的方法,汇集到Context中待执行。

IID不能用IUnknown的,方法索引不能小于3。似乎因为COM对IUnknown(以及IInspectable还有IWeakReference)有什么特殊处理。

不同场景下推荐使用的IID和方法索引:

  • Classic IID_Context­Callback 5
  • No activity lock IID_IEnter­Activity­With­No­Lock 5
  • No ASTA reentrancy IID_ICallback­With­No­Reentrancy­To­Application­STA 5

Setting up private COM contexts to allow yourself to unload cleanly

上文提到可以用CLSID_Context­Switcher 打隔断。这样做的好处是可以让外部客户不直接引用隔断里面的内容,这样方便卸载隔断(调用Co­Disconnect­Context )。

在隔断中创建的对象,如果移到隔断外使用呢?一是可以使用Ro­Get­Agile­Reference 获取机动引用,可以在不同的套间隔断中切换。二是将接口列集成流。(一可能是通过二的方式实现的)

Yo dawg, I hear you like COM apartments, so I put a COM apartment in your COM apartment so you can COM apartment while you COM apartment

COM不仅有套间,你还可以在套间中创建隔断(CLSID_Context­Switcher):

IContextCallback* context;
CoCreateInstance(CLSID_ContextSwitcher, nullptr,
    CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&context));

可以通过IContext­Callback::Context­Callback,在隔断中派发工作。在不需要的时候可以调用Co­Disconnect­Context 来短裤Context的proxies,随后客户访问会返回RPC_E_DISCONNECTED。

A slightly less brief introduction to COM apartments (but it’s still brief)

为什么COM需要有套间(Aparment),大概是因为早期的程序都是单线程的,一次只能执行一个任务,剩余的任务需要等待,而套间,就是其他任务等待的区域。等待中的任务需要缓存在内存中,可能需要序列化之后才能缓存。

对于传值的类型,序列化比较直接。对于引用类型,为了使其看起来像值操作,则需要在Proxy侧生成一个伪对象,然后把操作转交给原生的对象执行,然后返回结果。

WinNT引入了多线程进程。为了后向兼容,原先单线程的套间模型也扩展到了多线程,每个套间在一个线程里面运行,不同线程的调用,和之前多进程间的调用一样,也需要通过套间进行缓存,也需要序列化。一个套间只能供一个线程访问,这称之为单进程套间(STA)。

可是毕竟有需求让多个线程访问一个套间,所以后来就引入了MTA(Multithread Apartment)。MTA的好处是不需要序列化操作,反正访问的都是同一个进程的内存。代价是用于MTA的对象需要保证自己的状态被多个线程访问的时候不会出错,不会死锁。

这样一来,一个程序里面就可以有任意个STA,以及至多一个MTA(多个MTA没有必要)。一个线程启动的时候可以选择自己是STA或者MTA,默认是MTA。

但是线程毕竟跟套间是两码事,所以Windows 2000引入了NTA(neutral-threaded aparment)。也就是不指定套间的线程。但是用的不多,所以基本不再提了。

Windows 8引入了application single-threaded apartment (ASTA)。是STA的变体,禁止了某些可重入操作。

How do I make a clone of a Windows Runtime vector in C++/WinRT?

IVector<Thing> original = GetTheThings();
std::vector<Thing> temp{ original.Size() };
original.GetMany(0, temp);
IVector<Thing> clone = single_threaded_vector(std::move(temp));

Can the MTA thread exit while keeping its COM class registrations alive?

使用CoIncrementMTAUsage(&cookie);和CoDecrementMTAUsage(cookie);可以让MTA套间没有关联到线程时依然存在。所以其中注册的类对象可以存在:

CoRegisterClassObject(CLSID_Something1, something1Factory,
    CLSCTX_LOCAL_SRVER, REGCLS_MULTI_SEPARATE, &token1);
CoRegisterClassObject(CLSID_Something2, something2Factory,
    CLSCTX_LOCAL_SRVER, REGCLS_MULTI_SEPARATE, &token2);

Why does my single-threaded program have multiple threads?

Windows的Thread Pools

winrt::fire_and_forget was too forgetful

(本篇完)