RegisterWaitForSingleObject To Watch a Directory using RealBasic - windows

I'm trying to watch a directory for changes using the FindFirstChangeNotification function. This works if I take the handle returned by FindFirstChangeNotification and stuff it into WaitForSingleObject. The problem is that WaitForSingleObject blocks the entire application until it returns.
So, I looked around and it seems that RegisterWaitForSingleObject was the way to go:
Sub monitorDir(dir As FolderItem)
Declare Function FindFirstChangeNotificationW Lib "Kernel32" (dirPath As WString, watchChildren As Boolean, eventTypeFilter As Integer) As Integer
Declare Function RegisterWaitForSingleObject Lib "Kernel32" (ByRef waiterHWND As Integer, HWND As Integer, cllbck As Ptr, _
context As Integer, wait As Integer, flags As Integer) As Integer
Dim allFilters As Integer = &h00000001 Or &h00000002 Or &h00000004 Or &h00000008 Or &h00000010_
Or &h00000100
Dim monitorHandle As Integer = FindFirstChangeNotificationW(dir.AbsolutePath, True, allFilters)
If monitorHandle <> 0 Then
Call RegisterWaitForSingleObject(myCallbackHWND, monitorHandle, AddressOf MyCallbackFn, 0, &hFFFFFFFF, 0)
End Sub
This appears to work as the application continues to execute normally. However, as soon as the MyCallbackFn is called (that is, when a change occurs in the directory) things get... weird. Applications start crashing or locking up starting with Process Explorer and Windows Explorer. I have to log out of Windows in order to restore things.
At the moment, all that MyCallbackFn does is this:
Sub MyCallbackFn()
Declare Function UnregisterWaitEx Lib "Kernel32" (waitHWND As Integer, eventHandle As Integer) As Integer
Call UnregisterWaitEx(myCallbackHWND, 0)
MsgBox("Change Detected")
End Sub
Am I barking up the wrong tree by using RegisterWaitForSingleObject, have I used it wrongly, or there some limitation in RealBasic which causes callbacks to implode the system?

