From: Sarah M. Weinberger on
Hi All,

I found out that the problem is not just "my problem", but rather one that
anyone who creates VB6 based applications (possibly C# and VB.Net too from
what I am read on one post) suffer.

I found a tool, RMTool.exe, which Microsoft distributes as part of the
Windows Vista Qualification Test Tools. I got lucky to even find that.
That is what Microsoft evidently uses for both Windows Vista and Windows 7.
The tool allows a user to test the restart functionality. You simply use a
command line similar to the following just substituting the PID for the
application being tested. You can get the PID by going to the Windows Task
Manager and then "View | Select Columns" and then checking the box to view
PID (first entry).

"C:\Program Files\Microsoft\Logo Testing Tools for Windows\Restart
Manager\x86\rmtool.exe" -p 7256 -R

I created a simple VB6 project. When I say simple. I am not kidding. I
did not even change names. My first attempt was a plain Project1.exe
applet. I added nothing to the form, no background code. I saved and
clicked on make. I then got the process ID and ran the line above.

Guess what? VB6 failed. I ran the test against NotePad and that worked
like a charm. I suspect that all Microsoft applications work. VB6 based
ones do not.

STEP 2: I added subclassing.

I deciced to try some subclassing, as that was recommended. I took my plain
vanila application and added the following. By the way, I only added the two
message box statements later to see if I am even getting the messages
called. I am not, but I am getting ahead of myself.

--------------------------
New Subclassing Module:
--------------------------
Option Explicit

Public glPrevWndProc As Long

Public Const GWL_WNDPROC = (-4)
Public Const WM_QUERYENDSESSION = &H11
Public Const WM_ENDSESSION = &H16
Public Const ENDSESSION_LOGOFF = &H80000000
Public Const ENDSESSION_CRITICAL = &H40000000
Public Const ENDSESSION_CLOSEAPP = &H1

Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal
hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA"
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal
wParam As Long, ByVal lParam As Long) As Long

Public Function pMyWindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal
wParam As Long, ByVal lParam As Long) As Long
'Process the WM_QUERYENDSESSION message. We need to process this message
for Windows 7 compatibility issues.
Select Case uMsg
Case WM_ENDSESSION
MsgBox "Inside WM_ENDSESSION", vbOKOnly, App.ProductName

pMyWindowProc = 0
Exit Function

Case WM_QUERYENDSESSION
'Assume a standard response to start.
pMyWindowProc = 1 'TRUE, Allow the system to end gracefully.
MsgBox "Inside WM_QUERYENDSESSION", vbOKOnly, App.ProductName

'Handle the specific sub-message.
Select Case lParam
Case ENDSESSION_LOGOFF
'Fall through and take the default action.

Case ENDSESSION_CRITICAL
'Fall through and take the default action.

Case ENDSESSION_CLOSEAPP
'Fall through and take the default action.

Case Else
'Fall through and take the default action.
End Select

Exit Function

Case WM_ENDSESSION

End Select

'Pass back all unprocessed messages to the original procedure associated
with the form.
pMyWindowProc = CallWindowProc(glPrevWndProc, hWnd, uMsg, wParam, lParam)
End Function

--------------------------
Form1 Code
--------------------------
Option Explicit

Private Sub Form_Load()
'Subclass Main Form: We want to process windows messages.
glPrevWndProc = SetWindowLong(Me.hWnd, GWL_WNDPROC, AddressOf
pMyWindowProc)
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
'Stops the form from intercepting Window's messages by resetting the
default procedures associated with the form.
Call SetWindowLong(Me.hWnd, GWL_WNDPROC, glPrevWndProc)
End Sub
--------------------------

Here is a run from NotePad: A working scenario:

-----------------------------------------------------------------------------------------------------------------------
Note: PID 6564 was of a running NotePad.exe. All tests done on Microsoft
Windows Vista.

