进程内服务器线程问题

进程内服务器不会调用 CoInitializeCoInitializeExOleInitialize 来标记其线程模型。 对于基于 DLL 的线程感知对象或进程内对象,需要在注册表中设置线程模型。 如果未指定线程模型,则默认模型是每个进程单个线程。 若要指定模型,请将 ThreadingModel 值添加到注册表中的 InprocServer32 项。

支持类对象实例化的 DLL 必须实现和导出函数 DllGetClassObjectDllCanUnloadNow。 当客户端需要 DLL 支持的类实例时,调用 CoGetClassObject(直接或通过调用 CoCreateInstance)会调用 DllGetClassObject,以在 DLL 中实现对象时获取指向其类对象的指针。 因此,DllGetClassObject 应能够放弃多个类对象或单个线程安全对象(实质上只是在其内部引用计数上使用 InterlockedIncrement/InterlockedDecrement)。

顾名思义,调用 DllCanUnloadNow 以确定实现它的 DLL 是否正在使用中,使调用方能够在不使用 DLL 时安全地将其卸载。 从任何线程调用 CoFreeUnusedLibraries 始终会通过主单元的线程路由,以调用 DllCanUnloadNow

与其他服务器一样,进程内服务器可以是单线程服务器、单元线程服务器或自由线程服务器。 无论 OLE 客户端使用哪个线程模型,该客户端都可以使用这些服务器。

客户端和进程内对象之间允许所有线程模型互操作性组合。 使用不同线程模型的客户端与进程内对象之间的交互与客户端与进程外服务器之间的交互完全相同。 对于进程内服务器,当客户端和进程内服务器的线程模型不同时,COM 必须在客户端和对象之间调停。

当支持单线程模型的进程内对象由客户端的多个线程同时调用时,COM 不允许客户端线程直接访问对象的接口,因为该对象不支持此类访问。 相反,COM 必须确保调用已同步,并且仅由创建对象的客户端线程进行。 因此,COM 在客户端的主单元中创建对象,并要求所有其他客户端单元使用代理访问对象。

当客户端中的自由线程单元(多线程单元模型)创建单元线程进程内服务器时,COM 会在客户端中启动单线程单元模型“主机”线程。 此主机线程将创建对象,接口指针将封送回客户端的自由线程单元。 同样,当单元模型客户端中的单线程单元创建一个自由线程进程内服务器时,COM 将启动一个自由线程主机线程(将在多线程单元上创建对象,然后封送回客户端单线程单元)。

注意

通常,如果在进程内服务器上设计自定义接口,还应为其提供封送代码,以便 COM 封送客户端单元之间的接口。

 

COM 通过要求从创建对象所在同一客户端单元进行访问,帮助保护对单线程 DLL 提供的对象的访问。 此外,所有 DLL 入口点(如 DllGetClassObjectDllCanUnloadNow)和全局数据都应始终由同一单元访问。 COM 在客户端的主单元中创建此类对象,让主单元直接访问对象的指针。 其他单元调用使用线程间封送从代理转到主单元中的存根,然后转到对象。 这样,COM 就可以同步对象调用。 线程间调用速度缓慢,因此建议重写这些服务器以支持多个单元。

与单线程进程内服务器一样,单元模型 DLL 提供的对象必须由创建它的同一客户端单元访问。 但是,此服务器提供的对象可以在客户端的多个单元中创建,因此服务器必须实现其入口点(如 DllGetClassObjectDllCanUnloadNow),以便进行多线程使用。 例如,如果客户端的两个单元同时尝试创建两个进程内对象实例,则 DllGetClassObject 可由两个单元同时调用。 必须写入 DllCanUnloadNow,以便 DLL 不会卸载仍在执行的代码。

如果 DLL 仅提供一个类工厂实例来创建所有对象,则类工厂实现也必须设计为多线程使用,以供多个客户端单元访问。 如果每次调用 DllGetClassObject 时 DLL 都会创建新的类工厂实例,则类工厂不需要线程安全。

类工厂创建的对象不需要线程安全。 线程创建后的对象始终通过该线程访问,并且 COM 会同步所有对象调用。 创建此对象的客户端的单元模型将获得直接指向该对象的指针。 与创建对象的单元不同的客户端单元必须通过代理访问该对象。 当客户端封送其单元之间的接口时,将创建这些代理。

当进程内 DLL ThreadingModel 值设置为“两者”时,可以在单线程或多线程客户端单元中直接(无需代理)创建和使用此 DLL 提供的对象。 但是,只能直接在创建其的单元内使用。 若要将对象提供给任何其他单元,必须封送该对象。 DLL 对象必须实现自己的同步,然后可以同时由多个客户端单元访问。

COM 提供 CoCreateFreeThreadedMarshaler 函数来加快对进程内 DLL 对象的自由线程访问。 此函数创建一个可以与进程内服务器对象聚合的自由线程封送对象。 当同一进程中的客户端单元需要访问另一单元中的对象时,聚合自由线程封送处理器会在客户端将对象的接口封送给不同的单元时,向客户端提供直接指向服务器对象(而不是代理)的指针。 客户端不需要执行任何同步。 这仅适用于同一进程;标准封送用于引用发送到另一个进程的对象。

进程内 DLL 提供的仅支持自由线程处理的对象是一个自由线程对象。 它实现自己的同步,并可由多个客户端线程同时访问。 此服务器不会封送线程之间的接口,因此此服务器只能由客户端中的多线程单元直接创建和使用(无需代理)。 创建此服务器的单线程单元将通过代理对其进行访问。

跨单元访问接口

选择线程模型

多线程单元

进程、线程和单元

单线程通信和多线程通信

单线程单元