|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI wrote this utility to allow me to implement keyboard macros from any window. I have several macros in Visual Studio that I like, but I can't use them anywhere else. One example is my code comments. When I go into older code and fix bugs (always someone else's code, mind you), I comment the code like this: // 08/19/03 AlexR: Should have been checking the return value here... When I would go into our bug tracking system, I would use the same formatted text to precede my comment there. However, I couldn't use the VS macro there, so I had to type out the date and my username manually (oh, the pain!). This and other reasons prompted me to download some shareware macro programs. I found several that were very good, most of which do even more than this little utility. But by writing my own, I was able to learn quite a bit about Windows hooks and memory mapped files, as well as get a good refresher on using Windows messages as a form of interprocess communication (IPC). This application allows the user to define a simple script (I refer to it as a Macro) and assign it to one or more hotkeys (I call HotKees) and/or keywords (KeeWords). (I chose the name HotKee because it was unique, if not creative.) For instance, one might define a Macro that evaluates to the text, "ABC Broadcasting Networks, LLC", then create a HotKee for LeftWin-A, and then assign that Macro to that HotKee. Then, whenever the user hits LeftWin-A in a window, the said text is inserted. Likewise, he might define a KeeWord for something like, "abnl" and hook it up to the Macro, in which case whenever he typed "abnl", that text would be replaced with the Macro's text. Confused? Me too. :) There are three main technologies that are at the intermediate level, which this utility employs: Windows hooks, memory mapped files, and Windows messages (arguably intermediate). I. Windows hooksBy far the biggest challenge of this project for me was to figure out how hooks worked. There are some great articles here at CodeProject explaining the use of them, and some good info at MSDN as well. The real forehead-slapper was realizing that another process cannot call into my executable using My problem was getting the OS to call my code whenever it got a message, so that I could tell whether the message was one of my user's predefined hotkeys or whether it was the end of a predefined keyword. So I used this code: HHOOK hKeyboardHook = SetWindowsHookEx (WH_GETMESSAGE,
CMyApp::MessageProc, NULL, 0);
Since I specified 0 for the thread ID, I expected all the threads in the system to call my DWORD CHotKeeDlg::RegisterHook ()
{
DWORD rc = 0;
if (IsHookRegistered ()) UnregisterHook ();
HINSTANCE hinstDLL = LoadLibrary ((LPCTSTR) "HKMSGHND.DLL");
if (hinstDLL) {
// "CHotKeeMsgHandlerApp::MessageProc(int,unsigned int,long)"
HOOKPROC hkProc =
(HOOKPROC) GetProcAddress (hinstDLL, (LPCTSTR) 1);
ASSERT (hkProc);
int nHook = WH_GETMESSAGE;
DWORD dwThreadID = 0;
if (hkProc)
m_pHKData->m_hKeyboardHook =
SetWindowsHookEx (nHook, hkProc,
hinstDLL, dwThreadID);
ASSERT (m_pHKData->m_hKeyboardHook);
}
if (!IsHookRegistered ()) rc = GetLastError ();
return (rc);
}
II. Memory mapped filesNext problem: The main application allows the user to configure all the macros, hotkeys and keywords, but how do I tell each individual instance of the DLL where the data is? My first thought was to save it in a file and send everyone the filename for them to load. However, as you know, disk I/O is one of the biggest slowdowns of any application. Minimizing the amount of disk I/O is usually the first thing done when trying to optimize an application. Add to that the fact that we're looking at anywhere between two and five hundred threads loading up the DLL, so that simply is not an option. I found a solution in the form of mapped memory. Here is a technology I had never used before, so I was excited about learning it. Basically the way it works is, I ask the OS for a bit of publicly accessible memory, tell it how big I want it, and give it a unique name. The OS responds with a memory address and I can simply write out to that address all the data I want, up to the size I specified. Here's the code: CMemFile mf; CArchive ar (&mf, CArchive::store); m_pHKData->Serialize (ar); ar.Close (); DWORD dwLength = mf.GetLength (); m_hMap = CreateFileMapping ((HANDLE) 0xFFFFFFFF, NULL, PAGE_READWRITE, 0x0, dwLength + 4, // Extra 4 bytes for the buffer size HK_SHARED_MEMORY_FILENAME); ASSERT (m_hMap != NULL); if (m_hMap) { m_pMapBase = (BYTE *) MapViewOfFile (m_hMap, FILE_MAP_WRITE, 0, 0, 0); ASSERT (m_pMapBase); if (m_pMapBase) { memcpy (m_pMapBase, &dwLength, sizeof (dwLength)); mf.SeekToBegin (); mf.Read (m_pMapBase + sizeof (dwLength), dwLength); } } Now, all that's left is to tell all those threads that there is memory to be read. This brings us to our next section... III. Windows messagesThere are many forms of IPC that are both easy and efficient. However, in this particular case, I have a single application that is going to be talking to hundreds of other threads on the same machine. The obvious solution in my mind was using user-defined messages. The Windows API has a nice function called I didn't want to go message-crazy, so I only registered a single message. The message number indicates that the message is a communication between the HotKee main application and the instances of the DLL. I used the ::PostMessage (HWND_BROADCAST, m_nCommMessage,
WPARAM_HK_RELOAD_DATA, (LPARAM) m_hWnd);
Here I'm telling the DLL instances to reload their data from the mapped memory. I use the On the DLL side, messages are sent to both itself and to its parent window to accomplish much of what it does. Why are messages sent to itself? Because, as the DLL is filtering the messages for its parent window, instead of stopping the message pump to execute macros, it simply posts another message on the queue to do the work. This allows any other messages to be processed (like Messages are sent from the DLL to its parent window when a Macro is evaluated to some text which needs to be put into the window. Each character of the text is sent to the window as a message for the window to process, as it normally would. I discovered the Installing and Running the BinariesHere is a list of files included in the zip file:
Simply put all these files into one directory and run the executable. No other modification to your system should be required. ConclusionThe Windows API is a great tool that allows you to tap much of the power of the operating system on which your applications run. I hope you find this utility both useful and educational. It is wide open for expansion (as most free utilities are!), so if you do make some modifications you're willing to share, let me know! Above all remember, "Use your powers for good!"
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||