C:\>"C:\Program Files\Microsoft\Logo Testing Tools for Windows\Restart
Manager\x86\rmtool.exe" -p 6564 -R
Starting Session
StartSession() returned 0
SUCCESS: StartSession()
Session Key: ebcedc00899770428fa4f29c7338f7f0??�
Registering file
RegisterResources() returned 0
SUCCESS: RegisterResources()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 6640, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:6564, type 1, stat 1) - Notepad ()
Shuting down applications
SUCCESS: RmShutdown()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 6640, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:6564, type 1, stat 2) - Notepad ()
Restarting Applications
SUCCESS: RmRestart()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 6640, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:6564, type 1, stat 2) - Notepad ()
Ending Session
EndSession() returned 0
SUCCESS: EndSession()
-----------------------------------------------------------------------------------------------------------------------

Here is from a failed VB6 application (with or without subclassing and with
or without a Form_QueryUnload):

-----------------------------------------------------------------------------------------------------------------------
C:\>"C:\Program Files\Microsoft\Logo Testing Tools for Windows\Restart
Manager\x86\rmtool.exe" -p 1516 -R
Starting Session
StartSession() returned 0
SUCCESS: StartSession()
Session Key: bc96075ed55ce64caab5da4fd8c4f67f!!�
Registering file
RegisterResources() returned 0
SUCCESS: RegisterResources()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 2124, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:1516, type 1, stat 1) - Project1.exe ()
Shuting down applications
*** FAILURE ***: RmShutdown()
Ending Session
EndSession() returned 0
SUCCESS: EndSession()
-----------------------------------------------------------------------------------------------------------------------

Usually I hit Ctrl+C to break the RMTool execution after the "Shuting down
applications" line, as the timeout is hugely long. The application usually
winds up hanging and needs to be forcefully shutdown, but that depends on
whether or not RMTool is broken and the like.

I found out by adding in the MsgBox statements that the message handler,
when using RMTool.exe never gets the WM_QUERYENDSESSION or the
WM_ENDSESSION. VB6 never gets the Form_QueryUnload or Form_Unload. Further
testing without RMTool reveals that Form_QueryUnload comes after the
WM_QUERYENDSESSION message.

Summary:

VB6, which is a form based programming language, has some sort of issue
complying with Microsoft Restart Manager requiremment for Windows Vista and
Windows 7.

Out of sheer curiosity I decided to try a Visual Studio 2010 VB.Net
executable with no code or form items added, WindowsApplication1.exe. I did
not even change the names. The test was a success, so Visual Studio 2010
VB.Net passes the Restart Manager test with flying colors. Sadly, VB6 does
not. Here is the result of my test.

-----------------------------------------------------------------------------------------------------------------------
C:\>"C:\Program Files\Microsoft\Logo Testing Tools for Windows\Restart
Manager\x86\rmtool.exe" -p 4108 -R
Starting Session
StartSession() returned 0
SUCCESS: StartSession()
Session Key: f09a4c7bb7adbf488a9b9ec883f93527??g
Registering file
RegisterResources() returned 0
SUCCESS: RegisterResources()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 8156, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:4108, type 1, stat 1) - WindowsApplication1 ()
Shuting down applications
SUCCESS: RmShutdown()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 8156, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:4108, type 1, stat 2) - WindowsApplication1 ()
Restarting Applications
SUCCESS: RmRestart()
Getting affected apps.
RmGetList() needs 1 structs, reboot reasons 0, returned 0xea
SUCCESS: Allocating RM_PROCESS_INFO array
SUCCESS: GetAffectedApps()
My PID: 8156, Affected Apps: 1, needed 1, reboot reasons 0
PID(1:4108, type 1, stat 2) - WindowsApplication1 ()
Ending Session
EndSession() returned 0
SUCCESS: EndSession()
-----------------------------------------------------------------------------------------------------------------------

I need to get VB6 to work with Microsoft's Restart Manager.

Any thoughts? Like I said, anyone developing a VB6 based application will
suffer this problem as me.

Sorry for being long winded, but there was a lot of information to impart.

Thanks in advance,

Sarah