Mega Code Archive

 
Categories / Delphi / Examples
 

Debugwindow using atoms

Explains what Atoms are and how you can use them to send strings to other applications - without the need for shared memory. Topic: DebugWindow using Atoms and Messages Areas: Windows API, Component Development, Atoms Author: John Scalco, April 01, 2001, mailto:jscalco@idealsw.com Readers note: For an html version of this article and/or the Source code, you can download the code from http://www.idealsw.com/filedownloads.html A long time ago in a far off 16-bit Galaxy, I was using Delphi 1.0. At the time, I was working on an application, which was doing real-time communications. The Delphi 1.0 debugger was a C dll, which (in my case) frequently got hosed if I put breakpoints in certain functions. As a result, it seemed that I spent a lot of time rebooting my pc - as Windows 3.1 wouldn't release the dll which the debugger was using. In addition, for my application by placing breakpoints in the code I was running the risk that I might be changing the way the application was working (it was a communications type of software package) After awhile and many reboots later, I got tired of it and I found myself thinking, “Gee, I wish I had a debug window like Visual C++ and Visual Basic have”. Although, the use of Visual C++’s debug window is a good starting point feature wise, I needed more capabilities than it offered. If only Delphi had something like that. I wanted the following features: Ability to see data in the ‘Viewer’ while the program was running. The Ability to save this data to a file, Print it to a file, and Type In the text to add additional annotations. I also wanted to allow the user of my product to run this tool (on their pc) to perform diagnostics "out in the field". Now someo of you may be saying "Hey why not use the Event Viewer??" ... good idea, however, it wasn’t available in Win16 and it still didn’t have all the features I have mentioned above... especially that it could be used by a customer out in the field. With Delphi, I could easily get most of these features by writing a stand-alone application with a Memo component in it and some extra code. The problem was getting the output from my application to the other application. Enter the Windows SDK. I know it may sound twisted, but I sometimes snoop the SDK for interesting functions, well one day while I was 'snooping', I came across something called an Atom. An Atom allows you to place a string in a location in the OS where anyone can access it regardless of what process you are in. It's like shared memory, but you don't have do much work to allow other processes to get at the data. In order to access the atom you must know what the 'key' is to retrieve the data in the Atom. As long as any application 'knows' what the key is, it can retrieve data from the Atom. Using this Atom, I could place the string in memory and then I could let my other application ‘know’ that I had put the information there and then the app could retrieve it and display it. It might make it easier to think of this as a Client/Server relationship. The Client is the application, which is being debugged. The Server is the application or viewer, which will be in charge of displaying the message. This opens up some interesting possibilities: Imagine that you wanted to filter messages or perform other types of actions based on specific messages like print a message. So, the Client needs to do the following: Place the string message in an atom. For example, ‘Hello World' and Send a message to the Server application indicating that the string is available for reading. The Server needs to do the following: Handle the message (Windows Message), which indicates the string is available for reading and extract the string from the Atom Display it in the debug viewer. Here's Client’s code. I decided to place this in a procedure so that I didn’t have to repeat the same code every time I wanted to post a debug message. procedure DebugMsg(AMsg:string); var szMsg : array [0..255] of char; Atom : TAtom; begin // Add an atom the list of Global atoms // available from any application Atom := GlobalAddAtom (StrPCopy (szMsg, AMsg)); // broadcast the message that I have // updated my atom to all top windows - I don't know // which window is a debug window. SendMessage (hwnd_broadcast, wm_DebugId, 0, Atom); // delete the atom - by the time the // message has been sent above, the // atom is no longer needed // - in theory :) GlobalDeleteAtom (Atom); end; The Atom is added to the Global list of Atoms - which is accessible by any application - as long as they know the 'key'. By the way, the size of the string, according to the docs, is limited to 255; although this sounds like Win16 docs… maybe someone forgot to update them?? Next, a message is sent to all top windows - that is (pretty much) any application running. Rather than having to know the name of the application, which is running the debug viewer, I simply send the message to all top windows. This keeps our options open – maybe at some point, I might write another debug viewer app? This way I don’t have to change the code in the Client, I only have to implement another Server who knows what message to listen for and it too can receive messages about debugging activity. As a reminder, the function SendMessage() will not return until it has sent the message, so by the time the next line is reached, the Atom has already been read from by the 'viewer' application. A final note about this code snippet is the message, which I use wm_DebugId where does that come from??? Well, Windows actually uniquely generates it. There are lots of messages in Windows, if you just pick a random number; there is a chance that it may be used for something else. For example what if you were using the message, which changes the color of the window - you might have some pretty 'interesting' results. Windows addresses this potential problem by providing an API call, RegisterWindowMessage(), which allows you to create (and reserve) a unique Windows message. procedure DebugMsg_Init; begin WM_DebugId := RegisterWindowMessage ( 'IS_Debug' ); end; This message needs to be created before calls to the DebugMsg() are made. I do this by placing this code in the initialization section of the unit where I have all my code located. Placing this code in the Initialization section of the unit guarantees that the code will be called when the unit is loaded into memory at the start of the application, thus resulting in my custom message being registered before any calls to DebugMsg() are made. Ok, so we have an idea of how to send the message from the Client application to the Server. Now let's look at what the 'Server' has to do to capture the message. The Server Code The solution is to setup a Window Proc and watch for that message. It looks something like this: procedure TForm1.DefaultHandler (var Msg: TMessage); var szBuffer : Array[0..255] of Char ; sMsg: String; begin // check if it's the debug id. if Msg.Msg = GetDebugId then begin if GlobalGetAtomName (Msg.LParam, szBuffer, 255) = 0 then StrCopy ( szBuffer, 'Error: Atom not found' ); sMsg := StrPas(szBuffer); Memo1.Lines.Add ('Dbg >> ' + sMsg); end; end; The DefaultHandler method actually handles any messages, which are not handled by the form. So, we can override this method and add some code, which will check, for our custom message wm_debugId. If we get that message, we know we want to handle it; otherwise we let all other messages pass through. Using the Windows API GlobalGetAtomName (), I retrieve the Atom from the Atom table and I then convert the zero terminated string to a regular Pascal string. I then write it to the Memo component, which is on the form. To keep the dependencies (and hard coding) to a minimum, I wrote a function called GetDebugId(), which returns what the WM_DebugID is set as, this allows any application which wants to display messages from the viewer a way to get the correct WM_DebugId without accidentally changing it or depending on how it is implemented - since the function 'encapsulates' it's implementation and is in the Unit called IS_Debug.pas. If you have downloaded the source code for IS_Debug.pas, you may have noticed that I declared the WM_DebugId as a variable below the implementation section. This is not a mistake. Before the days of Delphi (Turbo Pascal) if you wanted to keep users of a unit from accessing certain types of data or even knowing how the type was implemented, you could put the declarations below the Implementation section and it would be private to the unit. Anything – including functions, methods, classes and so on declared below the implementation of the unit cannot be accessed by the code which uses the unit. So it’s basically a private variable without objects. Here it is for completeness: function GetDebugId: integer; begin Result := WM_DebugId; end; And there you have a nice way to allow an application to pass messages to other applications without having to use shared memory. This little tool has been so useful, I used it many times, by now the Event Viewer is available, but you know I still like this approach because I can print it, I can type into it like notepad and I can cut and paste - plus a customer can use it as a debug tool to diagnose a problem. So to say the least for me, it has been handy. I showed this to the local Delphi Interest Group here in Raleigh-Durham a few months back and everyone seemed to like it. A few said "How about a component version?” I had thought of this, but never got around to doing it ... until now. If you are interested in a component version of this, which you can drop into your applications, then read my upcoming article, which basically builds on this. I hope you find this useful and please feel free to ask any questions you might have about this article. Sincerely, John Scalco Ideal Software, Inc. Raleigh-Durham, NC 27502 mailto:jscalco@idealsw.com http://www.idealsw.com