What's the "best way" to organize a minifilter project?

This might not be a technical question about minifilters per se but I feel like this is also pretty important. I am working on a minifilter project with a few other people and we are all really beginners and have no experience. We would like to organize our repository so that the project remains somewhat easy to explore and ease newer people into. Convenience in this sense is the most important aspect to how the codebase is organized.
I was thinking of something along the lines of

  • CMakeLists.txt
  • source/
    • main.cpp
  • include/
    • IRP_MJ_xx.h

Basically, only one source file. The main source file just does all the driver initialization, filter registration and similarly generic things.
For the include folder, I am thinking since we can only have one PreOp and one PostOp callbacks per major function, it would be best to have a header file for each major function (which has the PreOp and PostOp callbacks) so that it’d be a bit easier to figure out where things are without necessarily remembering their names

I don’t know if this is good or bad, this is pretty much my first minifilter project so I have no experience whatsoever organizing a minifilter project. All feedback / alternative ideas (hopefully with some reasoning) will be appreciated!

Here’s how I like to lay things out for minifilters and C code in general. There’s a lot of room for preference/taste but I have a few things that have made my life easier over the years:

Let’s say we have a filter named OsrYakFlt and it handles IRP_MJ_CREATE and IRP_MJ_WRITE operations. In the source tree for this module I would have:

\Include                        - Includes shared across modules (e.g. UM/KM interface)
\OsrYakFlt\OsrYakFlt.cpp        - DriverEntry, InstanceSetup, InstanceTeardown, Unload
\OsrYakFlt\OsrYakFlt_Create.cpp - IRP_MJ_CREATE handling
\OsrYakFlt\OsrYakFlt_Write.cpp  - IRP_MJ_WRITE handling
\OsrYakFlt\OsrYakFlt_Util.cpp   - Generic utility functions/wrappers
\OsrYakFlt\OsrYakFlt.h          - The ONE header for everything internal to the driver. 

Note that this is all C code but using CPP modules to use “CPP as a better C” (I love my typed enums).

Function names used should reflect the source tree. For example, the IRP_MJ_CREATE Pre/PostOperation callbacks should be called OsrYakFltCreatePre/OsrYakFltCreatePost. Break out handling in Post for directories vs files? Those should be OsrYakFltCreatePostFile/OsrYakFltCreatePostDirectory (XxxFilePost/XxxDirectoryPost would also be fine depending on preference…I’d probably end up changing my mind at least few times :))

Have utility functions to query/set a reparse point? Those should be OsrYakFltUtilReparsePointQuery/OsrYakFltUtilReparsePointSet. Suddenly realize that you have a zillion reparse point functions? Then promote that to its own module and change the names accordingly:

\OsrYakFlt\OsrYakFlt_ReparsePoint.cpp - Reparse Point functions (e.g. OsrYakFltReparsePointQuery/OsrYakFltReparsePointSet)

Basically trying to follow OO principles in C using the source tree layout and function names. Over the years I’ve found this makes the code easier to conceptualize and navigate. There’s also never any question about whether or not a function is supplied by this driver or elsewhere.