X
439 Rate this article:
No rating

INTERNAL/REVIEW: How to make two widget applications running in separate processes communicate

Anonym

Needs to be reviewed for Compliance and IP issues (i.e. .pro file included)]

Topic:

This help article provides an example in which two or more IDL programs running in separate sessions communicate with each other and share data using a mapped region of shared memory . The IDL shared memory routines were introduced in IDL 5.6.

In this example, 2 graphical widget programs (GUIs) will communicate with each other. The first program provides a set of 5 check box widgets that the user can change state by clicking, and the second program will read an array out of shared memory and report the status of the first program's GUI.

The example programs are fully documented explaining the use of the shared memory procedures in IDL, and for those who are unfamiliar with programming graphical interfaces, the example programs will give you a quick introduction to the steps required to create the interfaces.

Warning: Unlike most IDL functionality, incorrect use of IDL's shared memory functionality can corrupt or even crash your IDL process. Proper use of these low level operating system features requires systems programming experience, and is not recommended for those without such experience. You should be familiar with the memory and file mapping features of your operating system and the terminology used to describe such features.

Discussion:
In the example below, there are two widget applications (shared_memory_proc1.pro and shared_memory_proc2.pro).
SHARED_MEMORY_PROC1 maps a block of shared memory in which a data array is stored to be shared with SHARED_MEMORY_PROC2. Elements 0 through (N-1) represent the state of N buttons on SHARED_MEMORY_PROC1 and element N is used as a flag to determine if SHARED_MEMORY_PROC1 is active.

The programs make use of the shared memory functions in IDL: shmmap, shm_var and shmunmap

    ; Mapping a shared memory block
    shmmap, 'shm', shm_size, /byte

    ; Assigning a variable to access shared memory
    shm_var = shmvar('shm')

    ; Unmapping the shared memory block
    shmunmap, 'shm'

SHARED_MEMORY_PROC1 establishes a shared memory region, and both programs create variables to access the shared memory region and share data with each other.

Start one IDL session and run SHARED_MEMORY_PROC1. Then start a completely separate IDL session and run SHARED_MEMORY_PROC2. Alternatively, both programs can be run within the same IDL session by loading and running each program. Multiple copies of each program can be run and all will communicate with the same shared memory region.

;;;;;;;;;;;;;;;
The example code below represents the code from two separate widget application files: shared_memory_proc1.pro and shared_memory_proc2.pro.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; shared_memory_proc1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FILE: SHARED_MEMORY_PROC1.pro

; SHM PROCESS #1
; This pair of programs demonstrates how to have 2 cooperating IDL programs
; acheive inter-process communication and share variables via a shared memory segment.

; In the mainline for this program, a shared memory region named 'shm' was setup
; and assigned a byte array of 6 elements.
; byte[0:4]    Contain the checked status of the 5 check boxes
; byte[5]       The 6th element is set to indicate that PROC#1 is running.
;
; The mainline sets the user value (uvalue) for the GUI to 5 which is the number
; (0 origin) of byte-elements in the shared memory array.
;

  pro clean1_event, gui1

; Retreive the user value for the GUI panel which equals the
; the size (in bytes) of the array in shared memory.
;
  widget_control, gui1, get_uvalue = shm_size

; Point a local variable to the shared memory bytes located by the handle 'shm'
;
  shm_var = shmvar('shm')

; The last byte of the array in shm [shm_size] is the byte that indicates that this GUI 
; is active. We'll set that byte 0, indicating that this application's GUI has been 
; destroyed, and the parallel program will detect this change.
;
  shm_var[shm_size] = 0

; Unmap the shared memory variable
;
  shmunmap, 'shm'

end

pro event_handler1, event
;
; This is the xmanager event handler for the GUI named "shared_memory_proc1"
; The status of the event is passed to us in the structure named 'event'

; Each of the check boxes has a user assigned value 0-4 corresponding to the checkbox 
; number. By retreiving the user value, we know which of the 5 check boxes was just 
; clicked on.
;
  widget_control, event.id, get_uvalue = box_number

; Create a variable to access the shared memory bytes.
;
  shm_var = shmvar('shm')

; Set the byte in shm_var[box_number] corresponding to the
; check box that was just checked/unchecked
;
; the event structure is reported from the GUI within the event handler:
; event.id           id of the widget creating the event
; event.top         id of the top level base containing the widget generating the event
; event.handler   the id of the event handler routine
; event.select     the boolean value 1|0 of the check box

 shm_var[box_number] = event.select

end


pro shared_memory_proc1

