0%

Windows下WinDDK驱动开发

WinDDK

  • 初始化 DDK
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这个函数的,所以在链接时会出错,只是链接时出错,不是运行时.这里有两个办法可以解决.
  1. 我在Linux下交叉编译过Qt5.8,GCC-5.4,可以用nm查看了一下winspool.a是否有导出XcvDataW这个函数?我尝试把它拿过来,需要在QT里指定链接它LIBS +=-L$$PWD/lib -lwinspool,竟然编译通过,而且注意了,我在windows下的MinGW GCC-5.3,也是可以编译链接的.
  2. 下面这种是常用的,就是直接加载驱动程序文件,通过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, //这里这度必须是WCHAR的长度,(wcslen(pOut) + 1)*sizeof(WCHAR)
_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,
// 原来把面直接写成常数6,该函数执行成功,就是不返回下一步,调试其它一切正常,最后才把它改成用WCHAR的长度就OK了.
(wcslen(pOut) + 1)*sizeof(WCHAR),
dwOutputData, 0, &dwNeeded, &dwStatus);

所以总结一句,魔鬼在细节,windows开发,WCHAR等宽字符问题一定要注意.比如PBYTEQString,尝试了很多次都不成功,最后找到方法如下:

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 %1
set 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

谢谢支持