Even though it provides a lot of nice features, what really hit the sweet spot with me was the ability it gives you to write 'anywhere' on a given page. This lends itself naturally to the process of design as I conceive it. I've always preferred working with pencil and paper whenever creativity or even mere thinking was involved. I've never been able to exactly pinpoint what the main difference between pencil-and-paper and Word-Processor was, until I tried OneNote.
Freedom to write something at the top of the page, leave it and quickly scribble something at the top-right corner of the page, get back to what I was writing, then expanding whatever I've scribbled into a box on the same page, dragging an arrow from this box to that - that sort of thing. Typical word-processor software forces you into a limited top-down flow of words, and consequently, thoughts. And I've always hated that. And I really couldn't work around that inherent limitation, no matter how I tried.
Another fine difference is the definition of what a page is. Word-processors view pages as sequential entities - they come after each other, again, top-to-bottom - and as part of one larger entity, "The Document". Which really is not true at all during the design process, as it applies to me.
When designing, the concept of cards (for lack of a better term - I'm not specifically referring to
Class-Responsibility-Collaborator (CRC) Cards, even though they might be part of the design process) takes over. Each page is a separate entity of its own, with its own title, and it becomes desirable to have all the pages visible at the same time. And if not possible, one should be able to switch between them quickly.
Another thing that Word-processors don't have is hierarchal organization. A project consists of a number of components. Each components may yet consist of additional sub-components. And each of these has, associated with it, a number of so-called cards. And that's not really a software-specific trait. Generalizing, anything consists of a number of component things. You can choose to break it down to its components or not depending on your desired level of abstraction - and the software program had better provide you with the ability to do just that.
Finally, all this isn't very useful if you don't have the ability to properly search these collections of pages or parts of it, in an information-hunting fashion, like a search engine.
OneNote solves all that through what I consider a very elegant design:
First, the "Projects" dropdown you see is part of a tree. A tree consisting of a number of folders, subfolders, and sections. Each section contains a number of pages. The section pages are tabbed to the right of the page, in a collapsible area. When you collapse it, only the page numbers appear, instead of their titles.
A new page is quickly created by clicking the 'New page' icon (numbered 3). You can quickly switch to other pages simply by clicking their tabs.
Whenever you click on a page, you get a writing area similar to that to the top-right. The one in the top-right has the mouse hovering it, which is why it has the additional rectangular bar on-top. You can resize any such area quickly using the arrows (numbered 2 in the picture). You can move the area around by clicking anywhere in the dotted rectangle (numbered 1) and dragging. Areas resize automatically when you type in them and when there's enough space to resize.
That enough makes it a super-cool tool for me - I now have a centralized place for my design documents, technical designs, journal posts, informative posts and notes. And I can save any of them as Word documents.
But I was pleasantly surprised when I discovered that you don't have an explicit saving mechanism. It saves everything transparently. Yes, that's the closest thing to orthogonal persistence I've seen on today's systems. Mind you, I've not tested ending the Windows session abruptly to check how much information I'd lose, but it's quite enough to me that it saves everything if you switch away or shutdown Windows normally.
So, in conclusion, give it a try. Now, let me get into what I actually started writing the entry for, before going off at a tangent and kissing OneNote's boots.
The following are snippets from my notes, written during the design and implementation phases of the DirectInput hooking component. Microsoft OneNote really shined in this part. It allowed me to easily layout the scenarios before-hand, in English, then fill in the details with the needed API calls and how they were to be used.
When developing the DirectInput hooking component - and before it, the Direct3D one - the only 'real' issue was external world communication. I needed such communication in 2 situations:
Hook application determination
It had to know the name of the application to be hooked. When writing the Direct3D hook, I went with the simple solution of using the registry, and it had worked fine. When writing the input hook, I saw no need to change the way this works.
Keyboard state control
After successfully hooking the target application, we need to be able to receive key-press commands and forward them to the target application.
After digging for a while in the documentation, the most simple IPC methods I found were:
. Mail slots
Named pipes were the most obvious and simple solution. So I laid out the following scenarios in an English-only pass, then filled it with API references and information in the second pass, using the MSDN documentation.
First, a named pipe is created by the keyboard device using CreateNamedPipe. The pipe handle is stored within the keyboard device for further use. Pipe creation parameters are:
. Name: \\.\pipe\DInput-Hook
. Open Mode: In-bound
. Pipe mode: PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE
. Max instances: 1
. Out buffer size: 0
. In buffer size: 256
. Default timeout: 200
. Security attributes: null
Whenever the hooked application retrieves the keyboard state, we peek into the pipe to see if we've got any data. A peek is done using PeekNamedPipe, as follows:
. Named pipe: Pipe handle
. Buffer: null
. Buffer size: 0
. Bytes read: 0
. Total bytes available: 0
. Bytes left in this message: messageBytes
The call retrieves the number of bytes available in the message in messageBytes.
If we've got some data, we read them via ReadFile:
. Handle: Pipe handle
. Buffer: A 256 byte buffer
. Num bytes to read: messageBytes
. Num bytes read: numBytesRead
. Overlapped: null
The message consists of a string of numbers, each number representing a scan-code.
The application should loop endlessly, calling WaitNamedPipe until the pipe is created - this signals the target application's launch and creation of a keyboard device.
It then connects using CreateFile:
. File name: \\.\pipe\DInput-Hook
. Access: GENERIC_WRITE
. Share mode: 0
. Security attributes: null
. Disposition: OPEN_EXISTING
. Attributes: FILE_ATTRIBUTE_NORMAL
. Template file: null
It can keep forwarding messages using WriteFile:
. Handle: Pipe handle
. Buffer: The message buffer
. Num bytes to write: Message size
. Num bytes written: numWritten
. Overlapped: null
When it's done, or an error occurs, it can go ahead and close the pipe using CloseHandle.
Using the above scenarios, I quickly laid out how I want my classes (which was really simple given how simple the scenarios are)
. Pipe handle
. Pipe handle
And the implementation of the above design worked almost right away, hassle-free, and life was good. Until Daphyd The Bard contacted me - he was having trouble to hook a (cool) asteroids game he's been working on. Whenever his launcher loaded the hooking DLL, VB6 would fail with a "File Not Found" error. I tested it on my PC with a VB6 application, and it worked. And when I sent the generated EXE and DLL to him, it didn't.
I had Jack "OMG-FIFTY-REPLIES-PER-SECOND" Hoxely test the same sample and it worked on his PC, promptly popping up a message box saying "LOL", which was the expected behavior. And then it hit me. Both Jack and I use Visual C++ 8, so we both have the runtime installed. And guess what C++ runtime VC8 uses by default? Yep, Multi-threaded DLL runtime. Which David didn't have.
So I switched to the static runtime and the DLL loaded successfully on his computer. But it still didn't work with his game.
After loads of experimentation, debugging and hair-pulling, I discovered that his game was written using DI7. And of course I only hooked DI8 applications.
So, I decided to support DI7 too. Initially, I thought it was going to be a minimal amount of work - a copy-n-paste job. Hook DirectInputCreate and there you go. Unfortunately, that didn't work. And Oh God, it didn't work in so many ways.
After a bit of code revision and fiddling to make sure it's not something in my code, I decided it wasn't. The application in question wasn't calling DirectInputCreate - simple as that. So I went looking for alternative ways to create the DirectInput object, and found:
I hooked both of them. And it didn't work - the application wasn't calling them either.
So now I was back at square one. How does the application go about creating a DirectInput object without these?
Then it struck me. DirectInputCreate is actually a macro that resolves to either DirectInputCreateA or DirectInputCreateW, depending on whether the code in question is compiled with the UNICODE flag. So I quickly hooked these two too, and ran the target application.
And it still didn't work.
Another time, I revised the code, did a couple of experiments, but still couldn't get it. Then It Finally Hit Me. We hook functions by simply searching for references to their container DLLs in the EXE headers and replacing their entries in the import tables with entries of our own functions in our DLL. There's no way that's going to work when you're not directly calling the functions in questions, because then you wouldn't have them listed in your headers.
Visual Basic 6 does this. It calls into dx7vb.dll and dx8vb.dll for 7 and 8, respectively. A quick look at dx8vb.dll using the pexports utility that comes with MinGW wasn't very enlightening. The only exported function that looked promising was DllGetClassObject. I hooked that but it wasn't being called.
Unfortunately, I don't have much experience when it comes to COM and all the VB-C++ interaction. I have a couple of ideas, and I might try them out later, but for now I'll move on to something else. I'm currently at a dead-end with VB-based applications.
Hilarious comics by our very own 'boolean'
EDIT: Made the image a clicky.