最近在开发网路协议及小端口驱动,在http://www.ndis.com/下载了协议驱动安装的例子ProtInstall(http://www.ndis.com/ndis-general/ndisinstall/programinstall.htm),研究了一下,特记录一下。
网络方面的驱动可以使用INetCfg接口安装,其安装步骤大致如下
1. 使用CoCreateInstance创建Com对象
hr = CoCreateInstance( CLSID_CNetCfg,
NULL, CLSCTX_INPROC_SERVER,
IID_INetCfg,
(void**)&pnc );
2. 获取接口IID_INetCfgLock并请求写锁
- hr = pnc->QueryInterface( IID_INetCfgLock,
- (LPVOID *)&pncLock );
- if ( hr == S_OK ) {
- //
- // Attempt to lock the INetCfg for read/write
- //
- hr = pncLock->AcquireWriteLock( LOCK_TIME_OUT,
- lpszAppName,
- lpszLockedBy);
- if (hr == S_FALSE ) {
- hr = NETCFG_E_NO_WRITE_LOCK;
- }
- }
hr = pnc->QueryInterface( IID_INetCfgLock,
(LPVOID *)&pncLock );
if ( hr == S_OK ) {
//
// Attempt to lock the INetCfg for read/write
//
hr = pncLock->AcquireWriteLock( LOCK_TIME_OUT,
lpszAppName,
lpszLockedBy);
if (hr == S_FALSE ) {
hr = NETCFG_E_NO_WRITE_LOCK;
}
}
3. 初始化接口IID_INetCfg
- hr = pnc->Initialize( NULL );
- if ( hr == S_OK ) {
- *ppnc = pnc;
- pnc->AddRef();
- }
- else {
- //
- // Initialize failed, if obtained lock, release it
- //
- if ( pncLock ) {
- pncLock->ReleaseWriteLock();
- }
- }
hr = pnc->Initialize( NULL );
if ( hr == S_OK ) {
*ppnc = pnc;
pnc->AddRef();
}
else {
//
// Initialize failed, if obtained lock, release it
//
if ( pncLock ) {
pncLock->ReleaseWriteLock();
}
}
4. 调用SetupCopyOEMInf执行预安装(preinstall)
- if ( !SetupCopyOEMInfW(
- lpszInfFullPath,
- szDirWithDrive, // Other files are in the
- // same dir. as primary INF
- SPOST_PATH, // First param is path to INF
- 0, // Default copy style
- NULL, // Name of the INF after
- // it's copied to %windir%\inf
- 0, // Max buf. size for the above
- NULL, // Required size if non-null
- NULL) // Optionally get the filename
- // part of Inf name after it is copied.
- )
- {
- dwError = GetLastError();
- hr = HRESULT_FROM_WIN32( dwError );
- }
if ( !SetupCopyOEMInfW(
lpszInfFullPath,
szDirWithDrive, // Other files are in the
// same dir. as primary INF
SPOST_PATH, // First param is path to INF
0, // Default copy style
NULL, // Name of the INF after
// it's copied to %windir%\inf
0, // Max buf. size for the above
NULL, // Required size if non-null
NULL) // Optionally get the filename
// part of Inf name after it is copied.
)
{
dwError = GetLastError();
hr = HRESULT_FROM_WIN32( dwError );
}
执行完这个函数后,inf文件会被拷贝到%SystemRoot%\Inf文件夹下,并生成预编译文件(*.pnf),其中第二个参数 OEMSourceMediaLocation将会被编译进这个文件,当真正执行安装函数时,源文件地址信息会被从这个文件里解析出来。这个函数可以设置拷贝模式,具体不写了。
5. 查询安装类的接口并安装
- hr = pnc->QueryNetCfgClass ( pguidClass,
- IID_INetCfgClassSetup,
- (void**)&pncClassSetup );
- if ( hr == S_OK )
- {
- MessageBox(NULL,_T("Before Install"),_T("Tip"), MB_OK);
- hr = pncClassSetup->Install( szComponentId,
- &OboToken,
- 0,
- 0, // Upgrade from build number.
- NULL, // Answerfile name
- NULL, // Answerfile section name
- &pncc ); // Reference after the component
- if ( S_OK == hr ) { // is installed.
- //
- // we don't need to use pncc (INetCfgComponent), release it
- //
- ReleaseRef( pncc );
- }
- ReleaseRef( pncClassSetup );
- }
hr = pnc->QueryNetCfgClass ( pguidClass,
IID_INetCfgClassSetup,
(void**)&pncClassSetup );
if ( hr == S_OK )
{
MessageBox(NULL,_T("Before Install"),_T("Tip"), MB_OK);
hr = pncClassSetup->Install( szComponentId,
&OboToken,
0,
0, // Upgrade from build number.
NULL, // Answerfile name
NULL, // Answerfile section name
&pncc ); // Reference after the component
if ( S_OK == hr ) { // is installed.
//
// we don't need to use pncc (INetCfgComponent), release it
//
ReleaseRef( pncc );
}
ReleaseRef( pncClassSetup );
}
其中szComponentID指的是inf文件中的deviceID。
6. 很重要最后一步,应用。
pnc->Apply();
至此,安装代码完毕。
我测试以上代码是,发现协议驱动若是没有签名或者没有签名的cat文件,windows会有警告提示,体验不好。于是我首先对驱动签名,并使用了inf2cat文件生成了cat文件并签名。再安装发现windows弹出另外一个对话框
于是我启用ProcMon工具,发现勾选始终信任CheckBox框后其向注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\TrustedPublisher\Certificates
写入了一些值,根据注册表路径名称的暗示,这个位置存储了那些信任的发布者列表,于是我把其写入的注册表键值拷贝出来,合并进一台干净的虚拟机后再安装驱动,这是这个对话框消失了,用户体验更好了。
再来说说卸载,其步骤如下
1. 找到Component对象
2. 找到compoentID的class guid。
3. 找到安装类
4. 执行卸载
- HRESULT HrUninstallNetComponent(
- IN INetCfg* pnc,
- IN LPCTSTR szComponentId
- )
- {
- INetCfgComponent *pncc = NULL;
- INetCfgClass *pncClass = NULL;
- INetCfgClassSetup *pncClassSetup = NULL;
- OBO_TOKEN OboToken;
- GUID guidClass;
- HRESULT hr = S_OK;
- //
- // OBO_TOKEN specifies on whose behalf this
- // component is being installed.
- // Set it to OBO_USER so that szComponentId will be installed
- // on behalf of the user.
- //
- ZeroMemory( &OboToken,
- sizeof(OboToken) );
- OboToken.Type = OBO_USER;
- //
- // Get the component's reference.
- //
- hr = pnc->FindComponent( szComponentId,
- &pncc );
- if (S_OK == hr) {
- //
- // Get the component's class GUID.
- //
- hr = pncc->GetClassGuid( &guidClass );
- if ( hr == S_OK ) {
- //
- // Get component's class reference.
- //
- hr = pnc->QueryNetCfgClass( &guidClass,
- IID_INetCfgClass,
- (void**)&pncClass );
- if ( hr == S_OK ) {
- //
- // Get Setup reference.
- //
- hr = pncClass->QueryInterface( IID_INetCfgClassSetup,
- (void**)&pncClassSetup );
- if ( hr == S_OK ) {
- hr = pncClassSetup->DeInstall( pncc,
- &OboToken,
- NULL);
- if ( hr == S_OK ) {
- //
- // Apply the changes
- //
- hr = pnc->Apply();
- }
- ReleaseRef( pncClassSetup );
- }
- ReleaseRef( pncClass );
- }
- }
- ReleaseRef( pncc );
- }
- return hr;
- }
HRESULT HrUninstallNetComponent(
IN INetCfg* pnc,
IN LPCTSTR szComponentId
)
{
INetCfgComponent *pncc = NULL;
INetCfgClass *pncClass = NULL;
INetCfgClassSetup *pncClassSetup = NULL;
OBO_TOKEN OboToken;
GUID guidClass;
HRESULT hr = S_OK;
//
// OBO_TOKEN specifies on whose behalf this
// component is being installed.
// Set it to OBO_USER so that szComponentId will be installed
// on behalf of the user.
//
ZeroMemory( &OboToken,
sizeof(OboToken) );
OboToken.Type = OBO_USER;
//
// Get the component's reference.
//
hr = pnc->FindComponent( szComponentId,
&pncc );
if (S_OK == hr) {
//
// Get the component's class GUID.
//
hr = pncc->GetClassGuid( &guidClass );
if ( hr == S_OK ) {
//
// Get component's class reference.
//
hr = pnc->QueryNetCfgClass( &guidClass,
IID_INetCfgClass,
(void**)&pncClass );
if ( hr == S_OK ) {
//
// Get Setup reference.
//
hr = pncClass->QueryInterface( IID_INetCfgClassSetup,
(void**)&pncClassSetup );
if ( hr == S_OK ) {
hr = pncClassSetup->DeInstall( pncc,
&OboToken,
NULL);
if ( hr == S_OK ) {
//
// Apply the changes
//
hr = pnc->Apply();
}
ReleaseRef( pncClassSetup );
}
ReleaseRef( pncClass );
}
}
ReleaseRef( pncc );
}
return hr;
}
编译:
因为需要包含wdk安装目录inc\api目录 文件netcfgx.h,需要向工程属性添加$(WDKPATH)\inc\api目录,但是当向Configuration Properties -> C/C++ -> Additional Include Directories添加目录后,编译错误.
1>ClCompile:
1> stdafx.cpp
1>c:\program files\microsoft visual studio 10.0\vc\include\crtdefs.h(520): error C2065: '_In_opt_z_' : undeclared identifier
1>c:\program files\microsoft visual studio 10.0\vc\include\crtdefs.h(520): error C2143: syntax error : missing ')' before 'const........
百度得知原因C:\WinDDK\7600.16385.1\inc\api\目录下存在一个文件sal.h,而VC环境sdk同样包含一个较新的sal.h。而编译环境首先找到wdk中的sal.h,造成了编译错误。
处理这种错误有如下方案:
1. 删掉C:\WinDDK\7600.16385.1\inc\api\sal.h文件,或将其换成别的名字。
这个方案可能会影响到其它驱动程序的编译,所以这里不推荐这种极端恐怖的做法。
2. 将crtdefs.h文件中引用sal.h的方式改为 #include "sal.h"
include中使用双引号时,编译器会先从调用include的文件所在目录开始找起,这样,被引用的文件将是C:\Program Files\Microsoft Visual Studio 9.0\VC\include\sal.h。
这个改动使VS2008的工程每次都引用C:\Program Files\Microsoft Visual Studio 9.0\VC\include\sal.h,但目前看来,问题不会很大。或许会对一些驱动工程造成影响,所以这样修改时还是要小心为上。
3. Just use '$(IncludePath);C:\WinDDK\6001.18001\inc\api' as include directories. (好的方案)
代码开发完毕后,觉得直接使用WDK提供的ndisprot.inf文件不够专业,于是对其改造了一番,最主要的就是更改了注册的服务名称及与服务名称相关的其它项。但是安装后,发现绑定回调函数并未被调用,经排查发现,仅仅修改inf文件中的服务名是不行的,还要修改注册协议时NDIS_PROTOCOL_DRIVER_CHARACTERISTICS结构体的名称,使其和服务名称一致,才能使协议驱动正常工作。