Saturday, August 06, 2005

The ability for BindEvent to hook into windows message events in Visual FoxPro 9 is great. It is no doubt wrapping the SetWindowLong API function somewhere in the source and facilitates the magic by subclassing the target window. My hat goes off to the Fox Team for coming up with this. But, what if you want to modify a messagebox before it appears (such as change the button captions and centering it with the form that called it) or what if you want to create a Global Keyboard Hook? Basically, what if, as a Visual FoxPro developer, you want to take advantage of the SetWindowsHookEx API function and all the doors it opens up? Maybe I'm missing something, but BindEvent doesn't seem to be able to do this.

I've got some ideas on things that I want Visual FoxPro to do long before Sedna gets here, and some of these ideas require that I am able to use the SetWindowsHookEx. So, today I decided to figure out how we (the Visual FoxPro Community) could do this.

To start this show, here's an FLL I've created using Visual C++ 7.0, that gives us two more functions for Visual FoxPro. BindEventEX and UnBindEventEx. Download the vfpex.fll here. These functions will give you indirect access to SetWindowsHookEx for your Visual FoxPro applications/code. There is more to be done, but this is a good start for now.

Then, below are a couple of "cut-n-paste into a prg file and execute" examples I worked up in Visual FoxPro that use the vfpex.fll.

***************************************************
*!* MODIFY MESSAGEBOX EXAMPLE
*!*
*!* Requires: vfpex.fll
***************************************************
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN

DEFINE CLASS form1 AS form
 Top = 0
 Left = 0
 Height = 259
 Width = 373
 DoCreate = .T.
 Caption = "Using the New BindEventsEx - MessageBox Modification"
 WindowType = 1
 Name = "Form1"

 ADD OBJECT command1 AS commandbutton WITH ;
  Top = 216, ;
  Left = 144, ;
  Height = 27, ;
  Width = 132, ;
  Caption = "R\<un Example", ;
  Name = "Command1"

 ADD OBJECT command2 AS commandbutton WITH ;
  Top = 216, ;
  Left = 276, ;
  Height = 27, ;
  Width = 84, ;
  Caption = "E\<xit", ;
  Name = "Command2"

 PROCEDURE wineventhandler
  #DEFINE IDOK   1
  #DEFINE IDCANCEL  2
  #DEFINE IDABORT  3
  #DEFINE IDRETRY  4
  #DEFINE IDIGNORE  5
  #DEFINE IDYES   6
  #DEFINE IDNO   7
  #DEFINE IDCLOSE  8
  #DEFINE IDHELP   9
  #DEFINE IDTRYAGAIN 10
  #DEFINE IDCONTINUE 11
  #DEFINE IDPROMPT 65535

  IF nCode == 5
   SetWindowText(wParam, "VFP - WH_CBT HOOK") && Title
   SetDlgItemText(wParam, IDPROMPT, "Visual FoxPro BindEventEx Example") && Message
   SetDlgItemText(wParam, IDABORT, "VFP Rocks!") && First Button Caption
   SetDlgItemText(wParam, IDRETRY, "Always Has") && Second Button Caption
   SetDlgItemText(wParam, IDIGNORE, "Always Will") && Third Button Caption

   Thisform.CenterMessageBox(wParam)

   CallNextHookEx(hHook, nCode, wParam, LPARAM) && all 4 variables exist
   UnBindEventEx()
   SET LIBRARY TO
  ELSE
   CallNextHookEx(hHook, nCode, wParam, LPARAM) && all 4 variables created by FLL
  ENDIF

  RELEASE nCode, wParam, LPARAM, hHook
 ENDPROC

 PROCEDURE buf2dword
  LPARAMETERS tcBuffer
  RETURN Asc(SUBSTR(tcBuffer, 1,1)) + ;
       Asc(SUBSTR(tcBuffer, 2,1)) * 2^8 +;
       Asc(SUBSTR(tcBuffer, 3,1)) * 2^16 +;
       Asc(SUBSTR(tcBuffer, 4,1)) * 2^24
 ENDPROC

 PROCEDURE centermessagebox
  LPARAMETERS tnwParam

  LOCAL lcBuffer, lnMsgLeft, lnMsgTop, lnMsgRight, lnMsgBottom, lnMsgWidth, lnMsgHeight

  lcBuffer = REPLI(CHR(0), 2^4)

  =GetWindowRect(tnwParam, @lcBuffer)

  lnMsgLeft = THISFORM.buf2dword(SUBSTR(lcBuffer, 1, 4))
  lnMsgTop = THISFORM.buf2dword(SUBSTR(lcBuffer, 5, 4))
  lnMsgRight = THISFORM.buf2dword(SUBSTR(lcBuffer, 9, 4))
  lnMsgBottom = THISFORM.buf2dword(SUBSTR(lcBuffer, 13, 4))
  lnMsgWidth = lnMsgRight - lnMsgLeft
  lnMsgHeight = lnMsgBottom - lnMsgTop
  lnMsgLeft = THISFORM.LEFT + (THISFORM.WIDTH - lnMsgWidth) / 2
  lnMsgTop = _screen.Top + THISFORM.TOP + (THISFORM.HEIGHT - lnMsgHeight) / 2

  MoveWindow(tnwParam, lnMsgLeft, lnMsgTop, lnMsgWidth, lnMsgHeight, .T.)
 ENDPROC

 PROCEDURE Load
  DECLARE INTEGER SetDlgItemText IN user32;
   LONG hDlg,;
   LONG nIDDlgItem,;
   STRING lpString

  DECLARE INTEGER SetWindowText IN user32;
   LONG HWND,;
   STRING  lpString

  DECLARE LONG CallNextHookEx IN user32;
   LONG, LONG, LONG, LONG

  DECLARE INTEGER MoveWindow IN user32;
   INTEGER HWND,;
   INTEGER X,;
   INTEGER Y,;
   INTEGER nWidth,;
   INTEGER nHeight,;
   INTEGER bRepaint

  DECLARE SHORT GetWindowRect IN user32 INTEGER HWND, STRING @ lpRect
 ENDPROC
 
 PROCEDURE command1.Click
  *!* Some of the various Windows Hook constants
  #define WH_MSGFILTER -1
  #define WH_JOURNALRECORD 0
  #define WH_JOURNALPLAYBACK 1
  #define WH_KEYBOARD 2
  #define WH_GETMESSAGE 3
  #define WH_CALLWNDPROC 4
  #define WH_CBT 5 && the one used here
  #define WH_SYSMSGFILTER 6
  #define WH_MOUSE 7
  #define WH_HARDWARE 8
  #define WH_DEBUG 9
  #define WH_SHELL 10
  #define WH_FOREGROUNDIDLE 11
  #define WH_CALLWNDPROCRET 12
  #define WH_KEYBOARD_LL 13
  #define WH_MOUSE_LL 14
  
  *!* Set library so BindEventEx and UnBindEventEx can be used
  *!* in VFP
  SET LIBRARY TO (LOCFILE(ADDBS(JUSTPATH(SYS(16))) + "vfpex.fll"))
  *!* You must have a Named reference to the form or object
  *!* as thisform or this cannot be used with BindEventEx
  BindEventEx('oform1.wineventhandler()', WH_CBT) && SetWindowsHookEx
  *!* This messagebox will be modified before it is shown
  MESSAGEBOX(" ", 2, "") && Just A Blank Messagebox with Abort, Retry, Ignore buttons
 ENDPROC

 PROCEDURE command2.Click
  Thisform.Release()
 ENDPROC

ENDDEFINE

***************************************************
*!* GLOBAL KEYBOARD HOOK EXAMPLE
*!*
*!* Requires: vfpex.fll
***************************************************
PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN

DEFINE CLASS form1 AS form

 Top = 0
 Left = 1
 Height = 256
 Width = 454
 DoCreate = .T.
 Caption = "Using the New BindEventsEx - Global Keyboard Hook"
 Name = "form1"

 ADD OBJECT command1 AS commandbutton WITH ;
  Top = 216, ;
  Left = 311, ;
  Height = 27, ;
  Width = 132, ;
  Caption = "R\<un Example", ;
  Name = "Command1"

 ADD OBJECT edit1 AS editbox WITH ;
  Height = 193, ;
  Left = 11, ;
  ReadOnly = .T., ;
  Top = 12, ;
  Width = 432, ;
  Name = "Edit1"

 PROCEDURE wineventhandler
  LOCAL HookStruct
  *!* Real basic example, just to show it's possible
  IF wParam = 256 && keydown
   HookStruct = REPLICATE(CHR(0), 20)
   CopyMemory(@Hookstruct, lParam, 20)
   thisform.edit1.value = thisform.edit1.value + Chr(thisform.buf2dword(Hookstruct))
  ENDIF
  CallNextHookEx(hHook, nCode, wParam, lParam) && all 4 variables created by FLL
  RELEASE nCode, wParam, LPARAM, hHook
 ENDPROC

 PROCEDURE buf2dword
  LPARAMETERS tcBuffer
  RETURN Asc(SUBSTR(tcBuffer, 1,1)) + ;
       Asc(SUBSTR(tcBuffer, 2,1)) * 2^8 +;
       Asc(SUBSTR(tcBuffer, 3,1)) * 2^16 +;
       Asc(SUBSTR(tcBuffer, 4,1)) * 2^24
 ENDPROC

 PROCEDURE Load         
  DECLARE LONG CallNextHookEx IN user32;
   LONG, LONG, LONG, LONG

  DECLARE RtlMoveMemory IN kernel32 As CopyMemory;
      STRING  @ Destination,;
      INTEGER   Source,;
      INTEGER   nLength 

  SET LIBRARY TO (LOCFILE("vfpex.fll", "FLL"))
 ENDPROC

 PROCEDURE Destroy
  *!* Unhook when form is destroyed
  UnBindEventEx()
  SET LIBRARY TO
 ENDPROC

 PROCEDURE command1.Click
  #define WH_MSGFILTER -1
  #define WH_JOURNALRECORD 0
  #define WH_JOURNALPLAYBACK 1
  #define WH_KEYBOARD 2
  #define WH_GETMESSAGE 3
  #define WH_CALLWNDPROC 4
  #define WH_CBT 5
  #define WH_SYSMSGFILTER 6
  #define WH_MOUSE 7
  #define WH_HARDWARE 8
  #define WH_DEBUG 9
  #define WH_SHELL 10
  #define WH_FOREGROUNDIDLE 11
  #define WH_CALLWNDPROCRET 12
  #define WH_KEYBOARD_LL 13 && this is the one used
  #define WH_MOUSE_LL 14

  this.Enabled = .F.
  WAIT "Go type in another application - your keystrokes will be recorded" WINDOW TIMEOUT 2
  *!* You must have a Named reference to the form or object
  *!* as thisform or this cannot be used with BindEventEx
  BINDEVENTEX('oform1.wineventhandler()', WH_KEYBOARD_LL) && SetWindowsHookEx
 ENDPROC

ENDDEFINE

Sunday, August 07, 2005 2:38:46 AM (Central Daylight Time, UTC-05:00)  #    Comments [11]
Monday, August 08, 2005 1:48:31 PM (Central Daylight Time, UTC-05:00)
Cool,

I could use the global keyboard hook to watch over a internet server. Then maybe do some "selfdestruct" sequence if a hacker could log in but wouldn't type in some disarming code...silent alarm...

Bye, Olaf.
Saturday, August 13, 2005 5:26:32 PM (Central Daylight Time, UTC-05:00)
Craig,

If one wanted to change the alignment and width of VFP 9's autocomplete dropdown (to left align with and match the width of its parent control), would your BindEventEx() interface to SetWindowsHookEx() be the proper approach to implementing this change?

My assumption here is that your example of manipulating a messagebox window is no different than manipulating the autocomplete dropdown's window?

Malcolm
Sunday, August 14, 2005 8:43:02 PM (Central Daylight Time, UTC-05:00)
Hi Craig,

as for your Buff2DWord functions: Have you checked out the CTOBIN and BINTOC funtion extensions in VFP9 ? They should eliminate any of those handmade conversions.

