ATL开发ActiveX ICReader

(一)ActiveX微软定义:

根据微软权威的软件开发指南MSDN(Microsoft Developer Network)的定义,ActiveX插件以前也叫做OLE控件或OCX控件,它是一些软件组件或对象,可以将其插入到WEB网页或其它应用程序中

特点:

在Internet中ActiveX插件的特点是:一般软件需要用户单独下载然后执行安装,而ActiveX插件是当用户浏览到特定的网页时,IE浏览器即可自动下载并提示用户安装。 当然ActiveX插件安装的一个前提是必须经过用户的同意及确认。

ActiveX插件技术是国际上通用的基于Windows平台的软件技术,除了网络实名插件之外,许多软件均采用此种方式开发,例如Flash动画播放插件、Microsoft MediaPlayer插件、CNNIC通用网址插件等。个人觉得PC端网银插件无论在安全性上的考虑更值得学习,毕竟ActiveX的安全性是大家最担心的。

(二)使用ATL开发ActiveX

ATL:
ATL(Active Template Library)是微软的活动模板库,是一个产生C++/COM代码的框架,专门用于开发COM组件。ATL提供了小巧、高效、灵活的类,这些类为创建可互操作的COM组件提供了基本的设施。ATL完全面向COM组件,其结构完全针对COM中的诸多规范。是编写COM组件的快捷工具。

COM:
COM是Microsoft组件对象模型的简称。是一个说明如何建立可动态交替更新组件的规范。它提供了客户和组件为保证能够互操作应该遵循的标准。该标准对于组件架构的重要性同其他任何一个具有可交替更新部分的系统是一样的。

COM标准包括规范和实现两大部分,规范部分定义了组件和组件之间通信的机制,这些规范不依赖于任何特定的语言和操作系统,只要按照该规范,任何语言都可以使用;COM标准的实现部分是COM库,COM库为COM规范的具体实现提供了一些核心服务。

在COM模型中,对象本身对于客户来说是不可见的,客户请求服务时,只能通过接口进行。一般接口是不会改变的。

(三)ActiveX简单实例

首次接触ATL和ActiveX先做一个简单的实例了解一下套路

  1. 创建ATL项目并添加组件(ATL简单对象)
    这里写图片描述
    这里写图片描述
  2. 给接口添加方法

    .h文件声明定义变量、接口、类、函数等
    .cpp文件主要实现定义声明

  3. 生成dll文件 位于Debug下。本地注册dll文件 regsvr32 XXX.dll 提示注册成功就可以被调用
  4. web引入类 使用js调用
<html>  
<body>  
<script type="text/javascript" >  
function doAdd() {
    var myAddObj = new ActiveXObject("myAdd.1");
    //var myAddObj = document.getElementById("myAdd");
    var result = myAddObj.add(1,2);
    alert(result);
}  
</script>
<!-- classid是.idl文件中的class [uuid]-->
<object id="myAdd" name="myAdd" classid="clsid:BC834400-F770-41A6-A2D7-49459C71D0B4" >  
</object>  
<button onclick="doAdd();">add</button>  
</body>
</html>

(四)初步实现ICReader

第一次接触这类东西一定会遇到很多坑,所以先从最简单的地方开始,趟趟水熟悉一下流程,为后面的主要功能打下基础。

通过上面的例子,个人理ActiveX可以作为web应用和底层方法(比如驱动硬件设备、交互)之间的桥梁,简单流程如下:

  • 封装ActiveX,内部调用底层库,生成dll文件
  • JS通过classid可以实例化一个ActiveX dll中的类,从而直接调用其中方法

ActiveX简单实现思路:
使用ATL开发,加载底层库,调用库函数实现ActiveX接口函数,生成ActiveX dll,JS实例化ICReader对象,调用方法。

实践过程:
创建ATL项目,创建ATL简单对象,定义接口方法步骤参照上面的小例子即可,现在主要关注项目结构和文件中内容。

