简介:PDF OCX控件是一套面向桌面与网页开发者的ActiveX组件集成示例,适合需要在VB、VS2008、C++、C#及HTML页面中嵌入PDF阅读与操作能力的开发者参考。配套包共105个文件、约5.25MB,涵盖C++头文件与实现源码、动态链接库、OCX组件、可执行示例以及C#、VB工程文件和解决方案配置,另含少量HTML、批处理与说明文本,便于对照不同语言环境的调用方式。控件功能围绕分页浏览、按窗口区域自适应匹配、页面缩放与旋转展开,并可能提供书签导航、注释、文本搜索及PDF创建合并等扩展接口。开发者可借助其中的API与事件示例,实现点击响应、页面加载完成等自定义交互,减少从零封装PDF组件的成本。目前已有1213人学习下载,适合希望快速验证ActiveX嵌入方案、了解多语言工程组织的中高级开发者。
1. 从一张打不开的 PDF 说起:PDF OCX ActiveX 控件到底管哪一段
如果你维护的 Windows 桌面系统里,客户丢来一句“PDF 要在窗口里直接看、直接打”,这时候 PDF OCX ActiveX 控件往往比推翻重做更快落地。它不是独立阅读器,而是把 PDF 渲染、翻页、缩放和打印封成 COM 控件,嵌进 WinForms、MFC、Delphi 这类宿主。适合已经在维护桌面项目、需要把预览能力塞进业务界面的人。先别急着拖控件,进程位数、COM 注册和线程模型没对齐,后面全是白屏和 Class not registered。
2. 拆开 OCX 的壳:COM 注册、进程位数与渲染链路
2.1 OCX 不是普通 DLL:它靠 COM 注册被宿主创建
OCX 的扩展名看起来像一个动态库,实际使用方式不是“复制到目录然后 LoadLibrary”这么简单。它通常是一个进程内 COM 服务器,宿主通过 ProgID 或 CLSID 让 COM 运行时去注册表里找路径,再创建对象。宿主拿到的不是一个普通函数表,而是一组属性、方法和事件,很多控件还带窗口句柄,必须跑在有消息循环的 UI 线程上。把 OCX 当普通 PDF 解析库塞进控制台程序,或者丢进服务端批量任务里,往往会卡在“创建成功但无法渲染”这一步。
选型时先看宿主形态。已有 WinForms、MFC、Delphi 桌面端,界面里需要所见即所得的预览和打印,OCX 的链路最短:控件负责渲染,宿主负责按钮、页码、权限和业务状态。服务端批量转图片、抽取文本、无界面合并 PDF,这类场景不该选带窗口的 ActiveX 控件,应该换无 UI 的 PDF 库。跨平台应用、移动端、纯 Web 前端也不要硬套,COM 和 OCX 的边界就在 Windows 桌面容器里。这个判断不做清楚,后面所有注册和位数问题都会被误认为“控件坏了”。
还有一个容易被忽略的点:OCX 的“打开 PDF”通常不是把整个文件读进内存就完事。控件内部可能落临时文件、加载字体、调用系统打印子系统,甚至依赖当前用户的临时目录权限。开发机上管理员账户跑得通,不代表客户机上受限账户也能跑。我的习惯是先用一个最小宿主验证三件事:能不能创建控件、能不能打开本地 PDF、能不能打印到系统自带 PDF 打印机。三件事都过,再谈嵌业务界面。
2.2 先查位数再注册:regsvr32 不是万能钥匙
OCX 最常见的第一道坎是位数。32 位控件不能被 64 位进程加载,64 位控件也不能被 32 位进程加载。Windows 上regsvr32.exe本身也分 32 位和 64 位,注册时用错版本,会出现“明明注册成功,程序还是找不到”的假象。先看 OCX 的 PE 头,确认它是 x86 还是 x64,再决定宿主平台目标。下面这段 PowerShell 直接读文件头,不依赖开发工具:
$path = "C:\components\YourPdfOcx.ocx" $bytes = [System.IO.File]::ReadAllBytes($path) # DOS 头 0x3C 处存放 PE 头偏移,PE 头后 4 字节是 Machine $peOffset = [BitConverter]::ToInt32($bytes, 0x3C) $machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4) switch ($machine) { 0x014c { "x86" } 0x8664 { "x64" } default { "unknown: 0x{0:X4}" -f $machine } }$path换成实际 OCX 路径,输出x86就说明宿主进程必须编译成 32 位,输出x64就相反。0x014c和0x8664分别是 32 位和 64 位 PE 的 Machine 字段,这不是猜的,是文件本身写死的。查完位数再注册,能省掉一半“玄学加载失败”。
注册时按位数选注册器。64 位 Windows 上,%windir%\SysWOW64\regsvr32.exe是 32 位版本,%windir%\System32\regsvr32.exe是 64 位版本。下面两条命令不要同时无脑执行,先确认控件只有一种位数还是双位数版本:
:: 注册 32 位 OCX(64 位 Windows 上使用 SysWOW64 下的注册器) %windir%\SysWOW64\regsvr32.exe /s "C:\components\YourPdfOcx.ocx" :: 注册 64 位 OCX(使用 System32 下的注册器) %windir%\System32\regsvr32.exe /s "C:\components\YourPdfOcx.ocx"/s表示静默注册,失败时不弹窗,适合脚本;第一次排查建议去掉/s,让系统把 HRESULT 和失败原因弹出来。注册到 HKLM 需要管理员权限,注册到当前用户范围则不一定,具体看控件安装方式。注册完成后别只看“成功”提示,要查注册表路径是否指向你刚放的文件:
reg query "HKCR\YourPdfOcx.PdfViewer\CLSID" /ve reg query "HKCR\CLSID\{你的CLSID}\InprocServer32" /ve第一行用 ProgID 查 CLSID,第二行用 CLSID 查实际 DLL/OCX 路径。YourPdfOcx.PdfViewer只是占位,真实 ProgID 以控件类型库或安装说明为准。路径指到旧目录,说明系统里残留了旧注册;路径为空或查不到,说明注册没落到你预期的位置。把这两步做完,再打开宿主程序,很多“Class not registered”会当场消失。
2.3 渲染链路:文件路径、临时目录、打印驱动三件事
OCX 打开 PDF 的常见入口是文件路径,不是字节数组。宿主从数据库或上传接口拿到 PDF 字节后,稳妥做法是先落到本地临时文件,再把路径交给控件。临时目录要选当前用户可写的目录,不要写死C:\Windows\Temp,也不要用网络共享路径做第一次验证。文件名先用英文和短路径,比如C:\ocx-smoke\sample.pdf,排除中文、空格、长路径和 UNC 权限的干扰。等最小宿主跑通,再逐步换成真实业务路径。
打印是第二个黑匣子。控件通常把 PDF 页面交给系统打印子系统,纸张大小、方向、页边距、缩放模式只要有一个和 PDF 页面尺寸打架,就会出现裁切、偏移、多出空白页。第一次打印不要用共享打印机,也不要用虚拟打印机排队很长的环境,先用本机系统自带 PDF 打印机输出三页,核对页数、方向和页边距。打印前把默认打印机设置好,打印任务结束后再切回去,避免影响用户后续操作。打印过程中不要在主线程里做长时间阻塞等待,否则界面会像卡死一样。
字体和加密文件也归在这一段。PDF 内嵌字体缺失时,控件可能用系统字体替换,显示和打印会有细微差异;加密 PDF 则需要先设置密码再打开,顺序反了会一直弹密码框。我的验证顺序是:普通单页 PDF、多页 PDF、带中文的 PDF、加密 PDF、A4 和 Letter 各一份。每换一种样本只改一个变量,出问题才能定位到是路径、字体、加密还是纸张。渲染链路不是“能打开就行”,而是“打开、翻页、缩放、打印”四步都能复现。
3. 接入宿主程序:从工具箱拖拽到运行期创建的完整路径
3.1 三种宿主接入方式:WinForms 最省事,MFC 次之,Delphi 看类型库
WinForms 接入 OCX 的路径最短。设计器里打开“选择工具箱项”,从 COM 组件页签勾选已注册的控件,拖到窗体上,IDE 会生成Ax开头的包装类和互操作程序集。代码里操作的是包装类暴露的属性、方法和事件,不需要自己写CoCreateInstance。MFC 的思路类似,在对话框资源里插入 ActiveX 控件,用类向导生成成员变量,然后在OnInitDialog里打开文档。Delphi 则通过导入组件或类型库生成包装单元,位数同样要匹配。
三种宿主的差异主要在初始化和释放时机。WinForms 的控件句柄在窗体显示前后才稳定,太早在构造函数里打开 PDF,可能遇到控件还没创建窗口的问题。MFC 的 ActiveX 控件在对话框初始化阶段创建,OnInitDialog里可以调用打开方法,但要注意返回值。Delphi 导入的包装单元有时把方法名改写成驼峰或加前缀,不要照抄示例里的方法名,按类型库实际生成的声明来。共同点是:都依赖 COM 注册,都要求宿主进程位数匹配,都要在 UI 线程调用。
| 宿主 | 接入方式 | 主要注意点 |
|---|---|---|
| WinForms | 工具箱插入 COM 组件,生成 AxHost 包装类 | 平台目标改为 x86 或 x64,与 OCX 一致 |
| MFC | 对话框插入 ActiveX 控件,类向导生成成员 | 在OnInitDialog后调用打开方法 |
| Delphi | 导入 ActiveX 控件或类型库 | 包装单元方法名以实际导入结果为准 |
如果项目里已经有成熟的 PDF 预览窗口,只是缺打印和翻页,优先复用现有宿主,不要为了一个控件重写整个界面。反过来,如果业务只需要后台抽取文本,不要因为“OCX 能显示”就把它塞进服务端。接入方式的选择标准不是哪个看起来高级,而是哪个能把窗口生命周期、线程模型和部署位数管住。
3.2 WinForms 最小宿主:设计器生成包装类,代码只做生命周期
下面是一个最小 WinForms 宿主。重点不是方法名,而是控件创建、BeginInit/EndInit、Shown事件和平台目标。AxYourPdfOcxLib.AxYourPdfViewer是设计器导入 COM 组件后生成的类型,实际命名以你的导入结果为准,不要原样照抄:
using System; using System.Windows.Forms; namespace PdfOcxSmoke { public partial class MainForm : Form { // 该字段通常由设计器插入 COM 组件后生成 private AxYourPdfOcxLib.AxYourPdfViewer pdfViewer; public MainForm() { InitializeComponent(); pdfViewer = new AxYourPdfOcxLib.AxYourPdfViewer(); ((System.ComponentModel.ISupportInitialize)(pdfViewer)).BeginInit(); pdfViewer.Dock = DockStyle.Fill; Controls.Add(pdfViewer); ((System.ComponentModel.ISupportInitialize)(pdfViewer)).EndInit(); Shown += (sender, args) => { // 先用短英文路径,排除中文路径和权限干扰 pdfViewer.OpenPdf(@"C:\ocx-smoke\sample.pdf"); }; } } }BeginInit和EndInit是 ActiveX 包装类常用的初始化配对,跳过它可能出现属性设置顺序问题。Shown事件保证窗体已经显示、控件句柄已经创建,比在构造函数里直接打开更稳。Dock = Fill只是让控件铺满窗体,实际布局按业务来。OpenPdf是语义名,真实方法可能是OpenFile、LoadFile或带参数的重载,以类型库为准。
平台目标必须和 OCX 位数一致。WinForms 项目默认 AnyCPU,在 64 位系统上会跑成 64 位进程,加载 32 位 OCX 就会报BadImageFormatException。在项目文件里显式指定:
<PropertyGroup> <PlatformTarget>x86</PlatformTarget> </PropertyGroup>如果 OCX 是 64 位,就改成x64。不要用AnyCPU加Prefer32Bit去赌,ActiveX 加载失败时不会给你太友好的提示。入口点还要保持 STA 线程:
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); }STAThread不是可有可无的装饰。很多 ActiveX 控件内部依赖单线程套间,缺了它可能出现拖拽卡死、剪贴板异常或 COM 激活失败。WinForms 模板默认带这个特性,但控制台转过来的项目、自己手写入口的项目经常漏掉。
3.3 运行期创建与释放:别让 COM 对象活过窗口
控件释放顺序不对,最常见的结果是关闭窗口后进程不退,或者第二次打开 PDF 时报“对象已释放”。稳妥顺序是:先关闭文档,再把控件从容器移除,最后Dispose包装类。下面这段放在窗体关闭事件里:
protected override void OnFormClosed(FormClosedEventArgs e) { if (pdfViewer != null) { // 先关文档,再释放控件;方法名以实际类型库为准 pdfViewer.CloseDocument(); Controls.Remove(pdfViewer); pdfViewer.Dispose(); pdfViewer = null; } base.OnFormClosed(e); }CloseDocument用来释放 PDF 文件句柄,Controls.Remove把控件从窗口容器摘掉,Dispose释放 COM 包装和底层资源。不要手动对同一个对象反复Marshal.ReleaseComObject,包装类自己会管引用计数,手动释放过头会变成“用了已释放对象”。如果业务里有多个预览窗口,每个窗口各管各的控件,不要共用一个全局 ActiveX 实例。
MFC 的最小调用类似,只是成员变量由对话框资源生成:
BOOL CPdfHostDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // m_pdfViewer 由“插入 ActiveX 控件”生成 m_pdfViewer.OpenFile(_T("C:\\ocx-smoke\\sample.pdf")); return TRUE; }MFC 里尤其注意不要在OnInitDialog之前访问控件,也不要在工作线程直接调用控件方法。需要后台加载文件时,先把文件准备好,再PostMessage回 UI 线程打开。OCX 的窗口和消息循环是绑在宿主线程上的,跨线程调用不是“加个锁”就能解决。
4. 参数与事件:打开、缩放、翻页、打印怎么落到调用上
4.1 打开文档:文件路径、流、密码和临时文件
打开文档的入口通常有三种:本地路径、流、以及带密码的打开方法。路径方式最稳,流方式要看控件是否真的支持,有些 OCX 只接受文件路径,所谓“流加载”内部还是落临时文件。加密 PDF 要先设置密码再打开,顺序反了会一直停在密码输入状态。下面用语义名示意,真实成员名以导入的类型库为准:
string pdfPath = @"C:\ocx-smoke\sample.pdf"; if (!System.IO.File.Exists(pdfPath)) { throw new System.IO.FileNotFoundException(pdfPath); } // 普通文档直接打开 pdfViewer.OpenFile(pdfPath);密码文档的常见顺序是:
// 先设置密码,再打开;方法名和顺序以控件类型库为准 pdfViewer.SetPassword("123456"); pdfViewer.OpenFile(@"C:\ocx-smoke\encrypted.pdf");pdfPath要使用当前用户可读的本地路径,网络路径先映射或复制到本地。文件存在不代表控件能打开,还要看 PDF 是否损坏、是否加密、是否缺少字体。我的习惯是先用系统里能正常打开的阅读器验证样本,再交给 OCX,避免把坏文件误判成控件问题。
| 调用意图 | 常见成员形式 | 参数说明 |
|---|---|---|
| 打开文件 | OpenFile(path) | 本地路径优先,先测英文短路径 |
| 从流加载 | LoadFromStream(stream) | 并非所有控件支持,失败时落临时文件 |
| 加密文档 | SetPassword(pwd)后打开 | 顺序以类型库为准 |
| 关闭文档 | CloseDocument() | 释放文件句柄,切换文件前调用 |
如果宿主拿到的是上传字节,不要直接把MemoryStream传给控件然后立刻释放。控件可能异步渲染,流被关掉后页面就白了。稳妥做法是写到临时文件,打开成功后保留到文档关闭,再删除临时文件。临时文件名用 GUID 加.pdf,放在当前用户临时目录下,避免并发冲突。
4.2 视图参数:Zoom、FitMode、Page 不要同时抢控制权
视图参数里最容易打架的是缩放模式和固定缩放比例。Zoom = 125表示按百分比缩放,FitMode = 1可能表示适应宽度或适应页面,两者同时设置时,后设置的生效,但不同控件的内部处理不一样,有的会先记下缩放再被适配模式覆盖,表现就是“代码设了 125,界面却是 80”。我的做法是先设适配模式,再根据用户操作切换到固定缩放,不要在一个方法里把所有参数都写一遍。
| 参数 | 常见含义 | 注意点 |
|---|---|---|
Zoom | 百分比缩放 | 与适配模式二选一 |
FitMode | 适应宽度/页面 | 枚举值以类型库为准 |
Page | 当前页码 | 通常从 1 开始 |
Rotation | 旋转角度 | 只接受 0/90/180/270 |
ScrollX/Y | 滚动位置 | 翻页后可能被重置 |
// 先翻页,再设置视图;枚举值 1 只是示意,实际以类型库为准 pdfViewer.Page = 3; pdfViewer.Zoom = 125; // 适应宽度和固定缩放不要同时依赖,后设置的会覆盖前者 pdfViewer.FitMode = 1;页码参数要确认是从 0 还是从 1 开始。很多 OCX 的Page从 1 开始,但事件回调里的索引可能从 0 开始,翻页按钮和状态栏对不上,往往就是这里差了一位。高 DPI 屏幕上,控件按屏幕像素渲染,宿主是否声明 DPI 感知会影响实际显示大小。先用 100% 缩放和默认 DPI 验证,再开高分屏测试,不要一上来就在 4K 屏上调参数。
4.3 打印和事件:打印范围、份数、取消与异常
打印接口通常分“全部打印”和“按范围打印”。范围参数一般是起始页、结束页、份数,有的控件还会加一个“是否打印批注”的布尔值。打印前先确认默认打印机有效,打印范围不要超过总页数,份数不要传 0。下面代码里的方法签名是示意,实际以类型库为准:
// 打印第 1 到第 3 页,打印 2 份;参数顺序以实际控件为准 pdfViewer.PrintPages(1, 3, 2);打印前检查打印机:
var settings = new System.Drawing.Printing.PrinterSettings(); settings.PrinterName = "系统自带 PDF 打印机"; if (!settings.IsValid) { throw new InvalidOperationException("打印机不可用"); }事件是 OCX 比普通库更有价值的部分。文档加载完成、页码变化、打印进度、错误上报,都可以通过事件回传给宿主。下面用语义名示意,真实事件名和参数类型以导入结果为准:
pdfViewer.OnDocumentLoaded += (sender, args) => { // 文档加载完成后更新页码和按钮状态 }; pdfViewer.OnPrintProgress += (sender, args) => { // 进度事件可能来自非 UI 线程,更新界面前先 Invoke }; pdfViewer.OnError += (sender, args) => { System.Diagnostics.Debug.WriteLine(args.Message); };事件回调里不要做重活。打印进度事件可能触发很频繁,在里面刷新整个界面会拖慢打印。需要更新进度条时,记录最新值,用定时器或Invoke合并刷新。错误事件不要只弹窗,至少写到日志里,包含文件路径、页码、HRESULT 和打印机名称。出问题时,这些字段比“打印失败”四个字有用得多。
5. 排查清单:加载失败、白屏、打印错位的五个现场
5.1 现象:Class not registered / 0x80040154
现象是宿主一启动就抛COMException,HRESULT 为0x80040154,提示类未注册。原因通常有三种:OCX 根本没注册、注册到了另一个位数、注册表指向了旧路径。先查 ProgID 到 CLSID 的映射,再查 CLSID 到实际文件的路径:
reg query "HKCR\YourPdfOcx.PdfViewer\CLSID" /ve reg query "HKCR\CLSID\{你的CLSID}\InprocServer32" /ve第一条命令查不到,说明 ProgID 不对或未注册;第二条命令路径为空,说明注册损坏;路径指向旧目录,说明需要重新注册覆盖。用管理员权限运行匹配位数的regsvr32,注册后重新打开宿主,不要指望已经运行的进程自动刷新 COM 注册。还有一点:32 位宿主查 32 位注册视图,64 位宿主查 64 位视图,不要只看一个注册表位置就下结论。
5.2 现象:BadImageFormatException 或“不是有效的 Win32 程序”
现象是编译通过,运行到创建控件时抛BadImageFormatException,或者系统直接提示“不是有效的 Win32 程序”。原因基本就是位数不匹配:64 位进程加载了 32 位 OCX,或者反过来。先确认当前进程位数:
[Environment]::Is64BitProcess输出True表示 64 位进程,False表示 32 位进程。再对照 2.2 节的 PE 头检查结果。解决方式不是“多注册几次”,而是把宿主平台目标改成与 OCX 一致:32 位 OCX 用 x86,64 位 OCX 用 x64。WinForms 项目还要检查发布配置和生成配置是否都改了,只改 Debug 没改 Release,发版时照样翻车。
5.3 现象:窗口白屏、无响应、拖拽时卡死
白屏和无响应通常不是 PDF 文件的问题,而是线程模型问题。ActiveX 控件需要在 STA 线程上创建和调用,很多控件还要求宿主有消息循环。把控件创建在Task.Run里,或者在后台线程直接调用OpenFile,可能表现为白屏、卡死、甚至进程无提示退出。需要跨线程打开时,先把文件准备好,再切回 UI 线程:
private void OpenOnUiThread(string path) { if (pdfViewer.InvokeRequired) { pdfViewer.Invoke(new Action<string>(OpenOnUiThread), path); return; } pdfViewer.OpenFile(path); }InvokeRequired判断当前线程是不是控件创建线程,Invoke把调用切回 UI 线程。不要在 UI 线程里同步等待一个后台任务,然后再让后台任务回调 UI,容易互相等死。另一个常见原因是 PDF 文件被独占锁定,控件打不开又不报错,表现就是空白页。打开前确认文件没有被其他进程占用,临时目录有写权限。
5.4 现象:打印偏移、裁切、页数不对
打印错位先看纸张,不要先改控件参数。PDF 页面是 A4,打印机默认是 Letter,或者驱动默认加了页边距,输出就会裁切。打印前设置纸张大小和页边距,再调用控件打印:
var paper = new System.Drawing.Printing.PaperSize("A4", 827, 1169); var printer = new System.Drawing.Printing.PrinterSettings(); printer.DefaultPageSettings.PaperSize = paper; printer.DefaultPageSettings.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);PaperSize的宽高单位是百分之一英寸,A4 约 827×1169。Margins设为 0 只是告诉驱动不要额外加边距,实际能否无边距打印取决于打印机能力。控件内部如果还有“适应页面”选项,不要再叠加一次缩放,否则会出现内容缩小、四周留白。打印页数不对时,先确认范围参数是闭区间还是半开区间,再确认总页数是不是从事件里拿到的真实值。
5.5 现象:开发机正常,客户机加载失败
开发机能跑、客户机不能跑,优先查部署,不要先怀疑代码。常见原因包括:客户机没注册 OCX、注册了但位数不对、缺少控件依赖的运行库、临时目录不可写、授权文件没部署、安全软件拦截了 COM 注册。部署脚本不能只复制一个 OCX,注册动作、依赖安装、位数检查都要有。客户机上先用reg query查路径,再用最小宿主打开一个固定样本 PDF,把问题锁在“注册层”还是“业务层”。
授权范围也要在部署前确认清楚,尤其是随产品分发给第三方客户时。控件能在本机跑,不代表允许无限分发。另一个隐蔽问题是客户机装了多个版本,注册表指向旧版本,而旧版本的方法签名和新代码不一致,表现就是“方法找不到”或参数错位。遇到这种现场,先卸载旧注册,再注册当前版本,最后用最小宿主回归,不要直接在业务工程里反复试。
6. 发布前自检:最小宿主回归与一次血泪教训
发布前我会把验收拆成三层:注册层、加载层、打印层。注册层用脚本查 ProgID、CLSID 和实际路径,确认路径存在且位数匹配;加载层在新机器上启动最小宿主,打开固定样本 PDF;打印层用系统自带 PDF 打印机输出三页,核对页数、方向和页边距。下面这段脚本放在发布检查清单里,比人工点注册表可靠:
$progId = "YourPdfOcx.PdfViewer" $clsid = (Get-Item "Registry::HKEY_CLASSES_ROOT\$progId\CLSID").GetValue("") $dll = (Get-Item "Registry::HKEY_CLASSES_ROOT\CLSID\$clsid\InprocServer32").GetValue("") [pscustomobject]@{ ProgID = $progId CLSID = $clsid Path = $dll Exists = Test-Path $dll }GetValue("")取的是注册表默认值,Test-Path只证明文件在,不证明位数对,所以还要配合 2.2 节的 PE 检查。最小宿主不要复用业务工程,单独建一个空窗体,只放控件和三个按钮:打开、翻页、打印。业务工程出问题时,最小宿主能跑就直接查业务代码;最小宿主也跑不了,就回到注册和位数。
| 检查层 | 动作 | 通过标准 |
|---|---|---|
| 注册 | 查 ProgID、CLSID、路径 |
本文还有配套的精品资源,点击获取