三人行,必有我师矣!读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
IContextCallback是一个同步调用,一个线程使用IContextCallback在另一个线程上执行操作的时候会挂起等待。
Using contexts to return to a COM apartment later
使用CoGetObjectContext可以获取当前COM的套间(apartment)
auto CaptureCurrentApartmentContext()
{
winrt::com_ptr<IContextCallback> context;
check_hresult(CoGetObjectContext(IID_PPV_ARGS(context.put())));
return context;
}
使用IContextCallback::ContextCallback 可以在保存的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?
使用RoGetAgileReference创建机动引用的时候,可以选择早列集或者晚列集:
- AGILEREFERENCE_DEFAULT Eager marshaling
- AGILEREFERENCE_DELAYEDMARSHAL Lazy marshaling
早列集在创建引用的时候就列集必要信息,当这个引用在另一个线程中使用的时候,就有足够的信息创建代理对象。而晚列集则是将信息列集推迟到使用的时候,再到原线程收集。如果你的机动引用是要到其他线程使用的,那么可以使用早列集,避免晚列集导致的线程回切。
How do you get into a context via IContextCallback::ContextCallback?
这篇文章是在说IContextCallback::ContextCallback, 但是看得不是太懂。
IContextCallback::ContextCallback似乎涉及了COM的底层汇集(marshal)机制。IContextCallback::ContextCallback把传入的回调函数,回调函数的参数(ComCallData.pUserDefined)以及IID,IID方法索引等待传给COM。COM则要假装把回调当初索引指示的IID的方法,汇集到Context中待执行。
IID不能用IUnknown的,方法索引不能小于3。似乎因为COM对IUnknown(以及IInspectable还有IWeakReference)有什么特殊处理。
不同场景下推荐使用的IID和方法索引:
- Classic IID_ContextCallback 5
- No activity lock IID_IEnterActivityWithNoLock 5
- No ASTA reentrancy IID_ICallbackWithNoReentrancyToApplicationSTA 5
Setting up private COM contexts to allow yourself to unload cleanly
上文提到可以用CLSID_ContextSwitcher 打隔断。这样做的好处是可以让外部客户不直接引用隔断里面的内容,这样方便卸载隔断(调用CoDisconnectContext )。
在隔断中创建的对象,如果移到隔断外使用呢?一是可以使用RoGetAgileReference 获取机动引用,可以在不同的套间隔断中切换。二是将接口列集成流。(一可能是通过二的方式实现的)
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_ContextSwitcher):
IContextCallback* context;
CoCreateInstance(CLSID_ContextSwitcher, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&context));
可以通过IContextCallback::ContextCallback,在隔断中派发工作。在不需要的时候可以调用CoDisconnectContext 来短裤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
(本篇完)