完成ATL简单对象创建之后,关注新生成的.h和.cpp文件

// ICReader.h : CICReader 的声明

#pragma once
#include "resource.h"       // 主符号
#include <Windows.h>
#include "ICReaderActiveX_i.h"
#include "_IICReaderEvents_CP.h"

/*IDE自动生成的部分暂不关注,无非就是我们在创建时的一些选项配置,如果项目后期需要调整再学习修改不迟*/
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Windows CE 平台(如不提供完全 DCOM 支持的 Windows Mobile 平台)上无法正确支持单线程 COM 对象。定义 _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制 ATL 支持创建单线程 COM 对象实现并允许使用其单线程 COM 对象实现。rgs 文件中的线程模型已被设置为“Free”,原因是该模型是非 DCOM Windows CE 平台支持的唯一线程模型。"
#endif

using namespace ATL;

// 接口函数定义在IICReader中,CICReader继承接口最后实现接口函数

class ATL_NO_VTABLE CICReader :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CICReader, &CLSID_ICReader>,
    public IConnectionPointContainerImpl<CICReader>,
    public CProxy_IICReaderEvents<CICReader>,
    public IObjectWithSiteImpl<CICReader>,
    public IDispatchImpl<IICReader, &IID_IICReader, &LIBID_ICReaderActiveXLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
    /*成员变量*/
    //加载dll的句柄
    HINSTANCE hInstLibrary;

    short port;
    int baud;
    int errorcode;
    FPTRIsOurCard _isOurCard;

public:

    /*Constructor*/
    CICReader()
    {
        USES_CONVERSION;
        port = 3;
        baud = 9600;

        //加载dll 获得句柄
        hInstLibrary = LoadLibraryEx(L"D:\\Microsoft_VS\\Project\\ICReaderActiveX\\ICReaderActiveX\\SC_IC_DLL.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
        if (hInstLibrary == NULL)
        {
            errorcode = GetLastError();
            FreeLibrary(hInstLibrary);
        }else{
        //获取库函数入口地址

        _isOurCard = (FPTRIsOurCard)GetProcAddress(hInstLibrary, "IsOurCard");
        if(_isOurCard == NULL)
            free(_isOurCard);
        }
}

DECLARE_REGISTRY_RESOURCEID(IDR_ICREADER)

BEGIN_COM_MAP(CICReader)
    COM_INTERFACE_ENTRY(IICReader)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IConnectionPointContainer)
    COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()

BEGIN_CONNECTION_POINT_MAP(CICReader)
    CONNECTION_POINT_ENTRY(__uuidof(_IICReaderEvents))
END_CONNECTION_POINT_MAP()


    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct()
    {
        return S_OK;
    }

    void FinalRelease()
    {
    }

//接口函数
public:

    STDMETHOD(TestICCard)(LONG* ReturnCode);
    STDMETHOD(isOurCard)(LONG* isFlag);
    STDMETHOD(add)(LONG arg1, LONG arg2, SHORT* result);
};

OBJECT_ENTRY_AUTO(__uuidof(ICReader), CICReader)
// ICReader.cpp : CICReader 的实现

#include "stdafx.h"
#include "ICReader.h"

STDMETHODIMP CICReader::isOurCard(LONG* isFlag)
{
    // TODO:  在此添加实现代码
    if (this->hInstLibrary == NULL)
        *isFlag = this->errorcode;
    else if (this->_isOurCard == NULL)
        *isFlag = 1000;
    else
        *isFlag = this->_isOurCard(this->port, this->baud);
    return S_OK;
}

//测试方法,如果正确执行表明JS实例没有问题
STDMETHODIMP CICReader::add(LONG arg1, LONG arg2, SHORT* result)
{
    // TODO:  在此添加实现代码
    *result = arg1+arg2;
    return S_OK;
}
<!--test page-->

