Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Safely reading text from HWND

134 views
Skip to first unread message

Hans-J. Ude

unread,
May 19, 2008, 5:36:09 PM5/19/08
to
To avoid using fixed size buffers I want to read the text of an unknown
window into a CString. All I know about is it's HWND. To do that I wrote
this code:

CWnd *pWndTemp;
CWnd *pWndClone;
CString strText;

pWndTemp = new CWnd;
pWndClone = pWndTemp->FromHandle(hwndUnknown);
pWndClone->GetWindowText(strText);
delete pWndTemp;

That works, but I have a feeling that there's a more elegant way. Any ideas?

Hans

AliR (VC++ MVP)

unread,
May 19, 2008, 5:44:52 PM5/19/08
to
I think the elegent way would be:

CString strText;
CWnd *pWnd = CWnd::FromHandle(hwndUnknown);
pWnd->GetWindowText(strText);

AliR.

"Hans-J. Ude" <ne...@wolptec.de> wrote in message
news:69ea58F...@mid.individual.net...

Hans-J. Ude

unread,
May 19, 2008, 6:11:54 PM5/19/08
to
"AliR (VC++ MVP)" <Al...@online.nospam> wrote

>I think the elegent way would be:
>
> CString strText;
> CWnd *pWnd = CWnd::FromHandle(hwndUnknown);
> pWnd->GetWindowText(strText);

Thanks Ali,
that was quick! I really didn't see that, too late here now. Since i
validated the HWND before one can even write.

CWnd::FromHandle(hwndUnknown)->GetWindowText(strText);

thanks,
Hans

Giovanni Dicanio

unread,
May 19, 2008, 6:18:22 PM5/19/08
to

"Hans-J. Ude" <ne...@wolptec.de> ha scritto nel messaggio
news:69ea58F...@mid.individual.net...

> To avoid using fixed size buffers I want to read the text of an unknown
> window into a CString. All I know about is it's HWND. To do that I wrote
> this code:

AliR already showed you the elegant way.

Of course you can wrap AliR's solution in a function:

<code>

inline CString GetWndText( HWND hWnd )
{
CString strText;
CWnd *pWnd = CWnd::FromHandle( hWnd );
pWnd->GetWindowText( strText );
return strText;
}

</code>

Some more comments:

> pWndTemp = new CWnd;
> pWndClone = pWndTemp->FromHandle(hwndUnknown);

The above 'new' is useless, because CWnd::FromHandle is a *static* method.
So you don't need to create an instance of CWnd to call it.

> delete pWndTemp;

This delete is uselss, because the window object pointer (CWnd *) returned
by FromHandle is *temporary* :

CWnd::FromHandle
http://msdn.microsoft.com/en-us/library/e547yfza(VS.80).aspx

There is a static method (CWnd::DeleteTempMap) that deletes temporary
objects created from CWnd::FromHandle.
This method is automatically called by the MFC framework (you don't need to
explicitly call it).

Giovanni


Joseph M. Newcomer

unread,
May 19, 2008, 10:28:45 PM5/19/08
to
See below,,,

On Mon, 19 May 2008 23:36:09 +0200, "Hans-J. Ude" <ne...@wolptec.de> wrote:

>To avoid using fixed size buffers I want to read the text of an unknown
>window into a CString. All I know about is it's HWND. To do that I wrote
>this code:
>
>CWnd *pWndTemp;
>CWnd *pWndClone;
>CString strText;
>
>pWndTemp = new CWnd;

****
And what role does pWndTemp serve here, other than taking up space, forcing a meaningless
storage allocation, and cluttering up your code with some pointless variable that serves
no useful purpose?
****


>pWndClone = pWndTemp->FromHandle(hwndUnknown);
>pWndClone->GetWindowText(strText);
>delete pWndTemp;
>
>That works, but I have a feeling that there's a more elegant way. Any ideas?

****
No, the code I would write would be

