| Forum List • Thread List • Reply • Refresh • New Topic • Search • Previous • Next 1 | 1. Determine if another script is running #12596 Posted by: PeterJ 2008-12-30 20:48:46 Last edited by: Jeff Glatt 2008-12-31 02:43:01 (Total edited 1 time) | I would like to check if a rex script is still running. I know its PID, and want to query it via a function, is there an easy way to do this?
In my current implementation I use PSLIST from Microsoft's SysInternals Suite and this procedure:
testpid: PROCEDURE
PARSE ARG pid
LIBRARY rxcmd
command('PSLIST.EXE','Output',,'HIDE')
DO i=1 TO output.0
IF WORD(output.i,2)=pid THEN RETURN 1
END
RETURN 0
it works but is relatively expensive.
Regards
Peter | 2. #12597 Posted by: Jeff Glatt 2008-12-30 22:39:55 Last edited by: Jeff Glatt 2009-01-01 05:54:53 (Total edited 1 time) | There are a couple easy ways to do this, if you've written both scripts (which I'm assuming you have). I'll refer to the running script as "Target.rex".
1) Target.rex uses the clipboard add-on DLL to register its own custom clipboard data format, and then posts some arbitrary data to the clipboard (in that custom format). The search script then tries to read (from the clipboard) any data in that same custom format. If no such data exists, then Target.rex isn't running.
2) If Target.rex has a REXX dialog window open, you can use FUNCDEF and call the operating system function FindWindowA to locate its open window. The advantage to this method over method #1 is that you can then communicate with Target.rex using the window handle that FindWindowA returns, and passing your own custom events to that window via GuiSendMsg().
FUNCDEF('FindWindowA', 'void, str, str', 'user32', , 'o 0')
targetwindow = findwindowa('rexxgui', 'My Window')
IF targetwindow \== '' THEN DO
guisendmsg(targetwindow, 40000, )
END 3). A disk file can be used to tell if a script is running. Target.rex calls STREAM() to create a file with "OPEN BOTH SHARED". Then Target.rex leaves this file open until the script ends. As Target.rex is ending, it calls STREAM() to close the file, and then DELETEFILE() to delete it.
Your server script tries to open that file with "OPEN READ". If the file doesn't exist, then Target.rex isn't running.
If you didn't write Target.rex, there is a more complicated way to figure out if a script is running, but if you have more than one script being run by Reginald, it doesn't distinguish between scripts. | 3. #12600 Posted by: PeterJ 2008-12-31 00:41:49 | yr 2 is very interesting! I did get it running for the PID replacement. Even more interesting is you remark about sending a signal which causes an event in the target process. I tried it, but couldn't get the event handler to react on the sginal. I used the modified code in the GUI handler:
DO FOREVER
guigetmsg(,mysignalnumber)
IF EXISTS('GuiObject') == 0 THEN DO
IF EXISTS('GuiSignal') THEN DO
END
END
...
hoping that it reacts on the signal which I have sent from another rex:
guisendmsg(targetwindow, 40000, mysignalnumber)
but it didn't work expect. It would be wonderful if you give me another hint what to do! | 4. #12601 Posted by: Jeff Glatt 2008-12-31 02:34:20 Last edited by: Jeff Glatt 2008-12-31 02:54:19 (Total edited 3 times) | No, Pete. Only (mostly) GuiWake() sets GuiObject and GuiSignal. You're not calling GuiWake() from the second script. (And even if you did, it can't affect the first script. GuiWake doesn't interact across processes). GuiWake is essentially a really easy alternative to creating your own custom events, but which is limited to one process, and triggers an event that doesn't need to be handled by a specific window script. (ie, The event isn't triggered in a particular window, and therefore doesn't need to be handled by a particular window script).
You're instead calling GuiSendMsg which (normally) doesn't set GuiSignal and GuiObject. With GuiSendMsg, you need a specific subroutine (what I call an "event handler") to process that event. Of course, that subroutine must have a particular name, depending upon what event you're sending, and whether it's going to a window or a control.
RPC's GUI builder has a listbox of standard events to aid you in adding subroutines (to handle those standard events) to your script. For example, if you want to handle a CLOSE event for a window, you just highlight the CLOSE event in the "Events" listbox, click the "Add Event Handler" button, and RPC automatically adds a "WM_CLOSE" subroutine to your script. As another example, if you want to handle the "CLICK" event for a push button, you just highlight the CLICK event in the "Events" listbox, click the "Add Event Handler" button, and RPC automatically adds a "WM_CLICK_xxx" subroutine to your script (where xxx is the variable name you associated with your push button). RPC tries to help you write subroutines to handle all these standard events for windows and controls.
But RPC doesn't know about any custom events you create. Therefore, you need to manually add one subroutine to handle them all. Your subroutine must be named "WM_EXTRA". (Well, there is a "EXTRA" event listed in a window's "Events" listbox. If you highlight this, and click "Add Event Handler", it will add that WM_EXTRA subroutine to your script. So whether you do it this way, or just manually type it into your script is your choice). Your call to GuiGetMsg automatically calls this WM_EXTRA subroutine whenever it encounters an event that isn't one of the standard ones (ie, a custom event).
The third arg passed to WM_EXTRA is whatever number you passed as the second arg to GuiSendMsg(). So assume one script calls GuiSendMsg as so, with a message (event) number of 40000:guisendmsg(targetwindow, 40000) Then your first script handles receiving this custom message as so:
wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
SAY "Received event 40000"
END
OTHERWISE
END
RETURN "" Now, you can optionally pass 1 or 2 more numeric args to GuiSendMsg. Your WM_EXTRA subroutine receives them as ARG(1) and ARG(2) respectively. For example, your second script can call GuiSendMsg as so:guisendmsg(targetwindow, 40000, 1) And process it in your first script as so:wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
SAY "Received event 40000 with ARG(1) =" ARG(1)
END
OTHERWISE
END
RETURN "" Here's another example with 2 additional args. NOTE: The first arg must be positive. The second arg can be positive or negative.guisendmsg(targetwindow, 40000, 1, -1) And process it in your first script as so:wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
SAY "Received event 40000 with ARG(1) =" ARG(1) "and ARG(2) =" ARG(2)
END
OTHERWISE
END
RETURN "" You can also pass only the second arg (in which case any omitted arg automatically is given the value 0. So ARG(1) = 0):guisendmsg(targetwindow, 40000, , -1) And of course, you can create more custom events, as long as each has a different event number. Here, we send custom event 40001 with one additional arg.guisendmsg(targetwindow, 40001, 100) And of course, your WM_EXTRA needs to look for that event number too:wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
SAY "Received event 40000 with ARG(1) =" ARG(1) "and ARG(2) =" ARG(2)
END
WHEN 40001 THEN DO
SAY "Received event 40001 with ARG(1) =" ARG(1) "and ARG(2) =" ARG(2)
END
OTHERWISE
END
RETURN "" You can optionally return a numeric value to your second script. For example, here we return the value 1000 if receiving a 40001 event:wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
END
WHEN 40001 THEN DO
RETURN 1000
END
OTHERWISE
END
RETURN "" In this case, GuiSendMsg does in fact set the GuiSignal variable in your second script. It's set to that returned numeric value:guisendmsg(targetwindow, 40001, 100)
SAY "Return value from event 40001 =" guisignal
Of course, this not only works in sending messages from one app to another app's window, but also within one app. You can have a child script GuiSendMsg an event to a particular window in a parent script. You just need the handle to that window. But GuiWake() is easier to use within one app.
And actually, you can also use this technique to pass messages to any app's window, not just a REXX GUI window. For example, if you use FindWindowA to obtain notepad's window handle, then you can GuiSendMsg some standard message to that window. For example, maybe to close it:guisendmsg(notepadwindow, "CLOSE") There's actually a lot you can do with GuiSendMsg. | 5. #12602 Posted by: PeterJ 2008-12-31 02:58:10 | This is great, I will experiment with it! Currently I use 2 clipboards to send over the request and pass back the result to the caller. I liked the clipboard event, which automatically notifies the script in caseof new entries. But I think the GUISENDMSG is more elegant!
Thanks a lot for help!
Peter | 6. #12603 Posted by: Jeff Glatt 2008-12-31 03:26:18 Last edited by: Jeff Glatt 2009-01-01 05:55:28 (Total edited 6 times) | It also should be faster.
Note that you automatically get notified of a custom event as soon as you call GuiGetMsg (and all previous events have been handled). So yes, if you have a REXX GUI window open, it's better to use these window events since it works better with your user interface. Other notification methods can adversely affect your UI (since Reginald isn't multi-threaded and everything runs from the UI thread). But DRAWCLIPBOARD event is a standard window event anyway, so this is ok.
But of course, it does mean that any script you send an event to must have an open REXX GUI window. And when communicating between scripts, you need to get the window handle. You can use FindWindowA to do that, but then you need to know the text in the other window's title bar.
Alternatives to using FindWindowA are:
1) Have the first script register a custom clipboard format. Write the value of its GuiWindow variable as clipboard data (in this custom format). The second script then reads this clipboard data to get the window handle. One caveat: Only one script at a time can store its window handle on the clipboard. Several simultaneously running scripts, trying to do this at the same time, is a problem.
2) Have the first script create a disk file, and write out the window handle as so:STREAM("C:\MyFilename", "C", "OPEN WRITE REPLACE")
VALUEOUT("C:\MyFilename", guiwindow, , 4)
STREAM("C:\MyFilename", "C", "CLOSE") Have the second script get the handle as so:STREAM("C:\MyFilename", "C", "OPEN READ")
targetwindow = VALUEIN("C:\MyFilename", , 4)
STREAM("C:\MyFilename", "C", "CLOSE") Same caveat as the first method, unless each script uses a different filename to store its handle. But then your second script needs to know what filename is used. | 7. #12604 Posted by: PeterJ 2008-12-31 05:24:47 Last edited by: Jeff Glatt 2009-01-01 05:56:19 (Total edited 1 time) | Yr 1/ I realise that concurrency of serveral scripts accessing a server is a problem, but not impossible to solve. This might be on my agenda later.
yr 2/ I use a very similar method to exchange the PID (later the window handle) and the clipboard name: the registry. It seems faster and more secure than "simple" files (also more elegant). | 8. #12608 | Yeah, I omitted mentioning using the registry to exchange data. It can be done but I advise against it because you can fragment the registry file. (Things being added to the registry is what accounts for most of the slowdown you see with a computer after some months of use. And a fresh install of the OS is what accounts for most of the speedup since this creates a brand new, lean registry file. You definitely want to avoid writing any sort of unnecessary, temporary data to the registry. It's mostly for saving permanent, per-enduser settings. Use it for that purpose only). | 9. #12609 Posted by: Jeff Glatt 2008-12-31 13:59:38 Last edited by: Jeff Glatt 2008-12-31 14:38:36 (Total edited 2 times) | Actually, I just realized. It's your server script that puts its window handle on the clipboard (in your own custom format). The other scripts simply read that data. Those other scripts aren't writing any new data (in your custom format) to the clipboard.
So as long as you run only one copy of your server script, you don't need to worry about any data overwritng issues with the clipboard.
Another idea you could consider is making sure your server script does a ClipClear() before it exits. That way, when the other scripts don't find its window handle on the clipboard, they'll know your server isn't running.
If your other scripts need to give any window handle to your server script, they can send it via GuiSendMsg() in a custom event. After all, once the child script has your server window handle, it can start sending events with GuiSendMsg(), and pass its own window handle as an additional arg (since a window handle is just a numeric value).LIBRARY rexxgui, rxclip
FORMAT = clipnewformat("WindowHandle")
IF clipavailable(format) THEN DO
clipget("ServerWindowHandle", format)
guisendmsg(serverwindowhandle, 50000, guiwindow)
END
ELSE guisay("Server script isn't running") And here's some code in your server script:LIBRARY rexxgui, rxclip
guicreatewindow('NORMAL')
FORMAT = clipnewformat("WindowHandle")
clipset("GuiWindow", format)
handlecount = 0
again:
DO FOREVER
guigetmsg()
CATCH SYNTAX
CONDITION()
SIGNAL again
CATCH HALT
FINALLY
guidestroywindow()
clipclear()
END
RETURN
wm_extra:
SELECT ARG(3)
WHEN 50000 THEN DO
handlecount = handlecount + 1
child.handlecount = ARG(1)
guisendmsg(child.handlecount, 40000, 1, -1)
END
OTHERWISE
END
RETURN "" If your server wants to send some custom message to all the child scripts:DO i = 1 TO handlecount
guisendmsg(child.i "POST", 40000, 1, -1)
END For that matter, you can design all of your custom events so that you always pass a window handle for the first, additional arg (to GuiSendMsg). That way, when you receive any given custom event, your WM_EXTRA can look at ARG(1) to tell from which window (ie, script) you received the message.
In fact, your custom message to give the server script a window handle could pass two args: the window handle, and then some numeric value that your server script uses to uniquely identify that script. Then you truly know what script is running, and sending that event. (Actually, you can pass a string of upto 4 chars in lieu of a numeric arg).
guisendmsg(serverwindowhandle, 50000, guiwindow, "__ME") Your server:wm_extra:
SELECT ARG(3)
WHEN 50000 THEN DO
handlecount = handlecount + 1
child.handlecount = ARG(1)
id.handlecount = ARG(2)
END
OTHERWISE
END
RETURN "" Now your child script sends some custom event and identifies itself with an additional arg of its ID string:guisendmsg(serverwindowhandle, 40000, 1, "__ME") And your server knows who sent it via that ID string:wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
guisay("Received event 40000 from" ARG(2) || ". ARG(1) =" ARG(1))
END
OTHERWISE
END
RETURN "" There's lots of possibilities here. | 10. #12611 Posted by: PeterJ 2008-12-31 16:42:11 Last edited by: Jeff Glatt 2009-01-01 06:16:15 (Total edited 2 times) | You say I can pass string parameters like so:guisendmsg(serverwindowhandle, 50000, guiwindow, "__ME") I tried it, but my WM_EXTRA always gets passed 26 as the argument, whatever string I pass to GuiSendMsg. Numbers are always passed correctly. Is this an error?
Here's another question:
I pass the input via clipset to the server, followed by a sendmsg statment. As sendmsg triggers an event after the clip event I use it to see when the clip processing is completed and signal this back to the client.
This sequence works fine, but I am not sure whether there are scenarios when GuiSendMsg is overtaking the clip event and is processed first:
clipset("MyVar.", dictin)
...
guisendmsg(target, event, p0,p1) | 11. #12613 Posted by: Jeff Glatt 2009-01-01 05:42:08 Last edited by: Jeff Glatt 2009-01-01 05:43:50 (Total edited 1 time) | Oops. Yes, you can pass a string of upto 4 chars, but you must first encode it as a numeric value, which you can do with the C2D() function:guisendmsg(target, 40000, C2D("__ME")) And then your server decodes it back into a string using D2C():wm_extra:
SELECT ARG(3)
WHEN 40000 THEN DO
id = D2C(ARG(1))
guisay("id string =" id)
END
OTHERWISE
END
RETURN ""
ClipSet() doesn't return until the data is copied to the clipboard. So by the time you follow up with GuiSendMsg(), the data is already there. You don't need to worry about the event happening before the clipboard data is there.
But judging by your example, it appears that you want to provide some data to another script, and then followup with GuiSendMsg in order to determine when the other script has actually done its ClipGet. Here's a better suggestion to achieve that.
If the data you're providing (to the other script) is a numeric integer value (not floating point -- just a whole number up to about 4 million), or a string of 4 chars or less, you're better off just passing this data as an additional arg to GuiSendMsg. That does two things simultaneously. It gives the data to the other script, and (if you don't use the "POST" option) waits for that script to actually retrieve/use that data. And since the other script can return a numeric value to you, you can even use this for error confirmation. (ie, The other script returns a 0 if all went well, otherwise a non-zero error number of your own choosing).
It's faster, more memory efficient, and allows for easier error confirmation -- all with one call to GuiSendMsg().
Furthermore, you have the "POST" option. If you don't use "POST", your call to GuiSendMsg behaves as described above, and won't return until the other script finishes processing that event (ie, retrieves and uses the data). If you don't care when the script gets around to processing it (for example, you don't need to wait for its WM_EXTRA to return some numeric value to you, or you don't have to wait for the other script to process the event before you can do something else), then you can use the "POST" option. This allows your call to GuiSendMsg to return immediately (before the other script may even get around to processing the event), and you can go on to do other things.
Note that posted events get accumulated into a list, and handled in the order that they're triggered. For example, assume one script posts two events to the other script's window as so:guisendmsg(target "POST", 40000, C2D("__ME"))
guisendmsg(target "POST", 50000, C2D("__ME")) Now the other script does two calls to GuiGetMsg() after both those events are posted:guigetmsg()
guigetmsg() The first GuiGetMsg calls the script's WM_EXTRA to handle the first posted event (with event number 40000). And the second GuiGetMsg calls the script's WM_EXTRA again to handle the second posted event (with event number 50000). In other words, posted events (and their data) get accumulated. (ie, It's not like the clipboard where you keep overwriting previously written data in your custom format). And they do get handled in the order that you're posting. This may be a useful feature that is not available via the clipboard method.
And as mentioned eariler, without "POST" you get the ability to automatically wait for confirmation that the other script has indeed finished processing the event. So that can be useful in other circumstance, whereas if you used ClipSet, now you have to come up with some followup method to determine when the other script finally gets around to doing ClipGet (not to mention letting the other script inform you of errors. After all, ClipGet could fail. Or maybe the script encounters some error when trying to do something with the data, and needs to inform you of that). | Forum List • Thread List • Reply • Refresh • New Topic • Search • Previous • Next 1 |
|
|