Theos PowerBasic Museum 2017

Archive => Discussion - Legacy Software (PBWIN 9.0+/PBCC 5.0+) => Topic started by: Frederick J. Harris on October 18, 2010, 04:06:37 AM

Title: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 04:06:37 AM
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
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 06:27:39 AM
 
From the help file:

Quote
RAISEEVENT may only appear within a class which declares the Event Source interface.
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 05:44:34 PM
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'?
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 06:20:43 PM
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.
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 07:35:48 PM
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?
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 08:25:23 PM
 
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.
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 09:26:16 PM
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.
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 10:23:47 PM
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. 



Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 10:56:21 PM
 
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.
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 11:08:40 PM
Then it looks like we'll just have to prototype the method with three parameters to balance the stack?
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 11:23:12 PM
 
And how are you going to call DefWindowProc with only 3 parameters?
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 18, 2010, 11:30:48 PM
Now there's a good question! ???
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 18, 2010, 11:52:39 PM
 
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.
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 19, 2010, 02:00:51 AM
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.   
Title: Re: RaiseEvent From WndProc()
Post by: José Roca on October 19, 2010, 02:39:02 AM
 
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.
Title: Re: RaiseEvent From WndProc()
Post by: James C. Fuller on October 19, 2010, 11:39:57 AM
Quote from: José Roca on October 19, 2010, 02:39:02 AM

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.


All the encapsulation code I've seen uses a c++ Friend Function for the callback.

James
Title: Re: RaiseEvent From WndProc()
Post by: Frederick J. Harris on October 19, 2010, 06:13:17 PM
Funny you should mention that James!  I had just been playing with that yesterday.  I don't much like it.  To me its convoluted and ugly, and comes in around three times the size of plain SDK.
Note 'friend' just below in CWindow.

#include <windows.h>


class CWindow
{
public:
CWindow(HINSTANCE);
~CWindow(void);
friend LRESULT __stdcall fnWndProc(HWND, UINT, WPARAM, LPARAM);
HWND Window(void);
HINSTANCE Instance(void);
BOOL Initialize(void);
BOOL Create(int);
WPARAM MessageLoop(void);

private:
HINSTANCE   m_hInst;
HWND        m_hWnd;
};


long __stdcall fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
if(msg==WM_DESTROY)
{
    PostQuitMessage(0);
    return 0;
}

return (DefWindowProc(hwnd, msg, wParam, lParam));
}


CWindow::CWindow(HINSTANCE hIns)
{
this->m_hInst=hIns;
}


CWindow::~CWindow()
{
//Entering CWindow Destructor!
}


HWND CWindow::Window()
{
return this->m_hWnd;
}


HINSTANCE CWindow::Instance()
{
return this->m_hInst;
}


BOOL CWindow::Initialize(void)
{
char szClassName[]="Form1";
WNDCLASSEX wc;

wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX);               wc.style=CS_HREDRAW | CS_VREDRAW;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);     wc.hInstance=this->m_hInst;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra=0;
wc.lpszMenuName=NULL;                        wc.cbClsExtra=0;
if(!RegisterClassEx(&wc))
    return FALSE;

return TRUE;
}


BOOL CWindow::Create(int iShow)
{
this->m_hWnd=CreateWindowEx(0,"Form1","Form1",WS_OVERLAPPEDWINDOW,100,100,350,300,HWND_DESKTOP,0,this->m_hInst,0);
ShowWindow(this->m_hWnd,iShow);
return (int)this->m_hWnd;
}


WPARAM CWindow::MessageLoop(void)
{
MSG msg;

while(GetMessage(&msg,NULL,0,0))
{
   TranslateMessage(&msg);
   DispatchMessage(&msg);
}

return msg.wParam;
}


int __stdcall WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
WPARAM wRet;

CWindow* pWnd=new CWindow(hIns);
pWnd->Initialize();
pWnd->Create(iShow);
wRet=pWnd->MessageLoop();
delete pWnd;

return wRet;
}


I'll tell ya what really hurts though.  That Com Control example of mine I posted down on my board can be done with the C++ Active Template Library ( ATL ) in about 10 minutes.  And its a 'Full' Visual ActiveX Control that is loaded into VB from the 'Controls' Tab, it shows up in the VB Toolbox with whatever icon you paint for it, can be drug and pasted onto a Form, and the properties set in the VB Property Window.  It implements all those OLE Interfaces related to embedding within a control container.  Microsoft pulled out all the stops when they built ATL to make it as efficient as possible.  It doesn't even link with the C runtime to save file size.  Even with all the stuff implemented that control comes in only around 40K.  All I can say is you have to 'give the devil his due'.  Of course it has to run in a control container.  I'll have to see if it works in Jose's.  I expect it will. 
Title: Re: RaiseEvent From WndProc()
Post by: Dominic Mitchell on October 20, 2010, 01:44:56 AM
Quote
pthis, wMsg, wParam and lParam

Let me muddy the waters a bit.
There are two cases when Windows(the COM subsystem) will call your ActiveX control
passing the values shown above.
The first case is when a windowed ActiveX control acts as a container for other controls
and implements the ISimpleFrameSite interface. The COM subsystem calls the PreMessageFilter
and PostMessageFilter methods of this interface passing the values

pthis, hWnd, wMsg, wParam, lParam, [p]lResult and [p]dwCookie.

The second case is when the ActiveX control is windowless. A windowless control does not have
a window procedure, but implements the IOleInPlaceObjectWindowless interface in its place.
The COM subsystem calls the OnWindowMessage method of this interface passing the values

pthis, hWnd, wMsg, wParam, lParam, plResult.

Here hWnd is the window handle of the OLE container.
The windowless control should call the OnDefWindowMessage mehod of the IOleInPlaceSiteWindowless
interface implemented by the container to obtain the default windows processing of the container.

Given the above information, if you want to use a PowerBASIC class, your only option is to write a
windowless control.

If you have ever written a control from the scratch, that is not piggybacking on a predefined control,
then you already know how to write the non-COM part of a windowless control.