Welcome to MSDN Blogs Sign in | Join | Help

Using C++ in a KMDF driver part 1- a pattern for using contexts as objects

This is an article I’ve started probably close to a dozen times since I started this blog, but never published.  In part because of all the heat the topic of using C++ in the kernel generates, and the rest perhaps because of my reaction to that heat.

So I’ll get one thing off my chest at the start and perhaps that will be enough to let me proceed.

I’ve been writing drivers with C++ in the kernel (or the Win 3.1/9x/ME VMM) pretty much since I began using the language (in the very early 90’s).  I routinely use paged code (paged data not so much- I’ve never had designs where there seemed to be any benefit to it).  That spans more than a decade- in fact, its close to two decades now.

I’ve NEVER had a problem with the issues raised in the paper.  I’m not even going to link to it.  If you want to find it- it isn’t hard to find.  On top of that, in a determined effort to find people who had, I didn’t find many- and the one clear case I did find was cured with a #pragma that would have made sense if you were a C programmer and had a basic understanding of where VTABLES and such get emitted to.  So I personally have a sense of mismatch between my own experience and the strongly worded severity there.

I’ve been told that if I persist, I’ll deserve all the paging problems I get [and that nobody will help me with them].  Well, I do get them (always have), and I’ve been developing long enough in this environment that I don’t really need anybody’s help to find the root cause of a bug like that.  The causes are always the ones I remember- acquiring a spin lock in pagable code- making a routine pagable that can be called at elevated IRQL- all the usual ways to screw up in that other language that is the hallmark of a true first-class kernel developer.  But none of them had anything to do with my choice of programming language.  Not once.  Not saying that it never happened across all those years, because in the early ones, the ability to catch a bug like that was severely limited- there was no Driver Verifier, no static analysis tools.  So it may not have been noticed early on.  But now that I have them, it’s still not happening…

Now there can be plenty of reasons for that- but I look at what people take away from the paper, and I do a lot of the things they think aren’t safe.  I use polymorphism and inheritance freely.  I don’t use multiple inheritance a lot, but I have used it and have not observed problems arising from its use.  On the other hand, there are common features that I don’t use as a matter of personal preference or style that may bear on this.  I don’t expose the implementation of functions in header files (meaning the compiler is not going to start out by inlining them and then give up later, dumping them in some unintended segment) with the exception of trivial accessor functions.  I don’t use templates (I’m not sure they were a language feature when I started, but at any rate, during my learning curve I never needed them, so while I can handle code with templates, I don’t use them myself).  I almost always code my own constructors, destructors, copy constructors and assignment operators (I usually have to- I prefer references to pointers and if you have reference members, the compiler can’t generate default code for most of those routines).

So now that its clear I’m not going to walk the company line on that topic [the opinions I expressed ought obviously to be clearly my own], I’ll proceed to something more useful…

Leveraging the KMDF Object Model

KMDF provides a nice object model, with managed lifetimes and one of the most delightful observations I had in my first KMDF driver was that it was easy for me to blend this with my usual coding patterns.

As an aside, I’ll note that I never use sample code to learn anything.  I take the reference materials and code to them.  If there are samples, I treat them as the last resort- and I will deliberately change as much as I can of them [in part to see what knowledge of things that can go wrong wasn’t explicitly represented in the sample].  I judge the quality of the reference material by how rarely I have to look at a sample to figure something out [yes, I don’t find much reference material i would call “good”].

So my first KMDF driver was a software bus driver, and I didn’t go near toaster in writing it.  In fact it was one of those “ActiveX” (OLE automation) test drivers I mentioned in our DDC presentation.  Now we had some samples for them, too- and not surprisingly, virtually no reference material.  The samples were fastidious about one thing- all the COM parts were in C++ (not a lot of choice there)- but all the parts using KMDF were in C- precisely because of said paper.  I might add that this was even though no attempt was made to mark any of the code or data pagable in those samples.  Well, I’m a stubborn <expletive of your choice here>, so I decided then and there I was going to write the whole driver in C++ in spite of the objections I received.  I was still in my “trial period” so they could always fire me if they wanted to, but the job market was good enough at that time that I was pretty sure I could find something…

Back to the proper topic- one fine thing is that most of the KMDF macros are agnostic enough they can handle at least straightforward C++.  That was one of the happiest discoveries of that time.

