ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

COM+编程实战解析:事务、对象池与遗留系统维护关键指南

COM+编程实战解析:事务、对象池与遗留系统维护关键指南 简介面向具备 C 基础、希望进入企业级分布式组件开发领域的开发者这份 COM 编程资料围绕组件服务模型展开涵盖组件注册、事务与安全、事件路由、并发控制、对象激活策略以及 DCOM 分布式计算等核心主题并对 MTS 与 COM 的关系做了梳理。压缩包共 1339 个文件以 cpp、h 源码为主体搭配 def、idl、rgs、mk、rc 等工程与类型库描述文件以及 txt、sql、vbs 等说明与辅助脚本整体包体约 836KB目录结构贴近工程组织方式便于按模块对照学习。已有 187 人学习下载。通过研读源码工程与接口定义读者可深入理解 COM 组件的接口与类工厂写法、发布/订阅事件模型、多线程激活策略及服务组件配置方法是一份适合系统性学习 COM 编程的参考资料。 COM编程说白了就是微软在Windows平台上搞的那套企业级组件运行时。很多人一听这个名字就觉得是上个世纪的古董但实际上只要你在维护过银行、保险、ERP这类老系统的核心业务就一定绕不开COM。它解决的核心问题是在分布式环境下让业务组件具备事务保护、并发控制、资源自动回收和异步调用的能力。这些年我接触过不少还在用VB6或早期VC写的COM组件也帮人把这类系统往现代架构迁移深深体会到不懂COM的底层原理遇到生产环境偶发的事务回滚、对象泄漏、性能骤降基本只能靠猜。这篇就系统拆一遍COM编程的重点从组件开发、事务配置到对象池和排队组件的实际用法让需要维护或改造这类系统的朋友有个完整抓手。1. COM的前世今生它到底解决了什么问题1.1 从COM到COM为什么需要中间件层COMComponent Object Model本身只是定义了组件之间如何通信的二进制规范它让不同语言写的对象能互相调用。但真正进入企业级应用后COM暴露出一堆让开发者头疼的问题事务控制要自己一点点调API对象生命周期没人管理高并发下频繁创建销毁对象导致性能崩盘跨进程调用时的安全上下文要手工处理。COM正是在这个背景下出现的。它不是一个全新的编程模型而是一层运行于Windows之上的中间件服务把那些企业应用共用的“脏活累活”下沉到系统层面。开发者写组件时只需要声明“我需要事务”“我需要对象池”“我需要异步调用”COM运行库在后台帮你完成资源分配、事务协调、上下文传递和实例管理。这有点像开餐厅你自己只负责炒菜传菜、收银、排号、后厨调度都由餐厅平台包了你只需要告诉前台你的菜需要麻辣还是清淡。1.2 COM六大核心服务一览COM提供的核心服务今天看依然是现代服务端框架的标配只不过换了马甲事务服务Transactions基于MSDTCMicrosoft Distributed Transaction Coordinator实现跨组件、跨数据库的原子操作要么全部提交要么全部回滚。JIT激活Just-In-Time Activation服务端对象在客户端调用时才激活调用完立刻可释放节省服务器资源。对象池Object Pooling把创建开销大的对象预先创建一批放池里客户端请求时从池中取出用完归还避免反复new和销毁。排队组件Queued Components提供异步调用能力客户端调用组件时消息先进入队列服务端组件稍后处理适用于“发后即忘”的场景。事件发布订阅COM Events实现发布/订阅模式的松耦合事件通知替代传统的紧耦合回调。基于角色的安全模型Role-based Security通过角色管理授权将业务权限与代码解耦。这些服务互相配合构成了一个完整的分布式组件运行环境。理解了这些后续写代码时你就能针对性地选择特性而不是一股脑全开。2. 开发一个COM组件从零开始环境与最小实现2.1 开发环境搭建别急着上VS Code虽然我用现代编辑器写代码已经成了习惯但开发COM组件最稳妥的工具还是Visual Studio老版本VB6也可以。COM组件本质是一个进程内COM DLL直接注册到COM目录中由dllhost.exe承载。所以你需要Windows操作系统开发机建议Windows 10/11专业版或Server版本因为COM管理界面在非Server版上也同样可用组件服务管理单元。开发语言选择传统首选VB6或VC但如果你不想碰老语言用C#或VB.NET也能开发COM组件只需要让程序集对COM可见并通过特性声明COM服务配置。本文示例用C#因为它语法清晰方便现网维护人员看懂。必须给项目启用“使程序集COM可见”并在生成时将程序集注册到COM。2.2 用C#写一个带事务的COM组件下面这个例子是一个处理订单的组件会同时写入订单表和库存表为了保证一致性它声明了事务支持。在C#中你主要通过System.EnterpriseServices命名空间来使用COM服务。using System; using System.EnterpriseServices; using System.Data.SqlClient; [assembly: ApplicationName(OrderService)] [assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl(false)] namespace OrderService { [Transaction(TransactionOption.Required)] public class OrderComponent : ServicedComponent { public void CreateOrder(string orderNo, string productId, int qty) { try { using (var conn new SqlConnection(Server.;DatabaseShop;Integrated Securitytrue)) { conn.Open(); var cmd1 new SqlCommand(INSERT INTO Orders(OrderNo,ProductId,Qty) VALUES(p1,p2,p3), conn); cmd1.Parameters.AddWithValue(p1, orderNo); cmd1.Parameters.AddWithValue(p2, productId); cmd1.Parameters.AddWithValue(p3, qty); cmd1.ExecuteNonQuery(); var cmd2 new SqlCommand(UPDATE Products SET Stock Stock - qty WHERE ProductId pid, conn); cmd2.Parameters.AddWithValue(qty, qty); cmd2.Parameters.AddWithValue(pid, productId); cmd2.ExecuteNonQuery(); } ContextUtil.SetComplete(); } catch { ContextUtil.SetAbort(); throw; } } } }这里要特别强调ContextUtil.SetComplete()和ContextUtil.SetAbort()的作用。在COM事务模型中你不显式提交或回滚COM无法知道业务方法成功还是失败。SetComplete是在告诉MSDTC这个方法已经成功完成事务可以在所有参与者投票一致时提交。SetAbort则明确回滚事务。很多新手写COM组件就漏了这两行结果事务根本没生效异常也不知道被吞到哪去了。3. 核心编程模型拆解事务、对象池与异步3.1 事务属性Required、Supported、RequiresNew怎么选COM事务属性设置看似简单实际踩坑很多。给组件方法设置[Transaction]时你有几个选项Disabled不参与事务、NotSupported不支持事务在事务外运行、Required需要事务如果没有则新建一个、RequiresNew总是新建一个独立事务、Supported有事务则继承没有则不用。最常用的组合是最外层的门面组件用Required内部资源操作组件也用Required这样它们会自动加入同一个事务。但如果你在里面加了一个发邮件的组件而且发邮件失败不应该影响主业务那这个发邮件组件就不能用Required否则邮件发送抛异常会把订单一起回滚。我见过线上生产事故就是给一个记录日志的组件加了Required日志表插入失败导致整个订单提交被回滚。正确做法是把这类组件设置成NotSupported或在独立事务中执行。还有一个细节事务默认隔离级别是Serializable这在绝大多数场景下有点过度。如果数据库压力大你可以在COM组件服务配置里或通过代码调整隔离级别到Read Committed但要注意这会牺牲一部分一致性保障必须结合业务场景评估。3.2 JIT激活与对象池并发提升的两张王牌COM组件默认是JIT激活的。什么意思客户端拿到的是代理每次调用方法时COM才真正在服务器上激活组件实例方法调用结束组件实例就可以被立刻“销毁”。这避免了一个客户端长期占用一个服务端对象大幅提升了并发处理能力。但代价是组件不能依赖成员变量保持跨方法的状态因为实例根本活不到下一次调用。要配合JIT使用对象池组件必须满足几个条件没有状态、构造函数不带参数、类必须能快速创建。在代码中通过[ObjectPooling(Enabledtrue, MinPoolSize5, MaxPoolSize50)]指定池大小。COM会把创建好的对象放进池里请求到达时从池中取用完通过Deactivate方法归还。要特别注意组件必须实现IDisposable或重写Activate/Deactivate方法在Deactivate里清理字段状态否则从池里取出的是一个“脏对象”。对象池并不是万能的。创建对象本就很快的话池化反而增加调度开销。我一般建议只有在组件构造耗时超过50毫秒或者初始化要建立网络连接、加载大配置时才考虑做池化。否则就别给Com组件加池化特性。3.3 异步调用排队组件解决“发后即忘”COM的异步编程模型和现代async/await不是一回事它更接近消息队列。排队组件Queued Components是COM提供的一种异步调用手段客户端调用组件方法时调用不是直接到达服务器而是先序列化成一个消息打进MSMQ队列微软消息队列服务端组件通过监听队列取出消息并执行。这种模式适合那些“立即返回不需要等待结果”的操作比如订单确认后发送通知、批量数据导出、外部系统数据同步。客户端代码调用方式几乎和正常调用一样但COM会在底层把调用转成消息发出去所以就算服务端暂时不可用消息也不会丢。有一点需要注意排队组件的所有方法参数必须可序列化方法返回值只能为void因为调用方根本不会同步等到结果。如果你需要结果回传得自己设计一个回调消息或者用查询状态的方式实现。我在实际项目里就用排队组件改造过一个“批量发送短信”的模块原来用户点击群发后页面要傻等几十秒后来改成排队组件页面立即返回短信后台慢慢发体验提升非常明显。4. 部署与调优实战组件服务管理全流程4.1 将程序集注册到COM应用写完了代码接下来是部署。用C#开发时通常可以写一个安装脚本用RegSvcs工具完成注册。以下是我常用的命令行流程regsvcs /c /nologo OrderService.dll/c表示创建COM应用/nologo隐藏版本信息。默认情况下它会根据程序集特性里的ApplicationName创建应用。如果应用已存在可以用/appname覆盖应用名。这一步会把DLL里的ServicedComponent类型注册到COM目录并自动生成必要的COM接口。注册完成后打开“组件服务”管理单元运行dcomcnfg展开“组件服务 → 计算机 → 我的电脑 → COM应用程序”就能看到你创建的OrderService应用。在“组件”节点下可以看到OrderComponent组件。右键属性可以调整事务隔离级别、池化参数、安全角色等。注意如果DLL更新了版本你需要先在组件服务里删除原应用再重新用RegSvcs注册否则可能出现“类型已被注册”的错误。这个坑我踩过很多次后来干脆写了一个批处理脚本先删除应用再重新注册。4.2 客户端调用与对象生命周期控制服务端组件注册成功后客户端就可以通过new关键字直接创建对象调用前提是客户端程序集里引用了服务端组件或者通过COM互操作注册了类型库。但这里有个关键点客户端拿到的对象是COM代理不是真实的服务器对象。所以你不能把它当成普通对象保存在静态字段里长期使用否则会持有服务器资源。推荐做法是客户端每次调用时创建对象调用完立即释放并注销。在C#里可以这样OrderComponent order null; try { order new OrderComponent(); order.CreateOrder(2025001, P001, 3); } finally { if (order ! null) { ((IDisposable)order).Dispose(); } }Dispose方法会通知COM将实例释放或归还到池中。如果客户端忘记释放对象实例将被JIT延迟回收最终由垃圾回收器处理但这样会浪费资源高并发场景下有大量这类行为就会导致服务器上活跃对象暴增内存和句柄被耗尽。4.3 性能评估与日志诊断部署完成后怎么判断COM是否正常工作我建议从几个层面观察在组件服务管理工具中查看组件节点的“对象”状态如果启用了对象池可以看到活跃对象数、池中可用对象数、对象调用次数。正常情况下活跃对象数应远小于总调用数。打开系统事务列表看MSDTC事务统计确认事务提交次数与业务量匹配。如果出现大量“Aborted”事务大概率是业务代码抛异常却没正确排查。检查Windows事件日志中的COM相关错误。很多COM异常在客户端看不到具体信息只会抛COMException关键在于服务端事件日志中的详细堆栈。我做过的其中一个生产系统上线后并发一高就报“服务器进程启动失败”后来发现是COM应用池的“空闲超时”设得太短导致进程频繁回收每次回收后第一波请求要重新初始化数据连接池响应时间剧增。把空闲超时调成0不超时并配置了预加载后问题才彻底解决。5. 常见问题与避坑清单5.1 典型故障速查表故障现象可能原因排查/解决方法调用组件时抛COMException: 拒绝访问调用方没有权限或角色配置不对检查COM应用安全属性确认方法级角色是否包含当前用户事务没有生效数据未回滚代码缺少ContextUtil.SetComplete()/SetAbort()组件事务属性设置成Disabled补全明确的投票代码检查[Transaction]特性方法执行超时客户端阻塞组件内部有死锁或数据库锁进程队列堆积检查SQL锁观察MSDTC事务状态调整组件超时时间新版本DLL注册失败提示已存在旧应用未删除/类型库缓存删除旧COM应用重启Dllhost进程再执行RegSvcs /c对象池中的对象状态混乱对象池组件有成员变量归还时未清理重写Deactivate()清理所有成员变量确保组件无状态排队组件收到的消息一直不执行服务端队列未监听队列权限不足确认排队应用是否启动检查MSMQ队列权限5.2 多年维护COM的经验心得关于COM编程有几点书本上看不到但实际非常重要的体会。第一COM组件的日志非常重要但很多人觉得麻烦写进业务代码里又怕影响性能。建议在组件入口和出口记两行日志一个标记开始时间一个标记结束时间和状态再配合事件日志排查问题会快很多。第二尽量保持组件“无状态”这是COM最核心的修养。哪怕你只是图省事在成员变量里缓存一个配置项当JIT激活或对象池复用场景下都可能出现共享状态错乱。真要缓存建议放在静态只读字段里。第三不要盲目升级到“最新”框架。COM毕竟是老技术如果你的系统运行得很稳定维护成本低就千万别因为“技术上过时”去重写。我也不是让大家继续用COM做新项目只是面对遗留系统理解并尊重它运行的规律比一上来就推倒重构要实在得多。我自己处理过很多次所谓“必须迁移”的项目最后分析下来大部分业务其实并不需要微服务用COM照样能扛住。最后再分享一个小技巧在开发调试时可以把COM应用设置成“本地面板”模式在应用属性“高级”选项卡里把“服务器进程”改成“库应用程序”这样组件会加载到调用方进程内调试特别方便。不过上线前一定要改回“服务器应用程序”模式否则并发隔离和故障隔离能力就没了。我见过测试环境调通了上线忘了改生产直接崩的例子这个细节务必记住。本文还有配套的精品资源点击获取
返回列表