Bob Lamberson
asked on
Get a vb label caption using WinApi
I'm running a vb application exe and need to get the caption of a Label in another vb application exe. Have no access to the second application. Any help greatly appreciated.
What version of vb and os are you running.
How will you identify the app that you want the label from? PID, Caption, etc.
A true label has no associated window handle, meaning you will not be able to grab its caption via API.
ASKER
BlueDevilFan,
Thanks, I'm checking the code out now.
Thanks, I'm checking the code out now.
ASKER
jkozee,
I'm using vb6 in vs6 enterprise and win2k with (hopefully) all the latest sps.
I'm using vb6 in vs6 enterprise and win2k with (hopefully) all the latest sps.
ASKER
AzraSound,
Yeah exactly, that's what makes it a complicated question.
Do you know of anyway to do it with these conditions:
The second application's form has a frame (could be any window with a handle) that contains the label.
We can get the win handle for the frame.
We can get a device context handle for the RECT of the frame.
But, Can't figure out how to pull the caption of the label out of the frame.
Maybe this stirs an idea?
Yeah exactly, that's what makes it a complicated question.
Do you know of anyway to do it with these conditions:
The second application's form has a frame (could be any window with a handle) that contains the label.
We can get the win handle for the frame.
We can get a device context handle for the RECT of the frame.
But, Can't figure out how to pull the caption of the label out of the frame.
Maybe this stirs an idea?
Ooops, I think I misread the question in which case my post is useless. I thought I read "set the caption" when instead it is "get the caption". Sorry!
ASKER
BlueDevilFan,
No problem.
Either get or set works for me if I can find an api function(s) that will just somehow deal with the information in the label caption. The problem seems to be communication with the label itself, since it has no handle.
No problem.
Either get or set works for me if I can find an api function(s) that will just somehow deal with the information in the label caption. The problem seems to be communication with the label itself, since it has no handle.
Again, no handle, no direct solution. You know those websites that show some image of a word you must type in to register, for example? The same idea would have to apply here...you would need some type of image/character recognition functionality because about the best you could do with a label is pull it as an image.
ASKER
AzraSound,
Thanks for the ideas. I guess I think that there is some way to accomplish this for my client, other than a com dll that would have to be on each users machine.
SnagIt 6 at http://www.techsmith.com/ has some way of getting the caption of a label inside a frame that is selected by outlining the frame with the mouse (I've used it), but I don't know how they are doing it.
I suspect that the API graphic library plays a part, hence my suggestion that maybe using the device context handle for the RECT of the frame might help.
Anyway, if you think of something I would appreciate hearing.
I can see it could get the caption of the frame itself (the frame has a window handle) but not a label within the frame. SnagIt admits it cannot retrieve text from a pdf or image file, which, I imagine, is similar to what would need to be done to get the text from a label control.
ASKER
AzraSound,
You might want to actually try SnagIt's trial and see it return the caption of a label in a frame, before you itellectually argue that it's not likely.
I'm just looking for answers, but here's some of what I have picked up so far.
Using windows GDI API lib provides graphical functions that relate to any rectangular area of the display, such as a frame or 'drawing context', that might be drawn to.
This area is considered an object and has a handle seperate from the window handle that is assigned to the frame, which is referred to as a Device Context handle (hDC).
There is also a Memory handle (hMem) that facilitates access to a block of memory, which I suppose could hold the caption of the label or contents of the frame.
It's possible that SnagIt's ability to return what is inside that frame, might have to do with either of these, I don't know and I'm hoping to find someone who knows the magic.
You might want to actually try SnagIt's trial and see it return the caption of a label in a frame, before you itellectually argue that it's not likely.
I'm just looking for answers, but here's some of what I have picked up so far.
Using windows GDI API lib provides graphical functions that relate to any rectangular area of the display, such as a frame or 'drawing context', that might be drawn to.
This area is considered an object and has a handle seperate from the window handle that is assigned to the frame, which is referred to as a Device Context handle (hDC).
There is also a Memory handle (hMem) that facilitates access to a block of memory, which I suppose could hold the caption of the label or contents of the frame.
It's possible that SnagIt's ability to return what is inside that frame, might have to do with either of these, I don't know and I'm hoping to find someone who knows the magic.
BobLamberson:
I have downloaded SnagIt v7 demo and I am unable to get the demo to capture text from a VB label in Win 2000 and Win XP. Can you confirm that it works in those OS's.
I have downloaded SnagIt v7 demo and I am unable to get the demo to capture text from a VB label in Win 2000 and Win XP. Can you confirm that it works in those OS's.
ASKER
jkozee,
Yes, I can confirm that SnagIt 6 works on win2k and will retrieve the captionof a VB Label control inside a frame using the mouse to outline the frame. I just tried it again and it works fine.
I will see if I can get trial v7 and try it.
Yes, I can confirm that SnagIt 6 works on win2k and will retrieve the captionof a VB Label control inside a frame using the mouse to outline the frame. I just tried it again and it works fine.
I will see if I can get trial v7 and try it.
ASKER
jkozee,
I just installed v7 and it is quite different looking but works the same way. I have a compiled vb app that is just a form with a frame, a textbox inside the frame, and a label inside the frame. The form also has a label that is not contained by a frame.
Using the text capture, I can get and save the text of the label inside the frame. Using the image capture, I can get and save the an image of the label on the form, outside the frame.
I also created an exe that has two different frames with a label in each and using the text setting on snagit, I am able to save capture and saave the label caption to a text file as ordinary text.
Snagit is set to
Text
Input: Region
Output: File
Filters: Space Formatted
These are just the default settings that came up when I opened SnagIt.
I just installed v7 and it is quite different looking but works the same way. I have a compiled vb app that is just a form with a frame, a textbox inside the frame, and a label inside the frame. The form also has a label that is not contained by a frame.
Using the text capture, I can get and save the text of the label inside the frame. Using the image capture, I can get and save the an image of the label on the form, outside the frame.
I also created an exe that has two different frames with a label in each and using the text setting on snagit, I am able to save capture and saave the label caption to a text file as ordinary text.
Snagit is set to
Text
Input: Region
Output: File
Filters: Space Formatted
These are just the default settings that came up when I opened SnagIt.
Ark:
I have been working on getting this code working on XP/2K but so far no go. It does however work on win 98.
I have been working on getting this code working on XP/2K but so far no go. It does however work on win 98.
ASKER
Ark and jkozee,
Ark's link looks like the answer, but jkozee, what happens on xp and 2k? The fact that it works on 98 is very encouraging. I will work with it and see what I can find also.
Thank you both for all your time and effort. - Bob
Ark's link looks like the answer, but jkozee, what happens on xp and 2k? The fact that it works on 98 is very encouraging. I will work with it and see what I can find also.
Thank you both for all your time and effort. - Bob
Seems this is due to different heap organization in 9x/NT. IMHO there can be another way for both platforms - scaning process memory for ControlProgID("VB.Label"), and then get controlname (offset 16) and controlcaption(offset 88). I'll try this a bit later.
ASKER
I've pasted the code from Ark's link, into a vb form and module, but don't understand how to run it. Do I call one of the vb functions, or? They all seem to require parameters and I don't know how to enter them.
ASKER
Ark,
Have you not had a chance to try your idea of scanning process memory, or did it just not work? I am encouraged by your apparent knowledge of using the api and hope you can help me get this working on win2k.
Have you not had a chance to try your idea of scanning process memory, or did it just not work? I am encouraged by your apparent knowledge of using the api and hope you can help me get this working on win2k.
BobLamberson and ARK,
IMHO the thread referenced will not work on Win 2k/XP. When I happened on this question (I'm new here), I knew that I had some code in my asrenal to accomplish this, but I couldn't lay my hands on it immediately. I had an OCX which did exactly what was requested given a PID, hence my original questions about how the caller would request the label info. I have since found my code, which is very similar to the proposed solution, and probably originated from it. After trying to port it to Win XP, I strongly feel that it will not work.
There are a couple of problems. Even if you account for any heap differences, the calls to ToolHelp functions in NT are unreliable at best. There are a couple of notes from MS regarding a memory leaks when calling it from Win2k, and from my tests it is safe to assume that the same holds true in XP. Also, when trying to walk the Heap in 2k/XP it is most likely that you will enter an endless loop, which is never acceptable.
The only viable solution is to accomplish this is to abandon this and revert to reading the process memory directly. Unfortunately, this is very cumbersome and doesn't make for easy coding, but it is still possible. Without the HeapEntries of ToolHelp, you will have a lot of work to do.
I will post an example shortly that should work in both Win98, XP, 2k, etc.....
Good luck!!!!!!!!
IMHO the thread referenced will not work on Win 2k/XP. When I happened on this question (I'm new here), I knew that I had some code in my asrenal to accomplish this, but I couldn't lay my hands on it immediately. I had an OCX which did exactly what was requested given a PID, hence my original questions about how the caller would request the label info. I have since found my code, which is very similar to the proposed solution, and probably originated from it. After trying to port it to Win XP, I strongly feel that it will not work.
There are a couple of problems. Even if you account for any heap differences, the calls to ToolHelp functions in NT are unreliable at best. There are a couple of notes from MS regarding a memory leaks when calling it from Win2k, and from my tests it is safe to assume that the same holds true in XP. Also, when trying to walk the Heap in 2k/XP it is most likely that you will enter an endless loop, which is never acceptable.
The only viable solution is to accomplish this is to abandon this and revert to reading the process memory directly. Unfortunately, this is very cumbersome and doesn't make for easy coding, but it is still possible. Without the HeapEntries of ToolHelp, you will have a lot of work to do.
I will post an example shortly that should work in both Win98, XP, 2k, etc.....
Good luck!!!!!!!!
BobLamberson,
Back to my oringinal question. How will you be coming after the info, PID, hwnd, exe name????
Back to my oringinal question. How will you be coming after the info, PID, hwnd, exe name????
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
I have confirmed that this code works (for me) on 98/ME/2K/XP. Let me know if you have any problems.
ASKER
jkozee,
Greatly appreciate the time and effort you have put in here. I will work with this today and let you know the result.
Greatly appreciate the time and effort you have put in here. I will work with this today and let you know the result.
ASKER
jkozee,
I get an error on this line
'A couple of sanity checks, just to be safe
If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
saying that MEM_PRIVATE is an undefined variable. I assume it is a type that should be defined in the declarations - right?
I get an error on this line
'A couple of sanity checks, just to be safe
If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
saying that MEM_PRIVATE is an undefined variable. I assume it is a type that should be defined in the declarations - right?
ASKER
john,
This is exactly what I needed. The errors in the sanity checks if statement are the only glitch. It seems to work fine if I just comment out that statement.
1. If I remove MEM_PRIVATE, I then get the var undefined for MEM_COMMIT.
2. I commented out the entire if stmt
'A couple of sanity checks, just to be safe
' If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
' MsgBox "Unexpected Heap Type, State, or Size."
' GoTo Finished
' End If
then it appears to work perfectly!!!
Is this something that can be easily remedied or can it just be left out without compromise?
Also, how difficult would it be to identify which label the caption is for, by the name of the label (or some other means). Like if we have two labels on a form, one is named lblVehicleNumber with a caption of 3554611243, and the second is named lblMilage with a caption of 12448?
Thanks, Bob
This is exactly what I needed. The errors in the sanity checks if statement are the only glitch. It seems to work fine if I just comment out that statement.
1. If I remove MEM_PRIVATE, I then get the var undefined for MEM_COMMIT.
2. I commented out the entire if stmt
'A couple of sanity checks, just to be safe
' If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
' MsgBox "Unexpected Heap Type, State, or Size."
' GoTo Finished
' End If
then it appears to work perfectly!!!
Is this something that can be easily remedied or can it just be left out without compromise?
Also, how difficult would it be to identify which label the caption is for, by the name of the label (or some other means). Like if we have two labels on a form, one is named lblVehicleNumber with a caption of 3554611243, and the second is named lblMilage with a caption of 12448?
Thanks, Bob
ASKER
jkozee,
> Back to my oringinal question. How will you be coming after the info,
> PID, hwnd, exe name????
Sorry I missed this question along the way, but you guessed right using hwnd. What I am doing is using EnumWindows(AddressOf enumTop, 0)
and my function
Public Function enumTop(ByVal lhwnd As Long, ByVal lParam As Long) As Long
If lParam = 0 Then 'enum windows
Dim retval As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
retval = GetClassName(lhwnd, WinClassBuf, 255)
WinClass = Left(WinClassBuf, Len("ThunderRT6FormDC")) ' remove extra Nulls & spaces
retval = GetWindowText(lhwnd, WinTitleBuf, 255)
WinTitle = Left(WinTitleBuf, Len("Capture From"))
If WinClass = "ThunderRT6FormDC" And WinTitle = "Capture From" Then
thisHandle = lhwnd
Debug.Print "winclass is " & WinClass
Debug.Print "winTitle at top level " & WinTitle
Dim si As Form
Dim d As Integer
Dim addr As String
End If
enumTop = 1
End If
End Function
to find a window with the text name I need and getting the hwnd from that.
> Back to my oringinal question. How will you be coming after the info,
> PID, hwnd, exe name????
Sorry I missed this question along the way, but you guessed right using hwnd. What I am doing is using EnumWindows(AddressOf enumTop, 0)
and my function
Public Function enumTop(ByVal lhwnd As Long, ByVal lParam As Long) As Long
If lParam = 0 Then 'enum windows
Dim retval As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
retval = GetClassName(lhwnd, WinClassBuf, 255)
WinClass = Left(WinClassBuf, Len("ThunderRT6FormDC")) ' remove extra Nulls & spaces
retval = GetWindowText(lhwnd, WinTitleBuf, 255)
WinTitle = Left(WinTitleBuf, Len("Capture From"))
If WinClass = "ThunderRT6FormDC" And WinTitle = "Capture From" Then
thisHandle = lhwnd
Debug.Print "winclass is " & WinClass
Debug.Print "winTitle at top level " & WinTitle
Dim si As Form
Dim d As Integer
Dim addr As String
End If
enumTop = 1
End If
End Function
to find a window with the text name I need and getting the hwnd from that.
It looks like I missed these to constants in the decalration. This should clear up the error you received.
Private Const MEM_PRIVATE = &H20000
Private Const MEM_COMMIT = &H1000
As for gettiing the name, there is a pointer to it but it is not in the same heap, so you'll have to read in a new process memory from the running EXE. When I get a few minutes I'll post the code, if you haven't already figured it out. BTW, the pointer to the Name can be found at Offset 4 of the location pointed to from Offset 60 of the Label Object (lpLocalObjPtr). Also, keep in mind that if the EXE has been obfuscated then the label may have no meaningful name.
Private Const MEM_PRIVATE = &H20000
Private Const MEM_COMMIT = &H1000
As for gettiing the name, there is a pointer to it but it is not in the same heap, so you'll have to read in a new process memory from the running EXE. When I get a few minutes I'll post the code, if you haven't already figured it out. BTW, the pointer to the Name can be found at Offset 4 of the location pointed to from Offset 60 of the Label Object (lpLocalObjPtr). Also, keep in mind that if the EXE has been obfuscated then the label may have no meaningful name.
ASKER
Great. the two constants did the trick. I'll try to get it figured out but would appreciate your back up with the real thing.
Thanks again for all your effort.
Here are my changes. I change the local buffer boundaries to match the heap, so here's the entire code. You should now be able to get the caption and name.
Option Explicit
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Const VBM_WINDOWTITLEADDR = &H1091
Private Declare Function SendMessage Lib "USER32.DLL" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, wParam As Any, lParam As Any) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal blnheritHandle As Long, ByVal dwAppProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Const PROCESS_VM_READ = (&H10)
Private Const PROCESS_VM_WRITE = (&H20)
Private Const PROCESS_VM_OPERATION = (&H8)
Private Const PROCESS_QUERY_INFORMATION = (&H400)
Private Const PROCESS_READ_WRITE_QUERY = PROCESS_VM_READ + PROCESS_VM_WRITE + PROCESS_VM_OPERATION + PROCESS_QUERY_INFORMATION
Private Const MEM_PRIVATE = &H20000
Private Const MEM_COMMIT = &H1000
Private Type MEMORY_BASIC_INFORMATION ' 28 bytes
BaseAddress As Long
AllocationBase As Long
AllocationProtect As Long
RegionSize As Long
State As Long
Protect As Long
lType As Long
End Type
Private Declare Function VirtualQueryEx& Lib "kernel32" (ByVal hProcess As Long, lpAddress As Any, lpBuffer As MEMORY_BASIC_INFORMATION, ByVal dwLength As Long)
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private abBuffer() As Byte 'Heap Buffer
Private hProcess As Long
Private lBaseAddress As Long
Public Sub GetFormLabels(hwnd As Long)
Dim sClass As String
Dim lRet As Long
Dim pid As Long
Dim lFormCaptionHeapAddress As Long
Dim lpMem As Long
Dim lLenMBI As Long
Dim lBytesRead As Long
Dim mbi As MEMORY_BASIC_INFORMATION
'Make sure we are working with a VB Form hWnd
sClass = Space(256)
lRet = GetClassName(hwnd, sClass, 255)
sClass = Left(sClass, lRet)
If Not sClass = "ThunderRT6FormDC" Then
MsgBox "This function only works on VB RunTime 6 Forms ThunderFormRT6DC"
Exit Sub
End If
'Now get the internal heap address of the form caption. All that we need can be found in this heap (hopefully!)
'This is done with a little undocumented SendMessage magic
lFormCaptionHeapAddress = SendMessage(hwnd, VBM_WINDOWTITLEADDR, ByVal 0&, ByVal 0&)
'Get a handle on the process with required access
lRet = GetWindowThreadProcessId(h wnd, pid)
If pid = 0 Then
MsgBox "Unable to determine pid of this hwnd."
Exit Sub
End If
hProcess = OpenProcess(PROCESS_READ_W RITE_QUERY , False, pid)
'Get the Heap at the caption point
lLenMBI = Len(mbi)
lpMem = lFormCaptionHeapAddress
mbi.AllocationBase = lpMem
mbi.BaseAddress = lpMem
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then GoTo Finished
'Now go back and get the entire heap
lBaseAddress = mbi.AllocationBase
lpMem = lBaseAddress
mbi.BaseAddress = lBaseAddress
mbi.RegionSize = 0
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then GoTo Finished
'A couple of sanity checks, just to be safe
If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
MsgBox "Unexpected Heap Type, State, or Size."
GoTo Finished
End If
'Allocate a buffer and read it in
ReDim abBuffer(mbi.AllocationBas e To mbi.AllocationBase + mbi.RegionSize - 1)
ReadProcessMemory hProcess, ByVal mbi.BaseAddress, abBuffer(LBound(abBuffer)) , mbi.RegionSize, lBytesRead
'So far, so good. Things get messy from here. We have to
'do some manual parsing of the buffer to get what we are after. To
'make things easier, I'll will get every label on every form in the
'exe. Otherwise, you will need to first find the form that is
'reference the caption. Then find every label between it and the next
'form.
Dim iCnt As Integer
Dim al() As Long
'Print all of the label captions
If EnumVBObjectPtrs("VB.Label ", 44, al) > 0 Then
For iCnt = LBound(al) To UBound(al)
Debug.Print "Hit at: " & Hex(al(iCnt) + 44), "Object At: " & Hex(al(iCnt)), GetLabelCaption(al(iCnt)), GetLabelName(al(iCnt))
Next iCnt
End If
Finished:
CloseHandle hProcess
abBuffer() = ""
End Sub
Private Function GetLabelName(lpObjPtr As Long) As String
Dim lpMem As Long
Dim lLenMBI As Long
Dim lBytesRead As Long
Dim mbi As MEMORY_BASIC_INFORMATION
Dim lRet As Long
Dim ab() As Byte
Dim lStrPtr As Long
Dim lInfoPtr As Long
'Get the local pointer to object info
CopyMemory lInfoPtr, abBuffer(lpObjPtr + 60), 4
'Get the pointer to label name
CopyMemory lStrPtr, abBuffer(lInfoPtr + 4), 4
'Get the EXE at the name point
lLenMBI = Len(mbi)
lpMem = lStrPtr
mbi.AllocationBase = lpMem
mbi.BaseAddress = lpMem
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then Exit Function
'Read in the EXE Heap
ReDim ab(0 To mbi.RegionSize - 1)
ReadProcessMemory hProcess, ByVal mbi.BaseAddress, ab(LBound(ab)), mbi.RegionSize, lBytesRead
GetLabelName = StrConv(MidB(ab, lStrPtr - mbi.BaseAddress + 1, 260), vbUnicode)
GetLabelName = Left$(GetLabelName, InStr(GetLabelName, vbNullChar) - 1)
End Function
Private Function GetLabelCaption(lpObjPtr As Long) As String
Dim lStrPtr As Long
'Get local pointer to caption
CopyMemory lStrPtr, abBuffer(lpObjPtr + 136), 4
'Get caption
If lStrPtr <> 0 Then
GetLabelCaption = StrConv(MidB(abBuffer, lStrPtr - lBaseAddress + 1, 260), vbUnicode)
End If
GetLabelCaption = Left$(GetLabelCaption, InStr(GetLabelCaption, vbNullChar) - 1)
End Function
'This function will search the buffer for a given VBObjectIDString, then
'find the start of that control by searching for a refence to it in the 600
'bytes prior.
'It then finds any object of that type by searching the buffer for any
'references to the Heap Location of that control, and adds it to the enumeration
'if the reference hit position is at the correct offset (pos-offset = lBaseAddress)
'setting the EnumObj entry to the start location (local buffer address) and
'returns the counrt
Private Function EnumVBObjectPtrs(VBObjectI DString As String, _
lOffset As Long, _
EnumObj() As Long) As Integer
Dim abObjectPtr(0 To 3) As Byte 'LittleEndian byte array of the Heap Address of the VBObject
Dim abBaseAddress(0 To 3) As Byte 'LittleEndian byte array of the Heap Base Memory Address
Dim abLong(0 To 3) As Byte 'Byte array for ptr manipulation
Dim lPtr As Long 'Buffer pointer for search hits
Dim iCnt As Integer
Dim alRet() As Long
'Find the location of the VBObjectIDString string
lPtr = InStrB(1, abBuffer, StrConv(VBObjectIDString, vbFromUnicode)) - 1
If lPtr = -1 Then Exit Function
lPtr = lBaseAddress + lPtr
'We now need to find the location that points to the start of the object
'which should be 244 bytes prior (on XP at least) we go back 600 just in
'case. This is at offset 36, so we'll need to adjust back to the beginning
'of the object
CopyMemory abLong(0), lPtr, 4
lPtr = InStrB(lPtr - lBaseAddress - 600, abBuffer, abLong) - 1
If lPtr = -1 Then Exit Function
lPtr = lPtr + lBaseAddress - 36 'Adjust back to the beginning of the object
CopyMemory abObjectPtr(0), lPtr, 4
'Turn the lBaseAddress into LittleEndian byte array for searching
CopyMemory abBaseAddress(0), lBaseAddress, 4
'Loop through the buffer
lPtr = 1
Do Until lPtr = 0
'Find a reference to this object
lPtr = InStrB(lPtr, abBuffer, abObjectPtr)
If lPtr > 0 Then
'make sure that this is really a VB object
'move back from the offset of the object
'and make sure that it has the correct base memory value
If InStrB(lPtr - lOffset - 1, abBuffer, abBaseAddress) = lPtr - lOffset Then
ReDim Preserve alRet(0 To iCnt)
alRet(iCnt) = lPtr + lBaseAddress - lOffset - 1
iCnt = iCnt + 1
End If
'Keep searching from the next byte
lPtr = lPtr + 1
End If
Loop
EnumVBObjectPtrs = iCnt
EnumObj = alRet
End Function
Option Explicit
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Const VBM_WINDOWTITLEADDR = &H1091
Private Declare Function SendMessage Lib "USER32.DLL" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, wParam As Any, lParam As Any) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal blnheritHandle As Long, ByVal dwAppProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Const PROCESS_VM_READ = (&H10)
Private Const PROCESS_VM_WRITE = (&H20)
Private Const PROCESS_VM_OPERATION = (&H8)
Private Const PROCESS_QUERY_INFORMATION = (&H400)
Private Const PROCESS_READ_WRITE_QUERY = PROCESS_VM_READ + PROCESS_VM_WRITE + PROCESS_VM_OPERATION + PROCESS_QUERY_INFORMATION
Private Const MEM_PRIVATE = &H20000
Private Const MEM_COMMIT = &H1000
Private Type MEMORY_BASIC_INFORMATION ' 28 bytes
BaseAddress As Long
AllocationBase As Long
AllocationProtect As Long
RegionSize As Long
State As Long
Protect As Long
lType As Long
End Type
Private Declare Function VirtualQueryEx& Lib "kernel32" (ByVal hProcess As Long, lpAddress As Any, lpBuffer As MEMORY_BASIC_INFORMATION, ByVal dwLength As Long)
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private abBuffer() As Byte 'Heap Buffer
Private hProcess As Long
Private lBaseAddress As Long
Public Sub GetFormLabels(hwnd As Long)
Dim sClass As String
Dim lRet As Long
Dim pid As Long
Dim lFormCaptionHeapAddress As Long
Dim lpMem As Long
Dim lLenMBI As Long
Dim lBytesRead As Long
Dim mbi As MEMORY_BASIC_INFORMATION
'Make sure we are working with a VB Form hWnd
sClass = Space(256)
lRet = GetClassName(hwnd, sClass, 255)
sClass = Left(sClass, lRet)
If Not sClass = "ThunderRT6FormDC" Then
MsgBox "This function only works on VB RunTime 6 Forms ThunderFormRT6DC"
Exit Sub
End If
'Now get the internal heap address of the form caption. All that we need can be found in this heap (hopefully!)
'This is done with a little undocumented SendMessage magic
lFormCaptionHeapAddress = SendMessage(hwnd, VBM_WINDOWTITLEADDR, ByVal 0&, ByVal 0&)
'Get a handle on the process with required access
lRet = GetWindowThreadProcessId(h
If pid = 0 Then
MsgBox "Unable to determine pid of this hwnd."
Exit Sub
End If
hProcess = OpenProcess(PROCESS_READ_W
'Get the Heap at the caption point
lLenMBI = Len(mbi)
lpMem = lFormCaptionHeapAddress
mbi.AllocationBase = lpMem
mbi.BaseAddress = lpMem
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then GoTo Finished
'Now go back and get the entire heap
lBaseAddress = mbi.AllocationBase
lpMem = lBaseAddress
mbi.BaseAddress = lBaseAddress
mbi.RegionSize = 0
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then GoTo Finished
'A couple of sanity checks, just to be safe
If Not ((mbi.lType = MEM_PRIVATE) And (mbi.State = MEM_COMMIT) And mbi.RegionSize > 0) Then
MsgBox "Unexpected Heap Type, State, or Size."
GoTo Finished
End If
'Allocate a buffer and read it in
ReDim abBuffer(mbi.AllocationBas
ReadProcessMemory hProcess, ByVal mbi.BaseAddress, abBuffer(LBound(abBuffer))
'So far, so good. Things get messy from here. We have to
'do some manual parsing of the buffer to get what we are after. To
'make things easier, I'll will get every label on every form in the
'exe. Otherwise, you will need to first find the form that is
'reference the caption. Then find every label between it and the next
'form.
Dim iCnt As Integer
Dim al() As Long
'Print all of the label captions
If EnumVBObjectPtrs("VB.Label
For iCnt = LBound(al) To UBound(al)
Debug.Print "Hit at: " & Hex(al(iCnt) + 44), "Object At: " & Hex(al(iCnt)), GetLabelCaption(al(iCnt)),
Next iCnt
End If
Finished:
CloseHandle hProcess
abBuffer() = ""
End Sub
Private Function GetLabelName(lpObjPtr As Long) As String
Dim lpMem As Long
Dim lLenMBI As Long
Dim lBytesRead As Long
Dim mbi As MEMORY_BASIC_INFORMATION
Dim lRet As Long
Dim ab() As Byte
Dim lStrPtr As Long
Dim lInfoPtr As Long
'Get the local pointer to object info
CopyMemory lInfoPtr, abBuffer(lpObjPtr + 60), 4
'Get the pointer to label name
CopyMemory lStrPtr, abBuffer(lInfoPtr + 4), 4
'Get the EXE at the name point
lLenMBI = Len(mbi)
lpMem = lStrPtr
mbi.AllocationBase = lpMem
mbi.BaseAddress = lpMem
lRet = VirtualQueryEx(hProcess, ByVal lpMem, mbi, lLenMBI)
If lRet <> lLenMBI Then Exit Function
'Read in the EXE Heap
ReDim ab(0 To mbi.RegionSize - 1)
ReadProcessMemory hProcess, ByVal mbi.BaseAddress, ab(LBound(ab)), mbi.RegionSize, lBytesRead
GetLabelName = StrConv(MidB(ab, lStrPtr - mbi.BaseAddress + 1, 260), vbUnicode)
GetLabelName = Left$(GetLabelName, InStr(GetLabelName, vbNullChar) - 1)
End Function
Private Function GetLabelCaption(lpObjPtr As Long) As String
Dim lStrPtr As Long
'Get local pointer to caption
CopyMemory lStrPtr, abBuffer(lpObjPtr + 136), 4
'Get caption
If lStrPtr <> 0 Then
GetLabelCaption = StrConv(MidB(abBuffer, lStrPtr - lBaseAddress + 1, 260), vbUnicode)
End If
GetLabelCaption = Left$(GetLabelCaption, InStr(GetLabelCaption, vbNullChar) - 1)
End Function
'This function will search the buffer for a given VBObjectIDString, then
'find the start of that control by searching for a refence to it in the 600
'bytes prior.
'It then finds any object of that type by searching the buffer for any
'references to the Heap Location of that control, and adds it to the enumeration
'if the reference hit position is at the correct offset (pos-offset = lBaseAddress)
'setting the EnumObj entry to the start location (local buffer address) and
'returns the counrt
Private Function EnumVBObjectPtrs(VBObjectI
lOffset As Long, _
EnumObj() As Long) As Integer
Dim abObjectPtr(0 To 3) As Byte 'LittleEndian byte array of the Heap Address of the VBObject
Dim abBaseAddress(0 To 3) As Byte 'LittleEndian byte array of the Heap Base Memory Address
Dim abLong(0 To 3) As Byte 'Byte array for ptr manipulation
Dim lPtr As Long 'Buffer pointer for search hits
Dim iCnt As Integer
Dim alRet() As Long
'Find the location of the VBObjectIDString string
lPtr = InStrB(1, abBuffer, StrConv(VBObjectIDString, vbFromUnicode)) - 1
If lPtr = -1 Then Exit Function
lPtr = lBaseAddress + lPtr
'We now need to find the location that points to the start of the object
'which should be 244 bytes prior (on XP at least) we go back 600 just in
'case. This is at offset 36, so we'll need to adjust back to the beginning
'of the object
CopyMemory abLong(0), lPtr, 4
lPtr = InStrB(lPtr - lBaseAddress - 600, abBuffer, abLong) - 1
If lPtr = -1 Then Exit Function
lPtr = lPtr + lBaseAddress - 36 'Adjust back to the beginning of the object
CopyMemory abObjectPtr(0), lPtr, 4
'Turn the lBaseAddress into LittleEndian byte array for searching
CopyMemory abBaseAddress(0), lBaseAddress, 4
'Loop through the buffer
lPtr = 1
Do Until lPtr = 0
'Find a reference to this object
lPtr = InStrB(lPtr, abBuffer, abObjectPtr)
If lPtr > 0 Then
'make sure that this is really a VB object
'move back from the offset of the object
'and make sure that it has the correct base memory value
If InStrB(lPtr - lOffset - 1, abBuffer, abBaseAddress) = lPtr - lOffset Then
ReDim Preserve alRet(0 To iCnt)
alRet(iCnt) = lPtr + lBaseAddress - lOffset - 1
iCnt = iCnt + 1
End If
'Keep searching from the next byte
lPtr = lPtr + 1
End If
Loop
EnumVBObjectPtrs = iCnt
EnumObj = alRet
End Function
ASKER
John,
The revisions work exactly. I will link with some code and do some tweaking to allow a user to save caption of a specific label, but you have done all the difficult part for me. This will also give me a very good basis to learn/understand some of the indepth workings of windows and the api. Thanks again. Bob
The revisions work exactly. I will link with some code and do some tweaking to allow a user to save caption of a specific label, but you have done all the difficult part for me. This will also give me a very good basis to learn/understand some of the indepth workings of windows and the api. Thanks again. Bob
hi jkozee,
I tried to use the same source code which you have provided in this question for finding the label caption. but the string search is failing in the EnumVBObjectPtrs function
lPtr = InStrB(1, abBuffer, StrConv(VBObjectIDString, vbFromUnicode)) - 1
If lPtr = -1 Then Exit Function
always getting return as -1 what could be the reason ??
if you can give me a solution within 12 hrs I'll give u 500 more points
Thanks & regards
Jobs
I tried to use the same source code which you have provided in this question for finding the label caption. but the string search is failing in the EnumVBObjectPtrs function
lPtr = InStrB(1, abBuffer, StrConv(VBObjectIDString, vbFromUnicode)) - 1
If lPtr = -1 Then Exit Function
always getting return as -1 what could be the reason ??
if you can give me a solution within 12 hrs I'll give u 500 more points
Thanks & regards
Jobs
Jobs,
What OS are you running on?
Are you trying to get the lable from a compiled VB exe?
John
What OS are you running on?
Are you trying to get the lable from a compiled VB exe?
John
hi,
I'm using win2k but I'm able to read from serveral other labels from other exes. but I don't know why i'm not getting the label from the required exe. Its having lot of custom controls and its a huge exe :(.
Failing during the vb.Label searh in the memory as i mentioned in the previous comment.
waiting for reply
Thanks
Jobs
I'm using win2k but I'm able to read from serveral other labels from other exes. but I don't know why i'm not getting the label from the required exe. Its having lot of custom controls and its a huge exe :(.
Failing during the vb.Label searh in the memory as i mentioned in the previous comment.
waiting for reply
Thanks
Jobs
When running this program I notice that it requries the form be a: ThunderFormRT6DC
Im needing a very similar solution (got this one to work on a differnet app)
but the form type is: ThunderRT6Main
Is there a way to do this ?
Im needing a very similar solution (got this one to work on a differnet app)
but the form type is: ThunderRT6Main
Is there a way to do this ?
ASKER
Raven65
I'm not positive, but I think the following is just checking to be sure it is a VB form and could be any form class in VB that contains labels. Try changing it to ThunderRT6Main and see if that works. Maybe jkozee is around and could comment on this.
> 'Make sure we are working with a VB Form hWnd
> sClass = Space(256)
> lRet = GetClassName(hwnd, sClass, 255)
> sClass = Left(sClass, lRet)
> If Not sClass = "ThunderRT6FormDC" Then
> MsgBox "This function only works on VB RunTime 6 Forms ThunderFormRT6DC"
> Exit Sub
> End If
Bob
I'm not positive, but I think the following is just checking to be sure it is a VB form and could be any form class in VB that contains labels. Try changing it to ThunderRT6Main and see if that works. Maybe jkozee is around and could comment on this.
> 'Make sure we are working with a VB Form hWnd
> sClass = Space(256)
> lRet = GetClassName(hwnd, sClass, 255)
> sClass = Left(sClass, lRet)
> If Not sClass = "ThunderRT6FormDC" Then
> MsgBox "This function only works on VB RunTime 6 Forms ThunderFormRT6DC"
> Exit Sub
> End If
Bob
Thanks Bob, that seems to have gotten me past problem 1.
Problem2: (Always another one..)
Turns out I need to be reading the text boxes on my target application. I can get the labels, their
names and captions, but I need to get the content of several text boxes (and their names/labels if possible). Has any one worked with this code to achieve that goal ? I see a few areas in it that give me hope but I am not the greatest vb programmer and need a little guidance here..
Thanks again,
chrisj
Problem2: (Always another one..)
Turns out I need to be reading the text boxes on my target application. I can get the labels, their
names and captions, but I need to get the content of several text boxes (and their names/labels if possible). Has any one worked with this code to achieve that goal ? I see a few areas in it that give me hope but I am not the greatest vb programmer and need a little guidance here..
Thanks again,
chrisj
ASKER
You can find the properties of the text boxes by just using a for each loop on the Form.Controls collection until you find the control you want. Just create a seperate function to do that part.
Bob
Bob
Bob,
Thanks for the quick response. Im starting to look at the information you gave there but in the mean time:
Since the app in question is external, can I use this Form.Controls to do this ? (I don't necessarily know the number of items, or their names on the form)
Can you post an example of this code ?
Thanks again for helping a rising coder,
chrisj
Thanks for the quick response. Im starting to look at the information you gave there but in the mean time:
Since the app in question is external, can I use this Form.Controls to do this ? (I don't necessarily know the number of items, or their names on the form)
Can you post an example of this code ?
Thanks again for helping a rising coder,
chrisj
ASKER
Sorry - you are right, I was a little quick on the trigger there. Since it is a seperate app you would have to use the Win API, but you can work with the window handles and properties instead of having to get into the memory stuff.
I have some code that will probably help you but can't lay my hands on it right now. Will get back to you in a day or two if you haven't gotten your answer by then. Meanwhile take a look at
Declare Function EnumWindows Lib "user32.dll" ( _
ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Declare Function EnumChildWindows Lib "user32.dll" ( _
ByVal hWndParent As Long, _
ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
and the API info http://www.winhelp-world.com/winapi_3.htm
and especially the APIViewer at http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html - it is a must.
Bob
I have some code that will probably help you but can't lay my hands on it right now. Will get back to you in a day or two if you haven't gotten your answer by then. Meanwhile take a look at
Declare Function EnumWindows Lib "user32.dll" ( _
ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Declare Function EnumChildWindows Lib "user32.dll" ( _
ByVal hWndParent As Long, _
ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
and the API info http://www.winhelp-world.com/winapi_3.htm
and especially the APIViewer at http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html - it is a must.
Bob
I will be looking at this in the mean time, but I would love to see that code if you can get ahold on it.
(Plus is would be a great addition to this thread anyway.)
Thanks for all your help Bob,
-chrisj
(Plus is would be a great addition to this thread anyway.)
Thanks for all your help Bob,
-chrisj
Bob, just an update here. Made a lot of progress with those hints you gave and have been able to get some data off the page in App2 (external).
There are a few text boxes on the page, and those are what I want but cannot get (so far I have been able to get labels and button captions.
I have been able to find some help using " FindWindowEx" to get what I think is the textboxes handle, and this would work for me I think, if I can use that handle, to get the content of the box.
Any help in this direction would be great,
Just to recap. New App1 needs to get data from inside textboxes in external App2 (no access to source, or any ability to change anything about App2)
Thanks a ton,
chrisj
There are a few text boxes on the page, and those are what I want but cannot get (so far I have been able to get labels and button captions.
I have been able to find some help using " FindWindowEx" to get what I think is the textboxes handle, and this would work for me I think, if I can use that handle, to get the content of the box.
Any help in this direction would be great,
Just to recap. New App1 needs to get data from inside textboxes in external App2 (no access to source, or any ability to change anything about App2)
Thanks a ton,
chrisj
ASKER
Chris,
Try the following;
In a new vbp add a couple of text boxes to a form, name it "Capture From" and compile the app. Run the exe.
In a second new app add a command button to the form and add this code to the click event of the button;
Private Sub cmdCaptureId_Click()
Dim lRet As Long
Dim lParam As Long
'enumerate the list
lRet = EnumWindows(AddressOf EnumWinProc, lParam)
End Sub
Add a module to this second app and paste this code into it.
Option Explicit
Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent _
As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hwnd As Long, ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, _
ByVal cch As Long) As Long
Public Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function SetActiveWindow Lib "user32" (ByVal hwnd As Long) As Long
Public f As Form
Private y As Integer
Private stextName As String
Private sPatientID As String
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public Declare Function GetForegroundWindow Lib "user32" () As Long
Function EnumWinProc(ByVal lhWnd As Long, ByVal lParam As Long) _
As Long
Dim RetVal As Long, ProcessID As Long, ThreadID As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
RetVal = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls(WinClassBuf) ' remove extra Nulls & spaces
RetVal = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls(WinTitleBuf)
Debug.Print "Top level Class = "; WinClass; ", Title = "; WinTitle
If WinClass = "ThunderRT6FormDC" And WinTitle = "Capture From" Then
RetVal = EnumChildWindows(lhWnd, AddressOf EnumChildProc, lParam)
EnumWinProc = False
Else
EnumWinProc = True
End If
End Function
'loop the forms control collection to find the control that has string patientidnumber in name and a caption property.
Function getID(f As Form)
Do While y < f.Controls.Count
stextName = f.Controls(y).Name
On Error Resume Next 'if not a label it will error on caption and skip it
If InStr(1, stextName, "lblPatientIDNumber") > 0 Then 'if not the patient id it will skip it
getID = f.Controls(y).Caption
frmTestCallingCapture.txtC allingCapt ure.Text = f.Controls(y).Caption
y = 0
Exit Do
End If
y = y + 1
Loop
On Error GoTo 0
End Function
Function EnumChildProc(ByVal lhWnd As Long, ByVal lParam As Long) _
As Long
Dim RetVal As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
RetVal = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls(WinClassBuf) ' remove extra Nulls & spaces
RetVal = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls(WinTitleBuf)
' see the Windows Class and Title for each Child Window enumerated
Debug.Print " Child Class = "; WinClass; ", Title = "; WinTitle
' You can find any type of Window by searching for its WinClass
' If WinTitle = "lblPatientIdCaption" Then
If WinTitle = "txtPatientId" Then
frmTestCallingCapture.txtC allingCapt ure.Text = WinTitle
EnumChildProc = False
Else
EnumChildProc = True
End If
End Function
Public Function StripNulls(OriginalStr As String) As String
' This removes the extra Nulls so String comparisons will work
If (InStr(OriginalStr, Chr(0)) > 0) Then
OriginalStr = Left(OriginalStr, InStr(OriginalStr, Chr(0)) - 1)
End If
StripNulls = OriginalStr
End Function
This will enum through all the top level windows you have open until it finds the one you want to see the child windows for (controls on it). In ThunderRT6FormDC as the triggering class, but you could use anything. Debug.print lists all the top level windows, then the child windows for the selected class in the immediate window. By working through this you should be able to come up with the right combination of top level window and child window for your purposes.
Also remember that classes have different class names in compiled exe and in .vbp when run, for example;
ThunderFormDC is the class of a form in uncompiled .vbp
ThunderRT6FormDC is the same class of a form in compiled exe
Hope this is helpful. If you get stuck, post again and we can go from there.
Bob
Try the following;
In a new vbp add a couple of text boxes to a form, name it "Capture From" and compile the app. Run the exe.
In a second new app add a command button to the form and add this code to the click event of the button;
Private Sub cmdCaptureId_Click()
Dim lRet As Long
Dim lParam As Long
'enumerate the list
lRet = EnumWindows(AddressOf EnumWinProc, lParam)
End Sub
Add a module to this second app and paste this code into it.
Option Explicit
Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent _
As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hwnd As Long, ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, _
ByVal cch As Long) As Long
Public Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function SetActiveWindow Lib "user32" (ByVal hwnd As Long) As Long
Public f As Form
Private y As Integer
Private stextName As String
Private sPatientID As String
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public Declare Function GetForegroundWindow Lib "user32" () As Long
Function EnumWinProc(ByVal lhWnd As Long, ByVal lParam As Long) _
As Long
Dim RetVal As Long, ProcessID As Long, ThreadID As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
RetVal = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls(WinClassBuf) ' remove extra Nulls & spaces
RetVal = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls(WinTitleBuf)
Debug.Print "Top level Class = "; WinClass; ", Title = "; WinTitle
If WinClass = "ThunderRT6FormDC" And WinTitle = "Capture From" Then
RetVal = EnumChildWindows(lhWnd, AddressOf EnumChildProc, lParam)
EnumWinProc = False
Else
EnumWinProc = True
End If
End Function
'loop the forms control collection to find the control that has string patientidnumber in name and a caption property.
Function getID(f As Form)
Do While y < f.Controls.Count
stextName = f.Controls(y).Name
On Error Resume Next 'if not a label it will error on caption and skip it
If InStr(1, stextName, "lblPatientIDNumber") > 0 Then 'if not the patient id it will skip it
getID = f.Controls(y).Caption
frmTestCallingCapture.txtC
y = 0
Exit Do
End If
y = y + 1
Loop
On Error GoTo 0
End Function
Function EnumChildProc(ByVal lhWnd As Long, ByVal lParam As Long) _
As Long
Dim RetVal As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
RetVal = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls(WinClassBuf) ' remove extra Nulls & spaces
RetVal = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls(WinTitleBuf)
' see the Windows Class and Title for each Child Window enumerated
Debug.Print " Child Class = "; WinClass; ", Title = "; WinTitle
' You can find any type of Window by searching for its WinClass
' If WinTitle = "lblPatientIdCaption" Then
If WinTitle = "txtPatientId" Then
frmTestCallingCapture.txtC
EnumChildProc = False
Else
EnumChildProc = True
End If
End Function
Public Function StripNulls(OriginalStr As String) As String
' This removes the extra Nulls so String comparisons will work
If (InStr(OriginalStr, Chr(0)) > 0) Then
OriginalStr = Left(OriginalStr, InStr(OriginalStr, Chr(0)) - 1)
End If
StripNulls = OriginalStr
End Function
This will enum through all the top level windows you have open until it finds the one you want to see the child windows for (controls on it). In ThunderRT6FormDC as the triggering class, but you could use anything. Debug.print lists all the top level windows, then the child windows for the selected class in the immediate window. By working through this you should be able to come up with the right combination of top level window and child window for your purposes.
Also remember that classes have different class names in compiled exe and in .vbp when run, for example;
ThunderFormDC is the class of a form in uncompiled .vbp
ThunderRT6FormDC is the same class of a form in compiled exe
Hope this is helpful. If you get stuck, post again and we can go from there.
Bob
Bob,
Thanks for that code, Im working with it now but had a question after a few initial tests.
This works the way I think I will want it to in the developer, but when I try to compile it,
the line:
frmTestCallingCapture.txtC allingCapt ure.Text = f.Controls(y).Caption
Halts with frmTestCallingCapture highlighted and a variable not defined.
[The target app is not on the development machine, so I'm testing on other apps and this appears to work, but I cannot compile and find out due to this error. Im researching this item now, but thought I would ask and see if you had a quick hint.]
Thanks again for all your help,
I feel like you gotten me on the right track here,
chrisj
Thanks for that code, Im working with it now but had a question after a few initial tests.
This works the way I think I will want it to in the developer, but when I try to compile it,
the line:
frmTestCallingCapture.txtC
Halts with frmTestCallingCapture highlighted and a variable not defined.
[The target app is not on the development machine, so I'm testing on other apps and this appears to work, but I cannot compile and find out due to this error. Im researching this item now, but thought I would ask and see if you had a quick hint.]
Thanks again for all your help,
I feel like you gotten me on the right track here,
chrisj
Please ignore that last comment, I think I was trying to make that line be more complex than
it was. I just added another text box and named it with that name so I think I got that right. Sorry for the extra post, but as I said, Im still new to VB and I think I just misread that statement.
I have done some more research into my form and would like to try to lay the scinerio out for you:
The form is laid out like this:
Label1 TxtBox1
Label2 TxtBox2
Label3 TxtBox3
Button1
Button2
This is apparently NOT a true VB app, but a 'vb' app developed through a simplified designer our company uses (long story). I was able to do an EnumWindows and find the window and then I did an EnumChild on it and got the following out to a text file:
"Child Class = ","ThunderRT6FormDC",",Tit le = ","CHS Scanning Page",",Hwind = ",983584
'This appears to be the proper window for my targeting. The ThunderRT6 name makes me think I can do this even though like I said, the app is not 'really' straight from the vb developer.
"Child Class = ","AfxWnd40",",Title = ","Corporate number:",",Hwind = ",853224
"Child Class = ","AfxWnd40",",Title = ","Unit number:",",Hwind = ",1442864
"Child Class = ","AfxWnd40",",Title = ","Account number:",",Hwind = ",1377380
'These are Label1-3 I just mention. As you can see, I can retrieve their data in this manner.
"Child Class = ","ThunderRT6TextBox",",Ti tle = ","",",Hwind = ",656682
'Then there are several more of these. But as you can see, the title returns blank...In the application these textboxes are 'locked' and cannot be edited, but there is data in them.
I can also get the buttons, and their captions.
I know this is hard to diagnose without being able to sit down @ it, but I really appreciate the help. If I can get any more/better information please let me know. (Screenshots ?)
Thank you again,
chrisj
it was. I just added another text box and named it with that name so I think I got that right. Sorry for the extra post, but as I said, Im still new to VB and I think I just misread that statement.
I have done some more research into my form and would like to try to lay the scinerio out for you:
The form is laid out like this:
Label1 TxtBox1
Label2 TxtBox2
Label3 TxtBox3
Button1
Button2
This is apparently NOT a true VB app, but a 'vb' app developed through a simplified designer our company uses (long story). I was able to do an EnumWindows and find the window and then I did an EnumChild on it and got the following out to a text file:
"Child Class = ","ThunderRT6FormDC",",Tit
'This appears to be the proper window for my targeting. The ThunderRT6 name makes me think I can do this even though like I said, the app is not 'really' straight from the vb developer.
"Child Class = ","AfxWnd40",",Title = ","Corporate number:",",Hwind = ",853224
"Child Class = ","AfxWnd40",",Title = ","Unit number:",",Hwind = ",1442864
"Child Class = ","AfxWnd40",",Title = ","Account number:",",Hwind = ",1377380
'These are Label1-3 I just mention. As you can see, I can retrieve their data in this manner.
"Child Class = ","ThunderRT6TextBox",",Ti
'Then there are several more of these. But as you can see, the title returns blank...In the application these textboxes are 'locked' and cannot be edited, but there is data in them.
I can also get the buttons, and their captions.
I know this is hard to diagnose without being able to sit down @ it, but I really appreciate the help. If I can get any more/better information please let me know. (Screenshots ?)
Thank you again,
chrisj
ASKER
did you try adding the DC to the end of the ThunderRT6TextBox ?
Bob
Bob
Im not sure I follow you here... Add the DC where ?
In the EnumAll app I wrote to crawl everything (using your hints) I just have it pull whatever is there and list it for me, the ones in the target app appear to be "ThunderRT6TextBox" as you can see in that file export.
The main form is comming across as a RT6FormDC, so that should be good by the code you posted just now, as that is what it looks for now.
One very interesting observation to go with what I said earlier about the fields being "locked" or something.
I wrote an additional "spy" program so that when I mouse over a given item, it gives me its hWnd and WinClass...
When I mouse over the labels, I get the appropriate address and the "AfxWnd40" but when I then move over the textboxes the return changes to the "RT6FormDC", as if Im pointing at any given blank spot on the form..almost like the boxes aren't there (?) though I can plainly see them...
chrisj
In the EnumAll app I wrote to crawl everything (using your hints) I just have it pull whatever is there and list it for me, the ones in the target app appear to be "ThunderRT6TextBox" as you can see in that file export.
The main form is comming across as a RT6FormDC, so that should be good by the code you posted just now, as that is what it looks for now.
One very interesting observation to go with what I said earlier about the fields being "locked" or something.
I wrote an additional "spy" program so that when I mouse over a given item, it gives me its hWnd and WinClass...
When I mouse over the labels, I get the appropriate address and the "AfxWnd40" but when I then move over the textboxes the return changes to the "RT6FormDC", as if Im pointing at any given blank spot on the form..almost like the boxes aren't there (?) though I can plainly see them...
chrisj
ASKER
What I was thinking was that the class name you would find would be ThunderRT6TextBoxDC, but it appears the the app you are working with has subclassed some of the vb classes and given them different names?
What is the "AfxWnd40" in the Child Class =............. line? It appears that it's the class name.
Another thought is that your problem with the mousing over the control might be that the boxes you are looking at and thinking are text boxes are actually graphics like the labels are.
Bob
What is the "AfxWnd40" in the Child Class =............. line? It appears that it's the class name.
Another thought is that your problem with the mousing over the control might be that the boxes you are looking at and thinking are text boxes are actually graphics like the labels are.
Bob
I believe you are right that they have done something to rename the standard controls.
The AfxWnd40 appears to be what they call a label, as when I pull those, its the text
from the labels beside the text boxes I want...
Im not @ the application now, but thinking back, I believe you may be onto something
with saying those "textboxes" may really be graphics, because yes, as I mentioned mousing
over them dosen't get the identifier "textbox" in my spy application..
If that is the case, would there be a way to identify that graphic and gets its content ?
Thanks again for your dedicated help to my effort here,
chrisj
The AfxWnd40 appears to be what they call a label, as when I pull those, its the text
from the labels beside the text boxes I want...
Im not @ the application now, but thinking back, I believe you may be onto something
with saying those "textboxes" may really be graphics, because yes, as I mentioned mousing
over them dosen't get the identifier "textbox" in my spy application..
If that is the case, would there be a way to identify that graphic and gets its content ?
Thanks again for your dedicated help to my effort here,
chrisj
ASKER
That's what jkozee's code is doing in the first part of this thread by finding the memory area that holds the information. Maybe check that out and see if you can make that work with the controls you are trying to capture.
Bob, that seems to be VERY close to what I need. I have been able to take jkozee's code and your suggestions and get several pieces of information off the target app, which makes me very excited that what I want, can be done.
So far, I have been able to capture lables, and buttons on that page, which is a start (as I do need that later) but I cannot seem to get the text boxes still. I developed a second fake app, and made several textboxes on it, some with Enabled set to False. This generated the same look as the locked boxes i mentioned, so I think perhaps they are text boxes, but I still cannot get them (and using the same code, I can get the locked ones on the fake app)
Do you have a list, or know where one exists, of the names of the VB Controls (i.e. Vb.Label, Vb.Textbox) ? I have been able to get those two, as mentioned, but wanted to try the others that exist and see if my target boxes are something I've just not tried (like a graphic you had mentioned) but I cannot find a list of these things..
Thanks again for all your help,
That list, or ANY other hints/ideas would be most welcomed (your help so far is the only reason Im able to get to the point to even ask this question, so..),
chrisj
So far, I have been able to capture lables, and buttons on that page, which is a start (as I do need that later) but I cannot seem to get the text boxes still. I developed a second fake app, and made several textboxes on it, some with Enabled set to False. This generated the same look as the locked boxes i mentioned, so I think perhaps they are text boxes, but I still cannot get them (and using the same code, I can get the locked ones on the fake app)
Do you have a list, or know where one exists, of the names of the VB Controls (i.e. Vb.Label, Vb.Textbox) ? I have been able to get those two, as mentioned, but wanted to try the others that exist and see if my target boxes are something I've just not tried (like a graphic you had mentioned) but I cannot find a list of these things..
Thanks again for all your help,
That list, or ANY other hints/ideas would be most welcomed (your help so far is the only reason Im able to get to the point to even ask this question, so..),
chrisj
ASKER
If you can get a copy of Dan Appleman's "Visual Basic Programmers Guide to the Win32 API" book, you will find the list you mention and a wealth of other info that is invaluable. This book is considered the final authority on the Win32 API by most. Here's a list of the classes from the cd that comes with the book.
Visual Basic Class Names
Control Class Name Windows Base Class
Check ThunderCheckBox BUTTON
Combo ThunderComboBox COMBOBOX
Command ThunderCommandButton BUTTON
Dir ThunderDirListBox LISTBOX
Drive ThunderDriveListBox COMBOBOX
File ThunderFileListBox LISTBOX
Form ThunderForm —
Frame ThunderFrame BUTTON
Label ThunderLabel —
List ThunderListBox LISTBOX
MDIForm ThunderMDIForm —
Option ThunderOptionButton BUTTON
Picture ThunderPictureBox —
Scroll (Horiz) ThunderHScrollBar SCROLLBAR
Scroll (Vert) ThunderVScrollBar SCROLLBAR
Text ThunderTextBox EDIT
Timer ThunderTimer —
Note: Visual Basic class names shown above are for design time. At runtime VB class names have a prefix depending on the version of Visual Basic, for example: ThunderCheckBox might become ThunderRTCheckBox at runtime. You can use the GetClassName API function described in chapter 5 to determine the class name for a window.
Bob
Visual Basic Class Names
Control Class Name Windows Base Class
Check ThunderCheckBox BUTTON
Combo ThunderComboBox COMBOBOX
Command ThunderCommandButton BUTTON
Dir ThunderDirListBox LISTBOX
Drive ThunderDriveListBox COMBOBOX
File ThunderFileListBox LISTBOX
Form ThunderForm —
Frame ThunderFrame BUTTON
Label ThunderLabel —
List ThunderListBox LISTBOX
MDIForm ThunderMDIForm —
Option ThunderOptionButton BUTTON
Picture ThunderPictureBox —
Scroll (Horiz) ThunderHScrollBar SCROLLBAR
Scroll (Vert) ThunderVScrollBar SCROLLBAR
Text ThunderTextBox EDIT
Timer ThunderTimer —
Note: Visual Basic class names shown above are for design time. At runtime VB class names have a prefix depending on the version of Visual Basic, for example: ThunderCheckBox might become ThunderRTCheckBox at runtime. You can use the GetClassName API function described in chapter 5 to determine the class name for a window.
Bob
http://www.thescarms.com/vbasic/PassString.asp