Setting a Hook on a console application

Posted on 1998-01-09
Medium Priority
Last Modified: 2013-12-03
I wish to install a window hook (a WH_GETMESSAGE hook) on the console window
(the 'tty' class window) of a Windows 95 console application. SetWindowsHookEx
does not return an error when installing the hook, but when I post messages to
the console window (or use PostThreadMessage - neither function returns an
error) my window hook code is not executed (the hook works with GUI
application - so I know that it is not due to a bug in my code).

The Microsoft Knowlege Base gives the following information (but it is nearly
three years old):

PSS ID Number: Q108232
Article last modified on 11-02-1995
PSS database name: WIN32SDK
4.00    | 3.10 3.50 3.51
The information in this article applies to:
 - Microsoft Win32 Application Programming Interface (API) included with:
    - Microsoft Windows NT versions 3.5 and 3.51
    - Microsoft Windows 95 version 4.0
Under Windows NT, system hooks are limited in two situations: hooking
console windows and hooking the desktop.
Because of the current design of the console and the fact that its user
interface runs in the Win32 server, Windows NT does not support hook calls
in the context of console applications. Thus, if application A sets a
system-wide input hook and text is typed in a console window, application
A's input hook will not be called. The same is true for every type of
Windows hook except for journal record and journal playback hooks.
Hooking a console application will be enabled in Windows NT 3.51.
Windows NT supports journaling by forcing the console input thread to
communicate with the application that set the hook. In the case of a
console, the call to the hook functions are run in the context of the
application that installed the hook. This forces Windows NT to
synchronously talk to this process in order for it to work; if this process
is busy or blocked (as it is when it is sitting at a breakpoint), the
console thread is hung.
If console applications were typical Win32-based applications, then this
would not be a problem. A design change such as this would require that
each console take an extra thread just to process input. This was not
acceptable to the designers, and therefore console applications are not
implemented in the same way that other Win32-based applications are
Similarly, if Windows NT allowed other hooks to freely hook any process,
then these processes could enter a hanging state as well. The reason that
journaling is allowed to hook consoles is that journaling already requires
synchronization between all processes in the system, and a mechanism to
disengage the journaling process (via the CTRL+ESC, ALT+ESC and
CTRL+ALT+DEL keystroke combinations) is provided to prevent hanging the
system message queue.
For similar reasons, 16-bit Windows-based applications cannot hook
Win32-based applications under Windows NT.
The issues above apply equally well to hooking the desktop. If an
application were allowed to hook the desktop, it could potentially hang it.
This is completely unacceptable and violates one of the design principles
of Windows NT: no application should be allowed to bring down the system or
hang the user interface.
Additional reference words: 3.10 3.50 3.51 4.00 95
KBCategory: kbui
KBSubcategory: UsrHks

Is there a work around to this problem? Basically, all I want to do is to grab
the console display (ie. the text) of a console application running in a
window - given the window's handle.
Question by:DavidDunn
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 4
  • 2

Expert Comment

ID: 1409979
No way, you cannot hook into a console window.

Ref: November or December 1997 issue of Microsoft Systems Journal
(I think the exact same question was asked)

Author Comment

ID: 1409980
I have looked through M.S.J. Ocober 97, November 97 & December 97 issues but I
cannot find a question being asked similar to mine.
If you use Spy++ (or a similar tool) to spy on a console application you will
see that is consists of two processes. The first process is the process with the
module name of the console application, the second is the process with the
module name WINOA386. The tty window class of the console applcation belongs to
the thread of the first process, the ttyGrab, ToolbarWindow and ComboBox window
classes belong to the thread of the second process - I can install a hook on
these windows (obviously, I am using WH_GETMESSAGE to 'inject a DLL into another
processes address space). If this second process is a child process created by
the first, then may be there is some infomation that is inherited in this process
from the first one that I can use - I don't know, it is just an idea.

The ttyGrab window appears to be the window that contains the text of the
console application - and I can hook into this window. May be I have missled
you a little, what I want to do is to grab the text in a console's window - I
thought it could be done using hooks, but there may be another way. It must be
possible to do since you can use the buttons on the ToolbarWindow window of the
second process to copy and paste the text to other applcations.

