Arlie, each of those features adds to the safety of your code.
Objects: everything’s an object, there are no dangling functions nor are
there lone pieces of data floating all over the place. The structure of your
driver fits naturally the structure of the i/o environment around it. Data
is either part of a public interface or it is invisible to all third parties
excepting possibly that type’s family. More, with judicious use of
interfaces, you don’t even need to know if a method sits in the same
process, in another process, or in a machine across the network.
Encapsulation: data and functions are only visible to the outside if they’re
part of an interface or of a public section. No chance of calling private
functions or accessing private pieces of data. Great separation between
interface and implementation: adds to the safety of the code.
Polymorphism: objects can implement the same method in different ways. An
object is a namespace and names are only visible inside that object. That
adds to safety and avoids misuse of code and data. Functions can have the
same name and yet differ in parameter signature, allowing automatic
defaulting of parameters not supplied - instead of the mess of NULLs and
zeros in funky places we have today. Two different packets can call the same
function name (such as IrpRead or IrpWrite) and achieve quite different
effects. Another example, you could have two subclasses of a superclass, one
for low IRQL and another for DIRQL: same name, implementing the same
interface (among others - you don’t only have to implement one interface),
only that one issues calls that can be issued at low IRQL, while the other
issues calls that can only be issued a high IRQL. That would concentrate a
lot of IRQL-depending manipulation in one or two classes: adds to locality,
makes it easy to spot errors, and hence increases the quality of your code.
More, functions can return object references and therefore no more calling
APIs with pointers as parameters, pointing to writable areas: by itself a
no-no, even worse if the caller must allocate the memory and state the
length of the object: another piece of nonsense that adds to the insecurity
of the code. Want a new object ? Call the method, which issues a “new” on
the object (lengths are automatically taken into account, no need for the
programmer to bother with them), and get the return value being a reference
to that object. Did it fail ? You get a reference to a null object or to a
void one that has a fail status inside it.
Inheritance: Deriving a class from an existing class keeps the existing code
static and hence minimizes the possibility that your addition breaks
existing code. Objects with different member variables and functions can
derive from the same superclass, allowing each derived class to have its own
handling of program runtime conditions - that avoids big “if” and “case”
statements with a zillion branches that clutter the listing and are hard to
follow. The code gets simpler and hence safer, and we know that the
condition for object A cannot possibly affect object B because they’re
different classes and one cannot see the private members of the other. Are
you dispatching an IRP ? Just write one subclass for each type of IRP, then
just invoke the method: no ifs or cases, each IRP object takes care of its
own handling. Much cleaner and hence safer. Want to write a filter driver ?
Inherit it from a mother driver class. Want to extend the driver stack ?
Derive a class from it. And so on. Clean, efficient, hierarchical,
compartmentalized: safe.
Call by reference: No pointers need apply. Need I say more ? You pass a
reference to the real object, not a pointer to it.
Constructors and Destructors: read “automatic memory management”. No more
mallocs and mfrees sprinkled all over, an object allocates its store at
construction time and releases it at destruction time. More, in a garbage
collected language like C# there’s no need to manage memory at destruction
time. In other words: memory leaks cease to be a problem.
Overloading: If I have two numbers, a+b is sum, but if I have two strings,
a+b can be made to be concatenation. I can construct arbitrary streams of
objects or data structures by overloading the “>>” and “<<” operators. I can
overload those same operators to achieve list operations such as append and
remove, so that I don’t need to memorize Byzantine APIs any longer: the
statements “item << queue” or “item >> queue” are jolly clear and much
easier to manage. In a really nicely put together interface, I could push an
IRP onto the stack by writing “irp >> stack” : clear, obvious, and no need
for yet another cumbersome and hard to remember call with half a zillion
parameters. Want special memory management, say, allocate from the nonpaged
pool ? Override the “new” operator. And so on.
Abstract classes and interfaces: allow you to individualize the interfaces
into objects, so that you achieve interface-centric programming. That alone
is a great improvement, just take a look at the COM spec if you doubt it. An
interface can be represented by an abstract class, and a class can be donned
an interface by using multiple inheritance: no more ad-hoc little functions
all over the place, the interface is cast in stone and exists independent of
your class. Moreover, you can get new classes to support the same interface
just by inheriting from its abstract class, no more compatibility issues due
to errors of transcription or bugs in the interface code: just inherit the
interface and implement it. This allows a separation between interface and
implementation that greatly improves the safety of your code.
Namespaces: No more two-level all-or-nothing global-or-local
everybody-sees-everything programming. Object names are organized in
namespaces, a class is just another namespace that comes and goes as it
creates and deletes objects, and so on. You can precisely control who sees
and who doesn’t see every item in your namespaces. Which, again, adds to the
safety of your code.
Golly, I could go on for a long time.
Alberto.
-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com]On Behalf Of Arlie Davis
Sent: Friday, December 05, 2003 1:11 PM
To: Windows System Software Devs Interest List
Subject: [ntdev] RE: how to ensure atomic code in a kernel mode driver
C++ has all of this, and it doesn’t magically fix anything. C++ is just
pretty C.
Type safety and GC would go a long way toward fixing a LOT of problems.
That, and maybe analytical verification that methods meets certain
constraints, such as calling other methods/functions at appropriate IRQL
levels, paged/non-paged rules, etc.
– arlie
-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Moreira, Alberto
Sent: Friday, December 05, 2003 12:38 PM
To: Windows System Software Devs Interest List
Subject: [ntdev] RE: how to ensure atomic code in a kernel mode driver
Objects. Encapsulation. Polymorphism. Inheritance. Call by reference.
Constructors. Destructors. Overloading. Abstract classes. Interfaces.
Namespaces.
I could go on.
Alberto.
Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256
You are currently subscribed to ntdev as: xxxxx@compuware.com
To unsubscribe send a blank email to xxxxx@lists.osr.com
The contents of this e-mail are intended for the named addressee only. It
contains information that may be confidential. Unless you are the named
addressee or an authorized designee, you may not copy or use it, or disclose
it to anyone else. If you received it in error please notify us immediately
and then destroy it.