From: J Prendergast on
Hello,

I'm working on an application that acts on WM_DEVICECHANGE messages with
wParam value == DBT_DEVICEARRIVAL (indicating the device has either just been
plugged into the system or has just been enabled in Device Manager), examines
the device ID, and if it matches a particular wildcard value, it disables the
device using calls to SetupDiSetClassInstallParams and
SetupDiCallClassInstaller with a control.context value == DICS_DISABLE.

Here's the phenomena I'm seeing:
1) In the above functionality, the call to SetupDiCallClassInstaller takes
approximately 30 seconds to execute, after which time the device gets
disabled successfully.
2) If I write similar code that responds to WM_DEVICECHANGE /
DBT_DEVICEREMOVECOMPLETE messages (when a device is disabled) by calling the
code to enable the device, it works fine and immediately.
3) If I have a button in my application that when pressed just calls these
functions to enable or disable a device, the code works absolutely fine and
the device state is changed immediately (for all of my tests, I'm running
Device Manager alongside my application so I can monitor whether the device
has a red cross on it, indicating a disabled state, or not, indicating an
enabled state).

My question then is - why does it take so long to disable a device in
response to it being added / enabled?
Is there an alternative approach that would give better results?

I wondered if it might be a timing problem, e.g. I'm trying to disable the
device too soon after it's been added / enabled, and it isn't fully
initialised yet. So I tried adding a Sleep into the code, even up to 20
seconds, but it made no difference.

The code I use for the actual enabling / disabling has been lifted from the
DDK Devcon sample application (which exposes all of Device Manager's
functionality via a command line interface), a combination of the cmdEnable,
cmdDisable and ControlCallback functions:

----------
bool CDeviceControlHelper::EnableDevice(const HDEVINFO Devs,
const PSP_DEVINFO_DATA DevInfo,
const bool& bEnable,
tstring& sRetMessage)
{
GenericContext context;
TCHAR strEnable[80];
TCHAR strDisable[80];
TCHAR strReboot[80];
TCHAR strFail[80];

if (bEnable)
{
if (!LoadString(NULL,IDS_ENABLED,strEnable,ARRAYSIZE(strEnable)))
{
return false;
}
if (!LoadString(NULL,IDS_ENABLED_REBOOT,strReboot,ARRAYSIZE(strReboot)))
{
return false;
}
if (!LoadString(NULL,IDS_ENABLE_FAILED,strFail,ARRAYSIZE(strFail)))
{
return false;
}

context.control = DICS_ENABLE; // DICS_PROPCHANGE DICS_ENABLE DICS_DISABLE
context.strSuccess = strEnable;
}
else
{
if (!LoadString(NULL,IDS_DISABLED,strDisable,ARRAYSIZE(strDisable)))
{
return false;
}
if (!LoadString(NULL,IDS_DISABLED_REBOOT,strReboot,ARRAYSIZE(strReboot)))
{
return false;
}
if (!LoadString(NULL,IDS_DISABLE_FAILED,strFail,ARRAYSIZE(strFail)))
{
return false;
}

context.control = DICS_DISABLE; // DICS_PROPCHANGE DICS_ENABLE DICS_DISABLE
context.strSuccess = strDisable;
}

context.reboot = FALSE;
context.count = 0;
context.strReboot = strReboot;
context.strFail = strFail;

// ControlCallback function body

SP_PROPCHANGE_PARAMS pcp;
SP_DEVINSTALL_PARAMS devParams;

switch(context.control)
{
case DICS_ENABLE:
//
// enable both on global and config-specific profile
// do global first and see if that succeeded in enabling the
device
// (global enable doesn't mark reboot required if device is still
// disabled on current config whereas vice-versa isn't true)
//
pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
pcp.StateChange = context.control;
pcp.Scope = DICS_FLAG_GLOBAL;
pcp.HwProfile = 0;
//
// don't worry if this fails, we'll get an error when we try
config-
// specific.
if (SetupDiSetClassInstallParams(Devs, DevInfo,
&pcp.ClassInstallHeader, sizeof(pcp)))
{
SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,Devs,DevInfo);
}
//
// now enable on config-specific
//
pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
pcp.StateChange = context.control;
pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
pcp.HwProfile = 0;
break;

default:
//
// operate on config-specific profile
//
pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
pcp.StateChange = context.control;
pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
pcp.HwProfile = 0;
break;

}

if (!SetupDiSetClassInstallParams(Devs, DevInfo, &pcp.ClassInstallHeader,
sizeof(pcp)) ||
!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,Devs,DevInfo))
{
//
// failed to invoke DIF_PROPERTYCHANGE
//
DumpDeviceWithInfo(Devs,DevInfo,context.strFail);
}
else
{
//
// see if device needs reboot
//
devParams.cbSize = sizeof(devParams);
if (SetupDiGetDeviceInstallParams(Devs,DevInfo,&devParams) &&
(devParams.Flags & (DI_NEEDRESTART|DI_NEEDREBOOT)))
{
DumpDeviceWithInfo(Devs,DevInfo,context.strReboot);
context.reboot = TRUE;
}
else
{
//
// appears to have succeeded
//
DumpDeviceWithInfo(Devs,DevInfo,context.strSuccess);
}
context.count++;
}