The callback function you register in RegisterWaitForSingleObject() is called on another thread (http://msdn.microsoft.com/en-us/library/ms685061.aspx):
The callback routine is executed by a worker thread when the object's state becomes signaled or the time-out interval elapses.
I don't know anything about RealBasic's threading support, but at the very least GUI operations on Windows typically need to occur on a specific thread, not just any old worker thread. So the call to MsgBox() on that worker thread is probably a problem.
A simple thing you can try is to call PostMessage() (or whatever the RealBasic equivalent is) to post a custom message to your window message queue that your application can respond to (for example by calling MsgBox()).

Maybe not related, but where is MyCallBackFn() declared? If it is an instance method you should use WeakAddressOf instead of AddressOf.

Related

Access any software through excel or Scripting or Task?

I am using software which actually send a message to LED message display but It needs to press send button every time.Is there anyway to access that button or make any task which actually press that button after some seconds.Because I want to send the message continuously after 2 seconds.
I realize this is not a full answer to your question, I do not have sufficient reputation to add a comment.
If the LED message display software that you are using does not have a published interface to allow you to control it then you may need to use API calls. You can declare a function which makes a call directly to the API library. A useful call is FindWindowExA. This finds information about the windows open on the computer. All the windows are in a hierarchy. You can then find the application window for the LED message software and step through the sub windows until you find the one that represents the send button. You should be able to active its press event.
API calls are declared like this:
Private Declare Function local_name Lib "user32" Alias "library_routine_name" (....) As long
where local_name is the name of the function in your code and library_routine_name is the name of the routine that you wish to call
for example:
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
declares a function called FindWindowEx which calls the library routine FindWindowExA. The arguments to the call are specified by the routine that you are calling.
MSDN provides descriptions of the API calls, for example see
FindWindow help
FindWindowExA help

Vb 6.0 Move mouse cursor to prevent sleep

i need to run a vb program which will copy all the data from different production database to our reporting database. it will run atleast for 5 hours . so in order to prevent the machine to go to sleep how can i move mouse cursor by 1 pixed at specified interval of time . as per my client company laws i cant do this in the computer power settings. so im trying to do within my program itself.
i tried with sendkeys ie by sending down arrow at specified interval of time but it is causing problem for me
thanks in advance
try this
Enum Execution_State
ES_SYSTEM_REQUIRED = &H1
ES_DISPLAY_REQUIRED = &H2
ES_USER_PRESENT = &H4
ES_CONTINUOUS = &H80000000
End Enum
Declare Sub SetThreadExecutionState Lib "kernel32" (ByRef esFlags As EXECUTION_STATE)
Public Sub DoNotSleep()
SetThreadExecutionState(esFlags:=Execution_State.ES_SYSTEM_REQUIRED Or Execution_State.ES_DISPLAY_REQUIRED Or Execution_State.ES_CONTINUOUS)
End Sub
picked from http://forums.codeguru.com/showthread.php?492366-How-to-run-a-very-long-SQL-statement
Why not just use the PowerQuerySuspend Event of the standard VB6 SysInfo control?
This allows your program to cancel autosleep or even user-initiated sleep requests (unlike the older SetThreadExecutionState API call).

Is it possible to write a App.PrevInstance Replacement to give real time information

App.Previnstance returns a value of True or False depending on whether a previous of the program is running when this instance starts.
Subsequently if the previous instance is terminated the value of App.PrevInstance does not change.
Is it possible to write a function that would be able to determine at any moment if previous
instances are in existence?
I guess that for this you would need the date/time processes started to be available.
As this information does not seem to be available from the task manager I wonder if windows stores it at all?
The problem you're seeing with App.PrevInstance might be because you're testing with the application running under the debugger (i.e., the VB 6 IDE). But I'm not entirely sure. It might just perform the check once and cache the value, which allows it to grow "stale" as the environment state changes. As you've already noticed, the App.PrevInstance property has a lot of limitations. Another common problem with it is its fragility. Changing the name of the executable is an easy way to make it fail. That is not always the desired behavior.
So it's a good idea to replace it with an alternative solution. Like wqw says in a comment, the best solution would be to create a mutex using a GUID for the name of the mutex whenever your application starts up. This should succeed the first time, but will fail subsequently because the mutex is already registered (by a previous instance of your application). That failure is your clue that a previous instance of your application is running. To do this in VB 6, you will need to import and call some Win32 functions, like CreateMutex, CloseHandle, and ReleaseMutex. There's a sample of how to use mutexes on MSDN, but that won't help you very much to write VB 6 code unless you are already rather familiar with the Win32 API. I've linked to a tutorial that contains the necessary code in VB 6 in my answer here.
If you're otherwise satisfied with the behavior of App.PrevInstance and you just want it to perform the check each time you call it (rather than using a stale cached value), then you can just replace it with a call to your own function that does essentially the same thing: iterate through all of the currently-running processes, and look for a match to the name of your executable. Unfortunately, this is not necessarily less work than the "better" solution involving the use of a mutex. You'll still need to import a number of Win32 functions, including EnumProcesses. There are instructions for this in an old knowledge base article—obviously you want to focus on the "Windows NT" section and ignore the "Windows 95/98" stuff.
I guess that for this you would need the date/time processes started to be available. As this information does not seem to be available from the task manager I wonder if windows stores it at all?
You don't actually need this information. In fact, I'm not sure what approach you had in mind that would require it. It doesn't matter when the process was started, it just matters whether or not it is currently running. Those are two completely different things.
However, just for fun, Windows does in fact store this information. Task Manager doesn't show it, but Process Explorer does. You can retrieve it programmatically either by calling the GetProcessTimes function, or querying WMI (specifically, the CreationDate property of the Win32_Process class).
Thanks to wqw's comment I looked up CreateMutex and it was exactly what I needed.
I found the code below here
'Code by Adam Verwijs
Const ERROR_ALREADY_EXISTS = 183&
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Form_Load()
Dim hMutex As Long
'Try to create a new Mutex
hMutex = CreateMutex(ByVal 0&, 1, App.Title)
'Did the mutex already exist?
If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
'Clean up
ReleaseMutex hMutex
CloseHandle hMutex
'More than one instance detected
MsgBox "More than one instance"
End
Else
'form load code
End If
End Sub
EDIT to show that same non zero mutex returned :
If you create a new vb6 project with 1 button, stick the code below in, make the project and then run multiple instances you'll see that all have the same non-zero mutex, at least on my computer (windows vista home basic)
Option Explicit
Const ERROR_ALREADY_EXISTS = 183&
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lpMutexAttributes As Any, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function ReleaseMutex Lib "kernel32" (ByVal hMutex As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub Command1_Click()
Dim hMutex As Long
'Try to create a new Mutex
hMutex = CreateMutex(ByVal 0&, 1, App.Title)
MsgBox hMutex
'Did the mutex already exist?
If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
'Clean up
ReleaseMutex hMutex
CloseHandle hMutex
'More than one instance detected
MsgBox "More than one instance"
End If
End Sub
EDIT 2016-04-17 DO NOT USE THIS CODE!!!
I have used it without noticing a problem until recently but have now discovered that it does not work across more than one user logged in on a computer. Use wqw's answer on this other thread instead

Return to an already open application when a user tries to open a new instance in vb6

Suppose a user minimize my visual basic application to the taskbar notification icon. Now I want when user open a new instance, the old one should restore.
Generally, the strategy used to create a single-instance application is to add some code to the application initialization that determines whether an instance is already running. If one is, it gets a handle to its main window, passes the focus to it, and silently dies. If one is not, it continues to run and completes the rest of the initialization sequence as usual.
You'll find lots of old VB 6 articles that accomplished this by iterating through all of the top-level windows, looking for one whose caption matches the one you expect. But this is a pretty fragile solution, it doesn't take very much to throw it off.
Same deal with the App.PrevInstance property. This is very simple to use, but also very simple in its implementation. It works based on the name of the executable and looks for a running process whose name is a match. However, this is easily defeated if a user creates and renames a copy of the executable. If this is acceptable for you, you could implement this very easily by querying the App.PrevInstance property. Otherwise, you'll need to use a more robust solution.
One such possibility is to create and register a named mutex when the first instance of your application is starting up. Then, when subsequent instances try to register that same mutex, they will fail, indicating that an instance is already running. You can find instructions on using mutexes in VB 6 in the answers to this question.
A couple of important caveats to using mutexes:
You need to make sure that you call the ReleaseMutex and CloseHandle functions when your application is closed in order to release ownership of and destroy the mutex that you created.
When you are running your program in the VB 6 IDE (e.g., to debug it) and it registers a mutex, the mutex belongs to the IDE and won't be released until you close the IDE and restart it. To prevent this, you can suppress the creation of the mutex when running inside of the IDE/debugger using conditional compilation. If you take this approach, make sure to test your program outside of the debugger to be sure that the mutex-related functionality is working as expected! You should never ship something to customers that you haven't thoroughly tested.
You can find all of the VB 6 declarations for these Windows API functions by using the API Viewer program that comes bundled with your VB 6 installation.
More information about handling multiple instances of a VB 6 application is available here on Karl Peterson's site. There's also a complete example implementation in this article on VB Accelerator—focus specifically at step 2, you don't need the rest of the code.
You can often do this fairly simply using DDE in a degenerate way:
Form1.frm
Option Explicit
'This is Form1. To use as DDE source at design time we set:
' Form1.LinkMode = 1 (Source, i.e. vbLinkSource).
' Form1.LinkTopic = "Form1" (default).
'
'Note we use (hidden) Label1 on this Form as a DDE destination.
Private PrevState As Integer
Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer)
'Got a "command" so restore Form1 and accept the command.
WindowState = PrevState
Caption = "I am awake!"
Cancel = False
End Sub
Private Sub Form_Load()
PrevState = WindowState
End Sub
Private Sub Form_Resize()
If WindowState <> vbMinimized Then PrevState = WindowState
End Sub
Module1.bas
Option Explicit
Private Sub Main()
Load Form1
'After Form1 is loaded (hidden), try DDE link to possible prior copy.
With Form1.Label1
.LinkTopic = App.EXEName & "|Form1"
On Error Resume Next
.LinkMode = vbLinkManual
If Err.Number = 0 Then
On Error GoTo 0
'Link succeeded. Wake up prior copy via pushback to
'the DDE source, then unload Form1 and terminate.
.LinkExecute "Wake up!"
Unload Form1
Else
On Error GoTo 0
'Link failed, so we're 1st. Show Form1.
Form1.Show vbModal
End If
End With
End Sub

Does the API call ExitProcess() work OK in VB6 if you follow the MS caveats?

Microsoft indicates that VB6 doesn't support ExitProcess (to exit and return a value).
However, it indicates that this call can fail under certain circumstances (if a thread hasn't been completed, etc.)
so I'm wondering whether this call will work OK (consistently :-) as long as you obey the caveats in the article.
I could go a step further and call ExitProcess() from the Sub Main or Form which stared the app.
Update: after some more reading (I really did research this a bit before asking ) I found a suggestion to use the TerminateProcess API instead. I'm investigating that option.
Any thoughts?
You could use Karl Peterson's method of creating a console app from VB6? Then use Con.ExitCode = 1 (as shown in the second sample).
He's distributing a free add-in vbAdvance that helps you build console apps.
Best option is probably to create a Sub Main entry point anyway, and call ExitProcess from there rather than from a class or form. Or (which is what I'm doing) call ExitProcess from the Form Unload event and have a Main entry point like:
Sub Main
'code
Launch Form
Exit Main
Then:
Form_Unload
'code
ExitProcess
End sub 'Form_Unload
So the ExitProcess will be the last bit of code you execute.
It's not going to be pretty and you will probably leak some handles and whatnot, but NT4 and higher are fairly good at handling that anyway. In other words, drive your application from Sub Main and call the API before you exit from there.
Note: this was posted by Kprobst but it was at the end of a post that had an incorrect answer. So I'm reposting it here for clarity.
Take a look at this:
What unloads the modules when a vb6 program terminates
Create a global variable:
Public ErrorLevel As Long
Create a sub like this:
Public Sub UnloadAll()
Dim f As Integer
f = Forms.Count
Do While f > 0
Unload Forms(f - 1)
If f = Forms.Count Then Exit Do
f = f - 1
Loop
ExitProcess ErrorLevel
End Sub
At the End of you 'main' or in the unload code of you main form put this:
UnloadAll
Works well!

Resources