Newbie question on PENDING request KMDF

Hi

I just want to add a asynchronous IOCTL in my sample KMDF driver.
From what I’ve learned The framework will pend all io so that I do not have do any work to mark the request Pending. Right ?
Just for testing, I use a WDF timer to completed my async request.
In the driver dispatch function I just return after setup a timer.
Later on when the timer expires, I complete the pending request.
My driver code looks like:

VOID
GageEvtIoDeviceControl(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t OutputBufferLength,
IN size_t InputBufferLength,
IN ULONG IoControlCode
)
{

switch (IoControlCode) {

case MY_ASYNC_IOCTL:
deviceData->pendingRequest = Request;
inTimerQueue = WdfTimerStart( deviceData->Timer, WDF_REL_TIMEOUT_IN_MS(1000) );
return;

} // end switch

}

VOID MyTimerFunc ( IN WDFTIMER Timer )
{

WdfRequestCompleteWithInformation(deviceData->pendingRequest, STATUS_SUCCESS, 0);

}

From my test application, I open the diver with FILE_FLAG_OVERLAP and I call DeviceIoControl with OVERLAPPED structure attached
an event that will be signaled when my requested is completed.
My application code looks like

u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0, &BytesRead, &OverlapStruct );
if ( 0 == u32RetCode )
{
uInt32 u32ErrorCode = GetLastError();
if ( ERROR_IO_PENDING == u32ErrorCode )
TRACE(“The request is pending …\n”);

WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
TRACE(“The request is completed\n”);
}

It seems to work. The request is completed after the expected eslapped time.

THE PROBLEM IS
From the application I do not receive the ERROR_IO_PENDING. The call DeviceIoControl() returns 0 and GetLatError() also returns 0.
What am I doing wrong ?

Thanks for help
QV

xxxxx@hotmail.com wrote:

I just want to add a asynchronous IOCTL in my sample KMDF driver.
From what I’ve learned The framework will pend all io so that I do not have do any work to mark the request Pending. Right ?

Right.

case MY_ASYNC_IOCTL:
deviceData->pendingRequest = Request;
inTimerQueue = WdfTimerStart( deviceData->Timer, WDF_REL_TIMEOUT_IN_MS(1000) );
return;

I understand this is just an experiment. But for future reference, it
is better to create a manual queue for things like this, even if that
queue only holds one request. That way, you get automatic cancellation
support.

From my test application, I open the diver with FILE_FLAG_OVERLAP and I call DeviceIoControl with OVERLAPPED structure attached
an event that will be signaled when my requested is completed.

Show us the call to CreateFile. It’s easy to put the
FILE_FLAP_OVERLAPPED flag in the wrong spot. You might also show us how
you created OverlapStruct.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

Hi Tim,

Thank you very much for your quick response. Here are my code for CreateFile and Overlapped structure:

// Open kernel driver by using its device symbolic link
hDriver = CreateFile( (LPCSTR)szDriverName,
GENERIC_READ|GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
(void *)NULL);

OVERLAPPED Overlapped = {0};

Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL );
if ( ! Overlapped.hEvent )
{
TRACE(“Cannot create the event for Async request\n”);
return;
}

u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0, &BytesRead, &OverlapStruct );
if ( 0 == u32RetCode )
{
uInt32 u32ErrorCode = GetLastError();
if ( ERROR_IO_PENDING == u32ErrorCode )
TRACE(“The request is pending …\n”);

WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
TRACE(“The request is completed\n”);
}

CloseHandle(Overlapped.hEvent);

Thanks

xxxxx@hotmail.com wrote:

OVERLAPPED Overlapped = {0};

Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL );
if ( ! Overlapped.hEvent )
{
TRACE(“Cannot create the event for Async request\n”);
return;
}

u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0, &BytesRead, &OverlapStruct );

“OverlapStruct” is not the same as “Overlapped”.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

Sorry for some typos errors. This correct one.

// Open kernel driver by using its device symbolic link
hDriver = CreateFile( (LPCSTR)szDriverName, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, (void *)NULL);

OVERLAPPED OverlapStruct = {0};
OverlapStruct.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL );
if ( ! OverlapStruct .hEvent )
{
TRACE(“Cannot create the event for Async request\n”);
return;
}

u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0, &BytesRead, &OverlapStruct );

if ( 0 == u32RetCode )
{
uInt32 u32ErrorCode = GetLastError();
if ( ERROR_IO_PENDING == u32ErrorCode )
TRACE(“The request is pending …\n”);

WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
TRACE(“The request is completed\n”);
}
CloseHandle(OverlapStruct.hEvent);

