驅動安裝+打包程序

背景

項目背景爲非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\"" )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章