Related
i am currently writing a service in VB6 for a real legacy application.
I am basing my work on Sergey Merzlikin "NT service sample" (http://www.smsoft.ru/en/ntservice.htm).
Until today I used NTSVC.OCX but it seems it does not work on Windows Server core because it involves a form in an ActiveX (i suppose).
So i jumped to full Winapi service and mainly i have a problem on :
MsgWaitforMultipleObjectsEx, it returns 87 (invalid parameter)
This API seems extremely tricky.
The original code uses MsgWaitForMultipleObjects but it resulted in an app crash (c0000005) in MSVBVM60.dll.
Testing platform : Windows 2012
I tweaked quite a bit the declarations, so maybe i made something wrong.
Anyway, here are the most relevant parts of the code :
Private Declare Function MsgWaitForMultipleObjectsEx Lib "user32" _
(ByVal nCount As Long, pHandles As Long, _
ByVal dwMilliseconds, _
ByVal dwWakeMask As Long, _
ByVal dwFlags As Long) As Long
Public Declare Function CreateEvent2 Lib "kernel32" Alias "CreateEventW"
(ByVal lpEventAttributes As Long, _
ByVal bManualReset As Long, _
ByVal bInitialState As Long, _
ByVal lpName As String) As Long
hStopEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
hStopPendingEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
hStartEvent = CreateEvent2(0&, 1&, 0&, vbNullString)
ServiceNamePtr = StrPtr(Service_Name)
I checked the handles values, they seem OK, but i suspect that they have no security descriptor with "SYNCHRONIZE"
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379607%28v=vs.85%29.aspx
then there is a wrapper called MsgWaitObj in the sample NT Service. this is where I replaced MsgWaitForMultipleObjects with :
MsgWaitForMultipleObjectsEx(nObj, hObj, T1, QS_ALLEVENTS, 0&)
The wrapper function declaration is :
Public Function MsgWaitObj(ByVal Interval As Long, _
Optional ByRef hObj As Long, _
Optional ByVal nObj As Long = 0&) As Long
The external declaration of MsgWaitObj in Main and the references passed are as follows :
hnd = NTService.StartAsService
h(0) = hnd
h(1) = hStartEvent
j(0) = hStopPendingEvent
<...>
IsNTService = MsgWaitObj(INFINITE, h(0), 2&)
<...>
Do
tm = MsgWaitObj(5000&, j(0), 1&)
Loop While tm = WAIT_TIMEOUT
I hope posting excerpts of code is sufficient, if not i think the easiest way would be to download the ntservice sample and figure what is going wrong and if i am in the right path or not.
thanks for your help.
I tested Sergey Merzlikin's sample application from "Writing NT service using VB6/VB5" article refrenced in the question, and observed quite different behavior. Details are below, but my guess is that Server Core installations do NOT support VB6 applications.
Windows 2012 with full GUI
Sample Windows service SvSample.exe works just fine on fresh installation of Windows 2012 with full GUI with no configuration changes applied.
Windows 2012 with Minimal Server Interface
Then I go from full-blown GUI to minimal interface via this PowerShell command: Uninstall-WindowsFeature -Name Server-Gui-Shell –Restart. After that sample service still does work without obvious problems.
Windows 2012 Server Core
Then I strip down remaining bits with Uninstall-WindowsFeature -Name Server-Gui-Mgmt-Infra –Restart. After configuration is done and server reboots, I double check that ServerCore-WOW64 feature is still installed (without it no 32-bit application would work as I understand).
At this point sample service indeed stops working. Though not with crashes in msvbvm60.dll, rather Service Control Manager bluntly reports that (as seen in System event log):
A timeout was reached (30000 milliseconds) while waiting for the
Sample VB6 Service service to connect.
The Sample VB6 Service service failed to start due to the following error:
The service did not respond to the start or control request in a timely fashion.
In fact, msvbvm60.dll is not even present in C:\Windows\SysWOW64\.
Conclusion
Server Core (as opposed to Windows 2012 in general) simply does not support VB6 runtime. I didn't check, but I'm pretty sure that situation is the same with Windows 2008 R2 Server Core too.
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
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
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.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 9 months ago.
Improve this question
I have a rather unusual application that isn't working the way I need, and I hope someone here will have some suggestions or at least a direction to investigate.
We have a museum exhibit that has a computer at the entrance driving two small receipt printers. There are two buttons on a console, wired to the left and right buttons of a disemboweled mouse. The two printers and associated buttons are for girls and boys, each button does a random selection from a database of names and prints a small ticket on the appropriate printer with a graphic image, a few words about the exhibit and the randomly chosen name.
Conceptually all is well, but it hangs quite often. I got the project at the last minute, because the original designer got bogged down and couldn't deliver, so the exhibit's author asked me the day before opening, whether I could write something that would work.
I did it in Word, since I am an experienced VBA programmer. Several other avenues I attempted first all lead to dead ends - one couldn't do graphics, another couldn't handle two printers, yet another couldn't change fonts and so on. The problem is that it simply isn't fast enough - Word can only drive one printer at a time and changing the active printer takes a long time. Not by office standards, where a second or two of delay before a printer starts working on your document is not an issue, but here I need more or less instant response. If kids press a button and nothing happens, they press it over and over until something does happen, resulting in maybe half a dozen commands being sent before the printer starts reacting. Sometimes it jams the program completely, since boys and girls will be pressing the two buttons simultaneously and Word locks up, and even when it doesn't jam, the printers then spit out a stream of tickets, making a mess. The kids start squabbling over which ticket is whose, pulling them out of the printers, snarling the paper tape, jamming the printer and generally making a mess of the whole affair, often necessitating the exhibit caretakers having to restart the computer and clear torn bits of paper out the printers.
What I need is some sort of fast programming language that can drive two printers *-simultaneously-*, not the MSOffice claptrap of having to switch the active printer, that can react to both left and right mouse button click events, can print a small graphic image and can print in different font sizes and styles and. I don't need many, but it's not all in one typeface.
Can anyone suggest what I might use for this? I don't even know if it's possible at all under Windows, whether the "single active printer" garbage is an Office artifact, or a Windows restriction. My little Commodore-64 twenty-five years ago had two printers attached to it and drove both simultaneously with no difficulties - it doesn't seem to me it should be such an impossible requirement today.
Being a Python programmer myself, I would use something like MSWinPrint.py, and render the documents directly using Python. It looks like it supports text and images, and you can easily select any printer in the system by name.
You would need to:
Install Python.
Install Python for Windows Extensions.
Install PIL.
Install MSWinPrint.
Then, you would need to write a program to do the printing. Something like the following.
#python
import sys
import Image, ImageWin
import MSWinPrint
# workaround for PIL namespace change
MSWinPrint.ImageWin = ImageWin
def print_name(name, printer_name):
doc = MSWinPrint.document(printer_name)
doc.begin_document('nametag for %s' % name)
# print the name at position 20,20
text_pos = 20, 20
doc.text(text_pos, name)
# add an image for this person
img_pos = 40, 40
img_size = 100, 100
doc.image(img_pos, get_image(), img_size)
doc.end_document()
def get_image():
image_filename = 'my image.jpg'
return Image.open(image_filename)
if __name__ == '__main__':
name, printer_name = sys.argv[1:]
print_name(name, printer_name)
If you save this as print_tag.py, then you can execute it with two command line arguments, the name to be printed and the image filename.
print_tag.py Sally "EPSON Artisan 810"
I ran this code and it worked great. I didn't know creating a custom print job could be so easy.
You can of course run the program as often as you like on as many printers as you would like. There's certainly more you can do to customize when an how the print job is run. You could customize the code to always run and interpret the mouse clicks (for that you might need wxPython), or you might have another program that just executes the script.
As other have said, the programming language isn't going to make much difference. However (and this is a BIG however), the print libraries built into most scripting languages, such as VBA and .NET, either only support printing to the system default printer (most common, and cannot be worked around by having two instances open simultaneously, as the system default printer is a global setting) or require you to configure a global variable to specify the active printer (this only affects one process, so could be worked around using two instances).
Instead, you will have to invoke the windows API directly. It most certainly allows printing directly to any printer on the system. Here is an example of how to use the default printer. Note that only one line of code (calling the GetDefaultPrinter function) ties this to the default printer. Supplying a different printer name to CreateDC gets you a different printer.
If you instead call the EnumPrinters function, you can find out ANY or ALL of the printer names, not just the default. Or have the administrator preconfigure the printer names to use in a registry setting or text file.
In any case, you can have device contexts for all printers open simultaneously. Of course, once you have the printer device context, you have to create a print job, send your content, and end the print job. There's a great deal of information available on MSDN.
All the examples are in C, which makes C++ the obvious language for printing to non-default printers, but as long as you know how to call WinAPI functions from your language, you can use it instead. In VBA, that'd require Declare Function XYZ Lib "gdi32" (params here)
This is not a problem with the programming language, its just a limitation of the printer stack you are using.
I am not sure if .NET would support it (or Windows for that matter) would support sending printing on a thread. You could put each printer in its own thread and block the UI from accepting more print requests.
You could also try to turn on print spooling for both printers. That will render it locally and return then print.
I wrote something a few years ago (in VB6 so could probably be ported to VBA easily) that bypassed the printer driver (a printer must be set up but the actual driver used was irrelevant) and sent raw commands to the printer. Depending on what sort of printer you are using this may or may not be an issue (some use complex escape sequences). If you want to print graphics, this could add another layer of complication - although it can be done if you know what commands the printer supports. I managed to get bitmaps printing on a dot matrix printer, but it wasn't a 5 minute task. If this is something you are interested in I can try and dig out the code?
The Code is as follows:
Public Type DOCINFO
pDocName As String
pOutputFile As String
pDatatype As String
End Type
Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Declare Function EndDocPrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Declare Function EndPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, ByVal pDefault As Long) As Long
Declare Function StartDocPrinter Lib "winspool.drv" Alias "StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pDocInfo As DOCINFO) As Long
Declare Function StartPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Declare Function WritePrinter Lib "winspool.drv" (ByVal hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, pcWritten As Long) As Long
Public Function PrintRawData(ByVal sPrinter As String, ByVal sDocName As String, ByVal sData As String) As Boolean
On Error GoTo PrintErr:
Dim lhPrinter As Long, lReturn As Long, lpcWritten As Long
Dim lDoc As Long, sWrittenData As String
Dim MyDocInfo As DOCINFO
Dim pOutput As Printer
Dim p As Printer
For Each p In Printers
If p.DeviceName = sPrinter Then
Set pOutput = p
GoTo StartPrinting
End If
Next p
MsgBox "Unable to find the specified printer [" & sPrinter & _
"] in the list of currently installed printers" & vbCrLf & _
"Printing will be aborted", vbCritical
Exit Function
StartPrinting:
lReturn = OpenPrinter(pOutput.DeviceName, lhPrinter, 0)
If lReturn = 0 Then
MsgBox "Print was unsuccessful. Make sure there is a printer installed on the port you are trying to print to"
Exit Function
End If
MyDocInfo.pDocName = sDocName
MyDocInfo.pOutputFile = vbNullString
MyDocInfo.pDatatype = vbNullString
lDoc = StartDocPrinter(lhPrinter, 1, MyDocInfo)
Call StartPagePrinter(lhPrinter)
sWrittenData = sData
lReturn = WritePrinter(lhPrinter, ByVal sWrittenData, Len(sWrittenData), lpcWritten)
lReturn = EndPagePrinter(lhPrinter) 'need this??
lReturn = EndDocPrinter(lhPrinter)
lReturn = ClosePrinter(lhPrinter)
Exit Function
PrintErr:
MsgBox "Print was unsuccessful. Make sure there is a printer installed on the port you are trying to print to"
Exit Function
End Function
To use it you need to install a printer that uses the correct port using any driver (I generally use the Generic / text Only Driver) and then call it as follows replacing the Hello with the data you want to send to the printer including control characters etc:
PrintRawData "Generic / Text Only", "My Document", "Hello"
Updated and completely overhauled Answer :
Pete, have you considered an entirely different kind of printer? The setting, changing and sending PostSCript commands can take, indeed, a lot of time.
A strange solution you might call it, but our instant B/W laser Brother label printer prints out instantly on a press of the button. Their specialty rock solid in house developed P-Touch Editor 5.4 Software is created in such a way as to output to multiple printers simeltaneously(!!), each printing the same or different lables (for example different serial numbers or dates from a database).
Its snappy: when letting go of a return button, just when my finger has reached half a centimeter in the button already IIIIEEEUUUUUU (1 second after release of return key) the entire lable is out. Albeit in black and white.
Aside lables there are also paper lables without sticky backs 102mm wide rols by either original or third party brands makes.
Cost: 100$ ~ 400$ per unit incl software to design and customise connections with database.
I know that those instant printers use much less windows-based drivers and more embedded technologies, relieving the system from standardized print jobs.
Perhaps this solves your problem or brings you on a new idea.