Page 1 of 2

how to run the program only once (windows)

Posted: Mon Oct 24, 2005 5:09 pm
by Dr. Dri
Code updated For 5.20+

No mutex
No semaphore
No file

Because if the program crash it won't be destroyed and you won't be able to relaunch the program (it happens with the PureBasic IDE but i doesn't crash since it's no more a beta :lol:)

It works with windows 98 AND windows XP (don't know for 95/ME/2000/2k3)
Create executable and try to run several instances of the program at the same time ;)

Code: Select all

#TH32CS_SNAPHEAPLIST = %0001
#TH32CS_SNAPPROCESS  = %0010
#TH32CS_SNAPTHREAD   = %0100
#TH32CS_SNAPMODULE   = %1000
#TH32CS_SNAPALL      = %1111

;     Structure PROCESSENTRY32
;       dwSize.l
;       cntUsage.l
;       th32ProcessID.l
;       *th32DefaultHeapID.l
;       th32ModuleID.l
;       cntThreads.l
;       th32ParentProcessID.l
;       pcPriClassBase.l
;       dwFlags.l
;       szExeFile.b[#MAX_PATH]
;       th32MemoryBase.l
;       th32AccessKey.l
;     EndStructure

DataSection
  
  Instance_Buffer:
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  Data.l 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  End_Instance_Buffer:
  
EndDataSection

Global MainInstance.l

Procedure.l CountProcesses(ExeFile.s)
  Protected Count.l, Proc32.PROCESSENTRY32, Temp.s
  Protected Kernel32ID.l, PSapiID.l, SnapshotID.l
  
  ExeFile       = LCase(ExeFile)
  Proc32\dwSize = SizeOf(PROCESSENTRY32)
  Kernel32ID    = OpenLibrary(#PB_Default, "kernel32.dll")
  
  If Kernel32ID
    PSapiID    = OpenLibrary(#PB_Default, "psapi.dll")
    SnapshotID = CallFunction(Kernel32ID, "CreateToolhelp32Snapshot", #TH32CS_SNAPPROCESS, #Null)
    
    If SnapshotID
      
      If CallFunction(Kernel32ID, "Process32First", SnapshotID, Proc32)
        Repeat
          
          If PSapiID
            ProcessID = OpenProcess_(#PROCESS_QUERY_INFORMATION|#PROCESS_VM_READ, #False, Proc32\th32ProcessID)
            Temp = Space(#MAX_PATH)
            CallFunction(PSapiID, "GetModuleFileNameExA", ProcessID, #Null, @Temp, #MAX_PATH)
            Temp = Trim(Temp)
            CloseHandle_(ProcessID)
          Else
            Temp = PeekS(@Proc32\szExeFile, #MAX_PATH)
          EndIf
          
          If LCase(Temp) = ExeFile
            If Count = 0
              MainInstance = Proc32\th32ProcessID
            EndIf
            Count + 1
          EndIf
          
        Until CallFunction(Kernel32ID, "Process32Next", SnapshotID, Proc32) = #False
      EndIf
      
      CloseHandle_(SnapshotID)
    EndIf
    
    CloseLibrary(PSapiID)
    CloseLibrary(Kernel32ID)
  EndIf
  
  ProcedureReturn Count
EndProcedure

Procedure.s GetExeFile()
  Protected Location.s
  
  Location = Space(#MAX_PATH)
  GetModuleFileName_(#Null, Location, #MAX_PATH)
  
  ProcedureReturn location
EndProcedure

Procedure.l WriteMainInstanceBuffer(Length.l)
  Protected Write.l, ProcessID.l
  
  If Length > 0 And Length <= (?End_Instance_Buffer - ?Instance_Buffer)
    ProcessID = OpenProcess_(#PROCESS_ALL_ACCESS, #False, MainInstance)
    
    If ProcessID
      Write = WriteProcessMemory_(ProcessID, ?Instance_Buffer, ?Instance_Buffer, Length, #Null)
      CloseHandle_(ProcessID)
    EndIf
  EndIf
  
  ProcedureReturn Write
EndProcedure

;-example

;create or get a custom message
Global WM_MYAPPCUSTOMMESSAGE
WM_MYAPPCUSTOMMESSAGE = RegisterWindowMessage_("my_app_custom_message")

;check for existing process
If CountProcesses( GetExeFile() ) > 1
  
  Define.s param, params
  
  ;get all params in a new line
  Repeat
    param = ProgramParameter()
    If params
      params + #CRLF$
    EndIf
    params + param
  Until param = #NULL$
  
  If params
    ;send the string to the buffer
    PokeS(?Instance_Buffer, params)
    WriteMainInstanceBuffer(Len(params))
    
    ;send the custom message
    SendMessage_(#HWND_BROADCAST, WM_MYAPPCUSTOMMESSAGE, "GET_PARAMS", Len(params))
  Else
    ;send a 'no param' message
    SendMessage_(#HWND_BROADCAST, WM_MYAPPCUSTOMMESSAGE, "NO_PARAMS", 0)
  EndIf
  
  ;only one instance so just end
  End
EndIf

;here starts the "MainInstance" program

;this var is used for small stats ^^
Global nMessages

;procedure where you receive the custom message
Procedure WindowCallback(WindowID.l, Message.l, wParam.l, lParam.l)
  
  If Message = WM_MYAPPCUSTOMMESSAGE
    
    nMessages + 1
    
    CustomMessage.s = PeekS(wParam)
    
    ;get the program parameter if any
    If CustomMessage = "GET_PARAMS"
      Text.s = "Here is a new custom message :" + #CRLF$ + PeekS(?Instance_Buffer, lParam)
    ElseIf CustomMessage = "NO_PARAMS"
      Text.s = "Here is a 'no param' message !" + #CRLF$
    EndIf
    
    ;update gadgets' text
    Text = GetGadgetText(1) + Text
    SetGadgetText(1, Text)
    SetGadgetText(0, "Custom messages : " + Str(nMessages))
    
    SetForegroundWindow_( WindowID(0) )
    
  EndIf
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

;the only once running program
If OpenWindow(0,0,0,320,280,"Instance",#PB_Window_SystemMenu|#PB_Window_ScreenCentered) 
  
  ;gadgets to get the result of the custom message
  TextGadget  (0,10,10,300, 20,"No custom message received")
  EditorGadget(1,10,40,300,230,#PB_String_ReadOnly)
  
  ;without this line, nothing is possible
  SetWindowCallback( @WindowCallback() )
  
  Repeat
    ;enjoy
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Dri ;)

Posted: Mon Oct 24, 2005 5:40 pm
by blueznl
oooooo nice
now i just need to expand it a little... i want every additional call to pass it's command line parameters to the first running program...

Posted: Mon Oct 24, 2005 6:32 pm
by Joakim Christiansen
blueznl wrote:oooooo nice
now i just need to expand it a little... i want every additional call to pass it's command line parameters to the first running program...
That's also what I want to do, real handy for media players...

Posted: Mon Oct 24, 2005 7:58 pm
by Dr. Dri
okay i will try to do it
maybe with sendmessage_()
(need to search about that)

[edit]
i think i can find something in the source code of the old PB IDE

Dri

Re: how to run the program only once (windows)

Posted: Mon Oct 24, 2005 9:06 pm
by PB
I prefer this shorter version (not my code):

Code: Select all

; Next single line prevents app being run more than once.  Much shorter and sweeter than using a mutex!  ;)
a=CreateSemaphore_(0,0,1,"MyApp") : If a<>0 And GetLastError_()=#ERROR_ALREADY_EXISTS : CloseHandle_(a) : End : EndIf
;)

Re: how to run the program only once (windows)

Posted: Tue Oct 25, 2005 1:47 am
by okasvi
PB wrote:I prefer this shorter version
Dr. Dri wrote: No mutex
No semaphore
No file

Because if the program crash it won't be destroyed and you won't be able to relaunch the program (it happens with the PureBasic IDE but i doesn't crash since it's no more a beta Laughing)
how often that kind of crashes does happen? so which one is "better" way?

Re: how to run the program only once (windows)

Posted: Tue Oct 25, 2005 4:51 am
by PB
> so which one is "better" way?

The short semaphore version works great, even if the app crashes (I've
tested it to make sure).

Posted: Tue Oct 25, 2005 7:55 am
by Dr. Dri
@PB
i tried file, mutex and semaphore and got this problem with each one :shock:
(windows 98 se)

@blueznl
i did something to send params to the "running once" program. real messy but working. (i have updated the code)

Dri

Posted: Tue Oct 25, 2005 12:20 pm
by PB
> i tried file, mutex and semaphore and got this problem with each one

Hmm, well the only other sure-fire way is if your app has a window caption
that no other window will ever have (eg. "MyApp v1.00 by PB") -- you can
then just do a FindWindow for it, and if it's found, it means one instance is
already running and you can abort the second instance. However, this has
its own problems, such as users who run apps that change the window text
of every window, and so on.

Posted: Tue Oct 25, 2005 12:22 pm
by DoubleDutch
I just check to see if a window of the same name exists...

If there is a crash then the window is destroyed.

Code: Select all

FindName$="ReportBuilder"
WindowID=FindWindow_("WindowClass_0",FindName$)
If WindowID=0
  WindowID=FindWindow_("WindowClass_1",FindName$)
EndIf
If WindowID
  MessageRequester(Name$,"Only one instance of ReportBuilder allowed at a time!")
  End
EndIf
Works for me :)

Posted: Tue Oct 25, 2005 12:28 pm
by Dr. Dri
You're right but for programs which don't use any window it won't work... that's why i made this. Of course if there is no window i don't know how to get the broadcast message...

Dri ;)

Posted: Tue Oct 25, 2005 12:54 pm
by dell_jockey
@ Dr.Dri

merci bien, that's very helpful for one of my projects!

Posted: Tue Oct 25, 2005 5:48 pm
by Dr. Dri
dell_jockey wrote:@ Dr.Dri

merci bien, that's very helpful for one of my projects!
de rien, that's also very helpful for one of my projects ;)
i appreciate when someone share this kinda tip so i just do the same

Dri :)

Posted: Tue Oct 25, 2005 9:36 pm
by blueznl
gonna' have another look at it, thanks dri, if i cook something up i'll post it here...

Posted: Wed Oct 26, 2005 8:46 am
by Dr. Dri
Updated :D
I found a better way than registry to send data. I use a datasection and write directly in it. It's now a very clean code (i think) and i it waors as well

Dri