驱动安装+打包程序

来源:互联网 发布:软件项目经理绩效考核 编辑:程序博客网 时间:2024/04/30 22:56

背景

项目背景为非UVC标准USB工业相机,连接Windows PC,需要安装特定USB驱动。我负责开发PC端相机软件部分(已完成),所以也需要把驱动安装这部分囊括进来。

USB芯片厂商已经提供了winxp/winvista/win7/win8/win8.1/win10、32bits/64bits不同平台的不同驱动文件,包括.inf、.sys、.cat、.dll文件。因此插上相机设备后,可以找到对应平台驱动,通过Windows设备管理器或者右键.inf文件进行驱动的安装。但是这些事情对于开发人员只是鼠标点点的操作,对于用户却会成为很麻烦的事情。因此开发PC端软件的我需要简化驱动安装的一切操作,尽量做到对用户透明。

捣鼓了不少时间,写出目前的解决方案。

驱动安装

为了让用户接触不到驱动安装的繁琐操作,只能用代码解决了,解决过程中在《竹林蹊径——深入浅出Windows驱动开发》这本书上受益匪浅,大概阅读,也算是加深了硬件设备与Windows系统间连接过程的理解。

开发环境win8.1 vs2013 c++ 控制台应用程序(有预编译头)
spdlog为日志输出(github开源项目)
使用的ANSI字符串

  • 驱动预安装部分,上代码,参考竹林蹊径第12章

API参考链接
该函数成功运行需要程序具有管理员权限,具体可在项目属性->链接器->清单文件->UAC执行级别中更改为requireAdministrator。

