← ALL ARTICLES

插件接口与对象借用

全库目录221
本文目录10

设宿主加载插件M,先创建缓存B,再在界面线程T创建解码器A。A借用B,后台任务W及延迟回调Q访问A。本文规定接口创建、对象登记、调用保护、借用关系与单对象关闭的交接;模块整体卸载见 插件关闭与卸载。

所有权与模块接口

RAII和唯一、共享、弱引用的完整语义见 对象生命周期。插件边界把这些规则落实到创建、销毁和卸载三个入口。C++构建与对象模型中的布局与异常保证在这里成为调用协议:宿主先提供有效接口头,插件校验并构造,成功后才交出句柄。

C ABI契约

声明片段使用固定位宽版本和不透明句柄,创建与释放由同一模块实现。调用约定与导出宏按平台补齐。

cpp
struct AssetApi { uint32_t version; uint32_t size; };
extern "C" int CreateAsset(const AssetApi* api, void** handle);
extern "C" int DestroyAsset(void* handle);

接口结构的版本协商

接口调用者先保证指针指向至少包含version和size的有效可读头部;空指针检查不能证明任意非空地址都可访问。插件读取这个头部以后验证版本和size,并要求声明范围内的内存仍由调用者保持有效。size允许调用方传入较长的新结构,旧插件只读取自己认识的前缀;传入比必需前缀更短的结构则拒绝。字段增加在尾部才能维持这种前缀约定,改变原字段类型、对齐或含义需要升级不兼容版本。

创建函数先检查输出句柄指针本身非空,再把它指向的句柄槽置空,参数校验通过后才构造对象;内部异常在导出边界转为错误码。只有成功时句柄才转交调用者。DestroyAsset返回0表示销毁成功,空句柄也返回0;返回非零要求对象保持有效,宿主保留原句柄和关闭记录。销毁函数先完成可能失败的校验,再进入不抛异常的实际析构,避免对象已经部分销毁却返回可重试错误。任意无效指针仍不能作为对象访问;跨模块的重复销毁检测需要句柄表与代次,单纯delete不具备这个保证。

CreateAsset成功后,宿主把句柄登记到该模块代次的对象表,记录匹配的Destroy入口;登记完成才归还本次创建的调用计数,销毁完成才移除对象记录。热替换产生新的模块代次,新代次的函数表只接收自己的句柄。对象内存仍存在时,虚表与析构代码仍依赖原模块,所以旧对象与旧函数表一起保留到本代清理完成。

宿主若把CreateAsset排队,任务记录先固定模块代次、输入版本、类型声明的执行线程和参数缓冲持有,再进入Queued。本例A的创建由T执行;执行线程在模块入口内同时检查Running与任务仍为Queued,取得调用计数并改为Invoking。队列取消只可领取Queued,Invoking结果必须等调用返回。重复队列通知按任务身份读取状态,不能再次创建对象。Closing关闭领取后,尚未调用的项转Cancelled并释放输入;已经调用的项仍完成登记,随后进入正常关闭清单。

调用前还预留对象表条目、停止与收尾及销毁的派发槽、结果记录空间。预留失败就不进入插件;成功返回后以不分配内存的写入保存句柄、模块代次、线程要求和Destroy入口,再把任务改为Recorded,最后释放输入与调用计数。这样Create已成功而结果登记失败不会留下一份无人跟踪的插件对象,后续关闭也已有可用记录。创建返回错误则记录原错误并归还空条目;宿主进程重启从资产版本重新建立任务,进程内任务身份和裸句柄均不跨进程续用。

对象使用与关闭

M的对象表已经登记B和A,A→B表示后台工作与析构都还可能访问缓存。W持有A的使用令牌,延迟回调Q另持一份;A的停止、显式收尾和销毁派发槽在发布对象前预留,清理线程记录为T。宿主常驻协调者可以运行在另一线程,负责推进步骤,T负责实际执行这些线程受限的调用。

关闭A还涉及它内部的三份资源。A持有COM接口I,通过I建立事件订阅,并管理一个窗口;窗口销毁通知要通过I归还预留通知令牌。它们按以下依赖收尾:

资源步骤完成条件仍须保留的资源
撤销订阅提供方确认撤销,原来源的排队及在途业务回调全部退出窗口、I及所有相关代码
清理窗口T上的DestroyWindow成功,销毁通知回调退出I及通知结算所需数据
释放IT在所属Apartment完成本对象持有引用的ReleaseA本体与B缓存,供随后析构使用

本页沿单对象关闭和模块接管推进;排空、销毁A和B及卸载M见插件关闭与卸载,三项收尾见线程受限资源的关闭。资源步骤完成以前,瞬时使用计数归零只说明暂时无人访问,不能授权提前释放其后继仍需使用的资源。

调用入口与依赖登记

宿主为每代模块保存状态、调用计数、后台活动计数和对象表;每个对象另有Active、Stopping、Destroyed状态、使用计数、代次与依赖者集合。模块调用计数保留代码,对象使用计数保留当前访问,存活对象表保留仍须调用Destroy的对象。三者分别记录,卸载方才能先销毁对象而不与等待条件形成循环。