wOOdy
Monday, August 15, 2005 7:19:09 PM (Central Daylight Time, UTC-05:00)
Olaf,
How's your hacker seek and destroy coming along?

Malcolm,
I think you are on the right track in your thinking about the autocomplete window. I haven't made time yet to mess with it, but I will shortly. I'm not sure whether that window will throw a create message like the messagebox does or not. But, the SetWindowsHookEx API function is extremely powerful, and I would be surprised if it couldn't do what you're asking of it. As for other solutions, I can't think of another way you could do it right off-hand.

Woody,
I tried using CTOBIN passing "S" as the second parameter. It doesn't appear to work the same. Would you happen to be able to post the snippet that will work?

To All,
Thanks for stopping by my blog.
Thursday, August 25, 2005 10:49:52 AM (Central Daylight Time, UTC-05:00)
Great Thanks Craig. The same for all of your posts here.
Boris
Boris
Saturday, October 01, 2005 8:53:41 AM (Central Daylight Time, UTC-05:00)
Hi Boris,

You're welcome. Glad you're enjoying them.
Thursday, August 24, 2006 9:57:30 AM (Central Daylight Time, UTC-05:00)
hi craig,

is it possible to use the wh_journalrecord and playback in your vfpex.fll, if possible can you show me a sample.

tnx,
anthony
anthony salting
Friday, October 06, 2006 1:04:42 PM (Central Daylight Time, UTC-05:00)
Dear sir,I'm a VFP LOVER from China. I'm very sorry I find vfpex.fll can't be used under my VFP6.0(simple Chinese Version,win98).it hints "library file e:\vfpex.fll no effect", I am crying...I have sent an Email to craig@sweetpotatosoftware,I want for your answer...like a baby waiting for mum.
Friday, October 06, 2006 9:36:58 PM (Central Daylight Time, UTC-05:00)
anthony,

Yes, I believe it would be possible. No time to create an example at the moment, but should my schedule loosen somewhat I would be happy to try it sometime and report back with the results. In the mean time, should you take it upon yourself to create a working example of this I would be interested in seeing it.

zyz995462,

Redownload the FLL which has been compiled with VS 2003. Then you will need to include the msvcrt71.dll with this FLL since it that is the runtime that VS 2003 DLLs (and FLLs) require. This should allow you to run the FLL with a VFP 6.0 application. If that doesn't work, then I would recommend updating Visual FoxPro to 9.0 which uses the msvcrt71.dll as its runtime.
Friday, October 06, 2006 9:37:07 PM (Central Daylight Time, UTC-05:00)
anthony,

Yes, I believe it would be possible. No time to create an example at the moment, but should my schedule loosen somewhat I would be happy to try it sometime and report back with the results. In the mean time, should you take it upon yourself to create a working example of this I would be interested in seeing it.

zyz995462,

Redownload the FLL which has been compiled with VS 2003. Then you will need to include the msvcrt71.dll with this FLL since it that is the runtime that VS 2003 DLLs (and FLLs) require. This should allow you to run the FLL with a VFP 6.0 application. If that doesn't work, then I would recommend updating Visual FoxPro to 9.0 which uses the msvcrt71.dll as its runtime.
Saturday, October 07, 2006 1:24:54 AM (Central Daylight Time, UTC-05:00)
Dear sir,thanks for your answer,how quick ,how detail and how kind! I'll remember your warm-hearted.
I have downloaded your new VFPEX.FLL and run the program under VFP6.0 again. but very very sorry I will tell you that the same error "library file e:\vfpex.fll no effect" appeared. then next I download a "msvcrt71.dll " and put it under my VFP6.0,but it's no use at all.
Why I haven't updated VFP6.0 TO 9.0 ? first,I'm a greenhand of VFP, VFP6.0 is enough.
what's more,VFP6.0 has simple Chinese version,but 7.0-9.0 are all English version,my poor English makes me not update to 9.0.
But I'll select VFP9.0 later after my VFP skills make progress. Then I'll come here to see you and learn your sweet potato program,I'll remember you for ever.
GOD BLESS YOU AND GOOD LUCK TO YOU,BEST WISHES
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u)  

Enter the code shown (prevents robots):


 

Archive

<October 2008>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678