Page 1 of 1

Double Subclassing

Posted: Thu Aug 17, 2006 4:10 pm
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 

Posted: Thu Aug 17, 2006 4:26 pm
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 

Posted: Thu Aug 17, 2006 6:48 pm
by Konne
Damn s*** that's good news;) Thx for sharing!

Posted: Thu Aug 17, 2006 8:40 pm
by Konne
Simple question:
What is this for ?
oldproc=GetWindowLong_(GadgetID(200), #GWL_WNDPROC)

Posted: Fri Aug 18, 2006 1:26 am
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).

Re: Double Subclassing

Posted: Fri Aug 18, 2006 2:46 am
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

Posted: Fri Aug 18, 2006 4:40 am
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.

Posted: Fri Aug 18, 2006 12:58 pm
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.