BOOL InstallDriver(TCHAR* inf_path, TCHAR* inf_name_out){    /*    * inf_path: .inf文件所在路径,例如C:\\driver.inf    * inf_name_out: inf文件预安装成功或系统已存在,则返回相应文件名,如oemxx.inf    */    TCHAR path1[MAX_PATH] = {0};    TCHAR *path2;    if (FALSE == SetupCopyOEMInf(inf_path, NULL, SPOST_PATH, SP_COPY_NOOVERWRITE, path1, MAX_PATH, NULL, &path2))   //需要管理员权限    {        DWORD error = GetLastError();        if (error == ERROR_FILE_EXISTS)        {            spdlog::get("driver_install_info")->info("the driver file has existed, so succeeded");            //cout << "the driver file has existed, so succeeded" << endl;            //_tprintf(_T("the oem name is: %s\n"), path2);            spdlog::get("driver_install_info")->info("the oem name is: \n" + string(path2));            //cout << path2;            //printf("the file name is: %s", path2);            _tcscpy_s(inf_name_out, MAX_PATH, path2);            return TRUE;        }        else        {            spdlog::get("driver_install_info")->info("install failed, ");            spdlog::get("driver_install_info")->info("the error code is: " + to_string(error));            //cout << "install failed, ";            //cout << "the error code is: " << error << endl;            return FALSE;        }    }    else    {        spdlog::get("driver_install_info")->info("install succeeded\n");        spdlog::get("driver_install_info")->info("the oem name is: " + string(path2) + "\n");        //_tprintf(_T("the oem name is: %s\n"), path2);        _tcscpy_s(inf_name_out, MAX_PATH, path2);        return TRUE;    }}
  • 不同平台系统对应不同驱动文件,因此程序中还需要获取系统信息,上代码

API参考链接

/** 该API准确判断系统版本,还需要对程序的manifest文件进行修改,* 也需要有win10 SDK的VersionHelper.h以及sdkddkver.h等头文件适配,具体可以百度*/enum WINDOWS_VERSION{    WINDOWS_XP,    WINDOWS_VISTA,    WINDOWS_7,    WINDOWS_8,    WINDOWS_8_1,    WINDOWS_10,    OTHER_VERSION};WINDOWS_VERSION GetSystemVersion(){    if (IsWindows10OrGreater())    {        return WINDOWS_10;    }    else if (IsWindows8Point1OrGreater())    {        return WINDOWS_8_1;    }    else if (IsWindows8OrGreater())    {        return WINDOWS_8;    }    else if (IsWindows7OrGreater())    {        return WINDOWS_7;    }    else if (IsWindowsVistaOrGreater())    {        return WINDOWS_VISTA;    }    else if (IsWindowsXPOrGreater())    {        return WINDOWS_XP;    }    else    {        return OTHER_VERSION;    }}
  • 判断操作系统位数,代码参考于网络,如下
enum WINDOWS_BITS_WIDTH{    WINDOWS_32BITS,    WINDOWS_64BITS};// 安全的取得真实系统信息  VOID SafeGetNativeSystemInfo(__out LPSYSTEM_INFO lpSystemInfo){    if (NULL == lpSystemInfo)    return;    typedef VOID(WINAPI *LPFN_GetNativeSystemInfo)(LPSYSTEM_INFO lpSystemInfo);    LPFN_GetNativeSystemInfo fnGetNativeSystemInfo = (LPFN_GetNativeSystemInfo)GetProcAddress(GetModuleHandle(_T("kernel32")), "GetNativeSystemInfo");    if (NULL != fnGetNativeSystemInfo)    {        fnGetNativeSystemInfo(lpSystemInfo);    }    else    {        GetSystemInfo(lpSystemInfo);    }}// 获取操作系统位数  WINDOWS_BITS_WIDTH GetSystemBits(){    SYSTEM_INFO si;    SafeGetNativeSystemInfo(&si);    if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||        si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)    {        return WINDOWS_64BITS;    }    return WINDOWS_32BITS;}
  • 驱动预安装最终代码
int InstallDriver(){    //获取.exe执行文件所在路径    TCHAR path[MAX_PATH] = {0};    GetModuleFileName(NULL, path, MAX_PATH);    *(_tcsrchr(path, _T('\\'))) = 0;    string msg(path);    _tcscat_s(path, MAX_PATH, _T("\\Drivers\\"));    //日志初始化    //spdlog::set_async_mode(4096);     auto logger = spdlog::basic_logger_st("driver_install_info", msg + "\\driverlog.txt");    spdlog::get("driver_install_info")->info("the driver files' directory is " + msg);    //_tcscat_s(path, _T("winxp"));    switch (GetSystemVersion())    {    case OTHER_VERSION:        _tcscat_s(path, MAX_PATH, _T("wxp\\"));        msg = "before winxp or windows server \n";        //cout << "before winxp or windows server " << endl;        break;    case WINDOWS_XP:        _tcscat_s(path, MAX_PATH, _T("wxp\\"));        msg = "winxp ";        //cout << "winxp ";        break;    case WINDOWS_VISTA:        _tcscat_s(path, MAX_PATH, _T("vista\\"));        msg = "vista ";        //cout << "vista ";        break;    case WINDOWS_7:        _tcscat_s(path, MAX_PATH, _T("Win7\\"));        //cout << "win7 ";        msg = "win7 ";        break;    case WINDOWS_8:        _tcscat_s(path, MAX_PATH, _T("Win8\\"));        msg = "win8 ";        //cout << "win8 ";        break;    case WINDOWS_8_1:        _tcscat_s(path, MAX_PATH, _T("Win81\\"));        msg = "win8.1 ";        //cout << "win8.1 ";        break;    case WINDOWS_10:        _tcscat_s(path, MAX_PATH, _T("Win10\\"));        msg = "win10 ";        //cout << "win10 ";        break;    default:        msg = "can't find related windows version\n";        //cout << "can't find related windows version" << endl;        break;    }    switch (GetSystemBits())    {    case WINDOWS_32BITS:        _tcscat_s(path, MAX_PATH, _T("x86\\cyusb3.inf"));        msg += "32bits system\n";        //cout << "32bits system" << endl;        break;    case WINDOWS_64BITS:        _tcscat_s(path, MAX_PATH, _T("x64\\cyusb3.inf"));        msg += "64bits system\n";        //cout << "64bits system" << endl;        break;    default:        msg += "can't find related machine's drivers of other bits width\n";        //cout << "can't find related machine's drivers of other bits width" << endl;        break;    }    spdlog::get("driver_install_info")->info(msg);    msg = "";    TCHAR cur_dir[MAX_PATH] = { 0 };    GetCurrentDirectory(MAX_PATH, cur_dir);    spdlog::get("driver_install_info")->info("the current dir is " + string(cur_dir));    //_tprintf(_T("the current dir is: %s\n"), cur_dir);    //_tprintf(_T("the .exe dir is: %s\n"), path);    BOOL flag = FALSE;    TCHAR inf_name[MAX_PATH] = { 0 };    flag = InstallDriver(path, inf_name);    //关闭日志    spdlog::drop("driver_install_info");    return 0;}

相机程序安装包制作

利用InstallShield Limited制作,目标点击setup.exe之后,驱动程序、相机程序全部安装。主要在于程序安装之后,驱动程序的自动运行。InstallShield提供了Custom Actions去进行相应操作。如下,打开Custom Actions窗口,添加DriverInstall,相应设置根据图中显示。
这里写图片描述
这里写图片描述

日志输出

为了使驱动安装对用户透明,同时输出一定运行调试信息,便于程序出错时查错,因此控制台输出需要替代为日志输出。

这里使用github开源项目spdlog输出日志,用法简单。

遇到的问题与收获

  • ansi与unicode字符串的不同
  • win8.1 sdk没有IsWindows10OrGreater()函数,需要额外加入,还需要添加相应的宏

    该问题可以将需要的头文件从win10 sdk复制到项目中,在项目中修改这些文件为需要的版本,如果相应头文件没有复制到项目文件夹根目录下,项目属性需要设置好头文件附加包含目录
    所需头文件具体包括sdkddver.h、VersionHelpers.h 、winapifamily.h

  • 兼容xp,需要设置平台工具集,由于使用了spdlog,还需要在targetver.h中定义宏
    #define _WIN32_WINNT 0x0501,以使用xp具有的API,该发现源于spdlog\details\os.h代码段

     #ifdef _WIN32 #if _WIN32_WINNT < _WIN32_WINNT_WS08    TIME_ZONE_INFORMATION tzinfo;    auto rv = GetTimeZoneInformation(&tzinfo); #else    DYNAMIC_TIME_ZONE_INFORMATION tzinfo;    auto rv = GetDynamicTimeZoneInformation(&tzinfo); #endif
  • 程序运行不显示控制台窗口,可以在main函数前添加
    #pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
0 0