Windows API design is samples-driven. This means that when a team proposes an API, we want them to demonstrate how to use that API with a sample code fragment. We do this so that we can evaluate the ergonomics of the API and see how well it fits with related features.
The code in the API sample often exposes hidden assumptions like “this always happens on the same thread” or “these two things never happen simultaneously.” Sometimes these hidden assumptions are dependencies on implementation details of the API that are not explicitly mentioned in the documentation, at which point the team has to decide whether to make them contractual (by documenting them) or to leave them as implementation details (and the sample cannot rely on them). Sometimes these hidden assumptions are actually bugs in the sample, and they end up highlighting ways that the API could inadvertently be used incorrectly.¹
Comments in the sample are often used to describe what each step is doing. But sometimes the samples will use comments to describe code that has been elided. For example, we might have this:
class Sample
{
void RegisterEvents()
{
// Register a handler to run when a message is received.
widget.MessageReceived += (_, e) => {
// Is this the sender we are waiting for?
if (e.SenderId == expectedSenderId) {
// Process the message.
e.Handled = true;
} else {
// Ignore messages from unexpected senders.
}
};
}
}
The comment “Process the message” is intended to mean “Here is where you write code the process the message.” But it could easily be misinterpreted to mean “The way you process the message is by setting the Handled property to true.”
The convention for API samples is that comments are for describing what the code does, or better, why the code is doing what it’s doing.
If you want to say “And here is where you would insert your app-specific business logic”, then don’t use a comment. Call out to a method that is not implemented in the sample.
class Sample
{
void RegisterEvents()
{
// Register a handler to run when a message is received.
widget.MessageReceived += (_, e) => {
// Is this the sender we are waiting for>?
if (e.SenderId == expectedSenderId) {
ProcessMessage(e.MessageKind, e.MessageData);
e.Handled = true;
} else {
// Ignore messages from unexpected senders.
}
};
}
void ProcessMessage(WidgetMessageKind kind, byte[] data)
{
// Business logic goes here.
}
}
¹ Sometimes the team says, “Well, if we added to the sample all the code needed for dealing with edge cases and proper error handling, then the sample would have been too complicated.” This tells us that your API is already too complicated because the only way to use it correctly is to write code that is so complex, not even the team that wrote the API wants to do it! (In extreme cases, the API is so complex that there is no way to use it correctly.)
Amusingly, there are also times where the team does something in the sample that their own documentation says that you shouldn’t do.
Ah yes the bad sample problem. I had a completely wrong idea about how GCHandle worked because the samples were broken.
Wrong:
“`
GCHandle handle = GCHandle.Alloc(…);
/* do something with handle */
handle.Free();
“`
Wrong:
“`
GCHandle handle = GCHandle.Alloc(…);
try {
/* do something with handle */
} finally {
handle.Free();
}
“`
Right (no samples):
GCHandle handle = default;
try {
handle = GCHandle.Alloc(…);
/* do something with handle */
} finally {
if (handle.IsAllocated()) handle.Free();
}
EDIT: removed miracle compilation dependency. I note this edit to avoid confusion below.
That code will only compile by miracle on .NET Standard due to the way C# treats uninitialized structs. Since reference assemblies do not expose private fields of structs, C# thinks there are no fields and permits using an uninitialized variable.
This is very wrong and could easily bite you if you happen to use SkipLocalsInit.
To remove miracle compilation: GCHandle handle = default;
On looking up SkipLocalsInit, it is very broken in almost any case. A reference assembly containing a struct where the release assembly has a reference to a class in the struct will generate heap-trashing code. Unfortunately, a likely case of such a beast is ValueTuple.
> Well, if we added to the sample all the code needed for dealing with edge cases and proper error handling...
The teams at Microsoft often doesn't add the code needed for the sample to work either.
What is worse though is them writing so many APIs for the same thing - ActiveMovie, Video For Windows, DirectShow, Media Foundation... and yet none of those has ever solved the problem with capture device falling off the USB bus.
Namely, you can register for window/service notifications for USB device (for example USB camera) being added/removed, but if said USB camera's controller stops responding and the...
> if (handle.IsAllocated()) handle.Free();
This is a prime example of bad API design — Free should be no-op if handle isn’t allocated, you shouldn’t need to pepper the code with IsAllocated calls.
Good point about the assumptions. Good example is Widget implementation documentation https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-cs The sample assumes that WidgetProvider runs on single thread (or at least there are no concurrent calls). However, Program.cs does not specify that it should be STA. I assume, based on that sample, that the Window Widget Board application does not call the same WidgetProvider concurrently. But it’s not explained anywhere in the documentation.
Maybe the other API teams as MS should follow this guideline as well, or it isn't enforced anymore. Most of the REST APIs coming out from MS are simplistic HTTP client calls with not even contextual information about the steps needed to get there. Authentication, paging and error handling are left out because they distract from the couple of lines that demo the actual call. Yet using that in a realistic application won't work reliably. If MS truly built samples that demonstrated the real-world code that has to be written to work with them then they'd realize how complicated they...
They write complicated code, and the reason is Agile and lack of skill and forethought. Just one recent example of a hack job -- the registry value SearchboxTaskbarMode, they wanted to be able to determine whether user set it and if not to reset to default value. They introduced another registry value SearchboxTaskbarModeCache and if that one isn't set then the first one is reset to 2 (show search box). If there was any planning involved there would be a single enum:
<code> mapped to SearchboxTaskbarMode registry value. Missing value or value of 0 would mean it was never set...