C/C++ for driver development

Hey, I have invested in learning the essential theory / windows internals suggested before starting writing drivers Think it’s time to start actually doing it, do you guys favour c or cpp for writing drivers? Specifically 1. The STL / New , Delete is available? 2. General pros and cons? Would love to hear from pros like the people here in the community what’s the standard nowdays, and why :slight_smile:

I always write them in C++. In general, you can use any C++ construct that doesn’t involve exceptions, I/O, or the heap. That, unfortunately, excludes STL. You can use “new” and “delete” as you always have, providing that you supply your own implementations that call ExAllocPool. There are many, many examples of this.

It will be interesting to see how long its takes Rust to become practical in the kernel. I understand some work is already underway. I see big advantages to Rust, because the compiler is absolutely rigid about managing object lifetimes and ownership.

1 Like

This has been discussed on this forum and others many times and it a matter of strong personal opinions. I never use C++, but I accept arguments for a limited set of C++.

No library that hasn’t been designed for use in KM can be used directly. You are better off writing your own version of exactly what you need with inspiration from STL, boost etc.

I view Rust as a fad, but I could be proved wrong. Possibly it would be adopted more if there was more innovation happing in the OS / driver / system space, but there isn’t a lot going on at the moment

> @Tim_Roberts said: > I always write them in C++. In general, you can use any C++ construct that doesn’t involve exceptions, I/O, or the heap. That, unfortunately, excludes STL. You can use “new” and “delete” as you always have, providing that you supply your own implementations that call ExAllocPool. There are many, many examples of this. > > It will be interesting to see how long its takes Rust to become practical in the kernel. I understand some work is already underway. I see big advantages to Rust, because the compiler is absolutely rigid about managing object lifetimes and ownership. Cool :slight_smile: I’m wondering , there’s no runtime so say I define a global object, it’s constructor won’t get called right? what are you doing in such case?

That’s a restriction faced by C++ programmers today.

1 Like

I used to provide a basic C++ runtime for the kernel. It really isn’t needed, other than for a global object. I’d suggest not having any of those. I might put the runtime up on git hub.

I’m wondering , there’s no runtime so say I define a global object, it’s constructor won’t get called right? what are you doing in such case?

Yes you have always been able to use global constructors and destructors. Like new & delete, just a matter of adding the implementation. And by the way your code should never use new or delete for dynamic allocation. Smart pointers superseded them many years ago.

That, unfortunately, excludes STL

Not entirely true. First a note on terminology. STL is a 1990’s term meaning standard template library. Today it is just called the standard library. But you can indeed use a lot of the standard library in the kernel; there is a vast amount of goodies that do not throw exceptions. The standard libraries in the kernel are extremely useful in writing clear, error proof code. And c++ today is zero cost; the old 1990’s problems of stack usage and code bloat were ironed out long ago.

d = MAX(MAX(a, b), c); // c
d = MAX(a, b, c); // but you want to say this
d = std::max(a, b, c); // and you can with c++

Neat. And note how c++ explicitly denotes all places in the code where the standard libraries are used with a handy std:: prefix.

strong personal opinions

This is how we describe a few grouchy old folks, even at Microsoft it must be said, who made a decision to never use c++ and so wasted their career writing and evangelizing code not as productively as it could have been. It then became a case of sour grapes to them. You can show these people definitively how they can improve their code (like say the above max example) and they will refuse to let these thoughts enter their head because it is an admission their careers and advice were shall we say, suboptimal.

I and everyone I know have been writing C++ drivers for as long as 30 years. It is an absolute game changer in terms of productivity. And the language keeps adding new features for the kernel. For instance, in the kernel a lot of time is spent tediously passing around a buffer pointer and its associated length. Using c++20’s std::span for this job does out of this world things for code compactness, readability and safety.

It will be interesting to see how long its takes Rust to become practical in the kernel

Keep Rust out of the kernel. People who have experimented with Rust in the kernel have none too nice things to say about it. Let’s keep speaking the same familiar language especially given how c++ has evolved to what it is today and will continue to get even better with c++26 and beyond.

