背景
項目背景爲非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準確判斷系統版本,還需要對程序的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\"" )