• Welcome to Theos PowerBasic Museum 2017.

News:

Attachments are only available to registered users.
Please register using your full, real name.

Main Menu

RaiseEvent From WndProc()

Started by Frederick J. Harris, October 18, 2010, 04:06:37 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

Hi Jose!

    When I was working on the low level ConnectionPoint interfaces and client sinks for my visual COM control here...

http://www.jose.it-berater.org/smfforum/index.php?topic=3872.0

it did occur to me that I should be able to dispence with all my verbose low level code and just use PowerBASIC's version 9 high level ConnectionPoint technology to create the dll and control.  That would involve registering a window class in the dll and calling CreateWindowEx() when the method call came in.  However, events for any visual GUI element are generated in a Window Procedure, which of course would also be necessary in the dll for a RegisterClassEx() to succeed.  It is not obvious to me at this point how I can make this work with PowerBASIC's RaiseEvent keyword, as it seems it will only accept this call from inside an interface in a method (see PB ERROR below).  Here is the very brief test program I made which roughly parallels my aforementioned COM control posted on my board.  If one uncomments the WndProc() one receives a compile error.  Do you know of a way to make this work?  Or is it really necessary to do this only low level as in my posts of several weeks ago?


'Class CG  '''Class G

#Compile Dll   "CGServer.dll"
#Dim All
#Com TLib On
$IID_IOutGoing = GUID$("{20000000-0000-0000-0000-000000000072}")
$CLSID_CG      = GUID$("{20000000-0000-0000-0000-000000000073}")
$IID_IComCtrl  = GUID$("{20000000-0000-0000-0000-000000000074}")
Global         iCtrlId As Long

Interface IOutGoing $IID_IOutGoing As Event : Inherit Dual
  Method ControlEvent(Byval iMessage As Long)
End Interface

'Function WndProc() As Long            'ERROR >> Requires Object Procedure (Method/Property)
'  RaiseEvent IOutGoing.ConrolEvent(1) 'Line 16:   RaiseEvent IOutGoing.ConrolEvent(1)
'  Function=1
'End Function

Class CG $CLSID_CG As Com
  Instance hContainer As Dword
  Instance hControl   As Dword
  Instance iCtrlId    As Long
  Event Source IOutGoing

  Interface IComCtrl $IID_IComCtrl : Inherit Dual
    Method GetColor() As Long
      Method=1
    End Method
    Method SetColor(Byval iColor As Long) As Long
      Method=1
    End Method
    Property Get GetCtrlId As Long
      Property=iCtrlId
    End Property
    Property Get GetHWND() As Long
      Property=1
    End Property
    Method Initialize()
      'Would RegisterClassEx() A Window Class Here!
    End Method
    Method CreateControl(hContainer As Long)
      'Would CreateWindowEx() The Control's Window Here!
    End Method                        '
  End Interface
End Class

José Roca

 
From the help file:

Quote
RAISEEVENT may only appear within a class which declares the Event Source interface.

Frederick J. Harris

Well then, I guess I'd better RTFM too!

OK, well this strikes me as detestable, but that would mean putting the Window Procedure within a PowerBASIC class.  Is that possible?  To be perfectly honest, I've never even thought about it.  My initial reaction though is that its not possible because somewhere or other the WndProc function signature or return value would get messed up.

If one created an interface method which returned a long and passed the four required WndProc parameters, I suspect one way or another (through one mean hack) one would be able to find the address of that method's implementation in the VTable PowerBASIC would create, and one would be able to pass that address to the .lpfnWndProc member of WNDCLASSEX.  Boy, that's far out.  Is this something to even consider???  Or should I just give up and accept that it can't be done 'high level'?

José Roca

Quote
OK, well this strikes me as detestable, but that would mean putting the Window Procedure within a PowerBASIC class.  Is that possible?

No, it's not. Windows will call it passing hwnd, wMsg, wParam and lParam, but if it is a method, it needs to be called passing pthis, wMsg, wParam and lParam.

Frederick J. Harris

Quote
...but if it is a method, it needs to be called passing pthis, wMsg, wParam and lParam.