; SHM#1 Mainline
;
; This program creates a Graphical User Interface with 5 check boxes.
; and establishes a shared memory block (unless SHM #2 program runs first)
;
; The parallel program shm_process2.pro will access the state of the
; check boxes via shared memory and report their status on its interface.

; The GUI will contain 5 check boxes
; We'll create a shared memory size of 6 bytes  (0:5)
; 1 byte for each check box status, and a 6th byte for a 'Active Status Flag'
;
  shm_size = 5  ; (0:5)

; Create the Base Graphical User Panel
;
; gui1 (Top Level Base)   Is the parent GUI "container"
; column=1               Organize the GUI in a single-column format
; xsize=350              Width of GUI is 350 pixels
;
  gui1 = widget_base(column = 1, title = 'SHM Process #1', xsize = 350)

; create the base GUI for drawing the 5 check box widgets
; /nonexclusive will allow any or all check boxes to be set or unset
; at the same time
;
  base = widget_base(gui1, row = 1, /nonexclusive)

; Create 5 check boxes across the GUI in a row format
; Label them   "Button0" , "Button1" , "Button2" , "Button3", "Button4"
;
  b_arr = lonarr(shm_size)

  for i = 0, shm_size-1 do $
    b_arr[i] = widget_button(base, value = 'Button ' + strtrim(i,2), uvalue = byte(i))

; Paint the GUI
; Assign the user value (set_uvalue) = shm_size
; equal to the size of the shared mem byte-array
;
  widget_control, gui1, /realize, set_uvalue = shm_size

; Use the 'shmap' procedure to map out a shared memory space.
; Allocate enough space for an array of  6 (0:shm_size) bytes.
; The first 5 bytes will contain the state of the check boxes
; in this application. Application #2 will read these values and report them

; The 6th byte will be used to indicate if SHM#1 program is running
; Application #2 will wait until Application #1 is running.
;

; Establish an error handler routine in case we try to
; map shared memory when it is already mapped:
;
  err=0
  shm_mapped=0
  catch, err
  IF err NE 0 then  shm_mapped=1

; Map a block of Shared Memory
; shmmap will generate an error if the 'shm' space is already allocated
;
  if shm_mapped EQ 0 then shmmap, 'shm', shm_size+1, /byte

; Create a variable to access the shared memory block.
  shm_var = shmvar('shm')

; Set the contents of the shared memory
;
  shm_var[*] = bytarr(shm_size+1)
  shm_var[shm_size] = 1  ; We're running now, set the 'Active' Flag.

;
; We're ready to run the GUI by transferring control to the window manager (XManager)
; The XMANAGER procedure provides the main event loop and management for GUI widgets
; created using IDL.
;
; Calling XMANAGER "registers" the GUI with xmanager under the
; handle name 'shared_memory_proc1', and GUI id 'gui1'.

; XMANAGER will now take control of event processing for all GUI activity.
; The cleanup keyword specifies the service routine to run when the gui is closed or 
; program ends The event_handler keyword specifies the service routine to run when a gui 
; event is triggered no_block=0 makes the command line available while the program is 
; running

xmanager, 'shared_memory_proc1', gui1, cleanup = 'clean1_event', $
  event_handler='event_handler1', no_block=0

end



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; shared_memory_proc2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; FILE: SHARED_MEMORY_PROC2.pro

; SHM PROCESS #2
; This pair of programs demonstrates how 2 cooperating IDL programs
; acheive inter-process communication and share variables via a common shared memory 
; segment.
;
; The two programs can be run by loading and running both programs within the same IDL 
; process, or each program can be loaded within its own IDL process. Multiple instances 
; of each program can be launched, and all will cooperate.

; The mainline of this program will establish a simple GUI (Graphical User Interface) 
; window to report the status of the the check box widgets in the GUI for SHM PROCESS #1
;
; The mainline will check for the existence of a shared memory segment named "shm", and 
; if not found create it. The first program of the pair to run will establish the shared 
; memory segment.

pro clean2_event, our_gui2
;
; This is the GUI 'clean' event that removes all the graphical widgets on exit.
; It is called if the user closes a GUI, or when the program ends. It will also
; unmap the shared memory segment.

if widget_info(our_gui2, /active) then  widget_control, our_gui2, /destroy

; Unmap the shared memory.
;
  shmunmap, 'shm'

end

pro shared_memory_proc2_event, event
;
; This is the Graphical Interface Windows event handler for process #2.
; All GUI events are handled by this routine.
;
; In the mainline we instructed the GUI to generate a timer event every .01 seconds
; in the WIDGET_CONTROL statement. Our timer event will identify itself with a tagname of 
; 'WIDGET_TIMER' so if any other GUI event occurs with a different tagname, we'll ignore 
; it and exit the event handler.
;
  if tag_names(event, /structure_name) NE 'WIDGET_TIMER' THEN return

; This is the WIDGET_TIMER event we're expecting, continue.
; Grab the user value of the GUI into the variable named 'state'
;
  widget_control, event.top, get_uvalue = state, /no_copy

; Create a local variable to access shared memory.
;
  shm_var = shmvar('shm');

; shm_var points to the 6 bytes in shared memory:
; Byte[0:shm_size-1]    5 Flags to hold the state of the 5 checkboxes
; Byte[shm_size]          1 Flag to indicate that SHM Process #1 is currently running
; We'll check this byte to see if Test #1 is running before continuing.
;
  if shm_var[state.shm_size] then begin

; Compare the array in shared memory (shm_var) to our stored values (state) to see if SHM 
; PROC#1 has clicked any check boxes. If so, then update our local variables and report 
; the change in our display.
;
      if ~array_equal(shm_var[*], state.b_checkbox_flags) then begin

       ; Update the b_checkbox_flags array
       ;
         state.b_checkbox_flags = shm_var[0:state.shm_size-1]
         msg = [' is not set', ' is set']   ; our messages array

       ; Update the labels on our GUI#2 to reflect the change in SHM PROC#1
       ;
         for i = 0, state.shm_size-1 do $
           widget_control, state.widget_array[i], set_value = 'Button ' $
           + strtrim(i,2) + msg[shm_var[i]]

      endif ; state has changed

  endif else begin ; PROC#1 is not active

     ; If PROC#1 is not running yet, then clear all labels to ' '
     ; and display a 'waiting....' message
     ;
       for i = 0, state.shm_size-1 do $
          widget_control, state.widget_array[i], set_value = ' '

     ; Set the labels to inform that we're waiting for Test #1 to start
     ;
       widget_control, state.widget_array[state.shm_size/2], $
         set_value = 'Test #1 is not active.'

       widget_control, state.widget_array[state.shm_size/2+1], $
         set_value = 'Waiting for PROC#1 To Start'

  endelse  ; if PROC#1 is running or not

; Set the top level base's UVALUE and fire the
; Retrigger another 'timer' even in 0.1 Sec.

  widget_control, event.top, set_uvalue = state, /no_copy, timer = 0.1

end

pro shared_memory_proc2
;
; shared_memory_proc2 periodically checks the contents
; of the shared memory, set up by TEST_SHARED_MEMORY1,
; to get the state of button widegts on TEST_SHARED_MEMORY1.

; Number of buttons in TEST_SHARED_MEMORY1.
  shm_size = 5

; Create the widget hierarchy as a single column,
; 150 pixels wide, 360 pixels offset
;
  our_gui2 = widget_base(column = 1, title = 'SHM Process #2', $
     xsize = 200, xoffset = 360)

;
; Create an array of 5 Label Widgets within the base GUI
;
  widget_array = lonarr(shm_size)
  for i = 0, shm_size-1 do $
    widget_array[i] = widget_label(our_gui2, /dynamic_resize, value = '')

; Create a structure for the GUI current state
; popultae with
; state.shm_size = 5                 the number of checkbox widgets
; state.widget_array[*]       store the identifiers of all the checkboxes
; state.b_checkbox_flags[*]   store the values of tall the checkboxes
;
  state = {shm_size:shm_size, widget_array:widget_array, $  
    b_checkbox_flags:bytarr(shm_size)}

; Realize the top level base and set its UVALUE.
; Also start the timer to trigger a 'timer' event after 0.01 Sec.
;
  widget_control, our_gui2, /realize, timer = 0.01, set_uvalue = state, /no_copy

; Establish Shared Memory
;
;
; Catch any errors with Shared Mem
; Our error handler
;
  err = 0
  shm_defined = 1  ; Asume that Prog #1 has setup shared memory
  catch, err

  if (err ne 0) then begin

     ; Get the error code for the error we caught
     ;
     err_struct = !ERROR_STATE
     err_msg = err_struct.msg
     err_code = err_struct.code
     PRINT, err_code, err_msg

     CASE err_code OF
      -716:  BEGIN    ; -716 shm undefined
                 shm_defined = 0  ; We're the first program running, 
                                  ; Set flag to Allocate shared memory

               END
       -717: BEGIN   ; -717 attempt to redefine shm
                  shm_defined = 1
                END
       ELSE:BEGIN
                  print, 'Untrapped Error: ',err_msg, err_code
                  stop
               END
      ENDCASE

  endif  ; if error caught

  shmmap, 'shm', shm_size+1, /byte
  shm_defined = 1

 ; pass control to the XManager to handle all GUI events
 ; lmgr runs the license manager and checks for a /runtime lic present
 ; if we are running using a Run-Time License then the command line is blocked 
 ; (no_block=1-1)
 ;

  xmanager, 'shared_memory_proc2', our_gui2, cleanup = 'clean2_event', $
    no_block = 1-lmgr(/runtime)

end