Solved

Avoid multiple instances of my program and send content of Command$ to existing instance ?

Posted on 2001-07-23
78
680 Views
Last Modified: 2013-11-25
How do I avoid mulitple instances of my program ?
I want to have the same functionality as for example GetRight, Visual InterDev and other programs that, when a file associated with it is opened is opened in an existing instance and not in a new one.

I found some code that'll do it, but it doesn't seem to work.

It uses the FindWindow() API call, but as the caption of my program changes all the time I will need to use the class name to find an existing instance, but how do I find out what the class name of my program is !?
0
Comment
Question by:tdaugaard
78 Comments
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309720
If app.previnstance = true then
   'Close this instance
   'END
end if
0
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309725
You can put the code above into your Sub Main or Form_Load of your startup form.
0
 

Author Comment

by:tdaugaard
ID: 6309740
You didn't read my question ...

Imagine this:

My program is running.

Someone opens a file associated with my program or a shortcut to my program with some parameters (ex. "-toggle 1"), and instead of starting a new instance I need to pass these parameters to the instance already running and then close the new instance.
0
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309757
Ahh, the Title got truncated...  Let me try a couple of things...  This isn't going to be easy.
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6309763
>If app.previnstance = true then
>  'Close this instance
>  'END
>end if

Two pieces of advice about this:

   1. The END Statement is commented out, so it actually does not close the instance.

   2. NEVER use the End Statement. Instead, unload all forms, release all object references, and make sure no code is running. At that point, your program will terminate normally.
0
 
LVL 20

Expert Comment

by:hes
ID: 6309766
Use the following:

Option Explicit

      Private Const GW_HWNDPREV = 3

       Private Declare Function OpenIcon Lib "user32" (ByVal hwnd As Long) As Long
       Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) _
         As Long
       Private Declare Function GetWindow Lib "user32" _
        (ByVal hwnd As Long, ByVal wCmd As Long) As Long
       Private Declare Function SetForegroundWindow Lib "user32" _
        (ByVal hwnd As Long) As Long

      Private Sub Form_Load()
            If App.PrevInstance Then
               ActivatePrevInstance
            End If
         End Sub

      Private Sub ActivatePrevInstance()
         Dim OldTitle As String
         Dim PrevHndl As Long
         Dim result As Long

         'Save the title of the application.
         OldTitle = App.Title

         'Rename the title of this application so FindWindow
         'will not find this application instance.
         App.Title = "unwanted instance"

         'Attempt to get window handle using VB4 class name.
         PrevHndl = FindWindow("ThunderRTMain", OldTitle)

         'Check for no success.
         If PrevHndl = 0 Then
            'Attempt to get window handle using VB5 class name.
            PrevHndl = FindWindow("ThunderRT5Main", OldTitle)
         End If

         'Check if found
         If PrevHndl = 0 Then
         'Attempt to get window handle using VB6 class name
         PrevHndl = FindWindow("ThunderRT6Main", OldTitle)
         End If

         'Check if found
         If PrevHndl = 0 Then
            'No previous instance found.
            Exit Sub
         End If

         'Get handle to previous window.
         PrevHndl = GetWindow(PrevHndl, GW_HWNDPREV)

         'Restore the program.
         result = OpenIcon(PrevHndl)

         'Activate the application.
         result = SetForegroundWindow(PrevHndl)

         'End the application.
         End
      End Sub
 



0
 

Author Comment

by:tdaugaard
ID: 6309785
dave_greene, I know .. I've yet to find someone who knows a solution to this .. it's NOT easy :-(

DennisBorg, I use Sub Main() as the startup "object" and this would be the first thing in my program to be executed, so nothing is loaded - End would be fine.

hes, and how exactly does that help me ? That is the code I have myself that doesn't work. I searched for "previnstance" here at EE and found a few Q's where that code was linked to .. but it doesn't work - cause I don't know what the Class name of my program is.
0
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309788
Yes, Yes Dennis, just an example  :)
0
 

Author Comment

by:tdaugaard
ID: 6309795
.. and remember, I need to be able to pass the contents of Command$ to the existing instance, from a new instance - before it is close (of couse ;o) ..)
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6309816
>It uses the FindWindow() API call, but as the caption of
>my program changes all the time I will need
>to use the class name to find an existing instance, but
>how do I find out what the class name of my program is !?

Depending upon the version of VB you are using, the classname will be something like "ThunderForm", which will be true for every VB program. For example, in VB6, the class names of forms are "ThunderFormDC". For MDI Parent Forms, the classname is "ThunderMDIForm".

So, by going off of Class Name, you'd only be certain you came across a Visual Basic program. If you're confident that *YOUR* VB program is the only VB Program that would be running, then using FindWindow() would work.

If other VB programs could be running, then you'll want to take a different approach.

I'm not sure the best approach you should take, but perhaps it would be best done by making your program an ActiveX EXE. Perhaps someone else would be able to describe how you would implement this.

If I come across any further information for you, I'll let you know.

-Dennis Borg
0
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309820
I think I've got it, but the solution envolves an EXE creating an instance of an ActiveX DLL, then if called again, it gets the current object of the DLL and passes the new values to it...  Still working... thoughts anyone?
0
 

Author Comment

by:tdaugaard
ID: 6309841
DennisBorg, I'm using VB 6 Enterprise.
Is the class name the same for ALL VB programs ?

I can't be sure that my program is the only program running that were created in VB so I guess FindWindow() is not an option then.

Hmm .. by changing my project to an ActiveX EXE i can prevent multiple instances like with an ActiveX or ?


I can increase the points if 200 is not enough .. I *NEED* this in my program to finish it and I have no clue whatsoever how to do it :-(
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6309856
tdaugaard:

>DennisBorg, I use Sub Main() as the startup "object" and
>this would be the first thing in my program
>to be executed, so nothing is loaded - End would be fine.

Even then, END should NOT be used. First of all, it would be totally unnecessary, for when Sub Main() is exited, your program would terminate normally, so using END would not benefit you at all.

The END statement is a way of FORCING a program to HALT ABRUPTLY. One possible use is when your program is crashing and burning, and cannot terminate normally. Then it would be something of an Emergency ShutOff Valve.

There have been many times where I've seen questions posted asking why their program was generating General Protection Fault Errors, etc.  And there was no apparant reason for the problem, until they happened to mention that it always happened when it got to the End statement.

Not only do I, as a professional programmer of 11+ years, strongly recommend against it's use, but so do virtually all other professional programmers who program in VB. Furthermore, Microsoft also warns against its use; although even in some of their code samples, they unfortunately still haphazardly use it.

I'm just trying to prevent a few headaches.


-Dennis Borg
0
 

Author Comment

by:tdaugaard
ID: 6309857
I posted this Q on www.eksperten.dk (similar to EE) and someone gave me the below code, but I don't think that using a file like he has done is a good solution ...

---
Option Explicit

Private Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long

Public Function SendText(hwnd As Long, Text As String)
SendMessageByString hwnd, &HC, 0&, Text
End Function

Private Sub Form_Load()
Dim filpath As String 'sti
Dim SendCommando As Long 'hWnd

filpath = App.Path & "\hwnd.log" ' path to hwnd.log

If App.PrevInstance Then
  Open filpath For Input As #1
      SendCommando = Input(FileLen(filpath), #1) 'Open hWnd
  Close #1

SendText SendCommando, Command$ ' Sends text to Text1
Unload Me

Else
If Not filpath > "" Then Kill filpath

  Open filpath For Output As #1
      Print #1, Text1.hwnd;
  Close #1

Text1.Text = Command$
End If

End Sub
0
 
LVL 1

Expert Comment

by:garrenb
ID: 6309871
Why don't you write a flag out to the registry to state that the Application is running then when you try to open another instance, the application will check the value of the registry flag and tell you whether the application is opened or not.

I do this for quite a few applications & find it simple & easy.

Cheers,
GarrenB
0
 

Author Comment

by:tdaugaard
ID: 6309882
garrenb, nice .. I haven't even thought about that, but anyone could alter the registry entry .. of course i could set it every second (I have a timer in my program with interval : 1000 ms) but wouldn't that be a little overkill ?
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6309884
>DennisBorg, I'm using VB 6 Enterprise.
>Is the class name the same for ALL VB programs ?

The class name for Forms in VB5 may differ from the class name for Forms in VB6, which may also differ from class names for Forms in VB4, and VB3, etc. Sometimes Microsoft changes the class names, sometimes they don't.

But for all forms for programs written in VB6, the classname would be the same. The exception is MDI Parent Forms, which have a class name of "ThunderMDIForm"  The other forms in VB6 have a classname of "ThunderFormDC" (if I remember correctly)


>I can't be sure that my program is the only program
>running that were created in VB so I guess FindWindow()
>is not an option then.

One way you could get this to work is to use SendMessage to send a special message to any VB programs your second instance finds. If your second instance does not find any VB programs currently running, or if it doesn't find any that responds to the special message sent, then it really is the first instance instead of the second instance.

If it does find a VB program which responds to your special message, then you can somehow send it the information, and then the second instance can terminate itself.

Perhaps the best solution would be to make your program an ActiveX EXE, or to use an ActiveX DLL. As to *HOW* you would implement this, I do not know off-hand; I have not needed to do anything quite like this.


-Dennis Borg
0
 
LVL 7

Expert Comment

by:Z_Beeblebrox
ID: 6309890
Hi,

The only way I can see this working, without writing your main form's HWND to the registry or to a file, would be to make your app an activeX exe with a separate gatekeeper app. Every time you run the gatekeeper app, it will call the activeX. What it does with that activeX exe depend on your needs.

Zaphod.
0
 

Author Comment

by:tdaugaard
ID: 6309898
uhm .. I'm not really that much into ActiveX DLL's's or how to use them .. I was hoping to keep everything in a single EXE file..
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6309904
>Why don't you write a flag out to the registry to state
>that the Application is running then when you
>try to open another instance, the application will check
>the value of the registry flag and tell you
>whether the application is opened or not.

This may be a feasible solution. The only problem I see with this method is that should the user's system crash, then the registry setting will still be there, and the program will think there is another instance already running.

But if you were storing the window handle of the running application, then the program could use the WinAPI to see if that window still exists. If so, then pass along the information. If not, then there is no previous instance running and the current instance can simply do its own thing.


-Dennis Borg
0
 
LVL 8

Expert Comment

by:Dave_Greene
ID: 6309905
I thought about the registry entry too... but didn't want to put a timer in the Main app...  Still working...
0
 
LVL 7

Expert Comment

by:Z_Beeblebrox
ID: 6309945
How about this:

Add another form to your app, load it at startup, make it invisible, and then send messages to it... You can give it any caption you want, make it something unique.

Zaphod.
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310126
OK just some thoughts
if your app starts and app.previnstance is true then you know its already running and you can terminate it
now the only problem is sending the command line to the allready running instance
if you have on a loaded form in your app a textbox
then the 2e instance can send its commandline to this textbox and terminate
this textbox is only used just for that
if the 2e instance sends its commandline to this textbox
the 1e instance will receive a Text_Change event and knows it has to do someting with it
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310144
and to make sure you dont find just a random other vb program give the textbox a name that is probably unique
maybe use a guid for it ?
0
 

Author Comment

by:tdaugaard
ID: 6310164
pierrecampe .. that was a good idea ... but, how would I go around and actually code it ?
0
 

Author Comment

by:tdaugaard
ID: 6310171
Well I could just give the textbox a long name like

txtAssignmentTimeCommandLine
(my program is called AssignmentTime)
0
 
LVL 1

Expert Comment

by:Aaron_Young
ID: 6310250
I believe someone mentioned it before, but you can use an ActiveX EXE (Out-of-process server) as a part of your application which can then be used to "pass" the command line of a new instance to an already open instance of your application.  

Here's an example I created a while ago, called "AppExtender" which does just what you're looking for...


'----------------------------------------------------------
'Create a new ActiveX EXE, calling it "AppExtender", then add the following:
'----------------------------------------------------------
'
'----------------------------------------------------------
'In a Class Module Called:  CommandLine
'Instancing:                2 - PublicNotCreatable
'
Public Event Change()
Private sString As String

Public Property Get szString() As String
    'Return the Value of the szString Property
    szString = sString
End Property

Public Property Let szString(ByVal vNewValue As String)
    'Set the Value of the szString Property and Trigger
    'the Change Event
    sString = vNewValue
    RaiseEvent Change
End Property
'----------------------------------------------------------

'----------------------------------------------------------
'In a Class Module Called:  Connector
'Instancing:                5 - MultiUse
'
Private Sub Class_Initialize()
    'If the Global CommandLine Object isn't Initialized, then Initialize it
    If oCommandLine Is Nothing Then
        Set oCommandLine = New CommandLine
    End If
    'Increment the number of Connections to this Object
    lConnected = lConnected + 1
End Sub

Private Sub Class_Terminate()
    'Decrement the Number of Connections to this Object
    lConnected = lConnected - 1
    'If the No. of Connections is Zero, Destroy the Global
    'Command Line Object, Releasing the ActiveX EXE
    If lConnected = 0 Then Set oCommandLine = Nothing
End Sub

Public Property Get CommandLine() As CommandLine
    'Return a Reference to the Global CommandLine Object
    Set CommandLine = oCommandLine
End Property
'----------------------------------------------------------

'----------------------------------------------------------
'In a Standard Module..
'Global CommandLine Object
Public oCommandLine As CommandLine
'No. of Current Connections
Public lConnected As Long
'----------------------------------------------------------




Compiile the ActiveX EXE, then in a new "Standard EXE" project use it in the following manor (add a reference to the new "AppExtender" ActiveX EXE):

'--------------------------------------------------------
' In a Standard Module: (Set "Sub Main" as the Startup Object)
'
'Create a Private Instance of the CommandLine Object
'So that we can still change the Commandline if
'we have to unload this Instance..
Private oCommandLine As AppExtender.CommandLine
'Create a Public Connector Object, used to get a
'Reference to the Globally Shared CommandLine Object..
Public oConnector As New AppExtender.Connector

Sub Main()
    'Get the CommandLine Object..
    Set oCommandLine = oConnector.CommandLine
    'If this is the 1st Instance, show the Form
    If Not App.PrevInstance Then Form1.Show
    'Set the CommandLine Object's szString Property to
    'this instances CommandLine, Triggering the Change
    'Event of the CommandLine Object in the Other Instance.
    oCommandLine.szString = Command
End Sub
'---------------------------------------------------------


'---------------------------------------------------------
' In a Form with a RichTextbox control:
'
'Create a Private Instance of the CommandLine Object with
'the Change Event, which will Trigger Each Time the szString
'Property of the CommandLine Object is Changed, in any App.
Private WithEvents oCommandLine As AppExtender.CommandLine

Private Sub Form_Load()
    'Get the CommandLine Object from the Public Connector
    Set oCommandLine = oConnector.CommandLine
End Sub

Private Sub oCommandLine_Change()
    'Triggered when the value of szString changes
    Dim vFiles As Variant
    Dim lFile As Long
    Dim iFile As Integer
    Dim sFile As String
    Dim sCommandline As String
   
    sCommandline = oCommandLine.szString
   
    RichTextBox1.Text = ""
   
    'Split the command line into it's components
    vFiles = Split(sCommandline, " ")
   
    'Verify each file before attempting to open it
    For lFile = 0 To UBound(vFiles)
        If Len(Dir(vFiles(lFile))) Then
            iFile = FreeFile
            Open vFiles(lFile) For Binary Access Read As iFile
                sFile = Space(LOF(iFile))
                Get #iFile, , sFile
            Close iFile
            'Append each file to the RichTextbox (Holds more than a Standard Textbox)
            RichTextBox1.SelText = sFile & vbCrLf
        End If
    Next
   
End Sub
'---------------------------------------------------------

Compile the EXE, then drag and drop a text file onto it, it will load and display the contents of the Text file.

Drag antoher file onto the EXE and the same instance will show the new Text files contents.

Regards,

- Aaron.


If you want you can download the project files I created from:  http://www.pressenter.com/~ajyoung/downloads/AppExtender.zip
0
 

Author Comment

by:tdaugaard
ID: 6310289
Aaron_Young .. THAT looks like something that i can use .. but, how is the AppExtender terminated ?
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 6310304
You could use a technique like the following to send the command$ to the previous instance.  Note the use of an *invisible form* whose caption does not change for obtaining a window handle.

http://www.thescarms.com/vbasic/PassString.asp
0
 
LVL 1

Expert Comment

by:Aaron_Young
ID: 6310366
The ActiveX EXE is terminated when there are not more connections to it, i.e. when your instance of your application is terminated.

That's what the "lConnected" variable tracks in the ActiveX EXE module, when that hit's Zero it closes.

Regards,

- Aaron.
0
 

Author Comment

by:tdaugaard
ID: 6310378
aaron_young .. uhm .. so, when the AppExtender is started .. wouldn't the IConnected counter be zero ?
But anyway .. would I need to have this as an external independant EXE or could I implement the code in my program ?
0
 

Expert Comment

by:clangl
ID: 6310430
tdaugaard,
You could use the Winsock Control and have a Listen to the port on the PC and then throw your information to that port from the other executables.  This is if the Listening EXE is always on the same PC.  Even if it is not you could use other methods (the registry which was stated before) to tell the other executables where your Listening application resides.  

Hope this helps
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310470
Ok here is code
have a form
on this form have a textbox
give this textbox any name you want
make the textbox.text the same as its name<- *VERY IMPORTANT*
start your program in sub main
module code:
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
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
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Const WM_SETTEXT = &HC
Private Const WM_GETTEXT = &HD


Sub Main()
    Dim a$
    Dim windowhandle As Long
    Dim textboxhandle As Long
    Dim ret As Long
    Randomize Timer
    a$ = CStr(Rnd * 1000)
    If App.PrevInstance Then
       windowhandle = FindWindow("ThunderRT6FormDC", vbNullString)
       If windowhandle Then
          textboxhandle = FindWindowEx(windowhandle, 0, "ThunderRT6TextBox", "MyUniqueName")
          If textboxhandle Then
             ret = SendMessage(textboxhandle, WM_SETTEXT, 0, ByVal a$)
          End If
       End If
    Else
       Form1.Show
    End If
End Sub

form code:
Private Sub MyUniqueName_Change()
    MsgBox "I received " & MyUniqueName.Text
End Sub

note that here i used 'MyUniqueName' as the name of the textbox as well as its *initial text*
i had thought that would not be neseccery but for some reason (and i dont know why) it is needed

0
 

Author Comment

by:tdaugaard
ID: 6310496
Now we're getting somewhere ;-)
I'll try this immediatly ...
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310520
Hey I just visited the link by PaulHews
it is basicaly the same but is is however *much* easier to use then my system
for the simple reason that you dont have to search for a classname (you KNOW the windowcaption) and you dont even have to know the class/windowname of the textbox because if there is only 1 textbox it is simply the first child of that window so you could code it like this:
windowhandle = FindWindow(vbNullString, "TheWindowCaption")
    If windowhandle Then
       textboxhandle = FindWindowEx(windowhandle, 0, vbNullString, vbNullString)
       If textboxhandle Then
          ret = SendMessage(textboxhandle, WM_SETTEXT, 0, ByVal a$)
       End If
    End If
indeed easier and more secure
becuase even if there are multiple vb programs running it would still succeed
and with my system you would have to call findwindowex with the handle of the desktop(0) to enumerate all the top-level windows and for every top level window you so find have to call findwindowex again to find the textbox
so if i had to give points they would go to paulhews
(but i wont refuse them)he he
0
 

Author Comment

by:tdaugaard
ID: 6310538
pierrecampe, well .. I don't know the caption of my program as it changes all the time, so I can't use anything that relies on me knowing that.

But .. I can't really get your example to work .. ?
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 6310539
>so if i had to give points they would go to paulhews

Huh?  I think there were a lot of good ideas here.  I was only adding to the mix.  A point split would probably be more appropriate for a question that is collaborative like this one.
0
 
LVL 27

Accepted Solution

by:
Ark earned 200 total points
ID: 6310540
Hi
Take a look at http://www.freevbcode.com/ShowCode.Asp?ID=2201
I used simple trick with command button and it works.

Cheers
0
 

Author Comment

by:tdaugaard
ID: 6310564
Ark .. that was JUST what I needed, but unfortunately it relies on me knowing what the caption of my program is - and I don't :-(
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 

Author Comment

by:tdaugaard
ID: 6310572
Sorry, but I have to go to bed now .. it's 02:00am here in Denmark now :o)
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 6310574
>I don't know the caption of my program as it changes all the time, so I can't use
anything that relies on me knowing that.

Take a look at the example I posted with the *invisible form* that has a caption that doesn't change.
0
 

Author Comment

by:tdaugaard
ID: 6310584
PaulHews, I'll look at it tomorrow .. it's sleeeping time now :o)
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310594
Hi Paul just joking
tdaugaard if you have an invisible form with just 1 textbox
just for doing this and keep it always loaded then you always knows its caption right? you gave it,just dont change it
>>But .. I can't really get your example to work .. ? <<
are you using vb6 ?
an other version will probably have other classname
but you can easely find the classname of a window with spy++
and come to think of it you tried with a compiled program yes ?
have you tried printing the returnvalues to the debug window just to see if you get a handle ie <>0
all i can say is it works for me alright but i thought that the windowname of the textbox would always be the name you gave it and i still cant understand why its text has to initialy be set the same as its name

0
 
LVL 38

Expert Comment

by:PaulHews
ID: 6310607
>i still cant understand why its text has to initialy be set the same as
its name

Because the textbox "name" is not significant to windows, only to VB.  The "window text" that FindWindow uses would be the content of the text box.  So if you change that, it won't work as well the second time around, unless you change it back.
0
 
LVL 27

Expert Comment

by:Ark
ID: 6310615
tdaugaard:
1. you can use temp file/registry to store current caption.
2. if you know part of your caption, you can use this:
http://www.freevbcode.com/ShowCode.Asp?ID=1500

Cheers
0
 
LVL 27

Expert Comment

by:Ark
ID: 6310687
Hi

'======BAS module code===
Private Declare Function EnumWindows& Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long)
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare Function GetParent Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function EnumChildWindows& Lib "user32" (ByVal hParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long)
Dim bFound As Boolean
Dim hButton As Long
Dim sName As String

Function EnumWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
  If bFound Then
     EnumWinProc = 0
     Exit Function
  Else
     Call EnumChildWindows(hWnd, AddressOf EnumChildWinProc, 0)
     EnumWinProc = 1
  End If
End Function

Function EnumChildWinProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
  If GetWndText(hWnd) = sName Then
     EnumChildWinProc = 0
     bFound = True
     hButton = hWnd
     Exit Function
  Else
     EnumChildWinProc = 1
  End If
End Function

Private Function GetWndText(hWnd As Long) As String
  Dim k As Long, sName As String
  sName = Space$(128)
  k = GetWindowText(hWnd, sName, 128)
  If k > 0 Then sName = Left$(sName, k) Else sName = "No name"
  GetWndText = sName
End Function

Public Function GetButtonHandle(ByVal sCaption As String) As Long
  sName = sCaption
  Call EnumWindows(AddressOf EnumWinProc, 0)
  GetButtonHandle = hButton
End Function


'======FORM code===
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
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
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function IsIconic Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
 
Const SW_RESTORE = 9
Const WM_SETTEXT = &HC
Const BM_CLICK = &HF5 ' Top MS secret? Not included into API32.txt
 
'Code
 
Private Sub ProcessCommandLine(sCommand As String)
   'Your code here
'  MsgBox "Excuted with command: " & sCommand
  If Text1.Text <> "" Then Text1.Text = Text1.Text & vbCrLf
  Text1 = Text1 & "Excuted with command: " & sCommand
End Sub
 
Private Sub cmdDummy_Click()
  ProcessCommandLine cmdDummy.Caption
  cmdDummy.Caption = "Ready for command"
End Sub
 
Private Sub Form_Load()
 Dim h As Long, hButton As Long
 Dim sCaption As String, sCommand As String
 cmdDummy.Visible = False
 sCaption = "Your Caption"
 Text1 = ""
 sCommand = Command
 If App.PrevInstance Then
    hButton = GetButtonHandle("Ready for command")
    h = GetParent(hButton)
    ActivateWindow h
    SendMessage hButton, WM_SETTEXT, 0, ByVal sCommand
    SendMessage hButton, BM_CLICK, 0, ByVal 0&
    Unload Me
    Exit Sub
 Else
    Caption = sCaption
    cmdDummy.Caption = sCommand
    cmdDummy_Click
 End If
End Sub
 
Private Sub ActivateWindow(h As Long)
 If h Then
    If IsIconic(h) Then
        Call ShowWindow(h, SW_RESTORE)
    End If
    Call SetForegroundWindow(h)
 Else
    Exit Sub 'no need to continue.
 End If
End Sub

0
 
LVL 27

Expert Comment

by:Ark
ID: 6310693
PS
Forgot to remove sCaption =... and Caption = ... from form code. It's now not nesessary

Cheers
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310703
Hi Paul
>>Because the textbox "name" is not significant to windows<<
yes thats what i supposed
but i could not understand why my system *always* works,not only the first time
and after the first time i dont findwindowex for the text that is then in the textbox
i always findwindowex for the initial text
but i think i understand it now:
the windowname of a textbox gets set to its text when it is loaded and stays the same even if you change its text
0
 
LVL 5

Expert Comment

by:KDivad
ID: 6310755
Maybe I'm just too old-fashioned here, but I'd use a much simpler method that doesn't involve finding the window or external components or anything:

Use DDE! Yes, I know, people hate it 'cause it's slow, but you're only passing a short string. Something like this:

If App.PrevInstance Then
    Text1.LinkExecute Command$
    Exit Sub 'No need for end. Simply exit before anything's loaded
End If

And in the _LinkExecute event:

MsgBox CmdStr
Cancel = False

Simple, efficient, works.
0
 
LVL 5

Expert Comment

by:KDivad
ID: 6310757
P.S.

_LinkExecute would be a good place to restore your app and move it to the front (SetForegroundWindow?) like it should.
0
 
LVL 27

Expert Comment

by:Ark
ID: 6310780
BTW, recently submitted above code to change old one at FreeVBCode.com, so you can download it from above link.

Cheers
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6310795
Hi Ark
I liked your code very much ecspecially the enumproc
but to stay in the running
code that will work no matter what the form caption is
and no matter how many programs are running
(your code does that to)
and more important no matter how many vb programs are running
the only requeriment is that the textbox initial text be unique amongst the running vb programs
note that textboxes in non-vb programs can have the same windowname and the code will only find the vb prog textbox

module code:
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
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Const WM_SETTEXT = &HC
Private Const WM_GETTEXT = &HD

Sub Main()
    Dim a$
    Dim TheWindow As Long
    Dim windowhandle As Long
    Dim textboxhandle As Long
    Dim ret As Long
    Randomize Timer
    a$ = CStr(Rnd * 1000)
    If App.PrevInstance Then
       textboxhandle = 0
       TheWindow = 0
       Do While textboxhandle = 0
          windowhandle = FindWindowEx(0, TheWindow, "ThunderRT6FormDC", vbNullString)
          If windowhandle Then
             textboxhandle = FindWindowEx(0, 0, vbNullString, "MyUniqueName")
             If textboxhandle Then 'found our textbox
                ret = SendMessage(textboxhandle, WM_SETTEXT, 0, ByVal a$)
             End If
          Else
             Exit Do 'no running vb prog found
          End If
          TheWindow = TheWindow + 1
       Loop
    Else
       Form1.Show
    End If
End Sub

form code:
Private Sub MyUniqueName_Change()
    MsgBox "I received " & MyUniqueName.Text
End Sub
and again for this to work the textbox name and text must initialy be the same

0
 
LVL 27

Expert Comment

by:Ark
ID: 6310902
Hi
pierrecampe:
your approac is similiar to my - you also use hidden control. But FindWindow function and looking for VB form may fail. Whats about MDI? Or more then one VB form opened?
In case of using EnumWindows you can use really unique Button (or TextBox) name, "ready for command is just a sample, you can use smth like "123-abcd-456-xyz" and be sure not other button with same name exist.

Cheers
0
 
LVL 1

Expert Comment

by:Aaron_Young
ID: 6311018

You would include the ActiveX EXE as a separate component to your application, the whole point is it runs outside of your application so that it can communicate between instances.

The reason it wouldn't close when you first initialize it is because when you initialize the counter get's incremented to 1.

I garauntee this solution will work, I use it.
Download the example I put out there, (if you haven't already.), it relies in no way on the caption of your application.

Regards,

- Aaron
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6311137
Hi Ark
may i correct you
it *will* work under *all* circumstances
as long as there is not another VB program running with the same windowname for a textbox
and as long as it is not a MDIForm (back to that in a minute)
however there is a bug in the code:
where it says:
textboxhandle = FindWindowEx(0, 0, vbNullString, "MyUniqueName")
that has to be:
textboxhandle = FindWindowEx(windowhandle, 0, vbNullString, "MyUniqueName")
(sorry for not having tested)
and here is the code if the textbox is on a MDIChild
module code:
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
Public Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Public Const GW_CHILD = 5&
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Const WM_SETTEXT = &HC
Private Const WM_GETTEXT = &HD

Sub Main()
    Dim a$
    Dim TheWindow As Long
    Dim TheWindow2 As Long
   
    Dim windowhandle As Long
    Dim windowhandle2 As Long
    Dim textboxhandle As Long
    Dim ret As Long
    Randomize Timer
    a$ = CStr(Rnd * 1000)
    If App.PrevInstance Then
       textboxhandle = 0
       TheWindow = 0
       Do While textboxhandle = 0
          windowhandle = FindWindowEx(0, TheWindow, "ThunderRT6MDIForm", vbNullString)
          If windowhandle Then
             windowhandle2 = GetWindow(windowhandle, GW_CHILD)
             TheWindow2 = 0
             Do While textboxhandle = 0
                windowhandle = FindWindowEx(windowhandle2, TheWindow2, "ThunderRT6FormDC", vbNullString)
                If windowhandle Then
                   textboxhandle = FindWindowEx(windowhandle, 0, vbNullString, "MyUniqueName")
                   If textboxhandle Then 'found our textbox
                      ret = SendMessage(textboxhandle, WM_SETTEXT, 0, ByVal a$)
                   End If
                Else
                    Exit Do 'no mdi childform found
                End If
                TheWindow2 = TheWindow2 + 1
             Loop
          Else
             Exit Do 'no running vb mdi prog found
          End If
          TheWindow = TheWindow + 1
       Loop
    Else
       MDIForm1.Show
    End If
End Sub

form code does not change
and indeed the only technical difference is you use a callback to enumerate the windows
while i do it inline
now i have not tested your code but i think that there is margin for failure
if another non-VB program by chance has the same windowname as your commandbutton it may fail
ok i know thats very inprobable
but with my method there can be any number of non-VB programs with the same windowname for a control and it will still succeed
and are you sure enumwindows will return the handle of a MDIForms clientarea
 
           
           
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6311188
Hi Ark me again
I ran your code
and i admid it is *much* better than mine
so if i have to do this in the future i'll use your code
Thanks
0
 
LVL 27

Expert Comment

by:Ark
ID: 6311286
Thanks :)
0
 

Author Comment

by:tdaugaard
ID: 6311630
'morning :o)

I've looked at KDivad's DDE example, and if I can get it to work that is what I'll use. It seems more secure than the other examples with finding the window and stuff.

But, I can't get it to work .. it says "DDE method invoked with no channel open" ...
0
 

Author Comment

by:tdaugaard
ID: 6313770
Soo .. I've got something to work now .. I've used an invisible form to do it.

So ... who should get the points ?
You all contributed a lot to this Q .. so ?
0
 
LVL 8

Expert Comment

by:DennisBorg
ID: 6314036
>Soo .. I've got something to work now .. I've used an
>invisible form to do it.
>
>So ... who should get the points ?
>You all contributed a lot to this Q .. so ?

Off hand, I would say that you should award the points to the one who suggested using the invisible form.

Alternatively, you could divide the points you offered amongst those you feel deserving. This would be done by reducing the number of points for this question, and creating additional "questions" for the others you want give points to.

For example, suppose you feel you should divide the points between two people. You would reduce the points for this question from 200 points down to 100 points. I don't know if you'd need assistance from Customer Service for this or not. Once you have the points reduced, then you'd choose the first person's answer, thus awarding him/her the 100 points.

Then you would create a new 100-point "question", entitled something like, "Points For XXXXXX". The "question" could be merely "Thank you for your help with the following question" and include a hyperlink to the original question.


Take whichever course of action you feel appropriate.


-Dennis Borg
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 6314061
Post a zero point question in Community Support if you want to split the points between two or more experts.  Post the URL, tell them which experts you wish to award and how many points to each.

http://www.experts-exchange.com/jsp/qList.jsp?ta=commspt
0
 
LVL 5

Expert Comment

by:KDivad
ID: 6318241
<<But, I can't get it to work .. it says "DDE method invoked with no channel open" ...>>

That's expected. I don't usually provide the complete answer until the asker indicates an interest. If you are still interested, I will point out the appropriate properties that need to be changed...
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 6320193
Hi KDavid
i'm not tdaugaard
but if you have a foolproof DDE method i am *very* interested
0
 
LVL 5

Expert Comment

by:KDivad
ID: 6320216
Foolproof? I suppose it is. It's never given me trouble in the rare cases I've used it...

Back in a few...
0
 
LVL 5

Expert Comment

by:KDivad
ID: 6320279
Like I said, I rarely use it so I had to test and make sure I remembered how.

Pick a form in your app, doesn't matter in the slightest what else the form is used for as long as it's loaded at all times (Form1). Pick a textbox (or any other control that has the Link* properties) on that form. Same as the form, it doesn't matter what else it's used for (Text1).

Set Form1's LinkTopic to pretty much anything that would also be valid as a form name.
Set Form1's LinkMode to source.
Set Text1's LinkTopic property equal to "App.EXEName|Form1's LinkTopic". If the EXEName is going to be MyApp and Form1's LinkTopic is MyForm, then set Text1's LinkTopic to "MyApp|MyForm".
Set Text1's LinkItem property to Text1.

In Sub Main(), add this:

    If App.PrevInstance Then
'Opening a link can fail if the user starts multiple instances too quickly.
'Since it would probably be accidental, just drop out of the sub on an error.
On Error Resume Next
        Text1.LinkMode = 2 'Manual
        If Err.Number <> 0 Then
            Text1.LinkExecute Command$
            Text1.LinkMode = 0 'Close the link
        End If
        Exit Sub
    End If
On Error Goto 0 'Or Resume to your normal handler.

Form1's LinkExecute event: The value passed by the other app will be in CmdStr. Set Cancel to False to let the calling app know you accepted the command.

That should do it!
0
 
LVL 7

Expert Comment

by:Z_Beeblebrox
ID: 6325558
Hi,

Goto http://www.mvps.org/vb/, click on samples, and then previnst.zip. That should tell you everything you need to know.

Zaphod.
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7156393
Hi tdaugaard,
It appears that you have forgotten this question. I will ask Community Support to close it unless you finalize it within 7 days. I will ask a Community Support Moderator to:

    Save as PAQ -- No Refund.

    *** I have no idea how to distribute points here.  Anyway, as far as I can see, nobody hit upon the obvious expedient of posting a custom message to HWND_BROADCAST and nobody thought of CreateMutex...

tdaugaard, Please DO NOT accept this comment as an answer.
EXPERTS: Post a comment if you are certain that an expert deserves credit.  Explain why.
==========
DanRollins -- EE database cleanup volunteer
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 7156933
"obvious expedient"

Why don't you post sample code if it's that obvious.  
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7157569
PaulHews,
You can check the SendMessage API to read about HWND_BROADCAST; that should spark the main idea.  Perhaps I should not have posted that comment, but there are a dozen screwball suggestions here and not one employs the simple technique that is commonly used in C++ programming.

I am wearing my 'cleanup crew' hat and not my 'expert' hat.  Do you have a recommendation as to who should get points here?
-- Dan
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 7157826
>not one employs the simple technique that is commonly used in C++ programming.

Yeah I thought so, but in VB that would require subclassing, unless I'm mistaken, and that's another messy can of worms.  If these solutions seem like screwball hacks, well, welcome to our world. ;)


>Do you have a recommendation as to who should get points here?

In terms of a single answer, I think Ark's is probably the best.  
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7157943
Thanks.  I'll continue listening in case there are any other opinions.  -- Dan
0
 
LVL 6

Expert Comment

by:pierrecampe
ID: 7158096
DanRollins
>>Anyway, as far as I can see, nobody hit upon the obvious expedient of posting a custom message to HWND_BROADCAST and nobody thought of CreateMutex...
i really do not see how you can send a command line to a previous instance of your app by sending a message to all top level windows, or by using a mutex
it can be used to see if there is a previous instance of your app but for that app.previnstance is  a lot easier
>>but there are a dozen screwball suggestions
as there are some really good ones
if someone deserves the points here i think its Ark
please tell us how you think HWND_BROADCAST or a mutex could be used to send a command line to a previous instance of an app
 
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7158386
>>please tell us how you think HWND_BROADCAST or a mutex could be used to send a command line to a previous
>> instance of an app

I mentioned mutex because the PrevInstance value in the WinMain() in C++ is always 0 in 32-bit apps.  It seems that this is not true in VB (I wonder how VB knows if another instance is running...), so it appears that that part of what I said is irrelevant.

How I pass command-line paramters in my C++ programs is this:
 
    int nMyMsg= RegisterWindowMessage("YoungHotness");
    SendMessage( HWND_BROADCAST, nMyMsg, 0, 0 );

Then I have a handler for nMyMsg which checks for wParam=1.  When I get that, I know I am the newcomer.  I prepare a COPYDATA structure (rCD) with my command parameters and do a
    SendMessage( hwdWhoSentIt, WM_COPYDATA, hWndNewMe, &rCD).
And I shut down.

If I ever get an nMyMsg message in which wParam=0, I am the original window.  I post back a message to the window that sent it:
   PostMesage( hWndBroadcaster, nMsg, 1, 0);  

I also have a handler for WM_COPYDATA.  When I get one of these messages, I copy the data to my own local storage, and at an appropriate moment unminimize myself and process the command line.  It sounds perhaps complicated, but it boils down to:

New: Is there a window out there that recognizes this?
Old: Yes!  Here I am!
New: Well here's some data for you!
New: Goodbye, cruel world!
Old: I'm in charge around here.  Look at me!

-- Dan
0
 
LVL 38

Expert Comment

by:PaulHews
ID: 7158410
>I wonder how VB knows if another instance is running...),<

It is possible that behind the scenes it checks the mutex to see if the new instance can own it just as you would in c++.  But yes, App.PrevInstance actually works in VB, it is not a left-over from 16 bit windows.

>int nMyMsg= RegisterWindowMessage("YoungHotness");
   SendMessage( HWND_BROADCAST, nMyMsg, 0, 0 );

You can do this in VB, but you would have to subclass the window (form) message queue to be able to receive the messages.  Subclassing in VB is tricky and the subclassing code is difficult to debug in the IDE.  It is easy enough if you use a subclassing component, but otherwise it can introduce some *interesting* bugs.  Thus the tricky use of messages that windows will handle automatically (like wm_settext) to avoid the hassle of subclassing.
0
 
LVL 27

Expert Comment

by:Ark
ID: 7159000
Hi
If you insist on CreateMutex method, here it is :)

'====================================================
Option Explicit
Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (ByVal lpMutexAttributes As Long, ByVal bInitialOwner As Long, ByVal lpName As String) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Private Function IsAppExists(sApplicationTitle As String) As Boolean
    Static hMutex As Long
    Const ERROR_ALREADY_EXISTS = 183&
   
    If IsIDE Then
'CreateMutex function create mutex for main exe thread, so
'if you run 2 different apps in ide, the main exe will be
'vb.exe and CreateMutex fail
        IsAppExists = App.PrevInstance
    ElseIf hMutex Then
        'You call function from this instance
        IsAppExists = False
    Else
        'Attempt to create a Mutex object
        hMutex = CreateMutex(ByVal 0&, 1, sApplicationTitle)
        If (Err.LastDllError = ERROR_ALREADY_EXISTS) Then
           'Mutex object with this name already exists,
           'another copy already open
           CloseHandle hMutex
           hMutex = 0
           IsAppExists = True
        Else 'Mutex was created successfully
           IsAppExists = False
        End If
    End If
End Function

Private Function IsIDE() As Boolean
    On Error Resume Next
    Debug.Print 1 / 0
    IsIDE = (Err = 0)
End Function

'====================================================

Yes, this is the most 'correct' method and if you need only to check previnstance existance seems it's best choice. But to pass parameters into previnstance you have to subclass window and use SendMessage, while with the simple trick with hidden control you join this two tasks into one and don't need subclassing.

Cheers
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7159070
<hanging head in shame>  I should know better than making suggestions outside of my area of expertise.

Now I'm remembering why I normally program in C++;  if the most basic of Windows operations -- sending and receiving a valid, documented window message -- is made difficult by a language, the drawbacks are enormous.  This is the wrong place to badmouth VB :-) so I'll just grab my mop and bucket and slink off into the night...

-- Dan
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 7164227
Modified Recommendation:

     Accept Ark's comment as an answer

DanRollins -- EE database cleanup volunteer
0
 
LVL 1

Expert Comment

by:Computer101
ID: 7182398
Comment from expert accepted as answer

Computer101
E-E Moderator
0

Featured Post

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

When designing a form there are several BorderStyles to choose from, all of which can be classified as either 'Fixed' or 'Sizable' and I'd guess that 'Fixed Single' or one of the other fixed types is the most popular choice. I assume it's the most p…
If you need to start windows update installation remotely or as a scheduled task you will find this very helpful.
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…
This lesson covers basic error handling code in Microsoft Excel using VBA. This is the first lesson in a 3-part series that uses code to loop through an Excel spreadsheet in VBA and then fix errors, taking advantage of error handling code. This l…

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now