CString text;
CWnd * wnd = CWnd::FromHandle(hwndUnknown);
wnd->GetWindowText(text);

Note that, if you RTFM, CWnd::FromHandle is a static method, and therefore you do not need
any object to reference it. So you needed 7 lines to accomplish what 3 lines will do
joe
****
>
>Hans
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm

David Ching

unread,
May 20, 2008, 1:05:01 AM5/20/08
to
"Hans-J. Ude" <ne...@wolptec.de> wrote in message
news:69ea58F...@mid.individual.net...

// To avoid MFC:

// Get text length
int numChars = GetWindowTextLength(hwndUnknown);

// Note: If the HWND is in a different thread, the text could have changed
// after we called GetWindowTextLength() and before this call to
GetWindowText()
// Not sure how MFC works around this issue; I suspect it doesn't.

// Get Text
TCHAR buf[nNumChars+1]; // +1 for NULL
GetWindowText (hwndUnknown, buf, _countof(buf));

-- David

Giovanni Dicanio

unread,
May 20, 2008, 3:57:07 AM5/20/08
to

"David Ching" <d...@remove-this.dcsoft.com> ha scritto nel messaggio
news:uZsYj.4590$7k7....@flpi150.ffdc.sbc.com...

> // To avoid MFC:
>
> // Get text length
> int numChars = GetWindowTextLength(hwndUnknown);
>
> // Note: If the HWND is in a different thread, the text could have
> changed
> // after we called GetWindowTextLength() and before this call to
> GetWindowText()
> // Not sure how MFC works around this issue; I suspect it doesn't.
>
> // Get Text
> TCHAR buf[nNumChars+1]; // +1 for NULL
> GetWindowText (hwndUnknown, buf, _countof(buf));

Hi David,

Just a little note: I think that you can't define an array like you did,
because nNumChars is a run-time value, so the compiler can't reserve space
for that array at static (compile time).
(Of course your general idea is nice!)

I would use std::vector for the dynamic array, e.g.

std::vector< TCHAR > buf( nNumChars + 1);

GetWindowText( hwndUnknown, &buf[0], buf.size() );

Please correct me if I'm wrong.

Thanks,
G


Joseph M. Newcomer

unread,
May 20, 2008, 6:34:10 AM5/20/08
to
See below...

On Mon, 19 May 2008 22:05:01 -0700, "David Ching" <d...@remove-this.dcsoft.com> wrote:

>"Hans-J. Ude" <ne...@wolptec.de> wrote in message
>news:69ea58F...@mid.individual.net...
>> To avoid using fixed size buffers I want to read the text of an unknown
>> window into a CString. All I know about is it's HWND. To do that I wrote
>> this code:
>>
>> CWnd *pWndTemp;
>> CWnd *pWndClone;
>> CString strText;
>>
>> pWndTemp = new CWnd;
>> pWndClone = pWndTemp->FromHandle(hwndUnknown);
>> pWndClone->GetWindowText(strText);
>> delete pWndTemp;
>>
>> That works, but I have a feeling that there's a more elegant way. Any
>> ideas?
>>
>
>// To avoid MFC:
>
>// Get text length
>int numChars = GetWindowTextLength(hwndUnknown);
>
>// Note: If the HWND is in a different thread, the text could have changed
>// after we called GetWindowTextLength() and before this call to
>GetWindowText()
>// Not sure how MFC works around this issue; I suspect it doesn't.

****
There is no workaround possible. MFC assumes single-thread access, which makes sense,
because you shouldn't be accessing a window from another thread anyway...
****


>
>// Get Text
>TCHAR buf[nNumChars+1]; // +1 for NULL

****
This won't compile, since the bounds have to be compile-time constant

LPTSTR buf = new TCHAR[nNumChars + 1];
...
delete [ ] buf;
****


>GetWindowText (hwndUnknown, buf, _countof(buf));
>
>-- David
>
>
>
>