These are certainly examples of strong personal opinions

1 Like

Alert! Alert!! For the first time in recorded history, I actually agree with 80% of something that Mr @Rourke has written!

What Mr Rourke got wrong was this part of his diatribe:

This is how we describe a few grouchy old folks, even at Microsoft it must be said, who made a decision to never use c++ and so wasted their career writing and evangelizing code not as productively as it could have been.

Which fails to differentiate between “old C++”, which was mostly a random collection of poorly considered OO shite, and Modern C++, which has evolved into an actually useful and sort-of elegant language.

I count myself among those whom were vocal C++ haters up through and including the early aughts, but who now fully embrace the language that is Modern C++ and wishes for more support of it Windows kernel-mode programming.

1 Like

Now even ntoskrnl itself uses C++ in some of its components.

Now even ntoskrnl itself uses C++ in some of its components.

The sample kernel streaming drivers (AVStream and audio) have been in C++ from the very beginning, which was in the 20th Century. They worked on both Windows NT 4 and Windows 98.

@Tim_Roberts said:

Now even ntoskrnl itself uses C++ in some of its components.

The sample kernel streaming drivers (AVStream and audio) have been in C++ from the very beginning, which was in the 20th Century. They worked on both Windows NT 4 and Windows 98.

I’m aware of this, but it’s very old ‘‘C++’’, which is not equal to C++ with more modern standards after all. Today C++ has evolved considerably and it makes more sense to use it in KM. Drivers now use libraries like WIL, the kernel (ntkrnlmp.exe) itself uses modern C++ features like templates.

the kernel (ntkrnlmp.exe) itself uses modern C++ features like templates

Yeah? Can you give an example, please? Cuz I haven’t seen this (then again, I haven’t read any Windows source code for several years now).

For what it’s worth, templates have worked in the kernel right from the beginning. It’s hard to come up with good use cases, but they do work. I created a templated wrapper around the LINKED_LIST primitives, but it’s not a perfect fit.

Templates are extremely valuable in the kernel. Let’s talk containers. I too made a linked list class and mine is a perfect fit: absolutely beautiful to setup, walk and use lists with no type casting of any kind. Or even more fun iterate a list with a range based for. Or use “find_if” is crazy cool. All those familiar and useful std::list member functions are there since my list class has the exact same full functionality verbatim, but internally LIST_ENTRY is used. There are no under the covers memory allocations, no exceptions thrown, just a simple, zero cost, type safe linked list plug in replacement for anywhere LIST_ENTRY is used. Similarly I have a thread class and it has the same interface as std::jthread. Again, the ease of use is incredible. On and on.

Templates are also useful in the kernel for many other purposes, not just containers. Let’s dive into just one example. I am one that uses const when I can. It is a good practice. But unfortunately the kernel headers are not tidy and do not specify const on many read only items like they should. Examples are the value name passed to ZwQueryValueKey/ZwSetValueKey, the input buffer passed to IoBuildDeviceIoControlRequest, many read only buffer fields in structures, on and on.
So I made an unconst template that removes the const nature of a variable without blowing away the type information that I use to work around the WDK header file shortcomings. So now I can write ZwQueryValueKey(,unconst(vn),…) and type safety of the UNICODE_STRING is preserved in a simple to understand form. Here is the implementation:

template inline constexpr auto unconst(T *t) { return const_cast<std::remove_const_t *>(t); }

1 Like

IMHO to ‘un-cost’ something, you will do better with

#pragma warning(suppress : 4090)

It is just as safe / unsafe, and if provides future maintainers a clear hint that some kind of assumption is being made that is not explicit in the API contract. And it compiles faster

It should be noted that most of the ‘good’ features of C++ are now also present in C. From better compiler warnings, to constexp

It should also be noted that templates can readily be implemented in C using macros. They have the same problems as in C++ - long compile times and binary bloat - but it is easy to do.

It is also possible to implement ‘protection levels’, inheritance and all of the ‘simple’ C++ features in C with the proper use of header files and static qualifiers. The code isn’t really any different in length or complexity, but is organized differently. I prefer a manual transmission in my car too, and the difference is a lot like that compared with an automatic