xxxxx@hotmail.com wrote:

Sorry for some typos errors. This correct one.

Well, now we know that you aren’t really cutting and pasting your actual
code – you’re typing in your general flow. That means we don’t really
know what your code look like.

What DO you see? Does the DeviceIoControl return success immediately?
Does BytesRead get set to 0?

// Open kernel driver by using its device symbolic link
hDriver = CreateFile( (LPCSTR)szDriverName, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, (void *)NULL);

Casts are evil, and neither of the casts in that line should be
required. Are you checking that the CreateFile actually succeeds?

How did you define MY_ASYNC_IOCTL? Did you actually use the CTL_CODE
macro?

u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0, &BytesRead, &OverlapStruct );

if ( 0 == u32RetCode )
{
uInt32 u32ErrorCode = GetLastError();
if ( ERROR_IO_PENDING == u32ErrorCode )
TRACE(“The request is pending …\n”);

WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
TRACE(“The request is completed\n”);
}
CloseHandle(OverlapStruct.hEvent);

You should print something in the “success” case, so you can know what
actually happened. Also, if you do NOT get ERROR_IO_PENDING, then you
do not want to call WaitForSingleObject. That means something failed.
The flow in an overlapped request usually goes something like this:

u32RetCode = DeviceIoControl( … );
if( u32RetCode )
{
// DeviceIoControl returned success immediately.
}
else
{
DWORD ErrorCode = GetLastError();
if( ErrorCode == ERROR_IO_PENDING )
{
TRACE( “waiting…” );
u32RetCode = GetOverlappedResult( … );
if( u32RetCode )
{
TRACE( “Operation now finished normally.” );
}
else
{
ErrorCode = GetLastError();
TRACE( “Error %d after wait.”, ErrorCode );
}
}
else
{
TRACE( “Error %d issuing the request.”, ErrorCode );
}
}


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

DeviceIoControl is specified as returning a BOOL value, not a uInt32.

So I have no idea why someone would use the wrong type.

The last parameter of CreateFile is clearly specified as a HANDLE type, so
it is unclear why some weird cast would be used; in fact, NULL without a
cast is fine.

Note that the OVERLAPPED structure is a local variable, so the only reason
this works at all is the WFSO which keeps the variable in scope.

I hope this is in a separate thread unless the IOCTL has a small bounded
wait time. But if it does, why does it need to be asynch?

I agree with all other points you have raised.
joe

xxxxx@hotmail.com wrote:
> Sorry for some typos errors. This correct one.

Well, now we know that you aren’t really cutting and pasting your actual
code – you’re typing in your general flow. That means we don’t really
know what your code look like.

What DO you see? Does the DeviceIoControl return success immediately?
Does BytesRead get set to 0?

> // Open kernel driver by using its device symbolic link
> hDriver = CreateFile( (LPCSTR)szDriverName, GENERIC_READ|GENERIC_WRITE,
> 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, (void *)NULL);

Casts are evil, and neither of the casts in that line should be
required. Are you checking that the CreateFile actually succeeds?

How did you define MY_ASYNC_IOCTL? Did you actually use the CTL_CODE
macro?

> u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL, 0,
> &BytesRead, &OverlapStruct );
>
> if ( 0 == u32RetCode )
> {
> uInt32 u32ErrorCode = GetLastError();
> if ( ERROR_IO_PENDING == u32ErrorCode )
> TRACE(“The request is pending …\n”);
>
> WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
> TRACE(“The request is completed\n”);
> }
> CloseHandle(OverlapStruct.hEvent);

You should print something in the “success” case, so you can know what
actually happened. Also, if you do NOT get ERROR_IO_PENDING, then you
do not want to call WaitForSingleObject. That means something failed.
The flow in an overlapped request usually goes something like this:

u32RetCode = DeviceIoControl( … );
if( u32RetCode )
{
// DeviceIoControl returned success immediately.
}
else
{
DWORD ErrorCode = GetLastError();
if( ErrorCode == ERROR_IO_PENDING )
{
TRACE( “waiting…” );
u32RetCode = GetOverlappedResult( … );
if( u32RetCode )
{
TRACE( “Operation now finished normally.” );
}
else
{
ErrorCode = GetLastError();
TRACE( “Error %d after wait.”, ErrorCode );
}
}
else
{
TRACE( “Error %d issuing the request.”, ErrorCode );
}
}


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