David Ching

unread,
May 20, 2008, 7:51:05 AM5/20/08
to
"Giovanni Dicanio" <giovanni...@invalid.com> wrote in message
news:OGCxS9ku...@TK2MSFTNGP03.phx.gbl...

>
>> // Get Text
>> TCHAR buf[nNumChars+1]; // +1 for NULL
>> GetWindowText (hwndUnknown, buf, _countof(buf));
>
> Hi David,
>
> Just a little note: I think that you can't define an array like you did,
> because nNumChars is a run-time value, so the compiler can't reserve space
> for that array at static (compile time).
> (Of course your general idea is nice!)
>
> I would use std::vector for the dynamic array, e.g.
>
> std::vector< TCHAR > buf( nNumChars + 1);
>
> GetWindowText( hwndUnknown, &buf[0], buf.size() );
>
> Please correct me if I'm wrong.
>

You're absolutely right, thank you!

-- David


David Ching

unread,
May 20, 2008, 7:53:28 AM5/20/08
to
"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
news:t5a534tgd15enlm62...@4ax.com...

>>// Get text length
>>int numChars = GetWindowTextLength(hwndUnknown);
>>
>>// Note: If the HWND is in a different thread, the text could have
>>changed
>>// after we called GetWindowTextLength() and before this call to
>>GetWindowText()
>>// Not sure how MFC works around this issue; I suspect it doesn't.
> ****
> There is no workaround possible. MFC assumes single-thread access, which
> makes sense,
> because you shouldn't be accessing a window from another thread anyway...
> ****

Some apps need to read the caption bars and other text of other app's
windows; sounds like the OP is trying to do that.


>>
>>// Get Text
>>TCHAR buf[nNumChars+1]; // +1 for NULL
> ****
> This won't compile, since the bounds have to be compile-time constant
>
> LPTSTR buf = new TCHAR[nNumChars + 1];
> ...
> delete [ ] buf;
> ****

Yup, thanks.

-- David


Hans-J. Ude

unread,
May 20, 2008, 8:37:40 AM5/20/08
to
"David Ching" <d...@remove-this.dcsoft.com> wrote:

>"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
>news:t5a534tgd15enlm62...@4ax.com...
>>>// Get text length
>>>int numChars = GetWindowTextLength(hwndUnknown);
>>>
>>>// Note: If the HWND is in a different thread, the text could have
>>>changed
>>>// after we called GetWindowTextLength() and before this call to
>>>GetWindowText()
>>>// Not sure how MFC works around this issue; I suspect it doesn't.
>> ****
>> There is no workaround possible. MFC assumes single-thread access, which
>> makes sense,
>> because you shouldn't be accessing a window from another thread anyway...
>> ****
>
>Some apps need to read the caption bars and other text of other app's
>windows; sounds like the OP is trying to do that.

That's right. It runs from inside an injected DLL and seems to be
quite reliable, if not always 100% though. I don't want to crash
another application by buffer overruns. That's why I'm using CString,
it's reliable and easy to use.

Hans

David Ching

unread,
May 20, 2008, 10:24:03 AM5/20/08
to
"Hans-J. Ude" <ne...@wolptec.de> wrote in message
news:0pg534tn1rnd5mll6...@4ax.com...

>>Some apps need to read the caption bars and other text of other app's
>>windows; sounds like the OP is trying to do that.
>
> That's right. It runs from inside an injected DLL and seems to be
> quite reliable, if not always 100% though. I don't want to crash
> another application by buffer overruns. That's why I'm using CString,
> it's reliable and easy to use.
>

If your DLL functions run on the app's primary UI thread, such as when you
inject using a Windows GetMessage hook, then the text cannot change while
your function is running. If you do something like CreateRemoteThread() and
run your DLL functions on that, then of course, it is not synchronized with
the app changing the UI.

