uuid不匹配导致ActiveX控件编译后不能正确注册

近期需要修改一个以前做的ActiveX(ocx)控件(MFC架构),添加部分功能以作新用。为了防止新旧控件产生冲突,将ocx的ClassID进行修改。之后,在控件注册时出现了一些问题,费了些脑筋。现在将问题和解决方法都记录一下,算是自己的备忘录,也供遇到类似问题的朋友参考。

问题描述:
修改了ocx的ClassID之后,编译不能通过,注册不成功,编译不能通过,且弹出报错窗口。
注册出错弹窗
关闭窗口后,显示如下的错误信息。

1>Project : error PRJ0050: Failed to register output. Please try enabling Per-user Redirection or register the component from a command prompt with elevated permissions.

使用regsvr32命令手动注册也不能成功,报同样错误之后关闭。用Depend工具检查.ocx关联文件,查不出缺失的依赖文件。

问题分析:
回想一下MFC生成ocx的过程,会发现通过引导建立的ActiveX工程有以下重要文件:3个重要的类、.idl、.def。
为了方便描述,将控件工程名称定为OcxTest,则上文提到的“3个重要的类”分别是COcxTestAppCOcxTestCtrlCOcxTestPropPage,对应名为OcxTest(.cpp/.h)OcxTestCtrl(.cpp/.h)OcxTestPropPage(.cpp/.h)的3组文件。以下关于这3组文件的描述来源于“风思雨散”博客文章

OcxTest:定义了DllRegisterServer和DllUnregisterServer,主要实现ActiveX的注册和反注册。
OcxTestCtrl:可以发现该头文件中声明了消息映射(让ActiveX控件程序可以接受系统发送的事件通知,如窗体创建和关闭事件),调度映射(让外部调用程序(包含ActiveX的容器)可以方便地访问ActiveX控件的属性和方法),事件映射(让ActiveX控件可以向外部调用程序(包含ActiveX的容器)发送事件通知)。也就是说对ActiveX控件的窗口操作都将在这个类中完成,包括ActiveX控件的创建,重绘,以及在此类中创建可视MFC窗体。
OcxTestPropPage:ActiveX控件属性页的相关信息。

OcxTest.idl中则描述了控件的几个有用的编码,以及需要暴露的方法、属性和事件。可以在.idl文件中找到如下4种uuid:

[ uuid(E30B4E80-47D9-4735-87B5-2351805F3F8A), version(1.0),
helpfile("OcxTest.hlp"),
helpstring("OcxTest ActiveX Control module"),
control ]
library OcxTestLib
{
importlib(STDOLE_TLB);
……
[ uuid(9F00C8CE-A1BE-4C03-A199-3B8F626560DC),
helpstring("Dispatch interface for OcxTest Control")]
……
[ uuid(83DBBADD-C091-4168-8863-45BCB5273115),
helpstring("Event interface for OcxTest Control") ]
……
[ uuid(9A73DB73-2CA3-478D-9A3F-7E9D6A8D327C),
helpstring("OcxTest Control"), control ]

这4种uuid分别对应于ActiveX控件的库、调度映射、事件映射、控件类。
调度映射用于向外暴露ActiveX方法以及属性,当然在这里没有实现,实现是OcxTestCtrl.cpp里要做的。事件调度映射用于向外暴露ActiveX事件,当然也是在OcxTestCtrl.cpp里实现。最后一个uuid,也就是控件的ClassID了。

解决方法:
上面讲到,ActiveX工程最重要3组文件中,注册相关的方法在OcxTest.cpp中实现,自然由此查起。在OcxTest.cpp中,我们可以发现以下2句代码:

const GUID CDECL BASED_CODE _tlid =
{ 0xE30B4E80, 0x47D9, 0x4735, { 0x87, 0xB5, 0x23, 0x51, 0x80, 0x5F, 0x3F, 0x8A } };

const GUID CDECL CLSID_SafeItem =
{ 0x9A73DB73, 0x2CA3, 0x478D, { 0x9A, 0x3F, 0x7E, 0x9D, 0x6A, 0x8D, 0x32, 0x7C } };

打开原始工程,比对这两句代码中的十六进制数据,会发现是ActiveX库和ActiveX类的uuid的拆分。再往下查,会发现注册/反注册的两个方法都调用了这边定义的const GUID。由此得到启发,实现ActiveX调度映射和ActiveX事件映射的uuid,应该在OcxTestCtrl.cpp中也需要使用,查OcxTestCtrl.cpp果然得到验证,可以找到如下3处:

IMPLEMENT_OLECREATE_EX(COcxTestCtrl, "OCXTEST.OcxTestCtrl.1",
0x9A73DB73, 0x2CA3, 0x478D, 0x9A, 0x3F, 0x7E, 0x9D, 0x6A, 0x8D, 0x32, 0x7C)

const IID BASED_CODE IID_DOcxTest =
{ 0x9F00C8CE, 0xA1BE, 0x4C03, { 0xA1, 0x99, 0x3B, 0x8F, 0x62, 0x65, 0x60, 0xDC } };

const IID BASED_CODE IID_DOcxTestEvents =
{ 0x83DBBADD, 0xC091, 0x4168, { 0x88, 0x63, 0x45, 0xBC, 0xB5, 0x27, 0x31, 0x15 } };

将上述5处依次修改为正确的数据,重新编译即可通过并完成注册。当然,如果还有其他自定义方法需要调用这4组uuid的,也要相应的修改。
综上所述,修改ActiveX控件ClassID的时候,只在.idl中改了,却没有在对应的实现方法中改,导致控件自身uuid在定义和实现时不匹配,是造成这个场景中注册失败的原因。这种情况下,上述报错弹窗及错误信息并不是因为在Win7/8下没有使用管理员权限进行控件注册造成的,也不是因为控件注册路径及系统变量中缺少关联文件造成的。由MFC向导生成的ActiveX控件当然不会出现这种问题,因为.idl文件以及其他几处对uuid的调用,在工程建立的时候,就已经匹配好了。

 

二零一五年十二月三日

顾毅写于厦门