It would never be called that way, just as we never call our Window Procedures directly from within our programs (only indirectly through SendMessage()).  In other words, I'm thinking of a real hack here, that is, faking the compiler out into thinking there is a method there that is going to be called through an object call, when in point of fact it will never be done that way.  Create the method as the first method in the interface so that a pointer to it is stored at offset zero in the VTable ( obtainable through ObjPtr() ), and pass that to .lpfnWndProc.  I'm assumming PowerBASIC lays out its function pointers in the order of declaration of the methods in the interface.  If that's not so this crazy idea is out.  Of course I could try this.  But just wondering what you thought?

José Roca

 
Again: Windows will call it passing hwnd, wMsg, wParam and lParam, and, to work, Windows had to call it passing pthis, wMsg, wParam and lParam.

Every method has an hidden pthis parameter.

José Roca

Quote
Create the method as the first method in the interface so that a pointer to it is stored at offset zero in the VTable ( obtainable through ObjPtr() ), and pass that to .lpfnWndProc.

The offset 0 of the VTable is always the address of the QueryInterface method, followed by AddRef and Release. Then, if it inherits from IDispatch, come GetTypeInfoCount, GetTypeInfo, GetIDsOfNames and Invoke.

OBJPTR should return the address of a pointer (a pointer to a pointer) to the VTable (COM uses double indirection).

Anyway, it won't work because Windows won't call it correctly.

Frederick J. Harris

#7
You're right.  I forgot about that (QueryInterface @VTbl[0], AddRef @VTbl[1], etc).  But it doesn't make any difference.  Forget about IDispatch for a moment ( I know you would love to :) ) , and assume we're just inheriting from IUnknown.  Then, if PowerBASIC places its method addresses in the VTable in the order of declaration/implementation, and Method fnWndProc is right after .Release, then you would get its address like so...


Instance pVTbl,VTbl As Dword Ptr

pVTbl=ObjPtr(Me)
VTbl=@pVTbl[0]
fnWndProc=@VTbl[3]


That could be fed into the .lpfnWndProc member of a WNDCLASSEX in an .Initialize() call after object instantiation.  At this point I expect a window could be created in the normal fashion and Windows would have a valid Window Procedure to call.  The problem will come in if any code in the Window Procedure attempts to reference any of the instance /state data from the bogus 1st parameter which the PowerBASIC compiled code thinks is a pointer to the object but which is really a hWnd (as you maintain).  It may be that some code in the Window Procedure would need to reference 'state' data, and that would be what needs to be thought through IMHO (or crazy opinion).    

I'm very uneasy about the return value though. 




José Roca

 
Windows will push 4 parameters into the stack when calling your method, and the method will pop 5... Not very good for the health of the stack.

Frederick J. Harris

Then it looks like we'll just have to prototype the method with three parameters to balance the stack?

José Roca

 
And how are you going to call DefWindowProc with only 3 parameters?

Frederick J. Harris


José Roca

 
COM requires that the last parameter (parameters are passed from right to left) will be the address of a pointer to the VTable, and Windows will not pass it. Therefore, it won't work. Try to call any com method using CALL DWORD without passing the pthis parameter and see what happens.

Frederick J. Harris

Believe me Jose, I've done it many times with many crashes.  As a matter of fact, that's pretty much where my journey to higher COM knowledge began.

I'm just wanting to make sure there is absolutely no way to do it.   It actually surprised me this wouldn't work (using the Raise Event leyword like in my example at the top of this thread).  I accomplished pretty much what I wanted to do using low level connection point / sink code, but how many lines of code did that take - 1000, 1500, something like that - a pretty lot.  If that Raise Event keyword worked like I had hoped, my Visual COM Control could probably have been done in 250 lines or so. 

So unless some knowledgible ASM'er wants to figure out how to get that last parameter off the stack, I'll give it up.   

José Roca

 
It's not a matter of poping that parameter from the stack, but that Windows will call the method without passig the pthis parameter, and without it, it will crash.

Even C++ programmers that use classes for the GUI, have to put the window class procedure outside the class.