I would check the implementation of the CWnd function you're using to get
the text. I have a feeling internally it is calling the
GetWindowTextLength() function in order to know what to call
CString::GetBuffer() with to get the buffer memory. So internally it is
doing exactly what the Win32 code I showed does... meaning it's not
thread-safe. IOW, just because it returns you a CString does not guarantee
it filled it correctly.

-- David


Giovanni Dicanio

unread,
May 20, 2008, 11:06:28 AM5/20/08
to

"David Ching" <d...@remove-this.dcsoft.com> ha scritto nel messaggio
news:E9BYj.9258$nl7....@flpi146.ffdc.sbc.com...

> I would check the implementation of the CWnd function you're using to get
> the text. I have a feeling internally it is calling the
> GetWindowTextLength() function in order to know what to call
> CString::GetBuffer() with to get the buffer memory. So internally it is

> doing exactly what the Win32 code I showed does... [...]


> IOW, just because it returns you a CString does not guarantee it filled it
> correctly.

You have an excellent point, David.

I was curious and read the implementation (in VS2008) of

void CWnd::GetWindowText( CString & ) const

and (if we exclude some preprocessor #ifndef... stuff), it does exactly what
you showed in Win32 code, calling ::GetWindowTextLength() (and using
CString::GetBufferSetLength() and CString::ReleaseBuffer()).

So, maybe the OP should consider allocating some big-enough buffer (if
possible, in his working scenario) and use the other overload of
CWnd::GetWindowText (the one that just calls Win32's ::GetWindowText()).

G


David Ching

unread,
May 20, 2008, 12:37:20 PM5/20/08
to
"Giovanni Dicanio" <giovanni...@invalid.com> wrote in message
news:e83cMto...@TK2MSFTNGP06.phx.gbl...

>
> You have an excellent point, David.
>
> I was curious and read the implementation (in VS2008) of
>
> void CWnd::GetWindowText( CString & ) const
>

Thanks for doing the labor! :-)


> and (if we exclude some preprocessor #ifndef... stuff), it does exactly
> what you showed in Win32 code, calling ::GetWindowTextLength() (and using
> CString::GetBufferSetLength() and CString::ReleaseBuffer()).
>
> So, maybe the OP should consider allocating some big-enough buffer (if
> possible, in his working scenario) and use the other overload of
> CWnd::GetWindowText (the one that just calls Win32's ::GetWindowText()).
>

Yes, even if "big enough" is just a guess, GetWindowText() takes the buffer
length as a parameter and will truncate the text to fit. So it will not
crash (buffer overrun) but the text you get may be cut off. Since that is
the case, I'm wondering why the OP complained of crashes. It should not
crash unless the buffer length was specified incorrectly (perhaps specified
as buffer size instead of number of characters?).

Cheers,
David


Joseph M. Newcomer

unread,
May 20, 2008, 1:11:57 PM5/20/08
to
See below...

On Tue, 20 May 2008 04:53:28 -0700, "David Ching" <d...@remove-this.dcsoft.com> wrote:

>"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
>news:t5a534tgd15enlm62...@4ax.com...
>>>// Get text length
>>>int numChars = GetWindowTextLength(hwndUnknown);
>>>
>>>// Note: If the HWND is in a different thread, the text could have
>>>changed
>>>// after we called GetWindowTextLength() and before this call to
>>>GetWindowText()
>>>// Not sure how MFC works around this issue; I suspect it doesn't.
>> ****
>> There is no workaround possible. MFC assumes single-thread access, which
>> makes sense,
>> because you shouldn't be accessing a window from another thread anyway...
>> ****
>
>Some apps need to read the caption bars and other text of other app's
>windows; sounds like the OP is trying to do that.

****
Yes, but if the text changes in between the two APIs, there is going to be a truncation of
the text if the new text is longer than the buffer. There's no way I know of to prevent
this. One way to avoid this is to allocate a very large fixed buffer, and if the length
returned is the length of the buffer (taking the terminal NUL into account) there was a
truncation, and the operation should be tried again.
****


>
>
>>>
>>>// Get Text
>>>TCHAR buf[nNumChars+1]; // +1 for NULL
>> ****
>> This won't compile, since the bounds have to be compile-time constant
>>
>> LPTSTR buf = new TCHAR[nNumChars + 1];
>> ...
>> delete [ ] buf;
>> ****
>
>Yup, thanks.
>
>-- David
>

Giovanni Dicanio

unread,
May 20, 2008, 1:06:26 PM5/20/08
to

"David Ching" <d...@remove-this.dcsoft.com> ha scritto nel messaggio
news:B6DYj.9272$nl7....@flpi146.ffdc.sbc.com...

>> void CWnd::GetWindowText( CString & ) const
>>
>
> Thanks for doing the labor! :-)

I was very curious! :-)
(I like going deep into things.)

Cheers,
G


Joseph M. Newcomer

unread,
May 20, 2008, 1:15:09 PM5/20/08
to
Since WM_GETTEXT takes an explicit length, you won't get a buffer overrun, but you might
get truncation.

I'm curious why a complex mechanism like DLL injection is required to get a caption.

Also, if you have some mechanism that is doing something interesting, make sure that it
can be localized. Note that injecting a non-Unicode DLL in a Unicode app or vice-versa
will have potentially interesting effects.
joe

David Ching

unread,
May 20, 2008, 1:20:29 PM5/20/08
to
"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
news:qc1634pd7c0khgjfb...@4ax.com...

> Yes, but if the text changes in between the two APIs, there is going to be
> a truncation of
> the text if the new text is longer than the buffer. There's no way I
> know of to prevent
> this. One way to avoid this is to allocate a very large fixed buffer, and
> if the length
> returned is the length of the buffer (taking the terminal NUL into
> account) there was a
> truncation, and the operation should be tried again.

Not exactly, though your method is sound. If the buffer you pass it is
exactly the length of the window text (including NUL) then the text is not
truncated, but you can't tell whether it is or not. But truncated or not,
does it really matter? If you're showing the text in, say, a Task Manager
application, showing 4K of characters isn't exactly user friendly anyway.

BTW, Task Manager does not inject any DLL into all the processes, AFAIK. It
just calls GetWindowText(), or better, sends with a timeout the WM_GETTEXT
message, which returns the caption strings fine even from other processes.
OP does not need to inject a DLL to get the strings!

OTOH, if OP does go through the effort and performance overhead of injecting
a DLL, then if the DLL is injected using a Windows hook, e.g. a
WH_GETMESSAGE hook, then the callback is called in context of primary UI
thread, and then there is no way for caption text to change in the middle of
the callback.

-- David


Hans-J. Ude

unread,
May 20, 2008, 2:03:50 PM5/20/08
to
"David Ching" <d...@remove-this.dcsoft.com> wrote:

Well, I didn't complain about crashes, I want to avoid them. And I
need the complete text, otherwise it would be useless. CString is the
simplest way to operate on Text. Why should I do all that memory
management myself when there is a handy class which does all that for
me?

Hans

Giovanni Dicanio

unread,
May 20, 2008, 2:05:44 PM5/20/08
to

"Joseph M. Newcomer" <newc...@flounder.com> ha scritto nel messaggio
news:al16341repb67ejtn...@4ax.com...

> Note that injecting a non-Unicode DLL in a Unicode app or vice-versa
> will have potentially interesting effects.

I like this terminology: "potentially interesting effects" :)