So the basic pattern as I use it:

  1. Declare a static function in your class that takes a WDFOBJECT as input and returns a pointer to an object of your class.
  2. Declare a class-specific placement form new operator that takes as its additional input the type of handle you expect your object to live in the context of (that is, it can be more precise than the preceding function can and you can benefit from stronger typing in C++)
  3. Declare a class-specific delete operator that basically does nothing if you need to have your destructor invoked.
  4. If you have such a delete operator, also declare a static member with a void return that takes a WDFOBJECT as input.
  5. Use the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro to get the compiler to write that first function for you.
  6. Have your new operator implementation use the first function to return the address of the underlying context (you basically ignore or validate the size parameter) from the passed-in handle.
  7. When creating the context, and you need your destructor called, use a WDF_OBJECT_ATTRIBUTES structure with the EvtCleanupCallback set to the routine in item 4.
  8. Code that routine to use the routine in item 1 to get the context address out of the object handle- and delete that pointer.  This causes your destructor to be called at cleanup time (which is much more sensible than destroy time) and your do-nothing delete operator will also be invoked (or inlined out of existence if your compiler is any good).

There- you “create” your object when the KMDF object is created (or via a WdfObjectAllocateContext call if you add your object later), and “delete” it when the object dies.  But KMDF manages the memory and most of the object lifetime for you.  Sure works for me (a lot).

The following snippet is from that first driver (I’ve since dropped the usage of “C” on class definitions in my general drive toward anarchic style).  This is slightly convoluted because I have logic allowing only one instance of a device with this driver- so I’ve deliberately intermingled driver-level and device-level usages (always pushing those boundaries- but I think that’s a good way for an SDET to think).  I’ll admit this is slightly doctored (I removed some things related to the COM technology as that I can’t disclose, plus I tried to include support for the standard bus interface and that just complicates things without illustrating this method), but it should show I practiced what I am preaching…

class CTargetTestBus
{
    static WDFDEVICE                Owner;              //  WDF device that "owns" this bus object

    static void*    operator new(size_t size, WDFDEVICE OwningDevice);
    CTargetTestBus(WDFDEVICE OwningDevice);
    ~CTargetTestBus() {}

    //  Private callbacks (ie, accessed from within this class' code)
    static EVT_WDF_IO_QUEUE_IO_DEFAULT      OnIoDispatchDefault;

    static void operator delete(void*) {}
public:

    NTSTATUS    NewChild(LPCWSTR Name, int InstanceID);
    NTSTATUS    RemoveChild(LPCWSTR Name, int InstanceID);
    void        DoneWithBus();

    static CTargetTestBus*  GetThisTargetTestBus(__in WDFOBJECT Object);    //  WDF macro writes this code
    static CTargetTestBus&  GetTheBus(bool& BusPresent);
    
    //  Driver callbacks (public, because used in DriverEntry)
    static EVT_WDF_DRIVER_DEVICE_ADD        OnDriverDeviceAdd;
    static void             OnDriverUnload(IN WDFDRIVER Driver);

    //  This one is accessed from a member in CChildInfo
    static EVT_WDF_CHILD_LIST_CREATE_DEVICE OnAddNewChild;
};


// Macros to get the context
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(CTargetTestBus, CTargetTestBus::GetThisTargetTestBus);

Now playing:  Me (that old recording of Johnny B Goode again)!

Published Thursday, March 12, 2009 6:36 AM by BobKjelgaard
Filed under: , ,

Comments

# re: Using C++ in a KMDF driver part 1- a pattern for using contexts as objects

Great article! For 10+ years I have been using C++ using the DDK wrapper package called DriverWorks from NuMega. All of the DDK functionality is encapsulated in a bunch of nice neat classes (ie: KMemory, KIOPort, KInterrupt). That's something I've never seen anywhere else when it comes to driver code.

I'm now developing a new driver that uses an MSI IRQ and can not use a driver package that was created before MSI existed, so I had to write my very first "native WDM" driver. What an all around nightmare! To bad DriverWorks isn't still a product...

Friday, May 01, 2009 5:43 PM by James

# re: Using C++ in a KMDF driver part 1- a pattern for using contexts as objects

I 've encountered one other commercial package of that sort.  I've forgotten the name, but I believe the vendor was a Puget Sound area company called Bluewater Systems.

I was intrigued by both it and DriverWorks, but never had any real opportunity to use either one- just the way my contracts / customer interactions ran at that time.  I have heard a lot of negative things about DriverWorks on the OSR NTDEV list, though- you might want to see if there's anything there to concern you.

KMDF is C++ internally- that's no secret, but the external interfaces were all C language.  So I wind up wrapping the wrapper, so to speak.

You might want to consider KMDF rather than WDM for your project.  MSI is supported.

Monday, May 04, 2009 10:17 AM by BobKjelgaard
New Comments to this post are disabled
 
Page view tracker