创建ActiveX工程: 可以使用VC建立MFC工程或者ATL工程来进行ActiveX控件开发。使用MFC方式建立,系统会自动实现一些ActiveX控件必须的接口,开发人员只需关注业务就可以了;使用ATL方式建立,开发人员需要实现十几个COM接口(ActiveX实际上是COM的一种),需要对COM有一定的了解。但是使用MFC方式,最后发布的安装包要包含引入了的MFC的DLL(视频控件引入了两个,总共3M左右),会导致安装包会比ATL方式大一点。 1、MFC方式 使用MFC方式创建ActiveX工程比较简单,只要选择了MFC ActiveX工程,之后都按系统下一步就可以了。
2、ATL方式 使用ATL方式创建,没有仔细研究过,待补充。
调试ActiveX工程 可以使用两种方式调试ActiveX控件:ActiveX测试容器或者IE,建议使用IE作为调试工具,因为ActiveX测试容器有些地方和IE不太一样,而且实际使用是嵌入到IE中使用的。 1、使用ActiveX控件测试容器 可以使用系统自带的ActiveX测试容器来进行调试,调试时选择自己的ActiveX控件名,即可打开创建的工程。
2、使用IE 也可以使用IE来进行调试,需要自己先创建一个包含了此ActiveX控件(通过CLSID指定)的HTML文件,调试时指定参数为此文件。
HTML文件内容如: <BODY> <OBJECT ID="NVS_VAE" CLASSID="CLSID:c8cd5ebc-817b-401f-ab06-05cc55c8d9ee"> </OBJECT> </BODY> 其中ID可以随便定义,CLASSID是与创建的ActiveX工程中自动生成的ID保持一致(在创建工程的×××Ctrl.cpp中) // 初始化类工厂和guid
IMPLEMENT_OLECREATE_EX(CNVS_VAECtrl,"NVS_VAE.NVS_VAECtrl.1", 0xc8cd5ebc, 0x817b, 0x401f, 0xab, 0x6, 0x5, 0xcc, 0x55, 0xc8, 0xd9, 0xee) 注:使用IE8的话,默认是多线程,不支持ActiveX控件的调试,可以修改如下注册表,来支持调试 Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main] "TabProcGrowth"=dword:00000000
JavaScrip调用ActiveX控件中的对外方法 页面可以通过JavaScript调用ActiveX控件中的方法与控件进行交互: 1、传入参数 <BODY> <OBJECT ID="NVS_VAE" CLASSID="CLSID:c8cd5ebc-817b-401f-ab06-05cc55c8d9ee"> <PARAM NAME="lInitFacePara" VALUE="0"> </OBJECT> </BODY>
2、调用方法 通过getElementById 方法,传入控件ID(比如前面的MyPostItCtrl),来调用ActiveX控件中提供的接口(假定StartRealVideo为ActiveX中提供的对外接口) <SCRIPT language=JScript> function test() { document.getElementById("MyPostItCtrl").StartRealVideo (“摄像机ID”,”码流ID”); } </SCRIPT>
ActiveX控件的打包安装 可以把ActiveX控件打成CAB包,在页面中指定该CAB包的位置,如果目标机器访问页面时,没有安装过此控件,则IE会自动下载此CAB包到目标机器,并进行安装 1、 确认ActiveX控件依赖于哪些DLL 使用vc的DEPENDS.EXE(Microsoft Visual Studio\Common\Tools目录下)打开ocx/dll文件,即可看到其依赖的dll文件
2、 创建INF文件,用记事本编辑以下信息
[version] signature="$CHINA$" AdvancedINF=1.0
[Add.Code] evS1300.ocx=evS1300.ocx msvcr71.dll=msvcr71.dll mfc71.dll=mfc71.dll msvcp71.dll=msvcp71.dll
[evS1300.ocx] file=thiscab clsid={0440906E-9BD6-4F3E-B65A-39E1B339D9DA} FileVersion=1,0,0,0 RegisterServer=yes
[msvcr71.dll] file-win32-x86=thiscab RegisterServer=no DestDir=11 FileVersion=7,10,3052,4
[mfc71.dll] file-win32-x86=thiscab RegisterServer=no DestDir=11 FileVersion=7,10,3077,0
[msvcp71.dll] file-win32-x86=thiscab RegisterServer=no DestDir=11 FileVersion=7,10,3077,0 在evS1300.inf的内容里,[version]和[Add.Code]项是必须的,[Add.Code]的键值项的多少取决于以下你所配制项的多少。[msvcr71.dll]、[mfc71.dll]、[msvcp71.dll]就是上面我所说不是必须的项,只要你想把msvcr71.dll、mfc71.dll、msvcp71.dll包括在发布包里,那这么三项就必须写在inf里,而这三项的具体内容是固定的,可复制过去即可。最为关键的就是[evS1300.ocx]项,其中有clsid和FileVersion就是evS1300.ocx的classId和version,这要求必须一至,否我们发布出去的CAB包时不能在客户端自动更新下载安装。说到这里,那我们如何才能知道evS1300.ocx里面的classId和version呢?我在上面的必备条件里介绍到有一个用于查看ocx控件的工具ActvxDoc,对,就是用它,我们双击这个文件运行它,此时可以看到图13所示的界面:
在图13的界面里,点击“File”à“Open…”,打开您所要查看的OCX控件,如图14所示:
打开了控件之后,我们在界面的右边部位“Class”的下拉框里选择“”就可以看到我们想要查找的FileVersion和classId,如图15所示:
3、 使用iexpress进行打包 可以使用系统自带的iexpress.exe(C:\WINDOWS\system32目录下)工具进行打包。工具默认是8.3命名格式,会把不符合命名规则的文件名截断,可以在打包过程中选择长命名。
之后都下一步即可。 4、 对CAB包进行签名 1、 制作(购买)证书 正式的证书需要向相关机构购买,可以制作测试的证书,步骤如下: 在Visual Studio的安装目录下,有制作证书和签名的相关工具 E:\Microsoft Visual Studio 8\SDK\v2.0\Bin\ makecert.exe 命令行中执行如下命令来创建证书和私钥文件: E:\Microsoft Visual Studio 8\SDK\v2.0\Bin>makecert.exe -sv D:VAE.pvk -ss VAE -n "CN=huawei" -$ commercial -r D:VAE.cer 在弹出的窗口中输入证书密钥密码
再次确认密码
证书文件就生成在指定位置了。 2、 对CAB包进行签名 在Visual Studio的安装目录下,有制作证书和签名的相关工具 E:\Microsoft Visual Studio 8\SDK\v2.0\Bin\ signtool.exe 在命令行执行:signtool.exe signwizard命令,会启动签名向导
签名向导:
下一步,选择要打包的CAB文件
下一步选择自定义
下一步选择“从文件选择”,选择前面生成的证书文件
下一步选择“磁盘上的私钥文件”,选择前面生成的私钥文件
下一步输入密码
确定后选择MD5加密
之后一直下一步(根据需要填写内容,也可都默认) 最后再输入一次密码,就成功签名了
5、 页面指定路径 页面中指定CAB包位置和版本号 <BODY> <OBJECT ID="NVS_VAE" CLASSID="CLSID:c8cd5ebc-817b-401f-ab06-05cc55c8d9ee"codeBase="NVS_VAE.CAB#version=1,0,0,1"> </OBJECT> </BODY> 访问到此页面,IE自动判断CLSID在目标机器上是否有注册,没有注册重新下载,并注册,有注册,比较版本号,有更新则取根据setup.ini中的版本号进行更新。可以到C:\WINDOWS\Downloaded Program Files目录查看CAB包是否有下载安装。 6、 使用安装程序打包 另外,也可以制作安装程序(可以使用常用的安装程序制作工具,也可以使用VS自带的建立安装部署功能来进行),把安装程序打到CAB包中,这样当IE下载CAB包的客户机上后,会根据setup.ini中的指定执行命令,执行安装程序,由客户自己选择安装路径 脚本如下: ;//这里注意了,发行包安装的信息 [setup.exe] fileVersion=8.0.50727.42 hook=InstallerHook ;//这项并没有设置file-win32-x86的值,因为它并不在CAB里面,这里设置了一HOOK,让HOOK ;//来处理
//发行包的安装来源 [InstallerHook] ;//这里安装默认是在同X.CAB的目录,如果是在网站,可以更改为URL路径 ;//如:http://www./vcredist.cab
file-win32-x86=NVS_VAE.CAB ;//这里就是让CAB自动解压过后,运行CAB包里面的 setup.exe run=%EXTRACT_DIR%\setup.exe
开发过程中一些经验总结 以下总结主要由李俊峰(lijunfeng 00165774/huawei,)、李伟(liwei 00165242/huawei,)、姜川(j00132245)总结 1.自定义CListCtrl,使用自定义列表头CHeaderCtrl 在自定义CListCtrl的列表头时,需要替换系统自带的CHeaderCtrl,网上代码的处理方式通常是在自定义的MyListCtrl中重写PreSubclassWindow,并在其中调用(其中m_MyHeaderCtrl是自定义的CHeaderCtrl,作为MyListCtrl的成员变量) if(GetHeaderCtrl()) m_MyHeaderCtrl.SubclassWindow(GetHeaderCtrl()->m_hWnd); 这种方法在MFC桌面程序中可以奏效。但是放到ActiveX中时,此处的GetHeaderCtrl()==NULL,所以无法达到替换表头的效果(具体原理未知,有兴趣的同学可以研究)。解决办法是在创建自定义的MyListCtrl之后手动调用 m_MyHeaderCtrl.SubclassWindow(GetHeaderCtrl()->m_hWnd); 当然需要把这个包装成一个InitMyHeader之类的public方法放到MyListCtrl中。 完整代码请参考VAEListCtrl和VideoListCtrl及其使用。
2.为ActiveX窗口上的自定义控件添加ToolTip 为自定义控件添加ToolTip时,一般做法是在PreTranslateMessage中添加如下代码: if (NULL != m_pToolTipCtrl) m_pToolTipCtrl->RelayEvent(pMsg); 希望CToolTipCtrl能够捕获鼠标移动等消息,但是结果PreTranslateMessage方法在ActiveX程序中不会执行(经验证在ProjectName+Ctrl(即整个ActiveX获得输入焦点)时PreTranslateMessage方法会执行)。 解决办法是为控件添加OnMouseMove消息响应,在OnMouseMove(UINT nFlags, CPoint point)方法中添加代码: //构造一个MSG MSG msg; msg.hwnd = m_hWnd; msg.message = WM_MOUSEMOVE; msg.wParam = LOWORD(point.x); msg.lParam = LOWORD(point.y); msg.time = 0; msg.pt.x = LOWORD(point.y); msg.pt.y = HIWORD(point.y); m_ToolTip.RelayEvent(&msg); 这样才会使鼠标在控件上移动时显示ToolTip,但是要求要先点选过该控件才行。如果想要更进一步去掉这个先点选控件的限制,则需要在自定义控件的父窗口中响应OnMouseMove消息,并构造MSG关联到ToolTip。此时需要注意point的坐标转换。 完整的相关代码(PTZControlWnd和BitmapSlider)如下:
父窗口PTZControlWnd: //成员变量声明 CBitmapSlider m_bsSpeedOrStepsize; voidPTZControlWnd::OnMouseMove(UINTnFlags,CPointpoint) { (void)nFlags; UpdateToolTipText();//更新ToolTip文字 m_bsSpeedOrStepsize->RelayToolTipEventFromParent(point);//此处的point的坐标是相对PTZControlWnd的 VAEBaseWnd::OnMouseMove(nFlags,point); }
控件CBitmapSlider //成员变量声明 CToolTipCtrl m_ToolTip; voidCBitmapSlider::RelayToolTipEventFromParent(CPoint &point) { //将相对父窗口的坐标转换成屏幕坐标 GetParent()->ClientToScreen(&point);
//从屏幕坐标转换成相对控件自身CBitmapSlider的坐标 ScreenToClient(&point);
//因为在CBitmapSlider::OnMouseMove中也要用到,所以提取成函数 RelayToolTipEvent(point); }
voidCBitmapSlider::RelayToolTipEvent(constCPoint &point ) { if (m_ToolTip.m_hWnd !=NULL) { //构造一个MSG MSGmsg;
msg.hwnd =m_hWnd; msg.message =WM_MOUSEMOVE; msg.wParam =LOWORD(point.x); msg.lParam =LOWORD(point.y); msg.time = 0; msg.pt.x = LOWORD(point.y); msg.pt.y = HIWORD(point.y);
m_ToolTip.RelayEvent(&msg); } }
为整个ActiveX添加ToolTip的方法可以参考MSDN,同样没有使用PreTranslateMessage方法 http://support.microsoft.com/kb/141871/zh-cn
3.MFC ActiveX控件添加对外接口 打开类视图,找到ProjectName+Lib,此处为NVS_VAELib
在_D+ProjectName(此处为_DNVS_VAE)上右键弹出菜单,选择添加方法
弹出窗口如下所示,填写相关内容,注意字符串类型参数需要选择BSTR。 填写完毕点击『完成』,如果出现错误提示,可以关闭VisualStudio,删除解决方案文件夹下的“.ncb”文件,然后重新打开解决方案再添加方法。 修改已经添加的对外接口签名时注意除了声明和实现外还需要在“.idl”文件中更改相应的调度接口。
4.对外接口BSTR参数转换为char * 生成的方法签名中的对应BSTR类型的是LPCTSTR。 将javascript传进来的Unicode字符串转换为内部接口使用的char*,需要使用MFC宏, USES_CONVERSION; char *pszCameraID =W2A(bstrCameraID);
5.关于网页的刷新 当ocx加载在网页上时,如果F5刷新,ocx控件会销毁ocx的窗口类,但是ocx的app类是不销毁的, 只有当网页关闭时,才销毁app类。 1 刷新引起的问题 当app类中有成员变量时,请注意刷新回来后变量的值还是刷新前的值。 2 利用刷新app类的不析构恢复刷新前的状态 可在控件的APP类中保存刷新前的值,刷新后恢复刷新前的状态
6.一个网页中加载两次(或者多次)OCX控件 同一进程加载两次控件时,app类调用一次,ocx窗口类调用两次。也就是说两个控件实例使用的是同一个app类的实例,只是有各自的窗口。这时如果app类中有成员变量,值得注意。
7.当将UNICODE字符串转化为多字符集字符串时注意的问题 当unicode字符串中含有汉字时,注意转化前后的字符串长度。 例如:TCHAR* pUnicde=_T("abc例子"); // pUnicde长度5 char* pMutiBtye=T2A(pUnicde); // pMutiBtye长度是7,一个汉字占两个字节
8.开发DLL等控件时,加载字符串/图片等资源失败的原因 MFC的对话框装载资源是通过获取当前线程对应的ModuleState保存的ResourceHandler来装载资源的。 所以,DLL里的代码,需要在函数的入口,首先把当前执行线程的ModuleState换成该Dll的State,这样才能装载该dll的资源 即:使用AFX_MANAGE_STATE(AfxGetStaticModuleState());
9.SetEvent与PulseEvent的区别 SetEvent为设置事件对象为有信号状态;而PulseEvent也是将指定的事件设为有信号状态,不同的是如果是一个人工重设事件, 正在等候事件的、被挂起的所有线程都会进入活动状态,函数随后将事件设回,并返回;如果是一个自动重设事件, 则正在等候事件的、被挂起的单个线程会进入活动状态,事件随后设回无信号,并且函数返回。 也就是说在自动重置模式下PulseEvent和SetEvent的作用没有什么区别,但在手动模式下PulseEvent就有明显的不同, 可以比较容易的控制程序是单步走,还是连续走。如果让循环按要求执行一次就用PulseEvent,如果想让循环连续不停的运转就用SetEvent, 在要求停止的地方发个ResetEvent就OK了。
|
|