Giovanni


Hans-J. Ude

unread,
May 20, 2008, 2:23:48 PM5/20/08
to
Joseph M. Newcomer <newc...@flounder.com> schrieb:

>Since WM_GETTEXT takes an explicit length, you won't get a buffer overrun, but you might
>get truncation.
>
>I'm curious why a complex mechanism like DLL injection is required to get a caption.

It doesn't only read the caption. It extracts data from all kinds of
common controls of other programs and does other things with them,
like hiding annoying ads. Of course if one is subclassed and the
implementation is changed a lot it probably won't work. The user
handling is a bit Spy++ alike.

>Also, if you have some mechanism that is doing something interesting, make sure that it
>can be localized. Note that injecting a non-Unicode DLL in a Unicode app or vice-versa
>will have potentially interesting effects.

The DLL as well as the frontend-exe are Unicode. And the first I do
with the HWND is if(IsWindowUnicode(hwnd))... else ... to take the
appropriate actions.

Hans

Joseph M. Newcomer

unread,
May 20, 2008, 3:15:37 PM5/20/08
to
See below...

On Tue, 20 May 2008 10:20:29 -0700, "David Ching" <d...@remove-this.dcsoft.com> wrote:

>"Joseph M. Newcomer" <newc...@flounder.com> wrote in message
>news:qc1634pd7c0khgjfb...@4ax.com...
>> Yes, but if the text changes in between the two APIs, there is going to be
>> a truncation of
>> the text if the new text is longer than the buffer. There's no way I
>> know of to prevent
>> this. One way to avoid this is to allocate a very large fixed buffer, and
>> if the length
>> returned is the length of the buffer (taking the terminal NUL into
>> account) there was a
>> truncation, and the operation should be tried again.
>
>Not exactly, though your method is sound. If the buffer you pass it is
>exactly the length of the window text (including NUL) then the text is not
>truncated, but you can't tell whether it is or not. But truncated or not,
>does it really matter? If you're showing the text in, say, a Task Manager
>application, showing 4K of characters isn't exactly user friendly anyway.

