ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DLL 服务注册指南:regsvr32、regasm 与 sc create

DLL 服务注册指南:regsvr32、regasm 与 sc create 1. 先搞明白你说的注册到底是哪一种上周有个同事扔过来一个压缩包里面躺着三个 dll 和一句帮我注册一下。我打开一看第一个是十多年前某套老系统留下的 COM 组件第二个是 .NET 编译出来的业务库第三个其实只是某个服务的插件靠配置文件加载压根不需要碰注册表。这三个东西命运完全不同能直接注册的只有第一个。这几乎是所有人第一次接触 dll 服务注册流程时的共同困境——把几个名字相似、机理完全不同的动作混成了一个词。真正把这件事讲清楚得先从注册这两个字开始拆。因为你在搜索引擎里看到的 regsvr32、regasm、sc create、installutil它们解决的不是同一个问题甚至操作的对象都不是同一类文件。搞混了就会出现那种命令跑完了、没报错、但程序还是说找不到组件的诡异局面。1.1 三种完全不同的注册日常语境下说的注册至少对应下面三件互不相干的事情每一件的本质、工具和产物都不一样。第一件是COM 组件注册。这类 dll 遵循组件对象模型规范导出了DllRegisterServer和DllUnregisterServer两个函数。当你执行regsvr32 xxx.dll操作系统做的事情其实非常朴素把这个 dllLoadLibrary进来找到DllRegisterServer这个导出函数然后调用它。至于往注册表里写什么键值、写几个 CLSID全是 dll 作者在那段函数里自己写代码决定的regsvr32 只是个加载器 调用器。理解了这一点很多怪现象就顺了为什么有的 dll 用 regsvr32 注册会报找不到入口点——因为它根本就没导出那个函数。第二件是托管程序集注册。.NET 编译出来的 dll默认情况下就是一个普通的 PE 文件没有DllRegisterServer这种原生导出。它的类型信息全部藏在程序集元数据里。所以要用RegAsm.exe去读元数据然后由 RegAsm 自己生成对应的注册表项。这也是为什么很多人拿 regsvr32 去注册 .NET dll必然吃一个找不到 DllRegisterServer 入口点的报错。第三件是Windows 服务注册。这里的服务是 SCMService Control Manager服务控制管理器意义上的服务是操作系统层面的一个后台进程托管机制。注册的动作本质上是往 SCM 的数据库里加一条记录告诉系统有这么个 exe它的启动方式是这样登录账号是那个开机能自启。注意这条记录里登记的是exe 的路径不是 dll。dll 在这里只是被 exe 依赖的库文件它本身不需要注册只需要能被找到。把这三件事分开之后你会发现所谓的dll 服务注册流程实际落地的时候通常是两段拼起来的先用 regsvr32 或 regasm 把组件挂到注册表上再用 sc create 把承载它的可执行文件登记成服务。1.2 注册过后系统里多了什么要判断一次注册是否真的成功你得知道成功的标志长什么样。最直接的办法就是去注册表里翻。COM 组件的注册信息绝大多数落在下面这几个位置注册表路径含义HKLM\SOFTWARE\Classes\CLSID\{GUID}组件的唯一身份标识所有 COM 注册的根...\CLSID\{GUID}\InprocServer32进程内组件值为 dll 的完整路径另有 ThreadingModel...\CLSID\{GUID}\LocalServer32进程外组件值为 exe 路径...\CLSID\{GUID}\ProgID人类可读的别名比如MyApp.Widget.1HKLM\SOFTWARE\Classes\MyApp.Widget.1ProgID 反查 CLSID 的入口HKLM\SOFTWARE\Classes\Interface\{GUID}接口定义供跨进程编组使用HKLM\SOFTWARE\Classes\TypeLib\{GUID}类型库信息脚本语言和 IDE 靠它做提示这里有两个特别容易让人栽跟头的地方。第一个是Wow6432Node。在 64 位系统上32 位组件的注册信息会被重定向到HKLM\SOFTWARE\Classes\Wow6432Node\CLSID\...下面而 64 位组件的注册信息在HKLM\SOFTWARE\Classes\CLSID\...。它们看起来像是同一个目录其实是两套并行的注册表空间。你用 32 位的 regsvr32 注册完去 64 位的位置翻当然什么都找不到。第二个是ThreadingModel。这个值决定了组件被加载到哪种 COM 套间里。Apartment是最常见的 STA 单线程套间Both表示可以进 STA 也可以进 MTAFree是 MTANeutral用于无套间场景。这不是可选项写错了组件在某些宿主进程里会直接加载失败或者出现诡异的阻塞。如果你自己写 DllRegisterServerThreadingModel必须显式写进去不写的话默认行为在旧系统上可能碰巧能用在新系统上就不一定了。2. 注册前的准备位数、权限、依赖三件事我见过太多人一上来就敲命令报错了再回来找原因结果绕了一大圈发现是位数不对。其实注册这件事的前置检查就三条位数、权限、依赖。这三条过一遍能省掉后面八成的排错时间。2.1 位数对不上后面全是白费判断 dll 是 32 位还是 64 位最靠谱的工具是 Visual Studio 自带的dumpbin或者在开发者命令提示符里直接跑dumpbin /headers C:\path\to\your.dll | findstr machine输出里会出现x8632 位、x6464 位或者ARM64。如果你手头没有 VS也可以装一个轻量的 PE 头查看工具很多整理包管理器里都有 GUI 版本拖进去就能看。判断出来之后注册的时候就必须用对应位数的 regsvr32。这一点是硬性的系统位数组件位数应该使用的 regsvr32 路径64 位64 位C:\Windows\System32\regsvr32.exe64 位32 位C:\Windows\SysWOW64\regsvr32.exe32 位32 位C:\Windows\System32\regsvr32.exe这张表第一次看会很反直觉64 位系统里System32目录放的其实是64 位系统文件而SysWOW64目录放的才是32 位的文件。这个命名是历史包袱微软自己也承认是个糟糕的命名但它已经没法改了。所以当你要注册一个 32 位组件时必须显式写全路径C:\Windows\SysWOW64\regsvr32.exe而不是直接在命令行敲个regsvr32——后者从 64 位的命令行解析出来的通常是 64 位那一个。顺带说一个实际经验如果你在 64 位系统上用错了 regsvr32报的错往往不是位数不匹配这么友好而是0x8007007E 找不到指定的模块或者干脆无提示失败。因为 64 位进程去加载 32 位 dll 时LoadLibrary阶段就挂了连接下来要执行的 DllRegisterServer 都走不到。2.2 权限与注册表写入位置往HKLM下面写东西必须有管理员权限。所以正常的注册流程是以管理员身份打开命令提示符再执行 regsvr32。如果你只是普通权限双击运行组件自己的 DllRegisterServer 里那段写注册表的代码会拿到ERROR_ACCESS_DENIED最终 regsvr32 弹出来的就是个笼统的失败对话框。但这里有个非常隐蔽的坑值得单独拎出来说。提权之后你的用户上下文变了。假设你以管理员 A 登录然后提权执行注册这时候进程的 HKCU 指向的是管理员 A 的用户配置单元而不是当前交互登录会话的那个用户。如果某个组件的 DllRegisterServer 里恰好写的是HKCU\Software\Classes\CLSID\...这种情况在单用户免提权注册的设计里是存在的那么注册信息就写进了 A 的配置单元。你以普通用户 B 身份跑程序B 的 HKCU 里什么都没有程序就会报类未注册。处理这类问题的办法是明确区分两种注册模式机器级注册per-machine写HKLM需要管理员权限全机器所有用户可见。用户级注册per-user写HKCU不需要管理员权限只对当前用户可见。注册的时候用哪个模式应该由组件的部署场景决定而不是随手敲。对于安装在Program Files下面的正式组件老老实实走机器级对于绿色部署、放在用户目录下的组件走用户级反而更干净。2.3 依赖检查别等到报错才想起来一个 dll 从来不是孤立存在的。它自己会依赖 VC 运行库、.NET 运行时、系统 API 集、以及同目录或系统目录下的其他 dll。注册的时候 regsvr32 会把这个 dll 加载进内存只要有一条依赖链断了加载就会失败。查依赖链有几个手段。老牌的 Dependency Walker 在新系统上经常误报因为它不认识 API Set 的虚拟 dll比如api-ms-win-crt-*.dll现在更推荐用Dependencies一个开源的重写版本它能正确识别 API Set 和延迟加载。如果你想看最原始的导入表用 dumpbindumpbin /dependents C:\path\to\your.dll但真正最好用的还是Process Monitor。它能看到注册那一刻运行时实际去哪些路径找了哪些文件、哪些找失败了。过滤条件设成Process Name is regsvr32.exe Operation is CreateFile Result is NAME NOT FOUND跑一遍注册ProcMon 里列出来的每一个NAME NOT FOUND都是运行时尝试加载但没找到的文件。这里面有的是正常的探测行为系统会按顺序试多个路径有的就是真实缺失的依赖。判断方法很简单把路径拿去资源管理器里确认一下文件确实不存在而 dll 名字又是你程序相关的那就是缺了。注意不要图省事去网上随便下载所谓的 dll 修复工具或者单独的 dll 文件往系统目录里塞。这个做法有三个致命问题一是来路不明的二进制文件本身就是安全风险二是版本不匹配会导致更隐蔽的崩溃三是往System32里塞文件会污染系统目录后面出问题极难定位。正确的做法是找到这个 dll 的官方来源——通常是某个运行时可再发行包装上对应的安装包让它自己完成部署。3. COM 组件注册实操regsvr32 与 regasm前置检查做完就可以进入真正的注册环节了。这一段我把原生 COM 和托管程序集分开讲因为它们的工具链完全不重叠。3.1 regsvr32 的正确用法regsvr32 的参数不多但每个都有明确用途值得记牢:: 静默注册成功与否都不弹窗 regsvr32 /s C:\path\to\your.dll :: 卸载注册 regsvr32 /u /s C:\path\to\your.dll :: 带参数调用 DllInstall有些组件靠这个区分安装/卸载场景 regsvr32 /i C:\path\to\your.dll :: 只调用 DllInstall不调用 DllRegisterServer regsvr32 /n /i:user C:\path\to\your.dll/s这个参数在批量部署脚本里几乎是必加的因为默认情况下 regsvr32 会弹一个模态对话框脚本里没人去点它进程就会一直挂在那里。加了/s之后成败只能靠返回码判断。这里有个细节regsvr32 的返回码并不可靠它在某些失败场景下依然返回 0。所以真正严谨的部署脚本注册完之后一定还要做一次验证见 3.3 节。/n和/i的组合是给那些同时支持机器级和用户级注册的组件准备的。/i:user会把user这个字符串作为参数传给 dll 的DllInstall函数dll 内部根据这个参数决定写 HKCU 还是 HKLM。这是微软官方给出的用户级注册约定。还有一个容易忽略的点注册路径里不要有中文和非 ASCII 字符至少在排查阶段尽量避免。虽然现代 Windows 对 Unicode 路径支持已经很好了但一些老组件的内部实现用的是 ANSI 版本的 API遇到非 ASCII 路径会静默失败。我习惯把要注册的 dll 先复制到一个纯英文短路径下比如C:\temp\reg\注册验证通过之后再移到最终位置并重新注册一次。3.2 .NET 程序集regasm 才是正路托管程序集要靠RegAsm.exe。这里同样有位数问题32 位的用Framework目录下的64 位的用Framework64目录下的。:: 注册为 COM 可见组件同时导出类型库 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe ^ C:\path\to\MyLib.dll /codebase /tlb:MyLib.tlb /verbose :: 卸载 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /u C:\path\to\MyLib.dll/codebase这个参数值得单独讲。不加它RegAsm 只在注册表里写程序集名称和版本运行时靠探测去找 dll——它会沿着 exe 所在目录、AppDomain的私有路径、GAC 一层层找。加上了它注册表里会多写一个CodeBase值直接给出 dll 的绝对路径运行时优先按这个路径加载。问题在于CodeBase是一把双刃剑。它让加载变得确定但也把 dll 的位置锁死了——你后来把 dll 挪了位置注册信息里还是老路径程序就会报加载失败。而且微软明确建议强名称程序集应该装进 GAC而不是依赖 CodeBase。所以我的实践是dll 有强名称、要发布给多个程序共用gacutil /i MyLib.dll装 GACRegAsm 时不加/codebase。dll 是私有组件、只给某一个程序用把 dll 放在 exe 同目录RegAsm 不加/codebase靠探测路径找到。实在找不到、又不想动 GAC才用/codebase并且记住dll 不能挪位置这条约束。/tlb是导出类型库文件主要给脚本宿主、老式 IDE、跨进程编组用。如果调用方是 C# 或者 C通常用不上如果要用 VBScript、PowerShell 早期版本、或者通过 COM 跨进程调用那就必须导出并注册类型库否则会拿到0x8002801D 库未注册。3.3 验证注册是否成功注册命令跑完别急着庆祝。三种验证方式从粗到细第一种翻注册表。如果你知道组件的 CLSID直接查reg query HKLM\SOFTWARE\Classes\CLSID\{你的-GUID} /s输出里应该能看到InprocServer32分支、ThreadingModel值、以及指向的 dll 路径。注意路径必须是完整绝对路径而且要和你实际部署的位置一致。如果这里指向了一个旧路径说明注册信息是历史残留需要先/u卸载再重新注册。第二种实际实例化一次。这是最有说服力的验证$obj New-Object -ComObject YourApp.YourClass.1 $obj | Get-Member能创建出来说明 CLSID、ProgID、InprocServer32 这一整条链路是通的。报0x80040154 类未注册说明注册信息没写进去或者写错了 hive/位数报0x8007007E说明注册信息对了但加载 dll 的时候依赖断了。第三种用可视化工具看。OleView.NET 这类工具可以把 CLSID、接口、类型库都列出来对着看很直观。老牌的 OleView 在新系统上已经不太能用了找 OLE/COM Object Viewer 的 .NET 重制版更稳。4. 服务注册实操sc create 到 sc start 的完整链路组件注册完之后接下来这一步是把承载它的 exe 登记成 Windows 服务。这一步的操作对象是 exe不是 dll记住这一点能省掉很多困惑。4.1 sc create 参数逐个拆基础命令长这样sc create MyServiceName ^ binPath C:\app\MyService.exe ^ DisplayName 我的业务服务 ^ start auto ^ obj NT AUTHORITY\LocalService ^ password 这里有一个堪称经典中的经典的坑等号后面必须有一个空格。binPath是对的binPath 也对但binPath...是错的sc 会把整个东西当成参数名解析然后抱怨参数不正确或者创建一个 binPath 为空的服务。这个设计源于 sc 的参数解析器沿用了老式命令行工具的约定非常反直觉但必须遵守。第二个坑是路径里的空格。如果 exe 路径包含空格比如在Program Files下面光加引号还不够因为外层已经有一层引号了需要用反斜杠转义内层引号sc create MyServiceName binPath \C:\Program Files\My App\svc.exe\ --config \C:\Program Files\My App\conf.xml\如果参数再复杂一点建议先去写一个.bat或者.cmd启动脚本把复杂参数都放进脚本里binPath只指向那个 bat能避开大量的转义地狱。这也是很多实际项目的做法。几个关键参数的含义整理如下参数取值说明binPathexe 完整路径服务的实际启动命令含参数必须绝对路径startauto/demand/disabled/delayed-auto开机自启 / 手动 / 禁用 / 延迟自启obj账号名服务运行身份不写默认 LocalSystempassword密码配合 obj 使用系统内置账号留空DisplayName显示名服务管理器里看到的名字可以用中文typeown/share/interact独立进程 / 共享进程 / 允许桌面交互depend服务名依赖的服务多个用/分隔创建完之后启动和查询sc start MyServiceName sc query MyServiceName sc qc MyServiceName :: 查看配置详情 sc config MyServiceName start demand sc delete MyServiceNamesc delete之后服务在服务管理器里不会立刻消失要刷新或者重启服务管理控制台才会从列表里去掉。这不是删除失败只是 UI 缓存。4.2 PowerShell 与 .NET 服务安装器如果你更习惯 PowerShell等价的操作是New-ServiceNew-Service -Name MyServiceName -BinaryPathName C:\app\MyService.exe -DisplayName 我的业务服务 -StartupType Automatic它的好处是参数不用写那个诡异的空格写起来干净得多。但灵活性不如 sc——比如指定服务账号密码、设置依赖关系还是得回到sc config去补。我的习惯是创建用 PowerShell精细调整用 sc。还有一条路是 .NET 的ServiceInstaller组件配合installutil.exeC:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe C:\app\MyService.exe这种方式需要你的服务项目里显式添加ServiceProcessInstaller和ServiceInstaller并且把服务名、显示名、启动类型都写在代码或者设计器里。它的优势是可以跟着服务程序一起版本管理部署的时候一条命令搞定劣势是卸载的时候同样要跑InstallUtil /u如果这一步漏了残留的服务项在重启后会一直报找不到可执行文件。4.3 服务账号怎么选服务以什么身份跑安全性和便利性的平衡点差别很大。LocalSystem是权限最高的本地账号几乎可以对系统做任何事网络身份是计算机账号。图省事的时候很多人直接用这个但它的权限远远超出一个业务进程需要的范围。一旦服务被利用攻击面是整台机器。LocalService权限最小只有本地受限权限网络上是匿名身份。适合不需要访问网络资源的本地后台任务。NetworkService介于两者之间网络上是计算机账号身份本地权限也比较受限。自定义域账号在需要访问域内资源共享目录、数据库的 Windows 认证、其他服务的时候用。这时候密码管理是个问题——服务账号密码改了所有相关服务都要跟着改忘了哪个就是一堆启动失败。生产环境里更推荐用组托管服务账号gMSA密码由域控自动轮换不用人工维护。选择的时候有个简单原则从权限最低的那个开始试跑不起来再往上加。而不是一上来就给 LocalSystem出了问题再往后缩。4.4 注册成功但服务起不来问题出在哪这是最高频的一种求助场景。服务列表里已经有了但启动就报错 1053 或者 1067。这几个错误码背后对应的问题方向完全不同错误 1053服务没有及时响应启动或控制请求通常意味着服务进程启动了但没在规定时间内默认 30 秒实际是 SCM 等待 OnStart 返回向 SCM 报告自己已经就绪。常见原因有三个一是服务代码里的OnStart里做了耗时操作比如同步拉取大量数据、等待网络超时二是服务的入口点压根不是有效的服务宿主程序比如把一个普通的控制台 exe 强行注册成服务它跑起来打印几行字就退出了SCM 从来看不到状态上报三是依赖的服务没启动导致自己的初始化卡住。**错误 1067进程意外终止**这个更直白进程跑起来然后崩了。九成情况下是配置缺失、依赖文件找不到、端口被占用。这时候去看 Windows 事件查看器路径是Windows 日志 → 系统来源筛选Service Control Manager看事件 ID事件 ID含义7000服务因错误无法启动7009等待服务连接超时7011等待服务响应事务超时7031服务意外终止将被重启7034服务意外终止无恢复动作7043服务没有正常关闭**错误 5拒绝访问**通常在sc start阶段出现原因是服务的运行账号对 exe 文件或者工作目录没有读/执行权限。表现是注册好了一启动就拒绝访问。检查方法很直接把 exe 所在目录的权限列出来看看服务账号有没有读和执行权限。默认情况下LocalService和NetworkService对用户目录下的文件是没有访问权限的服务放在C:\Users\xxx\下面必然出问题。5. 常见报错与排查速查前面几节讲的是怎么让它跑起来这一节讲跑不起来怎么救。我把这些年攒下来的报错表和排查顺序整理一下遇到问题直接查表能省不少时间。5.1 高频错误码对照表错误码报错文案最可能的原因0x80070005拒绝访问未提权文件只读DCOM 启动权限不足0x8007007E找不到指定的模块依赖 dll 缺失VC 运行库未安装位数不匹配0x8002801D库未注册依赖的类型库没有注册0x80040154类未注册CLSID 未写入写错的 hive32/64 位错位0x800401F3无效的类字符串ProgID 不存在多打了版本号后缀或者拼错0x800700C1不是有效的 Win32 应用程序把 32 位 dll 当 64 位加载或文件损坏0x80004005未指定的错误DllRegisterServer 内部失败万能错误必须配合 ProcMon 查0x8007000E内存不足极少数情况是真的内存紧张更多是组件内部逻辑异常0x8007007E和0x80040154是最容易混淆的一对。区分它们的办法很简单0x80040154说明系统根本没找到注册信息0x8007007E说明注册信息找到了但按注册信息里的路径去加载 dll 的时候失败了。所以如果报 7E你应该去查注册表里那个 InprocServer32 的路径对不对、依赖全不全如果报 154先去确认注册表项到底写没写进去、写在哪个 hive、哪个位数分支下。5.2 排查工具链与推荐顺序我个人的排查顺序是这样的从便宜到贵第一步看返回码和事件日志。命令返回码、事件查看器里的 Service Control Manager 日志、注册表里对应的键值这些是零成本的。大量问题在这一步就能定位。第二步确认位数和路径。用 dumpbin 确认 dll 位数用资源管理器确认路径存在用reg query确认注册表项内容。这一步能解决注册表写进去了但程序找不到这一类问题。第三步上 Process Monitor。前面两步都没结论的时候ProcMon 是唯一能告诉你运行时到底发生了什么的工具。过滤Process Name和Result is NAME NOT FOUND跑一次完整的注册或启动过程看加载失败了哪些文件。第四步看导入表和依赖树。用 Dependencies 工具打开 dll看整个依赖图里哪些节点标红。注意对 API Set 的误报要忽略。第五步上事件日志的应用程序日志。如果是 .NET 服务这里会有托管异常堆栈如果是原生程序崩溃可能会有 WER 记录。5.3 几个反复踩到的坑坑一卸载不干净导致的幽灵注册。一个组件注册了两次指向两个不同的 dll 路径程序加载到的是旧的那个。这种情况在测试机上升级版本之后特别常见。处理办法是每次注册之前先/u一次不管上次有没有注册过。卸载失败也无所谓重点是让注册表被覆盖一次。坑二dll 被进程占用覆盖失败但错误被吞掉。部署脚本里先复制 dll 再注册如果 dll 被某个正在运行的服务占着复制会失败但脚本没检查返回码继续执行注册——注册的还是旧文件看起来一切正常实际跑的代码是旧的。所以部署脚本里每一步都要检查返回码尤其是文件复制这一步。坑三杀毒软件的实时防护拦截注册表写入。注册动作会往HKLM\Software\Classes下面写大量键值这在某些安全软件的启发式规则里是敏感行为会被静默拦截。表现是regsvr32 返回成功但注册表里什么都没有。排查方法是临时关掉实时防护重试一次或者去看安全软件的拦截日志。坑四服务路径没加引号导致启动到了别的程序。如果 binPath 是C:\Program Files\App\svc.exe但没加引号SCM 解析出来的可执行文件可能是C:\Program.exe而参数是Files\App\svc.exe。这个错误在早期系统上是一个真实的安全问题来源。所以只要路径里有空格就必须引号加转义没有例外。6. 工程化与免注册方案前面讲的都是手动操作层面的东西但实际项目里手动敲命令是不可维护的。这一节说说两种更工程化的思路。6.1 安装包自动注册能做但要谨慎大部分安装包制作工具WiX、Inno Setup、NSIS 等都提供了注册 COM 组件的能力。WiX 的做法是通过heat工具扫描 dll把它里面的注册信息提取成静态的注册表项写进安装包。这涉及到一个重要的取舍。微软从很早以前就把自我注册self-registration也就是安装过程中调用 dll 自己的 DllRegisterServer标记为不推荐的做法。WiX 里的SelfRegCost属性早就被标注为废弃。原因有三条一是自我注册需要在目标机器上执行组件自己的代码安装过程变得不可预测二是卸载时依赖 dll 自己的DllUnregisterServer这个函数经常写得有 bug导致卸载残留三是权限模型不好控制注册过程可能需要比安装本身更高的权限。所以更稳妥的做法是安装前用 heat 之类的工具把注册表项静态提取出来安装包只负责写这些静态键值不执行组件代码。这样做的好处是注册内容完全可审计、卸载能干净移除、安装过程也不需要额外提权。代价是 dll 里如果注册逻辑是动态的比如根据环境判断写不同的键静态提取就覆盖不了这时候只能回到自我注册。6.2 免注册 COM一个值得考虑的替代路线如果你的场景允许其实有一条完全绕开注册表的路——免注册 COMRegistration-Free COM也叫 Reg-Free COM 或 SxS 并行程序集。原理是在应用程序的清单文件manifest里直接声明我要用的 COM 组件在哪个 dll、它的 CLSID 是什么、ProgID 是什么、线程模型是什么。运行时Windows 的激活上下文会先查清单命中就直接按清单里的路径加载 dll完全不去查注册表。一份简化后的清单结构大概是这样assembly xmlnsurn:schemas-microsoft-com:asm.v1 manifestVersion1.0 assemblyIdentity typewin32 nameMyCom.SideBySide version1.0.0.0/ file nameMyCom.dll comClass clsid{你的-GUID} threadingModelApartment progidMyCom.Widget.1/ /file /assembly这份清单要嵌进调用方 exe 的资源里通常是RT_MANIFEST资源、ID 1。如果调用方不止一个还得用dependentAssembly把这份清单作为依赖声明进去。这条路的优势非常明显不写注册表、不需要管理员权限、可以多版本共存不同程序用不同版本的同一个组件、绿色部署直接拷贝就能用。我见过不少内部工具用这个方案效果很好。它的限制也很明确用之前必须确认只对显式声明了清单的进程有效。如果这个组件最终是被某个第三方宿主进程加载比如被浏览器、Office、或者某个系统组件按 CLSID 拉起那个进程的清单里没有你的声明照样得走注册表。跨进程编组依然需要类型库注册。如果你是进程内组件没问题如果真的要做 LocalServer32 那种跨进程 COM编组信息和代理 dll 还是会牵扯到注册表。调试体验稍差。清单里的 GUID 写错了报错信息不会告诉你哪一行错了只会在激活失败的时候给一个笼统的错误码。建议第一次配置的时候拿一个简单的宿主程序验证确认清单结构和 GUID 都对。我个人在内部小工具上的选择是如果组件只被自己写的程序用就优先走免注册路线省心如果组件要发布给外部方、或者会被系统或其他软件按 CLSID 拉起那就老老实实做注册并且把注册逻辑放进安装包里做成静态注册表项。这两种路线的分界线就是调用方是不是我控制的进程判断清楚之后选哪条路就不纠结了。
返回列表