bool bRet = false;

if (bEnable)
{
if (!context.count)
{
sRetMessage.LoadString(IDS_MSG_ENABLE_TAIL_NONE);
}
else if (!context.reboot)
{
sRetMessage.Format(IDS_MSG_ENABLE_TAIL, context.count);
bRet = true;
}
else
{
sRetMessage.Format(IDS_MSG_ENABLE_TAIL_REBOOT, context.count);
}
}
else
{
if (!context.count)
{
sRetMessage.LoadString(IDS_MSG_DISABLE_TAIL_NONE);
}
else if (!context.reboot)
{
sRetMessage.Format(IDS_MSG_DISABLE_TAIL, context.count);
bRet = true;
}
else
{
sRetMessage.Format(IDS_MSG_DISABLE_TAIL_REBOOT, context.count);
}
}

return bRet;
}
----------

Many thanks in advance for any help / advice that anyone can offer in
response to this problem.

JP
From: J Prendergast on
Hello again,

Following on from my earlier message, I've found something that helps with
the 30 second delay problem. Instead of calling the
SetupDiSetClassInstallParams / SetupDiCallClassInstaller functionality
directly from the WM_DEVICECHANGE / DBT_DEVICEARRIVAL case, I now set a
variable containing the device id to indicate that disabling is required.
I've set up a timer to fire every 5 seconds, and in the WM_TIMER case, I call
the SetupDiSetClassInstallParams / SetupDiCallClassInstaller functionality.
The disabling takes place immediately upon this code being called. I'm not
sure why this approach helps, but it does.

My changes have improved the responsiveness, but something else that I see
occasionally is that when I have sent a request to disable a device, the
device still looks enabled (no red cross through it) in Device Manager and is
accessible ? yet the right-click context menu for the item in Device Manager
shows an ?Enabled? option, giving the impression that the device _is_
disabled. This confusion seems to remain until the computer is restarted.
Does anyone have an idea how to get around this, so that the state changes
in Device Manager and no restart is required?

Interestingly, I saw a similar phenomenon when I set the timer to a shorter
period, e.g. 1 second, - i.e. the device looked enabled in Device Manager but
it was still accessible and the context menu for it showed an "Enable" option
implying that it's been disabled. It's almost as though the disabling had
been "half-done" somehow.

I'd be very grateful for any ideas about the above behaviour and possible
ways of dealing with, thanks again,

JP

"J Prendergast" wrote:

> Hello,
>
> I'm working on an application that acts on WM_DEVICECHANGE messages with
> wParam value == DBT_DEVICEARRIVAL (indicating the device has either just been
> plugged into the system or has just been enabled in Device Manager), examines
> the device ID, and if it matches a particular wildcard value, it disables the
> device using calls to SetupDiSetClassInstallParams and
> SetupDiCallClassInstaller with a control.context value == DICS_DISABLE.
>
> Here's the phenomena I'm seeing:
> 1) In the above functionality, the call to SetupDiCallClassInstaller takes
> approximately 30 seconds to execute, after which time the device gets
> disabled successfully.
> 2) If I write similar code that responds to WM_DEVICECHANGE /
> DBT_DEVICEREMOVECOMPLETE messages (when a device is disabled) by calling the
> code to enable the device, it works fine and immediately.
> 3) If I have a button in my application that when pressed just calls these
> functions to enable or disable a device, the code works absolutely fine and
> the device state is changed immediately (for all of my tests, I'm running
> Device Manager alongside my application so I can monitor whether the device
> has a red cross on it, indicating a disabled state, or not, indicating an
> enabled state).
>
> My question then is - why does it take so long to disable a device in
> response to it being added / enabled?
> Is there an alternative approach that would give better results?
>
> I wondered if it might be a timing problem, e.g. I'm trying to disable the
> device too soon after it's been added / enabled, and it isn't fully
> initialised yet. So I tried adding a Sleep into the code, even up to 20
> seconds, but it made no difference.
>
> The code I use for the actual enabling / disabling has been lifted from the
> DDK Devcon sample application (which exposes all of Device Manager's
> functionality via a command line interface), a combination of the cmdEnable,
> cmdDisable and ControlCallback functions:
>
> ----------
> bool CDeviceControlHelper::EnableDevice(const HDEVINFO Devs,
> const PSP_DEVINFO_DATA DevInfo,
> const bool& bEnable,
> tstring& sRetMessage)
> {
> GenericContext context;
> TCHAR strEnable[80];
> TCHAR strDisable[80];
> TCHAR strReboot[80];
> TCHAR strFail[80];
>
> if (bEnable)
> {
> if (!LoadString(NULL,IDS_ENABLED,strEnable,ARRAYSIZE(strEnable)))
> {
> return false;
> }
> if (!LoadString(NULL,IDS_ENABLED_REBOOT,strReboot,ARRAYSIZE(strReboot)))
> {
> return false;
> }
> if (!LoadString(NULL,IDS_ENABLE_FAILED,strFail,ARRAYSIZE(strFail)))
> {
> return false;
> }
>
> context.control = DICS_ENABLE; // DICS_PROPCHANGE DICS_ENABLE DICS_DISABLE
> context.strSuccess = strEnable;
> }
> else
> {
> if (!LoadString(NULL,IDS_DISABLED,strDisable,ARRAYSIZE(strDisable)))
> {
> return false;
> }
> if (!LoadString(NULL,IDS_DISABLED_REBOOT,strReboot,ARRAYSIZE(strReboot)))
> {
> return false;
> }
> if (!LoadString(NULL,IDS_DISABLE_FAILED,strFail,ARRAYSIZE(strFail)))
> {
> return false;
> }
>
> context.control = DICS_DISABLE; // DICS_PROPCHANGE DICS_ENABLE DICS_DISABLE
> context.strSuccess = strDisable;
> }
>
> context.reboot = FALSE;
> context.count = 0;
> context.strReboot = strReboot;
> context.strFail = strFail;
>
> // ControlCallback function body
>
> SP_PROPCHANGE_PARAMS pcp;
> SP_DEVINSTALL_PARAMS devParams;
>
> switch(context.control)
> {
> case DICS_ENABLE:
> //
> // enable both on global and config-specific profile
> // do global first and see if that succeeded in enabling the
> device
> // (global enable doesn't mark reboot required if device is still
> // disabled on current config whereas vice-versa isn't true)
> //
> pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
> pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
> pcp.StateChange = context.control;
> pcp.Scope = DICS_FLAG_GLOBAL;
> pcp.HwProfile = 0;
> //
> // don't worry if this fails, we'll get an error when we try
> config-
> // specific.
> if (SetupDiSetClassInstallParams(Devs, DevInfo,
> &pcp.ClassInstallHeader, sizeof(pcp)))
> {
> SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,Devs,DevInfo);
> }
> //
> // now enable on config-specific
> //
> pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
> pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
> pcp.StateChange = context.control;
> pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
> pcp.HwProfile = 0;
> break;
>
> default:
> //
> // operate on config-specific profile
> //
> pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
> pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
> pcp.StateChange = context.control;
> pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
> pcp.HwProfile = 0;
> break;
>
> }
>
> if (!SetupDiSetClassInstallParams(Devs, DevInfo, &pcp.ClassInstallHeader,
> sizeof(pcp)) ||
> !SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,Devs,DevInfo))
> {
> //
> // failed to invoke DIF_PROPERTYCHANGE
> //
> DumpDeviceWithInfo(Devs,DevInfo,context.strFail);
> }
> else
> {
> //
> // see if device needs reboot
> //
> devParams.cbSize = sizeof(devParams);
> if (SetupDiGetDeviceInstallParams(Devs,DevInfo,&devParams) &&
> (devParams.Flags & (DI_NEEDRESTART|DI_NEEDREBOOT)))
> {
> DumpDeviceWithInfo(Devs,DevInfo,context.strReboot);
> context.reboot = TRUE;
> }
> else
> {
> //
> // appears to have succeeded
> //
> DumpDeviceWithInfo(Devs,DevInfo,context.strSuccess);
> }
> context.count++;
> }
>
> bool bRet = false;
>
> if (bEnable)
> {
> if (!context.count)
> {
> sRetMessage.LoadString(IDS_MSG_ENABLE_TAIL_NONE);
> }
> else if (!context.reboot)
> {
> sRetMessage.Format(IDS_MSG_ENABLE_TAIL, context.count);
> bRet = true;
> }
> else
> {
> sRetMessage.Format(IDS_MSG_ENABLE_TAIL_REBOOT, context.count);
> }
> }
> else
> {
> if (!context.count)
> {
> sRetMessage.LoadString(IDS_MSG_DISABLE_TAIL_NONE);
> }
> else if (!context.reboot)
> {
> sRetMessage.Format(IDS_MSG_DISABLE_TAIL, context.count);
> bRet = true;
> }
> else
> {
> sRetMessage.Format(IDS_MSG_DISABLE_TAIL_REBOOT, context.count);
> }
> }
>
> return bRet;
> }
> ----------
>
> Many thanks in advance for any help / advice that anyone can offer in
> response to this problem.
>
> JP
From: Daniel Whitaker [MSFT] on
Greetings,

Interesting project :) Some of this is a bit curious, but I do know that
there is a difference when you are adding a new device/enabling for the
first time. Windows creates a special thread for the install and will not
allow other "PNP" events to occur until it completes. If it does not
succeed, you will normally see a "there was a problem with your hardware"
bubble. But otherwise there can be a delay for the next "PNP" event. They
sort of line up in a row and then are sent through this thread pipe. There
is a timeout value of up to 2 minutes, but generally it seems to be about
30 seconds to a minute.

Any other time, the function call is taken care of immediately. I would
think that calling the ClassInstaller when the thread is active would
probably cause a delay because of the install thread. Does that make any
sense?

Daniel Whitaker
DDK Support Team

This posting is provided "AS IS" with no warranties, and confers no rights


 | 
Pages: 1
Prev: MSVAD Simple Sample
Next: Duplicating NDIS_PACKET