WinDDK
1 2 3 4 5 C:\WinDDK\7600.16385.1\ C:\WinDDK\7600.16385.1\bin>setenv.bat C:\WinDDK\7600.16385.1 fre WXP OACR monitor running already
WinDDK
包自带了很多开发示例代码,可以直接编译,经过上一步设置了环境变量,如果在根目录下直接运行build
,或者bcz
就会把所有的示例全部编译出来.如下面只编译一个示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 C:\WinDDK\7600.16385.1\src\print \monitors\localui>bcz BUILD: Compile and Link for x86 BUILD: Start time: Thu Apr 06 13:02:47 2017 BUILD: Examining c:\winddk\7600.16385.1\src\print \monitors\localui directory for files to compile. c:\winddk\7600.16385.1\src\print \monitors\localui Auto-cleaning queue for 'W DKSamples:x86fre' (5 of 5 file(s) removed)Invalidating OACR warning log for 'WDKSamples:x86fre' BUILD: rmdir /q/s .obj\src\print \monitors\localui BUILD: Compiling c:\winddk\7600.16385.1\src\print \monitors\localui directory Configuring OACR for 'WDKSamples:x86fre' - <OACR on> _NT_TARGET_VERSION SET TO WINXP Precompiling - precomp.h Compiling - localui.c Compiling - util.c Compiling - dialogs.c Compiling - config.c Compiling - mem.c Compiling - generating code... Building Library - .obj\src\print \monitors\localui\objfre_wxp_x86\i386\ddklocalu i.lib BUILD: Linking for c:\winddk\7600.16385.1\src\print \monitors\localui directory _NT_TARGET_VERSION SET TO WINXP Compiling resources - localui.rc _NT_TARGET_VERSION SET TO WINXP Linking Executable - .obj\src\print \monitors\localui\objfre_wxp_x86\i386\ddkloca lui.dll BUILD: Finish time: Thu Apr 06 13:02:48 2017 BUILD: Done 10 files compiled 1 library built 1 executable built C:\WinDDK\7600.16385.1\src\print \monitors\localui>
使用 QT 开发 windows 驱动 本人也是从使用 VC++6.0 MFC 开始入门做开发,后来接触并使用 GTK,wxWidgets,QT 做过一些开发,现在来说对 MFC 的东西很抵触,.NET 与 VB 没有用过不好说,但是使用 QT,wxWidgets 做图形界面还是比较快捷的(对于本人来说).如果是纯软件,我一般都喜欢在 Linux 下用 QT 开发,交叉编译出一个静态 exe 放到 windows 下发布.但是如果是 WINDOWS 驱动开发,那就肯定只能在 WINDOWS 做开发了,vs2010 实在用不习惯,我还是装了 Qt 来折腾,Qt 加 MinGW 做驱动开发,主要是要用到 Qt 的图形界面,还有一些 QT 处理字符串的功能.
之前开发都没有注意 windows 入口函数的问题 exe 的入口函数WinMain
,DLL 的入口函数DllMain
,通过这次开发了解一些 winAPI 的功能,和一些需要注意的事项.
Windows 开发动态库都是使用 def 文件做导出的,以前 c/c++都是用 __declspec(dllexport)
直接声明在函数头文件,如果使用 MinGW GCC 的编译器就会出现一个问题,可以参考这里
,比如:要导出 DllMain 这个函数,它就会变成DllMain@12
这样的符号,所以肯是加载不了这个 dll 的,在使用 VC ++ 6.0 时会有一个工具叫做dependency walker
查看 dll 的依赖那些 DLL,导出了那些函数符号名,到 vs2010 就不自带了,现在要去单独下载.下载地址
.使用 def 文件声明就没有这个问题了.如 QT 工程设置如下:
1 2 3 4 QMAKE_LFLAGS += $$PWD /print-mon.def DEFINES += _UNICODE _UNICODE STRICT
def 文件如下:
1 2 3 4 5 6 7 8 LIBRARY print-mon ;DESCRIPTION 'Redirected Port Monitor for Windows NT 5.0' ;DATA MULTIPLE SHARED EXPORTS DllMain InitializePrintMonitor2 ; InitializePrintMonitorUI
开发遇到的问题
下面是我要开发一个打印机的监视器,参照MSDN API
文档开发.
库版本差异问题
Qt中自带的MinGW
的库文件如winspool.a
是没有导出XcvDataW
这个函数的,所以在链接时会出错,只是链接时出错 ,不是运行时.这里有两个办法可以解决.
我在Linux
下交叉编译过Qt5.8,GCC-5.4
,可以用nm
查看了一下winspool.a
是否有导出XcvDataW
这个函数?我尝试把它拿过来,需要在QT里指定链接它LIBS +=-L$$PWD/lib -lwinspool
,竟然编译通过,而且注意了,我在windows
下的MinGW GCC-5.3
,也是可以编译链接的.
下面这种是常用的,就是直接加载驱动程序文件,通过API
取得它的函数入口地址如:
1 2 3 4 5 typedef BOOL (WINAPI *pfnXcvData) (HANDLE hXcv, LPCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded, PDWORD pdwStatus) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HINSTANCE hInstance = LoadLibrary (TEXT ("winspool.drv" )); if (hInstance != NULL ) { pfnXcvData fpXcvData = NULL ; fpXcvData = (pfnXcvData)GetProcAddress (hInstance, "XcvDataW" ); if (fpXcvData != NULL ) { rc = fpXcvData (hXcv, L"AddPort" ,(PBYTE)pOut, (wcslen (pOut) + 1 )*sizeof (WCHAR), dwOutputData, 0 , &dwNeeded, &dwStatus); } FreeLibrary (hInstance); }
字符串的问题
使用Qt+MinGW
还有一个问题就是字符串转换的问题,被这个问题困了四五天.举例说明, 还是用XcvData
这个函数举例,它的接口定义来自https://docs.microsoft.com/en-us/previous-versions//ff564255(v=vs.85)?redirectedfrom=MSDN
1 2 3 4 5 6 7 8 9 10 11 12 BOOL XcvData ( _In_ HANDLE hXcv, _In_ PCTSTR pszDataName, _In_opt_ PBYTE pInputData, DWORD cbInputData, _Out_opt_ PBYTE pOutputData, DWORD cbOutputData, _Out_ PDWORD pcbOutputNeeded, _Out_opt_ PDWORD pdwStatus ) ;
1 2 3 4 5 6 7 8 9 10 size_t cbSize =sizeof (PWSTR) * 6 ;PWSTR pOut = (PWSTR)AllocSplMem (cbSize); StringCbCopy ((PWSTR)pOut,cbSize,L"Net3:" );pOut[5 ] = NULL ; pfnXcvData fpXcvData = NULL ; rc = XcvData (hXcv, L"AddPort" ,(PBYTE)pOut, (wcslen (pOut) + 1 )*sizeof (WCHAR), dwOutputData, 0 , &dwNeeded, &dwStatus);
所以总结一句,魔鬼在细节
,windows
开发,WCHAR
等宽字符问题一定要注意.比如PBYTE
转QString
,尝试了很多次都不成功,最后找到方法如下:
1 2 3 4 5 6 7 PBYTE pInputData = "Abc dddd" ; QString::fromUtf16 (reinterpret_cast <const unsigned short *>(pInputData)); QString t = "abc" ; # QString 转 LPCWSTR LPCWSTR tmp = (LPCWSTR)t.utf16 ();
总结
Windows 下开发驱动肯定少不了MSDN
,调试要用到GetLastError
的 API 去取得错误码
,重复性的操作最好是能用脚本来处理,比如:我开发调试一个打印监视器的驱动,每次要重启Spooler
服务才能替换新的程序.写一个如下脚本省事又不出错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @echo on echo %1set TARGET="C:\WINDOWS\system32\print-mon_i686.dll" set UITARGET="C:\WINDOWS\system32\netmonui_i686.dll" sc stop Spooler ;这个ping只是用来延时的 ping 8.8.8.8 -n1 -w 5 >nul IF EXIST %TARGET% del /f %TARGET% IF EXIST %UITARGET% del /f %UITARGET% copy %1\netmon\debug\print-mon_i686.dll %TARGET% copy %1\ui\debug\netmonui_i686.dll %UITARGET% sc start Spooler ;一次不成功,再运行一次保险 sc stop Spooler ping 8.8.8.8 -n1 -w 5 >nul IF EXIST %TARGET% del /f %TARGET% IF EXIST %UITARGET% del /f %UITARGET% copy %1\netmon\debug\print-mon_i686.dll %TARGET% copy %1\ui\debug\netmonui_i686.dll %UITARGET% sc start Spooler
谢谢支持