Double Subclassing

Share your advanced PureBasic knowledge/code with the community.
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Double Subclassing

Post by localmotion34 »

Code updated For 5.20+

I wrote this code to answer a post in the windows forum, but since it is a neat trick, i figured more people might see it in this section.

Its called double subclassing. You basically subclass a window or control to trap messages you want, in this case a container gadget. you "steal" the #wm_command message and do your own events. the button click in the container never reaches PB's event loop.

then, later on, you decide that you dont want to do those events anymore, or let events naturally pass to the eventloop, so you change the window procedure to a defined callback to let any event you didnt let pass before, now go on to PB's main loop.

you can actually do this any number of times, and just have to have procedures defined for each "state" of a callback routine you want to process.

EXAMPLE:

Code: Select all


Global oldproc.l

Procedure notpossibleproc(hwnd,msg,wParam,lParam)
  Select msg
    Case #WM_COMMAND
      Debug "The control I got the message from is" + Space(1) +Str(lParam)
      Debug "the gadgetid of button 1 is" + Space(1) + Str(GadgetID(1))
      Debug "I just Punk'd the eventloop" 
      ProcedureReturn 0 ;don't let the eventloop get this message
      ;alternatively, you can let this message pass to the eventloop
  EndSelect 
  ProcedureReturn DefWindowProc_(hwnd,msg,wParam,lParam)
EndProcedure 
    
    
Procedure letitpassproc(hwnd,msg,wParam,lParam)
  Select msg
    ;trap anyother messages you want here, EXCEPT the #wm_command
    
  EndSelect 
  ProcedureReturn CallWindowProc_(oldproc,hwnd,msg,wParam,lParam)
EndProcedure 
    
    
    
    


If OpenWindow(0, 869, 108, 237, 471, "Robbing and Stealing", #PB_Window_SystemMenu | #PB_Window_TitleBar  )
    
  ;  If CreateGadgetList(WindowID(0))
      ContainerGadget(200,20,20,150,90,#PB_Container_Raised)
      oldproc=GetWindowLong_(GadgetID(200), #GWL_WNDPROC)
      ButtonGadget(1, 10, 15, 80, 24, "Button 1")  
      oldproc=SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@notpossibleproc())
      CloseGadgetList()
      CheckBoxGadget(2,10,150,180,20,"Allow Container Events to Pass")
  ;  EndIf 
  EndIf


  Repeat
     
    
  Event = WaitWindowEvent()
  Select Event
    Case  #PB_Event_Menu
      Select EventMenu()
        
      EndSelect
    Case  #PB_Event_Gadget
      Select EventGadget()
        Case 1
          Debug "PB event loop got the message!!!"
          Debug "Localmotion34 Rocks!!!"
        Case 2
          State=GetGadgetState(2)
          Select State
            Case 0
              SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@notpossibleproc()) 
            Case 1
              SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@letitpassproc())
          EndSelect
      EndSelect
      
  EndSelect
  
Until Event = #PB_Event_CloseWindow
  
End 

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Post by localmotion34 »

Here is an example that does the change of the subclass from a created thread. the thread is executed with the container to be re-subclassed as the parameter.


Code: Select all


Global oldproc.l,thread.l

Procedure notpossibleproc(hwnd,msg,wParam,lParam)
  Select msg
    Case #WM_COMMAND
      Debug "The control I got the message from is" + Space(1) +Str(lParam)
      Debug "the gadgetid of button 1 is" + Space(1) + Str(GadgetID(1))
      Debug "I just Punk'd the eventloop" 
      ProcedureReturn 0 ;don't let the eventloop get this message
      ;alternatively, you can let this message pass to the eventloop
  EndSelect 
  ProcedureReturn DefWindowProc_(hwnd,msg,wParam,lParam)
EndProcedure 
    
    
Procedure letitpassproc(hwnd,msg,wParam,lParam)
  Select msg
    ;trap anyother messages you want here, EXCEPT the #wm_command
    
  EndSelect 
  ProcedureReturn CallWindowProc_(oldproc,hwnd,msg,wParam,lParam)