<html>  
<body>  
<script type="text/javascript" >
function doTest() {
    var myTestObj = new ActiveXObject("icreader.1");
    var returncode = myTestObj.isOurCard();
    switch(returncode){
        case 0: alert("操作成功");  break;
        case 1: alert("串口打开失败");break;
        case 2: alert("未插卡");   break;
        case 3: alert("密码校验失败"); break;
        case 4: alert("非法卡");   break;
        case 5: alert("读卡失败");  break;
        case 6: alert("写卡失败");  break;
        case 7: alert("改密码失败"); break;
        case 8: alert("数据校验错误");    break;
        case 9: alert("卡内有值");  break;
        case 10:alert("退气数据不对");    break;
        case 11:alert("设置参数格式有误");  break;
        case 12:alert("卡坏");    break;
        case 13:alert("新卡");    break;
        case 14:alert("工具卡");   break;
        case 15:alert("购气次数错误");    break;
        case 16:alert("用户编号不一致"); break;
        default:alert("未知错误,错误代码:%d");break;
    }
}

function doAdd(){
    var myAddObj = new ActiveXObject("icreader.1");
    var result = myAddObj.add(1,2);
    alert(result);
}

</script>  
<object id="myTest" name="myTest" classid="clsid:E99E58DB-ED4C-4DE9-AD1E-C8C275056B8F" >  
</object>  
<button onclick="doTest();">test card</button>
<button onclick="doAdd()">add</button>
</body>
</html>

问题与总结

  1. 函数指针:指向函数入口地址,可以通过指针直接调用函数。关注本质内存模型
  2. __stdcall:修饰符,一种函数调用方式的约定,在声明函数指针的时候不可遗漏,否则会报函数指针类型与指向函数类型不匹配的错误。
    __stdcall具体细节本查看链接
    这里写图片描述
  3. 问题现象:使用LoadLibrary时一只返回NULL,GetLastError( )返回值为126

    分析:换用绝对路径仍然存在这个问题,说明不是表面现行找不到模块那么简单,应该是SC_IC_DLL.dll内部及其他问题。

    LoadLibrary( )和LoadLibraryEx( ):
    这里写图片描述
    这里写图片描述
    若DLL不在调用方的同一目录下,可以用LoadLibrary(L”DLL绝对路径”)加载。但若被调DLL内部又调用另外一个DLL,此时调用仍会失败。解决办法是用LoadLibraryEx(“DLL绝对路径”, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); 通过指定LOAD_WITH_ALTERED_SEARCH_PATH,让系统DLL搜索顺序从DLL所在目录开始。
    使用绝对路径硬编码的弊端就是影响工程的灵活性,给部署和调整带来了很大难度。

  4. dll文件和lib文件的联系与区别

    DLL LIB
    运行期才会被调用 在编译期就连接到应用程序中
    动态链接库 lib包含了函数所在的dll文件和文件中函数入口,代码由运行时加载的dll提供 静态链接库

    使用lib需注意两个文件:
      (1).h头文件,包含 lib中说明输出的类或符号原型或数据结构。应用程序调用lib时,需要将该文件包含入应用程序的源文件中。
      (2).LIB文件,略。
      
    使用dll需注意三个文件:
      (1).h头文件,包含dll中说明输出的类或符号原型或数据结构的.h文件。应用程序调用dll时,需要将该文件包含 入应用程序的源文件中。
      (2).LIB文件,是dll在编译、链接成功之后生成的文件,作用是当其他应用程序调用dll时,需要将该文件引入应用 程序,否则产生错误。如果不想用lib文件或者没有lib文件,可以用WIN32 API函数LoadLibrary、GetProcAddress装载。
      (3).dll文件,真正的可执行文件,开发成功后的应用 程序在发布时,只需要有.exe文件和.dll文件,并不需要.lib文件和.h头文件

猜你喜欢

转载自blog.csdn.net/baidu_22153679/article/details/79085032