Hi, I just figured out what may be a really great way to
Subclass in VB 6. Here are the some of the features of this 'alpha'
product.
* Every handle you subclass gets a UNIQUE WindowProc ().
By 'unique' I mean the actual binary code for every
WindowProc is within it's own dynamically allocated chunk
of memory.
* Pressing the 'Stop' button does not crash the IDE. In fact
you can use breakpoints, step through code, and basically
perform all normal debugging operations.
* The implementation is an event-driven class. You can
respond before and after a window handle is subclassed
and before and after a WindowProc () is processed. If you
happen to respond before then you may also optinally
change the data before it reaches the original WindowProc ()
OR you may simply cancel the original WindowProc () (of
course meaning that you are handling the message 100%
on your own).
The attached ZIP file contains the class' code and a demo
application (code of course). I apologize for the lack of comments,
and the occasional un-used function... but this is hot off the grill.
To install and run everything:
* Unzip the attached .zip file to a location of your choosing.
* Compile \<Zip Location>\ldsClasser\ldsClasser.vbp (this is
the actual subclasser). A DLL will now exist (and the
compilation should have registered it). Note, when you first
open up the project you will get (an expected) error
message 'Unable to set version compatible component...'.
You can safely igore this because I had the project set to
binary compatibility in my environment.
* Compile \<Zip Location>\MITestApp\MultiTestAppProject.vbp
(this is the demo program). Note, you will need to change
the reference to the location of the newly compiled DLL or
you will get a missing-reference compilation error.
* Execute MultiTestAppProject.exe
My intention (aside from sharing this 'hopefully' new discovery)
is to get feedback from all you VB Subclassing maniacs out there...
whether you like it, hate it, think it needs to work differently,
think it's dangerous... (you get the point).
Thanks,
-CC
Last edited by Crunchy Cat; Sep 29th, 2002 at 01:12 AM.
One last thing I thought I would mention. I DON'T make any
use of the undocumented VarPtr (), ObjPtr (), etc... functions.
Everything is made with documented and supported
functions.
I can see by the downloads that some of you have tried this
allready. Give me some feedback on it .
-CC
Last edited by Crunchy Cat; Sep 28th, 2002 at 12:43 AM.
Sadly the end result was that it appeared it could not be done
(you could get close... but the high quality result was just not
within reach). The last suggestion I noted was simply to use a 3rd
party control. I really hate using 3rd party components because
you never know when the company in question will go out of
business or stop making the component (not to mention you have
very little control over defects). Long story short, it was not an
option. I actually had a hypothesis (documented in the post)
which may work. Basically I could subclass the RTB control,
intercept and send all WM_PAINT output to a memory DC, and
then bitblt () the DC to the controls real DC afterwards (basically
simulating double buffering). In order to do this I needed to
subclass the control. I have done VB 6 subclassing before and
extensively researched other peoples work on the topic. I was
simply not happy with the limitations imposed by these existing
methods of subclassing so I decided to go for the gold and create
my own 'perfect' subclass control (after which I could test out my
double buffered RTB hypothesis... which has not been done yet
but will happen soon).
So, how did I come up with this implementation? My analysis
of existing subclassing implementations told be that the bulk of
the known instabilites/limitations came from the use of a module
function as the replacement WindowProc (). It seemed that
stopping, breaking, debugging etc... caused the module function
to disappear or be relocated in memory. Of course when
messages were dispatched to an invalid memory location...
*poof* you crashed. In addition, it was next to impossible to get
a reference to more than one subclassed object into the window
proc. This meant the first thing I had to do was find a way to
get 1 or more (depending on how many controls I wanted to
subclass) stable memory locations and fill them with WindowProc
() code. Creating the memory was the easy part. HeapAlloc ()
did this quite nicely and it returns a pointer to the memory that
can be sent directly to SetWindowLong () to replace the existing
WindowProc (). Now I had to populate the allocated memory with
actual code (that acted as a WindowProc ()). To do this I
maintained the WindowProc () code in a module function. This
only acted as a template. I would then copy the code from the
function (at run time) directly into my memory location created
with HeapAlloc (). The copying part was done with the
CopyMemory () function of course. I was able to determine the
size of the base template function by looking for the 'ret 16'
assembly instruction (which all module functions end with when
returning). Now I could create a unique WindowProc () on the fly.
There was a problem with this however. If I attempted to call any
Win32 API function or even module function directly from a
dynamically created WindowProc () (dynamic code like this is
called a 'Thunk' FYI) then a crash would occur. This is because
the memory references to these calls are releative and
created at compile time in the template function. Of course the
relative addresses were invalid for code freshly copied into a
different memory location... so I turned to a different way of doing
things. Within my WindowProc () template I added code to
instantiate a class on the fly (so all addressing would be correct
because the call is made at run-time). This class could then call
any API function (or otherwise) that I wanted to... and do it
safely. So, now I needed a way to reference a unique 'subClass'
object every time a WindowProc () was executed. I did this by
passing the classes memory address via the actual window
handle referenced by the WindowProc () (SetProp () and GetProp
() worked perfectly for this). So how did I get a pointer to the
original 'SubClass' object and SetProp () it in the first place
without using ObjPtr ()? Simple:
So, once again the heap came to the rescue. Now the
infrastructure was in place and the rest was just regular coding.
I am probably making it sound easy, but this bastard was a *****
to conceptualize... but alas it's done and it works! After I get
some more feedback from others I am going to continue with
my double-buffered RTB project and update the thread I created
on the topic should I have any success (cross your fingers).
You're right on target. Just set 'returnCode' to the desired
value and ALSO set 'cancelWindowProc=True'... as this will
prevent the original WindowProc () from executing (otherwise
it would override your return code).
Well, I have received no negative feedback on this 'alpha'
component, so I will now develop a complete beta package, QA
it, and then finalize a release version. The final version won't
be posted on this forum (unless you sign an NDA with me...
perfeclty acceptable), but feel free to use and abuse the 'alpha'
build as you see fit.
in fact, your component has more features than the one i have made, but both have the same bug !
on your sample project, put a code on Button 1 as follows: -
Private Sub Button1_Click()
MsgBox 1 / 0
End Sub
when you run the project from VB, it will give you run time error 'Division by zero'
but when you compile it, and run it from the EXE file, the program crash when you click on the button WITHOUT showing any message, in fact the program will crash whenever there is any run time error occur.
on my project, i found the problem, but i dont know how to prevent it. on the DLL, you should NOT raise any even after the run time error occur on the EXE project, and if you did your EXE will immediately crash, btw i have tried this on all windows operating systems.
also, using On Error Goto ErrHandle: will solve the problem, but i have a big project really and i can not put error handle on each and every procedure, the most difficult part of this problem is when a client calls and says ‘the program crashes whenever i do xyz’!!! no message at all
division by zero was just a sample to demonstrate the bug, whereas in my case the run time error is not as simple as division by zero, the application will crash if any run time error occur, specially when it comes to complicated database application like ours, there is no chance to prevent these run time error
btw, my application still using subclassing on same method, and sometimes we suffer if the problem doesn't happen with us and it happens only on the customer side...
Hey Diamonds, thanks for bringing that bug to my attention.
It's very interesting that a run-time error seems to process fine
when running from the IDE but has causes a 'crash' when raised
from a pure binary (only if it's not caught by an On Error
statement). I made some observations on the problem (which
may only be relevant to my subclasser and not yours), and
what seems to be happening is that as soon as a run-time error
occurs, the VB engine enumerates all heaps and deallocates
them. Of course, in my subclasser the WindowProc () Thunk
was created in Heap space so it gets deallocated in the middle
of processing and a Floating Point Inexact Result mismatch occurs,
leading to a crash or disappearance of the program. A simple
workaround of catching run-time errors is available (as you know),
but I don't know if a permanent solution is possible. I
experimented with locking the heap from another thread, but
when the crash occurs the VB engine is aware of all threads and
will unlock the heap and destroy it accordingly. Of course I will
still make other attempts at solving this problem, and if I run
into any successes I'll post them here. If anybody else has any
ideas please by all means post them.
On a sidenote, I have found quite a few bugs in the 'alpha'
build (as to be expected). They are currently all corrected in my
beta build... with the exception of this latest problem of course.
-CC
P.S. Sorry for not responding sooner. I was out of town on
business all week.
Last edited by Crunchy Cat; Oct 6th, 2002 at 09:19 PM.
MerrionComputin, i am little bit hesitate to use EventVB.dll in my application coz i did not tested it properly, but i had a look at it, it was really nice...
We'll this has been a trippy ride. I finally found the definitive
cause of the problem. I thought it was heap deallocation, but
it turns out to be a stack corruption. When VB 6 (for binaries
only) partakes in a run-time error, it will nuke part of the stack.
Of course it nukes the most important part... where to return
to after the WndProc () is complete... so... *poof* there ya' go.
A solution? Well I hate to use the word impossible so, I'll
use 'improbable'. Every single VB subclasser is going to be
affected by this. The good news is that my subclasser wont
have any problems from the IDE and if you handle the error
in your binary then everything works fine (as you already
know).
MERRION & DIAMONDS, I am sorry to report that even
EventVB can't escape this negative 'behavior'. Take your version
'H' dll and do this:
A) Create a new project.
B) Add 1 form and 1 command button.
C) Link in the EventVB dll.
D) Add this code to the form:
Option Explicit
'\\ EventVB event sink classes, declared WithEvents
Private WithEvents apiLink As EventVB.APIFunctions
Private WithEvents apiWnd As EventVB.ApiWindow
Private Sub apiLink_ApiError(ByVal Number As Long, ByVal Source As String, ByVal Description As String)
'\\ Inform the user that an error has occured
MsgBox Description, vbCritical, "Error in " & Source
End Sub
Private Sub Command1_Click()
Dim a As Long
a = 1 / 0
End Sub
Private Sub Form_Load()
'\\ Initiate the EventVB link
Set apiLink = New EventVB.APIFunctions
'\\ Start subclassing this window
Set apiWnd = New EventVB.ApiWindow
apiWnd.hWnd = Me.hWnd
apiLink.SubclassedWindows.Add apiWnd
End Sub
Private Sub Form_Unload(Cancel As Integer)
'\\ Stop subclassing this window
apiLink.SubclassedWindows.Remove apiWnd
Set apiWnd = Nothing
'\\ Unlink the eventVB dll
Set apiLink = Nothing
End Sub
Private Sub apiWnd_Move(ByVal x As Long, ByVal y As Long, Cancel As Boolean)
Me.Caption = "Form at ( " & x & "," & y & ")"
End Sub
E) Compile the Project.
F) Close VB.
G) Run the compiled project.
H) Press the button.
You'll notice that the program crashes/disappears instead of
giving a run-time error dialog (I did this on NT 4.0 at the time
FYI).
MERRION, I know you are a long-time API/Subclassing wiz,
so if you have any ideas about this please let me know. Although
this problem can be dealt with quite easily by simply catching
and handling an error, I would prefer a purer solution if its
possible at all.
Thanks,
-CC
Last edited by Crunchy Cat; Oct 10th, 2002 at 12:13 AM.
i think the problem is not really diffecult to solve, just dont raise any event after the run time error occur, if there is a way to now that a run time error has occured, then the problem is easy to solve.
btw, r u using VB6 with or without SP ? if yes which SP ?
MerrionComputin, your Project1.exe shows ActiveX cant create object run time error, can you please send it again with the source code. probably there is something to do with the OS, i faced this problem with 98,2000 advanced servier and XP.
MerrionComputin, no use, just the same, but when i open the project VB didn't allocate the EventVB_H.DLL, it shows as missing, then i have selected the one installed with me, i think the version is not the same,
i found the the DLL is Beta R1.2.005 H, is it the same with you ?
MERRION/DIAMONDS... I am using VB6 sp5... perhaps that
accounts for the EventVB crash that Diamonds and I have
but MerrionComputin does not. I hate to think this is all related
to MS bugs...
DIAMONDS, you mentioned "i think the problem is not really diffecult to solve, just dont raise any event after the run time error occur, if there is a way to now that a run time error has occured, then the problem is easy to solve". I actually tried
eliminating all event raising in my subclasser, yet I still got
the same problem. Did I mis-understand something you were
trying to communicate with me?
Crunchy Cat, maybe i will explain in detailed what happen with me,
on my DLL project i have one module and one class only, i raise evnet by calling a function in the Class using CallByName. the function will raise an evnet.
Now if i raise WindowProc event for any windows message, the program will crash on run time error, just like the following code: -
Public Function WindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim p As Long
p = GetIndex(hwnd)
CallByName PrevProc(p).OBJ, "WindowProc", VbMethod, uMsg, wParam, lParam
WindowProc = CallWindowProc(PrevProc(p).Proc, hwnd, uMsg, wParam, lParam)
End Function
But, if i raise WindowProc for only resize messages the program doesn't crash, the code is :-
Public Function WindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Dim p As Long
p = GetIndex(hwnd)
If uMsg = &H5 Then CallByName PrevProc(p).OBJ, "WindowResize", VbMethod
WindowProc = CallWindowProc(PrevProc(p).Proc, hwnd, uMsg, wParam, lParam)
End Function
i have made a test project that will write a 'debug' file for each message, i found that after the run-time error occur, there are 6 to 9 messages is beeing sent, riasing events after the run time error will crash the EXE,
Now we just have to stop the raising events immediately after the run time error occur, i think there is message sent by windows that tell run time error occur, am not sure about it, and i couldn't find it as well.
by the way, before EXE crash, you will hear the sound that comes with the run time error message box, the message box suppose to be displayed by windows, that means the crash happens after the message box (that suppose to be displayed)
Ahh I get it... and I think the fact that you got things to work
in the manner that you did unfortunately may be luck. I made
the same observations about the run-time error sound occuring
before the crash. In fact you can even MessageBox () (from the
Win32 API) Err.Description and kind of see whats going on.
It seems like any class/object instatiations, references, method
calls from within a WndProc () do something to the stack that
invalidates pushed information from the original WndProc () (only
when a VB Run-time error occurs). It's a crazy problem (thankfully
with a workaround), but MerrionComputin's DLL works in sp2
where as it does not seem to work in sp5... indicating this whole
stack thing may be an MS "feature".
yeah, its a crazy problem, i think the reason why MerrionComputin doens't have the problem is because of he is using NT4.
anyway, i really need to solve this problem, i have one project with 130+ forms and one OCX with 45+ forms and object, both are complex, and subclassing in everywhere! i will just be in a maze when any run time error occur.
Yep. I am still working on this problem. I trying to figure out
the rules behind this stack corruption and work around it. I
have had some very positive results but I still need to do some
more R&D work.
I was able to use a subclasser and pointer access to the Refresh buffer array to make form-drag-updating translucency in win95 but i need a way to keep the window from being drawn at its new position until the Refresh buffer is done rendering. It seems that our goals for using a subclasser are not dissimilar; indeed I will stay tuned.
Stuff all your WM_PAINT msgs on a stack and then process them
once the buffer is complete perhaps?
TO ALL:
I figured out the solution to the stack problem! Unhandled
VB run-time errors for binaries now appear as expected! I am
cleaning up the code now and I'll post it within 3 days.
... or maybe I'll just post the code tonight! Ok, this new build
boasts the same great features as the first AND:
* Will not prevent a natural unhandled run-time error from
processing properly in a compiled binary.
DIAMONDS & MERRION - Please compile and try the new build.
I want to ensure that it explicitly works in both of your
environments (the 3rd button in the sample app will generate
a Divide by '0' run-time error).
EVERYONE ELSE - Give me some feedback on this puppy. Alot of
of blood, sweat, and... well blood went into fixing this freakish
problem (which I am convinced is a MS flaw more than anything).
I run your app and I saw yours messages and heard your sound than I press on the Button3, it raise an runtime error I saw the error code screen. Than I press the OK button the screen go and VB also.
I have VB6 pro (SP5) my OS is NT 4.00.1381 w IE 5 6.0.2600.0000
Is it normal that way or VB6 should have stay open I also haven't got any Dr Watson!
Hope this could help.
Mens sana in corpore sano
... pour mieux travailler!
"Is it normal that way or VB6 should have stay open I also haven't got any Dr Watson!"
The question is not clear. If you mean "Is the compiled binary
supposed to disappear after you press Button3 and and then
press 'OK' on the runtime error dialog, the answer is 'Yes'".
That behavior is very specific to VB. If you meant something else
then please rephrase the question.
Regarding your second post, once again I am not entirely clear
what you did. You are referencing a 'Debug' button thus implying
that you are pressing Button3 from the IDE? I just can't tell.
Please restate exactly what happened (in terms of physical
actions and results if necessary).
Aldragor, I just had a random idea on why you are experiencing
the problem at hand (BTW any questions from my previous posts
still stand). To test the hypothesis I have prepared two binary
DLLs for you to try. Please register them and let me know if
the problem you experienced reproduces. No miracles here... just
a test...
I try the 3rd button only 3 times (all within VB6 environment) 2 time I press the ok button on the run time error dialog -> result VB crach with any call to DrWatson and one time I pressed the debug button in that case DrWatson show up but I doesn't been about to look at the log message.
I look for these dll's and give you the result
Mens sana in corpore sano
... pour mieux travailler!