On the evilness of casts: (LPCSTR) is simply WRONG! The correct cast, if
one were required, is in accordance with the documentation: (LPCTSTR). A
*really* bad case of RTFM here!

DeviceIoControl is specified as returning a BOOL value, not a uInt32.

So I have no idea why someone would use the wrong type.

The last parameter of CreateFile is clearly specified as a HANDLE type, so
it is unclear why some weird cast would be used; in fact, NULL without a
cast is fine.

Note that the OVERLAPPED structure is a local variable, so the only reason
this works at all is the WFSO which keeps the variable in scope.

I hope this is in a separate thread unless the IOCTL has a small bounded
wait time. But if it does, why does it need to be asynch?

I agree with all other points you have raised.
joe

> xxxxx@hotmail.com wrote:
>> Sorry for some typos errors. This correct one.
>
> Well, now we know that you aren’t really cutting and pasting your actual
> code – you’re typing in your general flow. That means we don’t really
> know what your code look like.
>
> What DO you see? Does the DeviceIoControl return success immediately?
> Does BytesRead get set to 0?
>
>> // Open kernel driver by using its device symbolic link
>> hDriver = CreateFile( (LPCSTR)szDriverName, GENERIC_READ|GENERIC_WRITE,
>> 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, (void *)NULL);
>
> Casts are evil, and neither of the casts in that line should be
> required. Are you checking that the CreateFile actually succeeds?
>
> How did you define MY_ASYNC_IOCTL? Did you actually use the CTL_CODE
> macro?
>
>> u32RetCode = DeviceIoControl( hDriver, MY_ASYNC_IOCTL, NULL, 0, NULL,
>> 0,
>> &BytesRead, &OverlapStruct );
>>
>> if ( 0 == u32RetCode )
>> {
>> uInt32 u32ErrorCode = GetLastError();
>> if ( ERROR_IO_PENDING == u32ErrorCode )
>> TRACE(“The request is pending …\n”);
>>
>> WaitForSingleObject( OverlapStruct.hEvent, INFINITE );
>> TRACE(“The request is completed\n”);
>> }
>> CloseHandle(OverlapStruct.hEvent);
>
> You should print something in the “success” case, so you can know what
> actually happened. Also, if you do NOT get ERROR_IO_PENDING, then you
> do not want to call WaitForSingleObject. That means something failed.
> The flow in an overlapped request usually goes something like this:
>
> u32RetCode = DeviceIoControl( … );
> if( u32RetCode )
> {
> // DeviceIoControl returned success immediately.
> }
> else
> {
> DWORD ErrorCode = GetLastError();
> if( ErrorCode == ERROR_IO_PENDING )
> {
> TRACE( “waiting…” );
> u32RetCode = GetOverlappedResult( … );
> if( u32RetCode )
> {
> TRACE( “Operation now finished normally.” );
> }
> else
> {
> ErrorCode = GetLastError();
> TRACE( “Error %d after wait.”, ErrorCode );
> }
> }
> else
> {
> TRACE( “Error %d issuing the request.”, ErrorCode );
> }
> }
>
> –
> Tim Roberts, xxxxx@probo.com
> Providenza & Boekelheide, Inc.
>
>
> —
> NTDEV is sponsored by OSR
>
> For our schedule of WDF, WDM, debugging and other seminars visit:
> http://www.osr.com/seminars
>
> To unsubscribe, visit the List Server section of OSR Online at
> http://www.osronline.com/page.cfm?name=ListServer
>


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

xxxxx@flounder.com wrote:

On the evilness of casts: (LPCSTR) is simply WRONG! The correct cast, if
one were required, is in accordance with the documentation: (LPCTSTR).

Agreed, although in this direction, that’s unlikely to hide a bug. The
way people get in trouble is the other way around:

#define UNICODE