IMHO as the years go by there is less and less to choose between C and C++. Pick the one you like best. The work that I do is mostly to review the work of others, and I find C++ code harder to review than C code. Your milage will vary. When possible, I use C# instead of either (we have everything. Drivers, UM services, SQLCLR functions, C# UI, web assembly UI, so lots of different tools for different jobs)

@MBond2 said:
to ‘un-cost’ something, you will do better with…pragma warning(suppress : 4090)

You know I thought this was a windup until I read the rest of your post. Now unfortunately I see you are completely hopeless. Bracketing your code with pragma’s, seriously? Yeah great idea because everyone and their dog has memorized all the 4 digit compiler warning codes. And besides, it’s self explanatory–what? If you think sprinkling pragma’s all over is good readability, do it to someone else’s code and see what happens next. Also pragma is not portable and these days that matters more and more. And note this particular pragma 4090 you suggested furthermore has no effect with c++ files! Your proposal is completely farcical. You would immediately fail any job interview. Is programming even your profession? I am utterly speechless you cannot even conceive how ridiculous your proposal is or see better options.

And it (#pragma) compiles faster

And just how do you know this is true FOR THIS PARTICULAR CASE? I am skeptical. I want to see some compiler benchmarks to back up your claim.

It should be noted that most of the ‘good’ features of C++ are now also present in C

You have simply embarrassed yourself with how little you know. I am curious. Have you found std::array to be better than C arrays in the kernel? I’d love to hear your opinion on this.

It should also be noted that templates can readily be implemented in C using macros

C++ is going exactly the opposite direction. The future of the language is to dump the preprocessor altogether. No more evil macros, no bloaty preprocessor compiler step, hurray. Instead fast compiles with clean, partitioned, modern C++20 modules. It’s a wonderful future unfolding step by step.

templates …long compile times and binary bloat

There is no binary bloat using templates nor “long” compile times. These sound like telltale signs of misuse. Anything can be misused, especially macros so that is hardly a valid argument.

I find C++ code harder to review than C code

A good engineer will write easy to understand code in any language. A bad one will fail to do so in any language. Haven’t you run across C programmers who have the reputation of making “write only” code? They write it, no one else can read it. Don’t blame the language for one’s own deficiencies.

@“Peter_Viscarola_(OSR)” said:

the kernel (ntkrnlmp.exe) itself uses modern C++ features like templates

Yeah? Can you give an example, please? Cuz I haven’t seen this (then again, I haven’t read any Windows source code for several years now).

Of course, I’m not authorized to show you the code itself… But you can see C++ mangled symbols in the debug symbols (ntkrnlmp.pdb), for example.

Some examples:

long 
__cdecl 
PspFinalizeScpCfgPage(
    class gsl::span<unsigned char,-1>,
    enum _RTL_SCP_CFG_PAGE_TYPE,
    void * __ptr64,
    void * __ptr64,
    struct _RTL_SCP_CFG_NTDLL_EXPORTS * __ptr64,
    struct _RTL_SCP_CFG_NTDLL_EXPORTS_ARM64EC * __ptr64,
    unsigned char,
    unsigned long
    );

(Note it uses gsl::span)

public: 
    static 
    void 
    __cdecl 
    SMKM_STORE_MGR<struct SM_TRAITS>::SmAsyncReadQueueInsert(
        struct SMKM_STORE_MGR<struct SM_TRAITS> * __ptr64,
        struct SMKM_STORE_MGR<struct SM_TRAITS>::_SM_ASYNC_READ_QUEUE * __ptr64,
        struct SMKM_STORE_MGR<struct SM_TRAITS>::_SM_ASYNC_DIRECT_READ_CTX * __ptr64,
        unsigned long
        );

(The compressed store manager (…\minkernel\ntos\store) makes extensive use of C++17)

P.S. C++ in the kernel has been around for at least several years now (since Windows 7 IIRC).

It is clear that nothing will be gained by continuing this discussion further

2 Likes