Once again about atom leak and VCL bug
Introduction
Looking through the tape, I came across an article The misuse of atoms and the elusive bug in VCL . After reading the idea arose to describe another problem in the same area, which is not described in this article. Our team came across it on their own, then it turned out that this is already a known VCL bug. Development is underway on Delphi 7, and I'm not sure if there is a bug in newer versions. Judging by the links below, there is, as it is, a correction. There is no reason to hope for a fix for version 7, for obvious reasons.
In MrShoor's articledescribes the overflow of the so-called atom tables in case the application on Delphi is completed incorrectly and some atoms are not deleted. It turns out that to overwhelm the atomic table it’s not at all necessary to “forcefully kill” your application. It is quite enough to start it and close it correctly, but many, many times in a row.
Let's see how this happens:
Description of the overflow mechanism
After receiving another complaint a la “out of memory”, we found that the table of atoms was clogged with elements of the form ControlOfs <hexadecimal ID>. These elements appear at the start of each application (and our application server launches instances, one for each connection), and remain in the table forever.
Consider again the section of code from InitControls in Controls.pas - the same one as in the article mentioned above:
WindowAtomString := Format('Delphi%.8X',[GetCurrentProcessID]);
WindowAtom := GlobalAddAtom(PChar(WindowAtomStrinjg));
ControlAtomString := Format('ControlOfs%.8X%.8X', [HInstance, GetCurrentThreadID]);
ControlAtom := GlobalAddAtom(PChar(ControlAtomString));
RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));
The very last line, as it seemed, also creates an atom. When registering a new message type, a new atom is added to the table, which each time has a new name. The problem is that RegisterWindowsMessage see MSDN, in principle, does not imply a reverse unregister action, because This function allows several programs to use some Message ID together:
The RegisterWindowMessage function is typically used to register messages for communicating between two cooperating applications.
If two different applications register the same message string, the applications return the same message value. The message remains registered until the session ends .
This is serious, because we have no leverage to influence the situation. It is impossible to close such an atom with a third-party application - the program proposed in the article that prompted me to write this post is powerless here. Since the atom table has roots in the 16 bit era, which imposes a limit on the size of the table, this annoying bug pretty quickly put the server part out of order, because it was not possible to launch any Delphi application without rebooting the system.
Here is an example of what the atom table looks like after 20 iterations “normal start-up, correct termination” for a simple program from 1 window:

Those atoms are marked in red that remained after just 20 starts.
This error has been described in several places, for example:
Detailed error description
A shorter bug report
Well, and of course, Stack Overflow
Solution method
Since the atom cannot be removed post-factum, it is necessary to prevent its creation. Our team went the way of an IAT hook that intercepts a call to RegisterWindowMessageA and if a message with the name of the type ControlOfs <something there> is logged, any other identifier that is the same for all applications is logged instead. As it turned out, he did not have to be unique, which is also indicated in the bug report, to which I already referred .
The hook code is trivial, as is the mechanism for setting it. Moreover, on the Internet there are ready-made libraries for Delphi for IAT hooks. The hook itself merely checks as quickly as possible whether the message being logged matches the ControlOfs prefix, and if it does, it is replaced with RM_GetObjectInstance - a similar identifier that is the same for all applications, with the original RegisterWindowMessage being called.
I hope this helps someone avoid a long and difficult debugging.