Finally, article Q108232 given previously states that "Hooking a console
application will be enabled in Windows NT 3.51". Do you know whether or not it
will be enabled in Windows 98?

Expert Comment

ID: 1409981
if you cannnot find the reference i gave you (i really think i did read this in MSJ) please reject the answer i proposed.
(I think it is correct, but i'd better give correct references)

NFR key for Veeam Backup for Microsoft Office 365

Veeam is happy to provide a free NFR license (for 1 year, up to 10 users). This license allows for the non‑production use of Veeam Backup for Microsoft Office 365 in your home lab without any feature limitations.


Author Comment

ID: 1409982
Adjusted points to 220
LVL 39

Accepted Solution

abel earned 440 total points
ID: 1409983
If I understand your question correctly, your main purpose is to copy the contents of a console window to somewhere else. If the contents of the console window is static at the moment you want to grab it (i.e. not a fast scrolling dir-list) then here is an easy solution to your problem.
You probably know that you can copy the contents of a console window to the clipboard. I suggest you use that possibility and use the clipboard contents in your application.

-Use FindWindow to find the console-window
-Use PostMessage to post messages to the console
--WM_KEYDOWN with VK_DOWN (times 6), WM_KEYUP with VK_DOWN
--now you can select the area to copy to the clipboard, this is for the whole area:
--WM_KEYDOWN with VK_DOWN (times 24), WM_KEYUP with VK_DOWN
--WM_KEYDOWN with VK_RIGHT (times 80), WM_KEYUP with VK_RIGHT

By now you should have everything in the clipboard, you only have to empty that with your application to get the contents of the console-window.

I hope this helps you out. If you need more info, or some samplecode, post it here and I'll give it to you.

Regards, Abel

Author Comment

ID: 1409984
I had previously considered a similar method to yours, but there are four
problems with it.

1) Console windows are not fixed at 80 characters by 25 characters (The console
   demo program on MSDN Library CD-ROM shows this). Is there any way to
   determine the actual size in characters?

2) It is not possible to get each characters attributes, namely their
   foreground and background colours.

3) The console window may be running full screen or the the window may be
   be in the process of being marked (for copying and pasting). Is there any
   way to determine whether or not the window is currently being marked?

4) Some of the key combinations could be disabled, for example Alt + Enter
   so you could not switch between windowed and full screen.

I think that I can get the font (using WM_GETFONT) for the window. Is there
any way to get the current cursor position for the window?


LVL 39

Expert Comment

ID: 1409985
As you probably know, most functions concerning console windows are only used by the process that created the window. That makes it pretty complicated to accomplish the tasks you ask (I assume you don't create the console by yourself).
Does the app which has to grab the console window run in the GUI? If that's so and the console app is minimized, (or if you can control the calling of the console - if you call it, you can set it's properties first) you can access the properties of the window. Otherwise you can think of sending an VK_LWIN to the console, it will always go back to the GUI then, and you can access it't properties.

I think it will be difficult to get the character-attributes, unless you can use something like a DOS TSR (which can be written quite easily) to grab everything. It must be loaded before the console window is being written to. If you can use a TSR, you don't have to worry about anything actually, you just write everything that happens to a file, and read it when the console window ends.
You don't have to worry about your first point. Just send a lot extra VK_DOWNs and VK_RIGHTs to the console.

In what cases do you want to grab a console window? What do you want to use it for? Not that I'm curious :), but it might help me to know it, since there might be other ways to achieve your tasks.

regards, Abel

(meanwhile I'm thinking of some better work-arounds than the above, I'll keep in touch)

Author Comment

ID: 1409986
I am trying to write a Spy program (similar to Microsoft's Spy++ but,
hopefully, better!) for both study and work, using Visual Basic.

I would like to be able to use the console APIs on a console application
(which is to be 'spied' upon) but, as you pointed out, they can only be used
by the process that created the console window - if it was possible to install
a WH_GETMESSAGE hook on the window (to 'inject' a DLL into another process's
address space) this would not be a problem.

