{"id":30183,"date":"2006-08-09T10:00:18","date_gmt":"2006-08-09T10:00:18","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/08\/09\/how-were-window-hooks-implemented-in-16-bit-windows\/"},"modified":"2006-08-09T10:00:18","modified_gmt":"2006-08-09T10:00:18","slug":"how-were-window-hooks-implemented-in-16-bit-windows","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060809-18\/?p=30183","title":{"rendered":"How were window hooks implemented in 16-bit Windows?"},"content":{"rendered":"<p>\nThe mechanism for keeping track of window hooks was very\ndifferent in 16-bit Windows.\nThe functions involved were <code>SetWindowsHook<\/code>,\n<code>UnhookWindowsHook<\/code>\nand <code>DefHookProc<\/code>.\nThe first two functions still exist today, but the third one has\nbeen replaced with a macro:\n<\/p>\n<pre>\n\/\/ 16-bit prototype\nDWORD WINAPI DefHookProc(int nCode, WPARAM wParam,\n                         LPARAM lParam, HHOOK FAR *phk);\n\/\/ 32-bit macro\n#define DefHookProc(nCode, wParam, lParam, phhk)\\\n        CallNextHookEx(*phhk, nCode, wParam, lParam)\n<\/pre>\n<p>\nDisclaimer: All code below is &#8220;reconstructed from memory&#8221;.\nThe spirit of the code is intact, but the precise details\nmay be off.\n<\/p>\n<p>\nTo install a windows hook in 16-bit Windows, you started by\ncalling <code>SetWindowsHook<\/code>:\n<\/p>\n<pre>\nHHOOK g_hhkPrev;\ng_hhkPrev = SetWindowsHook(WH_WHATEVER, MyHookProc);\n<\/pre>\n<p>\nThe return value from <code>SetWindowsHook<\/code> must be saved\nin a global variable, which we gave the somewhat provocative\nname <code>g_hhkPrev<\/code>.\nThe hook procedure itself went something like this:\n<\/p>\n<pre>\n\/\/ In Win16, hook procedures returned a DWORD, not an LRESULT.\nDWORD CALLBACK MyHookProc(int nCode, WPARAM wParam, LPARAM lParam)\n{\n  if (nCode &gt;= 0) { ... }\n  return DefHookProc(nCode, wParam, lParam, &amp;g_hhkPrev);\n}\n<\/pre>\n<p>\nAnd then when you were finished, you removed the hook by\ncalling <code>UnhookWindowsHook<\/code>:\n<\/p>\n<pre>\nUnhookWindowsHook(WH_WHATEVER, MyhookProc);\ng_hhkPrev = NULL;\n<\/pre>\n<p>\nInternally, the chain of hook functions was managed as\na linked list, but instead of using some internal data structure\nto keep track of the hooks, the linked list was managed\n<strong>inside the HHOOK variables themselves<\/strong>.\n<\/p>\n<p>\nThe internal implementation of <code>SetWindowsHook<\/code>\nwas simply this:\n<\/p>\n<pre>\n\/\/ This array is initialized with a bunch\n\/\/ of \"do nothing\" hook procedures.\nHOOKPROC g_rgHook[NUMHOOKS];\nHHOOK WINAPI SetWindowsHook(int nType, HOOKPROC pfnHookProc)\n{\n HHOOK hhkPrev = (HHOOK)g_rgHook[nType];\n g_rgHook[nType] = pfnHookProc;\n return hhkPrev;\n}\n<\/pre>\n<p>\nInstalling a hook merely set your hook procedure as the head of\nthe hook chain, and it returned the previous head.\nInvoking a hook was a simple matter of calling the hook at the\nhead of the chain:\n<\/p>\n<pre>\nDWORD CallHook(int nType, int nCode, WPARAM wParam, LPARAM lParam)\n{\n return g_rgHook[nType](nCode, wParam, lParam);\n}\n<\/pre>\n<p>\nEach hook procedure did its work and then sent the call\ndown the hook chain by calling <code>DefHookProc<\/code>,\npassing the <code>HHOOK<\/code> <strong>by address<\/strong>.\n<\/p>\n<pre>\nDWORD WINAPI DefHookProc(int nCode, WPARAM wParam,\n                         LPARAM lParam, HHOOK FAR *phk)\n{\n HOOKPROC pfnNext = (HOOKPROC)*phk;\n if (nCode &gt;=0) {\n  return pfnNext(nCode, wParam, lParam);\n }\n ... more to come ...\n}\n<\/pre>\n<p>\nAs you can see, it&#8217;s all blindingly simple:\nInvoking a hook calls the first hook procedure,\nwhich then calls <code>DefHookProc<\/code>, which\nknows that a <code>HHOOK<\/code>\nis just a <code>HOOKPROC<\/code>, and it forwards the call down\nthe chain by merely calling the next hook procedure directly.\n<\/p>\n<p>\nThe real magic happens when somebody wants to unhook.\nRecall that the rule for hook procedures is that a negative\nhook code should be passed straight to <code>DefHookProc<\/code>\n(or in modern times, <code>CallNextHookEx<\/code>).\nThis convention allows the hook system to use negative codes\nto manage its own internal bookkeeping.\nIn this case, we&#8217;re using <code>-1<\/code> as the\n&#8220;unhook this hook procedure&#8221; code.\n<\/p>\n<pre>\nBOOL WINAPI UnhookWindowsHook(int nType, HOOKPROC pfnHookProc)\n{\n return DefHookProc(-1, 0, (LPARAM)pfnHookProc,\n                    (HHOOK FAR*)&amp;g_rgHook[nType]);\n}\n<\/pre>\n<p>And then the real magic begins:\n<\/p>\n<pre>\nDWORD WINAPI DefHookProc(int nCode, WPARAM wParam,\n                         LPARAM lParam, HHOOK FAR *phk)\n{\n HOOKPROC pfnNext = (HOOKPROC)*phk;\n if (nCode &gt;=0) {\n  return pfnNext(nCode, wParam, lParam);\n }\n switch (nCode) {\n case -1: \/\/ trying to unhook a node\n  if (pfnNext == (HOOKPROC)lParam) { \/\/ found it\n   *phk = (HHOOK)pfnNext(-2, 0, 0);\n   return TRUE;\n  }\n  \/\/ else keep looking\n  return pfnNext(nCode, wParam, lParam);\n case -2: \/\/ report the next hook procedure\n   return (DWORD)*phk;\n }\n return 0;\n}\n<\/pre>\n<p>\nAnd there you have it, the entire window hook system in\ntwo dozen lines of code.\nYou have to give 16-bit Windows credit for being small.\n<\/p>\n<p>\nLet&#8217;s walk through hook installation, dispatch, and removal to see\nhow this all works.\nSuppose there is one <code>WH_KEYBOARD<\/code> hook in the system.\nOur variables are therefore set up like this:\n<\/p>\n<pre>\n\/\/ In USER\ng_rgHook[WH_KEYBOARD] = Hook1;\n\/\/ In HOOK1.DLL\nHHOOK g_hhkPrev1 = DoNothingHookProc;\nDWORD CALLBACK Hook1(int nCode, WPARAM wParam, LPARAM lParam)\n{\n if (nCode &gt;= 0) { ... work ... }\n return DefHookProc(nCode, wParam, lParam, &amp;g_hhkPrev1);\n}\n<\/pre>\n<p>\nNow suppose you want to install a new hook, <code>Hook2<\/code>.<\/p>\n<pre>\n\/\/ In HOOK2.DLL\nHHOOK g_hhkPrev2;\ng_hhkPrev = SetWindowsHook(WH_KEYBOARD, Hook2);\n<\/pre>\n<p>\nThe <code>SetWindowsHook<\/code> function just puts your function\nin as the new &#8220;head&#8221; hook function and returns the old one.\n<\/p>\n<pre>\n\/\/ In USER\ng_rgHook[WH_KEYBOARD] = Hook2;\n\/\/ In HOOK2.DLL\nHHOOK g_hhkPrev2 = Hook1;\nDWORD CALLBACK Hook2(int nCode, WPARAM wParam, LPARAM lParam)\n{\n if (nCode &gt;= 0) { ... work ... }\n return DefHookProc(nCode, wParam, lParam, &amp;g_hhkPrev2);\n}\n\/\/ In HOOK1.DLL\nHHOOK g_hhkPrev1 = DoNothingHookProc;\nDWORD CALLBACK Hook1(int nCode, WPARAM wParam, LPARAM lParam)\n{\n if (nCode &gt;= 0) { ... work ... }\n return DefHookProc(nCode, wParam, lParam, &amp;g_hhkPrev1);\n}\n<\/pre>\n<p>\nNow suppose the window manager decides it&#8217;s time to fire the\n<code>WH_KEYBOARD<\/code> hook.\nIt starts with <code>CallHook<\/code> which calls\n<code>g_rgHook[WH_KEYBOARD]<\/code> that takes us to\n<code>Hook2<\/code>.\nThat hook function does its work, then calls\n<code>DefHookProc(..., &amp;g_hhkPrev2)<\/code>,\nwhich dispatches the hook to <code>g_hhkPrev2 == Hook1<\/code>.\nSimilarly, the hook travels through <code>Hook1<\/code>,\nthen\n<code>DefHookProc(..., &amp;g_hhkPrev1)<\/code>,\nwhere it finally reaches the <code>DoNothingHookProc<\/code>\nwhich does nothing and ends the hook chain.\n<\/p>\n<p>\nNow suppose that <code>HOOK1.DLL<\/code> decides to uninstall its\nhook. It therefore calls\n<code>UnhookWindowsHook(WH_KEYBOARD, Hook1)<\/code>.\nThis starts off the hook chain with the internal hook code <code>-1<\/code>\nand <code>&amp;g_rgHook[WH_KEYBOARD]<\/code> as the first hook pointer.\nThis activates the <code>case -1<\/code> in <code>DefHookProc<\/code> code\npath, which dereferences its <code>phk<\/code> parameter and obtains\n<code>g_rgHook[WH_KEYBOARD] == Hook2<\/code>.\nSince this is not equal to <code>Hook1<\/code>, the call forwards down\nthe chain to <code>Hook2<\/code>.\n<\/p>\n<p>\nLike a good hook function, <code>Hook2<\/code> reacts to the negative\nhook code by handing the call directly to\n<code>DefHookProc(-1, ..., &amp;g_hhkPrev2)<\/code>.\nThis time, <code>*phk == g_hhkPrev2 == Hook1<\/code>,\nso the test succeeds and we dispatch the hook down the chain\nwith a new internal code of <code>-2<\/code>, which means,\n&#8220;Tell me what the next hook procedure is&#8221;.\n<\/p>\n<p>\nThis dispatch calls <code>Hook1<\/code> which (since the notification\ncode is negative) immediately passes the call to\n<code>DefHookProc(-2, ..., &amp;g_hhkPrev1)<\/code>.\nThis now triggers the <code>case -2<\/code> code path, which\njust returns <code>*phk == g_hhkPrev1 == DoNothingHookProc<\/code>.\nThis value is returned to the <code>DefHookProc(-1, ...)<\/code>\nwhich stores the result into <code>*phk == g_hhkPrev2<\/code>;\nthe result is that you have <code>g_hhkPrev2 = DoNothingHookProc<\/code>.\nFinally, <code>DefHookProc<\/code> returns <code>TRUE<\/code>\nto indicate that the hook was successfully uninstalled.\nThis value is then returned out from all the nested function calls\nto the original caller of <code>UnhookWindowsHook<\/code>.\n<\/p>\n<p>\nObserve that at the end of this unhook exercise, we get the desired\nresult:\n<\/p>\n<pre>\n\/\/ In USER\ng_rgHook[WH_KEYBOARD] = Hook2; \/\/ unchanged\n\/\/ In HOOK2.DLL\ng_hhkPrev2 = DoNothingHookProc; \/\/ updated!\nDWORD CALLBACK Hook2(int nCode, WPARAM wParam, LPARAM lParam)\n{\n if (nCode &gt;= 0) { ... work ... }\n return DefHookProc(nCode, wParam, lParam, &amp;g_hhkPrev2);\n}\n<\/pre>\n<p>\nAnd <code>Hook1<\/code> is out of the hook chain, as we desired.\n<\/p>\n<p>\nThis really isn&#8217;t all that complicated.\nAll we did was delete a node from a linked list.\nIt&#8217;s just that this particular linked list cannot be traversed\nby just dereferencing pointers.\nInstead, we have to issue a function call and ask the\nrecursive function to perform the work on the &#8220;next&#8221; node for us.\nThat&#8217;s what the negative <code>nCode<\/code> values are for.\n<\/p>\n<p>\nEvery time I work through this exercise,\nI am impressed by how compactly 16-bit Windows was written.\nIn just two dozen lines of code, we managed a linked list of\nfunction calls, including a dispatching system as well as\narbitrary deletion from the middle\nof the linked list, and all without any memory allocation.\n<\/p>\n<p>\n(And because I know people are going to try to change the topic:\nRemember, I&#8217;m talking about 16-bit Windows, not 32-bit window hooks.)\n<\/p>\n<p>\nNext time, we&#8217;ll look at one way people abused this simple system.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The mechanism for keeping track of window hooks was very different in 16-bit Windows. The functions involved were SetWindowsHook, UnhookWindowsHook and DefHookProc. The first two functions still exist today, but the third one has been replaced with a macro: \/\/ 16-bit prototype DWORD WINAPI DefHookProc(int nCode, WPARAM wParam, LPARAM lParam, HHOOK FAR *phk); \/\/ 32-bit [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[2],"class_list":["post-30183","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-history"],"acf":[],"blog_post_summary":"<p>The mechanism for keeping track of window hooks was very different in 16-bit Windows. The functions involved were SetWindowsHook, UnhookWindowsHook and DefHookProc. The first two functions still exist today, but the third one has been replaced with a macro: \/\/ 16-bit prototype DWORD WINAPI DefHookProc(int nCode, WPARAM wParam, LPARAM lParam, HHOOK FAR *phk); \/\/ 32-bit [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30183","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=30183"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30183\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=30183"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=30183"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=30183"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}