****
That's why I said "taking the terminal NUL into account". You have to allocate one more
byte than the length you think you need, that is, the string length + 2 bytes. If the
result is string length+1, you have POTENTIAL truncation; if the length is string length,
then you are safe. I thought about writing the code to do this but it seemed obvious.

This is the old "double-the-buffer-size-and-try-again" trick seen in all kinds of other
contexts.
****


>
>BTW, Task Manager does not inject any DLL into all the processes, AFAIK. It
>just calls GetWindowText(), or better, sends with a timeout the WM_GETTEXT
>message, which returns the caption strings fine even from other processes.
>OP does not need to inject a DLL to get the strings!

****
Yes, that's why I was wondering why the need to inject a DLL to do such a trivial task.
****


>
>OTOH, if OP does go through the effort and performance overhead of injecting
>a DLL, then if the DLL is injected using a Windows hook, e.g. a
>WH_GETMESSAGE hook, then the callback is called in context of primary UI
>thread, and then there is no way for caption text to change in the middle of
>the callback.

****
Agreed.
joe
****

Joseph M. Newcomer

unread,
May 20, 2008, 3:17:36 PM5/20/08
to
As already pointed out, if you don't mind the fact that there is potential truncation in
the presence of concurrency, it works fine. Your injected DLL in the main GUI thread
would guarantee there would be no such change.
joe

David Ching

unread,
May 20, 2008, 3:55:50 PM5/20/08
to
"Hans-J. Ude" <ne...@wolptec.de> wrote in message
news:rv16345m2pm4749fh...@4ax.com...

> Well, I didn't complain about crashes, I want to avoid them.

Yes, my mistake.


> And I
> need the complete text, otherwise it would be useless. CString is the
> simplest way to operate on Text. Why should I do all that memory
> management myself when there is a handy class which does all that for
> me?
>

Nothing, except (and Giovanni has already done the hard part for you) that
"handy" class will screw you up because you're using it to get text from a
window that a) you don't own, and b) might be on a separate thread. Just
because the same source code is hidden within the implementation of a CWnd
member does not make it magically do the right thing!

-- David


0 new messages