If you spy on a console application (I assume you have Spy++ and a console
application eg. the console demo on the MSDN Library CD-ROM) you will see that
is consist of two processes. I cannot hook into the process which contains the
main window (the tty class window), but I can hook into the process that
contains the client window (the ttyGrab class window) - when I say 'hook' I mean
that I can 'inject' a DLL that I have written into this processes address
space. I think that some where in this process's memory (the process containing
the ttyGrab window) are the contents of the window ie. the text etc. However,
I cannot find any details about the process's module WINOA386 on the MSDN
library CD-ROM. Any ideas?

I also wish to copy the window to a picture box. I create a compatible
memory DC and compatible Bitmap, send a WM_PRINT message to the window
(specifying the memory DC) and then BitBlt the bitmap to the picture-box. This
works fine for GUI applications but not for console applications - it does not
get the contents of the window containing the text (the ttyGrab window).




Author Comment

ID: 1409987
I have discovered that the console (the tty class window) has three extra
properties (using Get/SetProp and EnumProps APIs).

flWinOldApp = 1
hvmHi = 50002
hvmLo = 282

The first property is obviously a flag indicating that the window is a tty
class window (windows 95 uses the same module WINOA386 for both Win32 console
applications and MS-DOS applications).
I think that the next two properties form the high and low words of a 32-bit
handle to a virtual machine (I've been reading up on VMs!). The handle will
also, therefore, be the base address of the virtual machine. From there I
would be able to obtain the CB_High_Linear member of the control block, and
[CB_High_Linear + 0400h] would be the BIOS area of the virtual machine (since
it would be the same as in an MS-DOS application).

However, the base address calculated from the two properties is greater than
2GB and ReadProcessMemory (and ToolHelp32ReadProcessMemory) does not appear to
be able to read a process's memory above 2GB, so I can't try out my theory.
Is this true? If so, is there a work around to reading a process's memory above



Author Comment

ID: 1409988
Sorry, ignore my previous comment about not being able to read memory above 2GB.
I'm using Visual Basic, which does not support DWORDs, so I needed to convert
the value to the equivalent LONG (32-bit signed integer) value (and pass it by
value rather than by reference).

I tested the theory about the hvmHi/hvmLo on an MS-DOS program. hvmHi and hvmLo
do indeed form a 32-bit pointer to the virtual machine. Reading the Control
Block structure, I can obtain the CB_High_Linear value (hvmHi/hvmLo + 4). This
value + 400h (ie. CB_High_Linear + 400h) is the start of the BIOS data. I have
also found the following artice in the Microsoft Knowledge Base that is


Knowledge Base

How to Read Text Mode Screen of MS-DOS Session in Windows

Article ID: Q120565
Creation Date: 14-SEP-1994
Revision Date: 24-FEB-1995

The information in this article applies to:
Microsoft Windows Device Driver Kit (DDK) for Windows version 3.1


Sometimes it is necessary to read the text from the screen of an MS-DOS session
running under Windows version 3.1 in 386 Enhanced mode. Use a simple VxD to read
the screen in an MS-DOS session.
You don't need to access the MS-DOS session screen through the VDD or GRABBER to
read it.


In a VxD, by using VM Control Block (handle) for the virtual machine (VM) that
contains the MS-DOS session, you can obtain the base linear address of the VM
from the CB_High_Linear member of the control block. CB_High_Linear will contain
the linear address of "virtual 0000:0000" for the MS-DOS session.

Use this linear address to offset to [CB_High_Linear + 0400h] in the VM to poke
around in the BIOS DATA area. Information such as what video mode is active, how
many screen columns are active, and so on can be found in the BIOS DATA area.
You can use this data to determine the address and the size of the text screen
in the MS-DOS session. Then you can use the address and size to read the whole
screen or a portion of it.

By using MEMORY.LST included with Ralf Brown's Interrupt List compilation, or by
using a good BIOS DATA reference found in many books, you can get this

Format of BIOS Data Segment at segment 40h:

                {items in curly braces not documented by IBM}
Offset  Size    Description
  . . .
 49h    BYTE    Video current mode
 4Ah    WORD    Video columns on screen
 4Ch    WORD    Video page (regen buffer) size in bytes
 4Eh    WORD    Video current page start address in regen buffer
 50h 16 BYTEs   Video cursor position (col, row) for eight pages,
                0 based
 60h    WORD    Video cursor type, 6845 compatible, hi=startline,
 62h    BYTE    Video current page number
 63h    WORD    Video CRT controller base address: color=03D4h,
 65h    BYTE    Video current setting of mode select register
 66h    BYTE    Video current setting of CGA palette register 03D9h

By reading at [CB_High_Linear + 449h], for example, you can determine the screen
mode active in the MS-DOS session. If it is 7, it is text mode on an MDA
adapter, and the text screen buffer is at [CB_High_Linear + 0B000h]. Otherwise,
if it is a text video mode number, a color text mode is in use in the MS-DOS
session, and the text screen buffer is at [CB_High_Linear + 0B800h].

If you determine that a color text mode is in use, the adapter could be CGA,
EGA, or VGA, so the MS-DOS session could be in either 25-, 43-, or 50-line

The size of the screen regenerate buffer is stored at [CB_High_Linear + 044Ch].
Based on the number of bytes the screen takes and the number of screen columns,
you can determine the number of screen rows.

The screen regenerate buffer will typically contain one of the following values:

  On CGA (25 lines, 40 columns), the buffer size will be 0800h (2048d)
  On CGA (25 lines, 80 columns), the buffer size will be 1000h (4096d)
  On EGA (43 lines, 80 columns), the buffer size will be 1BE0h (7136d)
  On VGA (50 lines, 80 columns), the buffer size will be 2040h (8256d)

The number of columns on the screen is stored at [CB_High_Linear + 044Ah]. By
dividing the buffer size by the number of columns, then dividing by two (unless
screen attributes need to be read, too), you can determine the number of rows on
the screen:

  Buffer size   Number of columns   Skip Attribs   Result
  2048          / 40                / 2             25.6
  4096          / 80                / 2             25.6
  7136          / 80                / 2             44.6
  8256          / 80                / 2             51.6

NOTE: the results are not exact. Therefore, if the regenerate buffer is greater
than 4096 bytes or if the number of screen columns is not equal to 25, the
result must be decremented by 1 to get the real number of screen lines.

Once you determine the screen buffer offset, number of columns, and size of text
screen, you can read the screen by dereferencing to the screen buffer and
reading the characters out of the screen buffer memory.


Windows 3.1 Device Driver Kit "Virtual Device Adaptation Guide" Microsoft
Developers Network "Developers Library" CD


If I use the CB_High_Linear + 44Ah I can read the number of screen columns as
decibed in the article, Indeed I can obtain all of them. However I cannot read
the screen buffer as described above ie. CB_High_Linear + B800h,
ReadProcessMemory returns zero. I seem to be able to retrieve everything except
the screen buffer. Any ideas why not?

regards, David
LVL 39

Expert Comment

ID: 1409989
Sorry for not posting that long,
You have a good point there, I was looking in the same direction, but I have a few other ideas as well. I hope I can come back here this day again, otherwise it will be monday.

Regards, Abel

Author Comment

ID: 1409990
Segment B8000 instead of B800 gets the screen buffer!
I can now grab the text and attributes.

All I need to do now is to get the console window's font, any
ideas WM_GETFONT does not seem to work.
LVL 39

Expert Comment

ID: 1409991
If WM_GETFONT returns NULL, the systemfont is used, otherwise it should return the handle of the font.
Usually the system font is being used in Win95 consoles. If you want the name of that font, a workaround can be looking to the .PIF file connected to the running console application. In that file you find the TT fontname, the system fontname and the size being used.  The problem is that you don't always have a PIF-file ready, but you might be able to look to the defaults when an application is run without a corresponding PIF-file (as long as the defaults aren't changed, there's no PIF-file, as soon as they ARE changed, the corresponding PIF-file holds the information).

In Win NT it's easier, just read HKCU\Console\Facename from the registry, that returns the font's name. But I don't know if your spy-program has to work on NT as well.

You did good research, hope you get somewhere!

Regards, Abel

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

zlib is a free compression library (a DLL) on which the popular gzip utility is built.  In this article, we'll see how to use the zlib functions to compress and decompress data in memory; that is, without needing to use a temporary file.  We'll be c…
A theme is a collection of property settings that allow you to define the look of pages and controls, and then apply the look consistently across pages in an application. Themes can be made up of a set of elements: skins, style sheets, images, and o…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…

765 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question