Single Instance and co

Share your advanced PureBasic knowledge/code with the community.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Single Instance and co

Post by blueznl »

Okay, it's a bit complex for just single instancing, but it shows a way to synchronice shared memory access, using messages and a mutex. Learning stuff, I guess. And as usual 3.94, but with a little effort that should not be an issue.

It looks longish, but if you take out all comments and (some of the) error checking it isn't that bad :-)

(Edit 1: added protection against buffer overrun.)
(Edit 2: I've also worked out a mailslot version.)

More on mutex here:
http://www.purebasic.fr/english/viewtopic.php?t=27439

... and filemapping objects here:
http://www.purebasic.fr/english/viewtopic.php?t=27457

... and mailslots here:
http://www.purebasic.fr/english/viewtop ... 997#198997

And here's the code:

Code: Select all

; purebasic survival guide - pb3.94
; singleinstance_1.pb - 11.06.2007 ejn (blueznl)
; http://www.xs4all.nl/~bluez/datatalk/purebasic.htm
;
; - single instancing, deduction host vs. client
; - sharing memory between applications
; - handshake mechanism for acces to shared memory between a single host and multiple clients
;
; see also filemapping_1.pb, filemapping_2.pb, mutex_1.pb
; you DEFINITELY want to have the WIN32.HLP file open and at your side
;
;
; (this sourcecode crashes the IDE sometimes, unless I edit it in a text editor and remove the ide options part, weird)
;
; okay, so first of all, all that I was looking for was a good valid way of passing command line parameters to other
; instances... then I went a bit overboard :-)
;
; it's probably easier to use a callback and the WMCOPYDATA message, but here's another approach that allows you to
; send data to another program (window)... or make a single instance of your program :-)
;
; data_size: create an executable and start that TWICE...
;
;
#MUTEX_ALL_ACCESS = $1F0001
#WAIT_TIMEOUT     = $102
#WAIT_ABANDONED   = $80
#WAIT_FAILED      = $FFFFFFFF
#WAIT_OBJECT_0    = $0
;
Structure buffer
  w_host_h.l                           ; handle of host window, easier sending messages to the proper window
  data_max.l                           ; max number of bytes in the data part of the buffer, should always be kept below data_max
  data_size.l                          ; number of bytes currently in the data part