handle = CreateFile( (LPCTSTR) “MyFileName”, …

The cast suppresses the error, but ends up passing an ANSI filename to
an API expecting Unicode.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.

A cast is the programmer’s way to tell the compiler “My uncle, in the
Nigerian army, left this hoard of gold in the bank, and if we can get this
line of code past the compiler, you can share it.” There are very few
places casts are actually required, and you can always spot a beginner by
looking for unnecessary casts in the code. Or the failure to RTFM, or to
use weird data types (like uInt32 instead of the specified BOOL).
joe

xxxxx@flounder.com wrote:
> On the evilness of casts: (LPCSTR) is simply WRONG! The correct cast, if
> one were required, is in accordance with the documentation: (LPCTSTR).

Agreed, although in this direction, that’s unlikely to hide a bug. The
way people get in trouble is the other way around:

#define UNICODE

handle = CreateFile( (LPCTSTR) “MyFileName”, …

The cast suppresses the error, but ends up passing an ANSI filename to
an API expecting Unicode.


Tim Roberts, xxxxx@probo.com
Providenza & Boekelheide, Inc.


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

(and now… continuing this off-topic tangent for no apparent reason, and even including a senseless rant just to add fuel to the off-topic tanget)

You write many drivers in C++ ?? Even just using C++ as “a better C”??

If you do, casts are a way of life, plain and simple. To consign everyone who casts as a “beginner” is simply silly.

The Windows DDIs were not designed for use with strong type checking. As a result, I find that I’m FREQUENTLY forced to cast – including cases of MORE specific pointers to LESS specific pointers. So, I have PMY_STRUCTURE and the DDI wants PVOID * or something. Why C++ doesn’t understand this basic type of upcasting is beyond me. But, aside from strong type checking, I hate C++ like a sickness, so this is just one more example of what I consider to be the stupidity of this poorly constructed language.

Peter
OSR

One of places that casts a required is exactly the case where something
has to be passed through a void*-shaped hole. Or a WPARAM, LPARAM, or
xxx_PTR-shaped hole. But the LPCSTR and void* casts of the example were
completely gratuitous. And wrong, to boot.

I see newbies casting unnecessarily all the time. A cast is only
necessary when it is necessary, and should not be used otherwise. Picking
up the device extension, for example.

Have you seen the classic pass a pointer to an array of ints:

int data[100];

function( (int *)&data[0] );

(sure, it works, but why go through all that trouble?)

It is important to not write a cast unless it is actually required by the
nature of the problem.

Of course, I usually make money on things like this, when the programs
fail and they come to me to fix them. So as far as I’m concerned,
gratuitous casts are profitable.
joe

(and now… continuing this off-topic tangent for no apparent reason, and
even including a senseless rant just to add fuel to the off-topic tanget)

You write many drivers in C++ ?? Even just using C++ as “a better C”??

If you do, casts are a way of life, plain and simple. To consign everyone
who casts as a “beginner” is simply silly.

The Windows DDIs were not designed for use with strong type checking. As
a result, I find that I’m FREQUENTLY forced to cast – including cases of
MORE specific pointers to LESS specific pointers. So, I have
PMY_STRUCTURE and the DDI wants PVOID * or something. Why C++ doesn’t
understand this basic type of upcasting is beyond me. But, aside from
strong type checking, I hate C++ like a sickness, so this is just one more
example of what I consider to be the stupidity of this poorly constructed
language.

Peter
OSR


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

+1

I resist the disease strongly, but sometimes one can’t help but to succumb.

I’m wasn’t arguing that gratuitous casts aren’t bad. Or that newbs don’t cast too much. They are, and they often do.

I was merely arguing that your statement:

is not true in my experience writing Windows drivers using the C++ compiler.

MY experience is that you’re FREQUENTLY forced to cast when writing a Windows driver. Usually in ugly circumstances, too. Like the one I cited, where the DDI is typed to return a PVOID in a PVOID * and you have a PFOO typed variable that you want to put that returned pointer into. Very annoying.

Other REALLY common case when you need to cast are, like, every time you call ExAllocatePool. Or WdfMemoryGetBuffer. Or WdfCollectionGetFirstItem. Obviously, right? These calls return PVOID, and you want it to be a PFOO. There’s obviously nothing the DDI could do to fix this. But C++ certainly could intelligently allow the upcast. But it doesn’t, which I find frustrating.

So, I’m just saying that every driver I write has LOTS of casts. And every time I’m forced to cast, it annoys me. I’m not saying these are problems in the DDI, instead they’re just either (a) a fact of life or (b) an annoying stupidity of C++. Thus, I was merely taking exception to “few places actually required.”

Peter
OSR

+1

Having been involved on the periphery of the C++ standard (I was on the
committee but got disgusted), all I can say is the people who created
the current C++ language meet the criteria of the expression “Those who
cannot remember the past are condemned to repeat it.”

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

“m” wrote in message news:xxxxx@ntdev:

>


>
> +1
>
> I resist the disease strongly, but sometimes one can’t help but to succumb.

I want a switch that says “give me strong type checking, in-line variable declarations, and // for comments and leave everything else out” because I don’t want to accidentally have somebody define FredDoMumble with two different sets of parameters and have the compiler ACTUALLY COMPILE that.

Maybe I’ll get that in VS 999 or something.

Peter
OSR

> Other REALLY common case when you need to cast are, like, every time
you

call ExAllocatePool. Or WdfMemoryGetBuffer. Or
WdfCollectionGetFirstItem. Obviously, right? These calls return
PVOID, and
you want it to be a PFOO. There’s obviously nothing the DDI could do
to fix
this. But C++ certainly could intelligently allow the upcast. But it
doesn’t,
which I find frustrating.

Has something changed in the new WDK or is this sarcasm? When do you
need a cast to assign a PVOID to a PFOO?

James

Sadly it’s a fact of life with almost every standards body. But then
standards bodies are rarely interested in the best technical solution,
their goal is to arrive at political consensus so that whilst what
everybody agrees to may be dumb, at least everybody implements the same
dumb thing.

Mark.

On 05/10/2011 23:21, Don Burn wrote:

+1

Having been involved on the periphery of the C++ standard (I was on
the committee but got disgusted), all I can say is the people who
created the current C++ language meet the criteria of the expression
“Those who cannot remember the past are condemned to repeat it.”

Don Burn
Windows Filesystem and Driver Consulting
Website: http://www.windrvr.com
Blog: http://msmvps.com/blogs/WinDrvr

“m” wrote in message news:xxxxx@ntdev:
>
>>


>>
>> +1
>>
>> I resist the disease strongly, but sometimes one can’t help but to
>> succumb.
>

You cut and pasted the reason, but must have overlooked it ? C++. Peter, at least from other responses I have seen him give, does what several us of us do ? write C, but compile using the cpp extension or the /TP switch, allowing the stricter type checking of C++.

Gary Little
H (952) 223-1349
C (952) 454-4629
xxxxx@comcast.net

On Oct 5, 2011, at 5:44 PM, James Harper wrote:

> Other REALLY common case when you need to cast are, like, every time
you
> call ExAllocatePool. Or WdfMemoryGetBuffer. Or
> WdfCollectionGetFirstItem. Obviously, right? These calls return
PVOID, and
> you want it to be a PFOO. There’s obviously nothing the DDI could do
to fix
> this. But C++ certainly could intelligently allow the upcast. But it
doesn’t,
> which I find frustrating.
>

Has something changed in the new WDK or is this sarcasm? When do you
need a cast to assign a PVOID to a PFOO?

James


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at http://www.osronline.com/page.cfm?name=ListServer

But you just enumerated many of the few cases actually required. Now a
C++ purist would say that you neded to define a ‘new’ operator for the
class that did the allocation and hid the casting, e.g.

PFOO p = new FOO;

but we are a long way from a world in C++ where this would be easy in the
kernel, and besides, if the header files were modified we’d probably break
all the C code in existence.

When you compare the very few places a void* needs to be downcast with the
kind of gratuitous casting I see in user-level newsgroups, while you may
think you use lots of casts, by comparison you use very few.

There sems to be a scool of thought that the right solution to a
compilation error is to thow casts at the code, like pixie dust, until it
compiles. Then someone asks me to create a 64-bit version, and the casts
let it compile. Run? Well, it compiled so it must be correct…
joe

I’m wasn’t arguing that gratuitous casts aren’t bad. Or that newbs don’t
cast too much. They are, and they often do.

I was merely arguing that your statement:

is not true in my experience writing Windows drivers using the C++
compiler.

MY experience is that you’re FREQUENTLY forced to cast when writing a
Windows driver. Usually in ugly circumstances, too. Like the one I
cited, where the DDI is typed to return a PVOID in a PVOID * and you have
a PFOO typed variable that you want to put that returned pointer into.
Very annoying.

Other REALLY common case when you need to cast are, like, every time you
call ExAllocatePool. Or WdfMemoryGetBuffer. Or
WdfCollectionGetFirstItem. Obviously, right? These calls return PVOID,
and you want it to be a PFOO. There’s obviously nothing the DDI could do
to fix this. But C++ certainly could intelligently allow the upcast. But
it doesn’t, which I find frustrating.

So, I’m just saying that every driver I write has LOTS of casts. And
every time I’m forced to cast, it annoys me. I’m not saying these are
problems in the DDI, instead they’re just either (a) a fact of life or
(b) an annoying stupidity of C++. Thus, I was merely taking exception to
“few places actually required.”

Peter
OSR


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer