how to run the program only once (windows)

Share your advanced PureBasic knowledge/code with the community.
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

how to run the program only once (windows)

Post 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 ;)
Last edited by Dr. Dri on Wed Oct 26, 2005 8:43 am, edited 2 times in total.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6161
Joined: Sat May 17, 2003 11:31 am
Contact:

Post 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...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Post 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...
I like logic, hence I dislike humans but love computers.
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post 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
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

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

Post 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
;)
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
okasvi
Enthusiast
Enthusiast
Posts: 150
Joined: Wed Apr 27, 2005 9:41 pm
Location: Finland

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

Post 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?
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

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

Post 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).
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post 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
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post 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.
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
DoubleDutch
Addict
Addict
Posts: 3219
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post 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 :)
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post 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 ;)
dell_jockey
Enthusiast
Enthusiast
Posts: 767
Joined: Sat Jan 24, 2004 6:56 pm

Post by dell_jockey »

@ Dr.Dri

merci bien, that's very helpful for one of my projects!
cheers,
dell_jockey
________
http://blog.forex-trading-ideas.com
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post 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 :)
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6161
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

gonna' have another look at it, thanks dri, if i cook something up i'll post it here...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Dr. Dri
Enthusiast
Enthusiast
Posts: 243
Joined: Sat Aug 23, 2003 6:45 pm

Post 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
Post Reply