宿主公开句柄使用槽位与对象代次,插件裸地址保存在表内。普通调用按“模块门→对象表”的固定顺序加锁,在同一临界区核对Running、模块代次、对象Active及代次,并取得模块和对象令牌,再释放锁进入插件。所有退出路径归还令牌。关闭入口也经过这把门,因此检查通过而未登记的调用不会落在卸载之后。

B先登记入对象表,建立A→B时在对象表锁内检查两者Active、预留依赖记录并检查无环,再发布借用字段;检查失败保持字段原值。边保留到A停止使用、析构结束且清除字段。瞬时使用计数为零不表示A以后不会再访问B,依赖边承担这份长期保护。

后台任务与延迟回调在投递前取得对象使用及模块活动令牌。宿主常驻跳板保存代次、任务身份与状态;投递失败归还令牌,执行结束或取消确认后归还。取消接口只禁止将来调度时,还须确认队列项已移除,或正在执行的调用已退出。裸插件函数指针不能在卸载后才领取保护。

普通销毁与对象借用

模块Running时单独关闭B,入口先检查依赖者。存在A→B就返回Busy并列出A,B保持Active。调用方先关闭A或显式结束借用再重试B。关闭没有依赖者的A时,把A置Stopping并登记唯一记录C_A,拒绝后续普通调用及新借用;重复关闭等待同一结果。

单对象协调者S先将A的RequestStop派给T,保存返回回执,再等待A的任务、回调和使用者退出。本例Q取消确认后归还使用令牌,W仍持有自己的令牌,所以C_A停在Waiting。B继续保留缓存,A还不能Destroy。若模块继续Running,S会在排空和显式收尾后,经 插件关闭与卸载的清理入口销毁A;成功时共同移除对象、释放A→B、递增槽位代次并保存回执。

协调者持模块引用到收尾结束,等待期间释放对象表与业务锁。失败保留Stopping、原句柄和依赖闭包,安全可重试的步骤续行;部分析构无法保证对象完整时进入隔离故障。关闭A以后再关闭B,才允许B释放原来借出的缓存;瞬时使用计数归零不能替代这条依赖。

新借用与普通关闭在同一入口排序。B先有A→B就返回Busy;无依赖者的B先进入Stopping,后来的A→B便被拒绝。两个关闭者竞争同一对象只有一份记录进入析构,旧句柄的代次也不能访问复用槽位。

关闭记录与执行权

关闭记录解决重复请求,执行权决定谁能推进它。每份记录保存关闭ID、对象代次、协调者ID、所有权代次、阶段及当前步骤。步骤状态为Ready、Queued、Invoking、Completed或Waiting;RequestStop和Destroy各有稳定步骤ID。协调者负责选择步骤,实际插件调用可在指定清理线程执行。排队跳板在宿主门内比较协调者及所有权代次,把Queued改为Invoking,再取得模块和清理保护,随后释放锁进入插件。重复派发只查询同一步骤。

关闭步骤与外部请求分别保存状态。步骤回答“当前允许关闭到哪里”,请求尝试回答“提供方接纳了哪一次调用”。同步RequestStop或Destroy没有独立外部尝试;异步订阅撤销则在步骤下保存Prepared、Accepted、Unknown或Terminal的尝试记录。Prepared表示调用参数已登记,Accepted表示已确认提供方任务,Unknown表示接纳结果待查,Terminal保存提供方的确定终态。撤销尝试已经Terminal成功而回调尚未排空时,关闭步骤仍为Waiting;两项条件齐备才为Completed。协调者交接同时保留这两层记录。

单对象关闭与模块关闭相遇时,模块协调者先登记接管请求。原协调者不再登记新步骤,在一个没有插件调用在途的边界交出记录;宿主门内共同替换协调者、递增所有权代次并保留原阶段与已完成回执。Waiting可包含计数等待或已接纳异步步骤的结果等待,交接保留原请求与接纳记录;Queued先取消或使旧跳板失去授权,原排队项的保留一直等到它确认退出。尚未Invoking的步骤可由新协调者继续,旧协调者凭旧代次无法再次调用。

Invoking时保留原调用者,模块协调者订阅该步骤回执并继续给其他对象发停止请求。调用返回后,宿主先按原步骤ID登记结果,再交接剩余工作;已成功的Destroy共同删除对象与依赖边,模块清单直接排除它。调用尚未返回,接管方就不能再执行一次Destroy。线程失联或调用超时也不改变这个事实,关闭保留模块与在途状态,进入故障处理。

此时用户要求卸载M,模块协调者H取得C_A的Waiting记录:在宿主门内把协调者S改为H,所有权代次从3增至4,原RequestStop回执保持。S的旧通知再到达时不能推进记录。若卸载请求来得更晚、S已进入A的Destroy,H只订阅这次结果,同时先停止B的业务;它不会重新领取A的销毁权。

实现与证据

C ABI声明是接口片段。本文说明机制与宿主协议,沿A、B、T手工推演。关闭步骤、协调者代次、结果收件表及清理授权均为示例宿主协议。尚未编译动态库、加载宿主或执行实际COM异步撤销;编译、运行与性能结果分别验收。

相关主题

参考资料

对象表、关闭记录及协调者交接由本文定义;COM与窗口API的来源和线程限定见线程受限资源的关闭。