EndStructure
buffer_size = 65536                    ; let's use a 64k buffer
data_max = buffer_size-SizeOf(buffer)  ; which contains max 64k - 12 bytes of data
;
error = #False                                                                 ; will be #True If there were problems
;
; in this example I am registering a 'global' wakeup message, in this specific case (as I am sending the message straight
; to the receiving window) this isn't necessary and I could have used any (valid ie. not yet used) message, but what the
; heck... look for the postmessage_() instruction and replace the w_host_h with #HWND_BROADCAST
;
message_wakeup_id = RegisterWindowMessage_("wakeup")                           ; register the wakeup message
;
; open the window, we'll deal with the title later
;
w_main_nr = OpenWindow(#PB_Any,100,100,400,200,#PB_Window_TitleBar|#PB_Window_SystemMenu,"Interprocess communication sample")
w_main_h = WindowID(w_main_nr)
gl_main_h = CreateGadgetList(w_main_h)
g_list_nr = ListIconGadget(#PB_Any,10,10,WindowWidth()-20,WindowHeight()-60,"Log",2000)
g_exit_nr = ButtonGadget(#PB_Any,WindowWidth()-70,WindowHeight()-40,60,30,"Exit")
;
; global stuff shared by host and client
;
mutex_busy_name.s = "busy"
filemap_buffer_name.s  = "buffer"
;
; flag the busy mutex, if we cannot create it, or it is already owned, then we are not the host
;
mutex_busy_h = CreateMutex_(0,#True,@mutex_busy_name)                          ; create and try to be the owner
lasterror = GetLastError_()
If mutex_busy_h = 0
  AddGadgetItem(g_list_nr,-1,"error opening busy mutex")
  error = #True
ElseIf lasterror = #ERROR_ALREADY_EXISTS
  AddGadgetItem(g_list_nr,-1,"busy mutex already exists")
  CloseHandle_(mutex_busy_h)
  ;
  ; *** client
  ;
  ; we can't create and own the mutex, so it's already in use, so we cannot be the host so we must be a client
  ; (you're still following? :-))
  ;
  MoveWindow(WindowX()+WindowWidth(),WindowY())                                ; make sure windows don't overlap :-)
  AddGadgetItem(g_list_nr,-1,"client!")
  SetWindowTitle(w_main_nr,"Client")
  g_send_nr = ButtonGadget(#PB_Any,WindowWidth()-150,WindowHeight()-40,60,30,"Send")
  ;
  AddGadgetItem(g_list_nr,-1,"waiting...")                                     ; so it's sure we are the host
  Repeat                                                                       ; wait for messages
    event = WaitWindowEvent()
    event_gadget = EventGadgetID()
    Select event
    Case #PB_Event_Gadget
      Select event_gadget
      Case g_exit_nr                                                           ; exit button was pushed
        event = #PB_Event_CloseWindow
      Case g_send_nr                                                           ; send button was pushed
        ;
        ; try to send a message
        ;
        message.s = "test "+Str(Random(999999))                                ; random message
        AddGadgetItem(g_list_nr,-1,"sending "+message)
        ;
        filemap_buffer_h = OpenFileMapping_(#FILE_MAP_WRITE,0,@filemap_buffer_name)  ; open the filemapping object
        lasterror = GetLastError_()
        If filemap_buffer_h = #INVALID_HANDLE_VALUE Or filemap_buffer_h = 0    ; some error
          AddGadgetItem(g_list_nr,-1,"error opening filemapping object")
          CloseHandle_(mutex_busy_h)
          error = #True
        Else                                                                   ; succes
          ;
          mutex_busy_h = OpenMutex_(#MUTEX_ALL_ACCESS,0,@mutex_busy_name)      ; try to open the mutex
          If mutex_busy_h = 0                                                  ; error
            AddGadgetItem(g_list_nr,-1,"can't open busy mutex")
            error = #True
          Else
            ;
            result = WaitForSingleObject_(mutex_busy_h,1000)                   ; try to own the mutex, wait max 1 sec
            If result <> 0                                                     ; eror
              AddGadgetItem(g_list_nr,-1,"troubles owning the busy mutex")
              error = #True
            Else                                                               ; success
              ;
              mapview_p = MapViewOfFile_(filemap_buffer_h,#FILE_MAP_WRITE,0,0,0)  ; map the memory
              *buffer.buffer = mapview_p
              If *buffer\data_max-*buffer\data_size < Len(message)+1           ; make sure message still fits
                AddGadgetItem(g_list_nr,-1,"buffer full")
              Else
                PokeS(*buffer+12+*buffer\data_size,message)                    ; write at next empty spot
                *buffer\data_size = *buffer\data_size+Len(message)+1           ; update data_size counter
                w_host_h = *buffer\w_host_h                                    ; window id of the host
              EndIf
              UnmapViewOfFile_(mapview_p)                                      ; no longer any need for view
              ReleaseMutex_(mutex_busy_h)                                      ; release the mutex
              ;
              ; we could also decide to wake up the host by sending it a message, uncomment the next line to do so,
              ; in which case every message is shown immediately
              ;
              ; PostMessage_(w_host_h,message_wakeup_id,0,0)                   ; wakeup the host
              ;
            EndIf
          EndIf
          CloseHandle_(mutex_busy_h)
        EndIf
        ;
      EndSelect
    EndSelect
    ;
  Until event = #PB_Event_CloseWindow
  ;
Else
  ;
  ; *** host
  ;
  filemap_buffer_h = CreateFileMapping_($FFFFFFFF,0,#PAGE_READWRITE,0,buffer_size,@filemap_buffer_name)
  lasterror = GetLastError_()
  If filemap_buffer_h = #INVALID_HANDLE_VALUE Or filemap_buffer_h = 0          ; some error
    AddGadgetItem(g_list_nr,-1,"error creating filemapping object")
    CloseHandle_(mutex_busy_h)
    error = #True
  ElseIf lasterror = #ERROR_ALREADY_EXISTS                                     ; if we are the host it can't exist yet
    AddGadgetItem(g_list_nr,-1,"filemapping object already exists")
    CloseHandle_(filemap_buffer_h)
    CloseHandle_(mutex_busy_h)
    error = #True
  Else
    ;
    ; okay, so we are the host after all...
    ;
    AddGadgetItem(g_list_nr,-1,"host!")                                        ; so it's sure we are the host
    SetWindowTitle(w_main_nr,"Host")
    ;
    ; any windows event will wake up the host and display any messages in its buffer, with a timer here we can
    ; tell the window to check every 10 seconds for messages, even if there are no other incoming events
    ;
    ; by deliberately sending the host window a message we will also trigger anything in the buffer to be dsiplayed
    ;
    SetTimer_(w_main_h,1,10000,0)
    ;
    mapview_p = MapViewOfFile_(filemap_buffer_h,#FILE_MAP_WRITE,0,0,0)         ; map the memory
    *buffer.buffer = mapview_p
    *buffer\w_host_h = w_main_h
    *buffer\data_max = data_max
    *buffer\data_size = 0
    ;
    ReleaseMutex_(mutex_busy_h)                                                ; we're open for business
    ;
    AddGadgetItem(g_list_nr,-1,"waiting...")                                   ; so it's sure we are the host
    Repeat                                                                     ; wait for messages
      event = WaitWindowEvent()
      event_gadget = EventGadgetID()
      Select event
      Case #PB_Event_Gadget
        Select event_gadget
        Case g_exit_nr                                                         ; exit button was pushed
          event = #PB_Event_CloseWindow
        EndSelect
      Case message_wakeup_id
        AddGadgetItem(g_list_nr,-1,"received wakeup message")
      EndSelect
      ;
      If *buffer\data_size >0                                                  ; check buffer change on every message
        result = WaitForSingleObject_(mutex_busy_h,1000)
        If result <> 0                                                         ; eror
          AddGadgetItem(g_list_nr,-1,"troubles owning the busy mutex")
          error = #True
        Else                                                                   ; success
          p = 0
          While p < *buffer\data_size
            message.s =PeekS(*buffer+12+p)                                     ; read message
            AddGadgetItem(g_list_nr,-1,message)
            p = p+Len(message)+1                                               ; look for the next message
          Wend
          *buffer\data_size = 0                                                ; we emptied the buffer
        EndIf
        ReleaseMutex_(mutex_busy_h)                                            ; and release the mutex
      EndIf
      ;
    Until event = #PB_Event_CloseWindow
    ;
    UnmapViewOfFile_(mapview_p)                                                ; we're done, clean up
    CloseHandle_(filemap_buffer_h)
    CloseHandle_(mutex_busy_h)
    ;
  EndIf
EndIf
;
Last edited by blueznl on Mon Jun 11, 2007 11:33 pm, edited 4 times in total.
( 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... )
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Post by ABBKlaus »

nice example :D runs with little modifications on PB4.10 Beta 1 :shock:
[remove]The only thing i don´t like is to wait 10 seconds for the window to respond :wink: [/endremove]
Heres source :

Code: Select all

; purebasic survival guide - pb4.XX version 
; singleinstance_1.pb - 11.06.2007 ejn (blueznl) 
; http://www.xs4all.nl/~bluez/datatalk/purebasic.htm 
; 
; - single instancing, deduction host vs. client 
; - sharing memory between applications 
; - handshake mechanism for acces to shared memory between a single host and multiple clients 
; 
; see also filemapping_1.pb, filemapping_2.pb, mutex_1.pb 
; you DEFINITELY want to have the WIN32.HLP file open and at your side 
; 
; 
; (this sourcecode crashes the IDE sometimes, unless I edit it in a text editor and remove the ide options part, weird) 
; 
; okay, so first of all, all that I was looking for was a good valid way of passing command line parameters to other 
; instances... then I went a bit overboard :-) 
; 
; it's probably easier to use a callback and the WMCOPYDATA message, but here's another approach that allows you to 
; send data to another program (window)... or make a single instance of your program :-) 
; 
; usage: create an executable and start that TWICE... 
; 
; 
#MUTEX_ALL_ACCESS = $1F0001 
#WAIT_TIMEOUT     = $102 
#WAIT_ABANDONED   = $80 
#WAIT_FAILED      = $FFFFFFFF 
#WAIT_OBJECT_0    = $0 
; 
Structure buffer 
  w_host_h.l                 ; handle of host window, easier sending messages to the proper window 
  maxsize.l                  ; max number of bytes in the buffer, buffer_usage_l should always be kept below buffer_size_max 
  usage.l                    ; number of bytes currently in the buffer 
EndStructure 
; 
error = #False                                                                 ; will be #True If there were problems 
; 
; in this example I am registering a 'global' wakeup message, in this specific case (as I am sending the message straight 
; to the receiving window) this isn't necessary and I could have used any (valid ie. not yet used) message, but what the 
; heck... look for the postmessage_() instruction and replace the w_host_h with #HWND_BROADCAST 
; 
message_wakeup_id = RegisterWindowMessage_("wakeup")                           ; register the wakeup message 
; 
; open the window, we'll deal with the title later 
; 
w_main_nr = OpenWindow(#PB_Any,100,100,400,200,"Interprocess communication sample",#PB_Window_TitleBar|#PB_Window_SystemMenu) 
w_main_h = WindowID(w_main_nr) 
gl_main_h = CreateGadgetList(w_main_h) 
g_list_nr = ListIconGadget(#PB_Any,10,10,WindowWidth(w_main_nr)-20,WindowHeight(w_main_nr)-60,"Log",2000) 
g_exit_nr = ButtonGadget(#PB_Any,WindowWidth(w_main_nr)-70,WindowHeight(w_main_nr)-40,60,30,"Exit") 
; 
; global stuff shared by host and client 
; 
mutex_busy_name.s = "busy" 
filemap_buffer_name.s  = "buffer" 
; 
; flag the busy mutex, if we cannot create it, or it is already owned, then we are not the host 
; 
mutex_busy_h = CreateMutex_(0,#True,@mutex_busy_name)                          ; create and try to be the owner 
lasterror = GetLastError_() 
If mutex_busy_h = 0 
  AddGadgetItem(g_list_nr,-1,"error opening busy mutex") 
  error = #True 
ElseIf lasterror = #ERROR_ALREADY_EXISTS 
  AddGadgetItem(g_list_nr,-1,"busy mutex already exists") 
  CloseHandle_(mutex_busy_h) 
  ; 
  ; *** client 
  ; 
  ; we can't create and own the mutex, so it's already in use, so we cannot be the host so we must be a client 
  ; 
  ResizeWindow(w_main_nr,WindowX(w_main_nr)+WindowWidth(w_main_nr),WindowY(w_main_nr),#PB_Ignore,#PB_Ignore); make sure windows don't overlap :-) 
  AddGadgetItem(g_list_nr,-1,"client!") 
  SetWindowTitle(w_main_nr,"Client") 
  g_send_nr = ButtonGadget(#PB_Any,WindowWidth(w_main_nr)-150,WindowHeight(w_main_nr)-40,60,30,"Send") 
  ; 
  AddGadgetItem(g_list_nr,-1,"waiting...")                                     ; so it's sure we are the host 
  Repeat                                                                       ; wait for messages 
    event = WaitWindowEvent(1) 
    event_gadget = EventGadget() 
    Select event 
    Case #PB_Event_Gadget 
      Select event_gadget 
      Case g_exit_nr                                                           ; exit button was pushed 
        event = #PB_Event_CloseWindow 
      Case g_send_nr                                                           ; send button was pushed 
        ; 
        ; try to send a message 
        ; 
        message.s = "test "+Str(Random(999999))                                ; random message 
        AddGadgetItem(g_list_nr,-1,"sending "+message) 
        ; 
        filemap_buffer_h = OpenFileMapping_(#FILE_MAP_WRITE,0,@filemap_buffer_name)  ; open the filemapping object 
        lasterror = GetLastError_() 
        If filemap_buffer_h = #INVALID_HANDLE_VALUE Or filemap_buffer_h = 0    ; some error 
          AddGadgetItem(g_list_nr,-1,"error opening filemapping object") 
          CloseHandle_(mutex_busy_h) 
          error = #True 
        Else                                                                   ; succes 
          ; 
          mutex_busy_h = OpenMutex_(#MUTEX_ALL_ACCESS,0,@mutex_busy_name)      ; try to open the mutex 
          If mutex_busy_h = 0                                                  ; error 
            AddGadgetItem(g_list_nr,-1,"can't open busy mutex") 
            error = #True 
          Else 
            ; 
            result = WaitForSingleObject_(mutex_busy_h,1000)                   ; try to own the mutex, wait max 1 sec 
            If result <> 0                                                     ; eror 
              AddGadgetItem(g_list_nr,-1,"troubles owning the busy mutex") 
              error = #True 
            Else                                                               ; success 
              ; 
              mapview_p = MapViewOfFile_(filemap_buffer_h,#FILE_MAP_WRITE,0,0,0)  ; map the memory 
              *buffer.buffer = mapview_p 
              PokeS(*buffer+12+*buffer\usage,message)                          ; write at next empty spot 
              *buffer\usage = *buffer\usage+Len(message)+1                     ; update usage counter 
              w_host_h = *buffer\w_host_h                                      ; window id of the host 
              UnmapViewOfFile_(mapview_p)                                      ; no longer any need for view 
              ReleaseMutex_(mutex_busy_h)                                      ; release the mutex 
              ; 
              ; we could also decide to wake up the host by sending it a message, uncomment the next line to do so, 
              ; in which case every message is shown immediately 
              ; 
              ; PostMessage_(w_host_h,message_wakeup_id,0,0)                   ; wakeup the host 
              ; 
            EndIf 
          EndIf 
          CloseHandle_(mutex_busy_h) 
        EndIf 
        ; 
      EndSelect 
    EndSelect 
    ; 
  Until event = #PB_Event_CloseWindow 
  ; 
Else 
  ; 
  ; *** host 
  ; 
  filemap_buffer_h = CreateFileMapping_($FFFFFFFF,0,#PAGE_READWRITE,0,65536,@filemap_buffer_name) 
  lasterror = GetLastError_() 
  If filemap_buffer_h = #INVALID_HANDLE_VALUE Or filemap_buffer_h = 0          ; some error 
    AddGadgetItem(g_list_nr,-1,"error creating filemapping object") 
    CloseHandle_(mutex_busy_h) 
    error = #True 
  ElseIf lasterror = #ERROR_ALREADY_EXISTS                                     ; if we are the host it can't exist yet 
    AddGadgetItem(g_list_nr,-1,"filemapping object already exists") 
    CloseHandle_(filemap_buffer_h) 
    CloseHandle_(mutex_busy_h) 
    error = #True 
  Else 
    ; 
    ; okay, so we are the host after all... 
    ; 
    AddGadgetItem(g_list_nr,-1,"host!")                                        ; so it's sure we are the host 
    SetWindowTitle(w_main_nr,"Host") 
    ; 
    ; any windows event will wake up the host and display any messages in its buffer, with a timer here we can 
    ; tell the window to check every 10 seconds for messages, even if there are no other incoming events 
    ; 
    ; by deliberately sending the host window a message we will also trigger anything in the buffer to be dsiplayed 
    ; 
    SetTimer_(w_main_h,1,10000,0)
    ; 
    mapview_p = MapViewOfFile_(filemap_buffer_h,#FILE_MAP_WRITE,0,0,0)         ; map the memory 
    *buffer.buffer = mapview_p 
    *buffer\w_host_h = w_main_h 
    *buffer\maxsize = 65536-8                                                  ; buffer starts at mapview_p+8 because of the 2 longs 
    *buffer\usage = 0 
    ; 
    ReleaseMutex_(mutex_busy_h)                                                ; we're open for business 
    ; 
    AddGadgetItem(g_list_nr,-1,"waiting...")                                   ; so it's sure we are the host 
    Repeat                                                                     ; wait for messages 
      event = WaitWindowEvent() 
      event_gadget = EventGadget() 
      Select event 
      Case #PB_Event_Gadget 
        Select event_gadget 
        Case g_exit_nr                                                         ; exit button was pushed 
          event = #PB_Event_CloseWindow 
        EndSelect 
      Case message_wakeup_id 
        AddGadgetItem(g_list_nr,-1,"received wakeup message") 
      EndSelect 
      ; 
      If *buffer\usage >0                                                      ; check buffer change on every message 
        result = WaitForSingleObject_(mutex_busy_h,1000) 
        If result <> 0                                                         ; eror 
          AddGadgetItem(g_list_nr,-1,"troubles owning the busy mutex") 
          error = #True 
        Else                                                                   ; success 
          p = 0 
          While p < *buffer\usage 
            Debug p 
            message.s =PeekS(*buffer+12+p)                                     ; read message 
            AddGadgetItem(g_list_nr,-1,message) 
            p = p+Len(message)+1                                               ; look for the next message 
          Wend 
          *buffer\usage = 0                                                    ; we emptied the buffer 
        EndIf 
        ReleaseMutex_(mutex_busy_h)                                            ; and release the mutex 
      EndIf 
      ; 
    Until event = #PB_Event_CloseWindow 
    ; 
    UnmapViewOfFile_(mapview_p)                                                ; we're done, clean up 
    CloseHandle_(filemap_buffer_h) 
    CloseHandle_(mutex_busy_h) 
    ; 
  EndIf 
EndIf 
; 
Regards Klaus
Last edited by ABBKlaus on Mon Jun 11, 2007 7:59 am, edited 2 times in total.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

Hey Claus, that's why I have that optional PostMessage_() in there :-) You didn't uncomment it...
( 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... )
ABBKlaus
Addict
Addict
Posts: 1143
Joined: Sat Apr 10, 2004 1:20 pm
Location: Germany

Post by ABBKlaus »

i totally overlooked that :oops:
My only excuse is : it was ~2:00 in the morning :)

Regards Klaus (with K not C)
Post Reply