EndProcedure 
    
    
Procedure changesubclass(containergadget.l)
  MessageRequester("Attention", "We are now going to change the procedure of the container. Here we go!!!", #PB_MessageRequester_Ok)
  If SetWindowLong_(GadgetID(containergadget.l),#GWL_WNDPROC,@letitpassproc())
    MessageRequester("Attention", "Done", #PB_MessageRequester_Ok)
    KillThread(thread)
  Else
    MessageRequester("Attention", "Uhh, something went wrong", #PB_MessageRequester_Ok)
  EndIf 
  EndProcedure
    


If OpenWindow(0, 869, 108, 237, 471, "Robbing and Stealing", #PB_Window_SystemMenu | #PB_Window_TitleBar  )
    
    If CreateGadgetList(WindowID(0))
      ContainerGadget(200,20,20,150,90,#PB_Container_Raised)
      oldproc=GetWindowLong_(GadgetID(200), #GWL_WNDPROC)
      ButtonGadget(1, 10, 15, 80, 24, "Button 1")  
      oldproc=SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@notpossibleproc())
      CloseGadgetList()
      ;CheckBoxGadget(2,10,150,180,20,"Allow Container Events to Pass")
      ButtonGadget(2,10,150,180,20,"Allow Container Events to Pass")
    EndIf 
  EndIf


  Repeat
     
    
  Event = WaitWindowEvent()
  Select Event
    Case  #PB_Event_Menu
      Select EventMenu()
        
      EndSelect
    Case  #PB_Event_Gadget
      Select EventGadget()
        Case 1
          Debug "PB event loop got the message!!!"
          Debug "Localmotion34 Rocks!!!"
        Case 2
          thread=CreateThread(@changesubclass(),200)
          
          ; State=GetGadgetState(2)
          ; Select State
            ; Case 0
              ; SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@notpossibleproc()) 
            ; Case 1
              ; SetWindowLong_(GadgetID(200),#GWL_WNDPROC,@letitpassproc())
          ; EndSelect
      EndSelect
      
  EndSelect
  
Until Event = #PB_Event_CloseWindow
  
End 

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
Konne
Enthusiast
Enthusiast
Posts: 434
Joined: Thu May 12, 2005 9:15 pm

Post by Konne »

Damn s*** that's good news;) Thx for sharing!
Apart from that Mrs Lincoln, how was the show?
Konne
Enthusiast
Enthusiast
Posts: 434
Joined: Thu May 12, 2005 9:15 pm

Post by Konne »

Simple question:
What is this for ?
oldproc=GetWindowLong_(GadgetID(200), #GWL_WNDPROC)
Apart from that Mrs Lincoln, how was the show?
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Post by localmotion34 »

Konne wrote:Simple question:
What is this for ?
oldproc=GetWindowLong_(GadgetID(200), #GWL_WNDPROC)
when PB creates the container gadget, it assigns a native window procedure to it. when the compiler spits out the ASM, it has to use the createwindowex function, and specify the window procedure address of is Windclassex structure for creation.

what the getwindowlong_ does is retrieve the address of the NATIVE PB procedure, which Fred designed to place the button or child clicks in the event loop, which are then detected by eventgadget().

you store that address for when you want any notifications to the container to reach the event loop. then you use callwindowproc_ with the address of the native PB window procedure. callwindowproc is basically a "callfunctionfast" because it executes a procedure from an ADDRESS.

think of it as two security guards at a checkpoint. the first guard, lets ANY PERSON (message) through. when you subclass, you change the guard at the checkpoint, and tell him to only let certain people through.

now to change back to the original guard, all you have to do is call him over by name. "hey Fred, get back over here and let anyone through again ". BUT YOU HAVE TO KNOW HIS NAME (procedure address).

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

Re: Double Subclassing

Post by NoahPhense »

It's late and I'm tired, and lazy.. Can you give me the idiots guide to
what this does. Or I'll have to wait for the weekend to read it again. ;)

- np
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Please give NoahPhense the "idiots guide" so that this idiot can also benefit.

I'm not tired or lazy, just your honest-to-goodness plain and unadulterated idiot. Reading it again on the weekend won't help me a bit. :)




Edit:

Wait!

I think I understand!

Now you had really better explain it because when I have those moments I am usually waaaaaay off track.
Dare2 cut down to size
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Post by localmotion34 »

this is about changing a window procedure "on the fly"to suit your needs.

you store the ORIGINAL window procedure of a gadget. this procedure lets events generated by the gadget pass to the PB event loop.

now when you Setwindowlong_(gadget_hwnd, #gwl_wndproc,@newproc()), you can trap any events you want. but, once you trap them, they are removed from the que.

so, based on a variable, active window, your mood, ect, you can change a window subclass callback at any timepoint in the program.

look at the example i gave. only when the checkbox is checked, does the button on the container generate an event in the event loop. otherwise, the callback procedure traps its #wm_command message.

this is PERFECT for a Registration/protection scheme.

Lets say a your application fails a simple CRC check, or MD5 fingerprint check. Lets say you protect your app with Armadillo, but someone uses Mr_Magic's DilloDie and strips out Armadillo.

you can detect all of this silently, and then based on that, set callback functions for certain main gadgets to a completely useless routine.

say, a button that says "update database". when clicked in an unaltered app, writes the data to a file just fine.

but after determining that your app is altered, you set the button callback to "mess_up_procedure", that writes nonsense encrypted data. you can even detect this fact deep into the program. have a registratin check when a user clicks a menu item, and if the check fails, then alter some gadgets behavior.

the possibilities are really endless.

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
Post Reply