Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Multiprocessing with a COM Server 3

Status
Not open for further replies.

Chris Miller

Programmer
Oct 28, 2020
4,755
DE
I programmed something for which I saw a demand from a few past threads about parallel processing, which are:

thread184-1817657
thread184-1819600
thread184-1819365
thread184-1818385

So this is for anybody interested in parallel code execution.

And I know I could point out several things already existing, I even did in posts in these threads. I mentioned the existence of ParallelFox: I mentioned the simplest parallel execution possible with RUN /N another.EXE, possibly with parameters.
I mentioned you could run two executables and do IPC - interprocess communication - with windows messages.
I mentioned an approach of Calvin Hsia to allow multithreading and pointed out it has the problem of being data execution, which Windows prevents with data execution prevention - in short DEP.

And there's more about parallel processing or multithreading with FoxPro out in the internet already, so actually no need to come up with anything new, but I wanted to point out one thing that I think all existing solutions overlooked so far. Making use of an out-of-process COM server as a background process. I'll go into the technical details in one or a few separate posts, this is just the instructions and usage post:

So, without going into details of why to do things this way, just the instructions of what to create and how to use it.
1. Build a COM Server "background.process"
1a) Create a new folder "background"
1b) Open VFP this way:
startvfpasadmin_kkoa8g.png

1c) Create a project in the background folder you call "background.pjx" - that way a build of the project will create a background.EXE
1d) Create this as the main.prg of the background.pjx project
Code:
#Define CRLF Chr(13)+Chr(10)

Define Class Process As Form OlePublic
   Hidden ExecuteScriptMessage
   Script = ''
   ExecuteScriptMessage = 0
   Executing = .F.
   ErrorHappened = .F.
   LastReturnValue = .null.
   LastError = ''

   Procedure Init()
      On Shutdown Quit
      * modal states are unwanted, which we can enfore:
      Sys(2335,0) && enable unattended server mode

      Declare Integer RegisterWindowMessage In User32.Dll String cMessage
      Declare Integer PostMessage In User32.Dll Integer nHWnd, Integer nMsg, Integer wParams, Integer lParams

      This.ExecuteScriptMessage = RegisterWindowMessage('BackgroundProcess_ExecuteQueuedTasks')
      If Between(This.ExecuteScriptMessage, 0xC000, 0xFFFF)
         Bindevent(Thisform.HWnd, This.ExecuteScriptMessage , This, 'ExecuteScript', 2+4)
      Else
         Quit
      EndIf      
   Endproc

   Procedure Execute() ;
      HelpString "Execute code in Script property"
      
      Local llDone
      If Not This.Executing && do nothing, if a script still runs
         This.Executing = .T.
         This.ErrorHappened = .F.
         * Postmessage causes ExecuteScript to run, because of the BindEvent in the Init().
         PostMessage(Thisform.HWnd, This.ExecuteScriptMessage, 0, 0)
         llDone = .T.   
      EndIf
            
      Return llDone
   Endproc

   Hidden Procedure ExecuteScript(HWnd As Integer, Msg As Integer, wParam As Integer, Lparam As Integer)
      This.LastReturnValue = Execscript(This.Script)
      This.Executing = .F.

      Return 0
   Endproc

   Procedure Error(nError, cMethod, nLine)
      This.Executing = .F.
      This.ErrorHappened = .T.
      This.LastError = Textmerge("Error <<nError>> in <<cMethod>> Line <<nLine>>: <<Message()>>",.T.)
   Endproc

   Procedure Quit() ;
      HelpString "end the background process"
      Do While Sys(3098,This)>0
         DoEvents
      Enddo
   Endproc

   Procedure Destroy()
      Sys(2335,1) && disable unattended server mode again
      On Shutdown
      Quit
   Endproc
Enddefine

1e) build the EXE

Now you will have a background.process COM server in your system.

2. Using the Background.Process COM server.
This new COM server has the following properties and methods of interest:

Script - a property you set to a script of VFP code you want to execute in parallel.
Executing - this property tells you whether the background process is executing .script
ErrorHappened - tells you whether an error happened
LastReturnValue - property containing the last value returned from the last successfully ran .script
LastError - the last error from the last unsuccessfully ran .script
Execute() - the method to execute the code in the Script property.
Quit() - quitting the background process

I explain why I chose this structure in a separate thread. A simple example of how to use this is:
Code:
Clear
Public goBackgroundprocess
goBackgroundprocess = Createobject("background.process")
Text To goBackgroundprocess.Script NoShow
Declare Integer Sleep in Win32API Integer milliseconds
Sleep(1000)
Return 42
EndText
goBackgroundprocess.Execute()
? Seconds()
Do while goBackgroundprocess.Executing
   Doevents
EndDo
? Seconds(), goBackgroundprocess.LastReturnValue
goBackgroundprocess.Quit()
Release goBackgroundprocess
It's actually a usage that waits for the result, which you'd normally not do, as you want this to run in parallel to what your application does, but printing the Seconds(), then waiting for goBAcgroundPRocess not executing anymore and printing seconds() with the lastreturnvalue proves, that the call goBackgroundprocess.Execute() returns immediately and the return value of the script (return 42) comes back a second later. So this is true multiprocessing.

In general, the usage is to
I. Set goBackgroundprocess.Script to the code you want to execute in parallel
II. call goBackgroundprocess.Execute()

If the script causes an error within goBackgroundprocess, goBackgroundprocess.ErrorHappened will be set .T. and the error message will be put into goBackgroundprocess.LastError. In this version of it, the line of error will always be the ExecScript line of the ExecuteScript method and not the line number within the script that actually caused the error, that's harder to refine, but maybe in an advanced version I'll provide this.

this concludes the "How To" part of it, just a few last remarks; The scripts you can execute will not see any of the main application process variables, not even public variables. It will not see any workareas of any data session, so the scripts you run have to be able to run completely standalone. There is no mechanism foreseen to pass in parameters, but you can of course generate the script code to set some variables to values as you like or need, or you pass data via DBFs the cript opens and scans or by adding properties to the goBackgroundprocess object.

I intentionally use a public variable goBackgroundprocess for the COM server, as you usally want to have the ability to run parallel code anywhere in your application and to avoid the overhead of creating the object, which can take up to a few seconds in the worst case I measured. This way this initialization becomes only a onetime penalty. What's important for the "How To" usage is, that you quit the background process by calling Quit() and releasing the variable goBackgroundprocess, the background.exe will not disappear from the task manager detail tab, if you only call Quit(). It will only disappear once you do the latter, once you release the variable as the last reference keeping the EXE from quitting.

I will describe technical details about this in a late thread and also some advanced ways of using it, which allow usage of code or classes from your own EXE within the background.process.

Chriss
 
Hi Chriss,

Thanks for your elaborate post. It’s a very good example of “how to”.

Can you give some examples when parallel (and more complex) execution using this construction could be benificial? Is it all about the program’s execution speed? What improvements in speed are possible? Other benefits?

Regards, Gerrit
 
Thanks a lot for the detailed explanation and time you put in investigating.
That sample worth to try some experiments !!.
Thanks again
okarl

When I said that I was concentrated on programming, so my state of consciousness was attenuated
 
Thanks for the feedback.

The benefit of this is less potent than multithreading in some cases, for example as you can't attach to the current datasession with a new process, but there are also benefits of multiprocessing, one of which is that this works thread-safe, whereas an MTDLL only is thread-safe in STA mode. To explain that I recommend reading a Rick Strahl post here:

Let me quote some sentences:
Rick Strahl said:
VFP9T is not thread safe internally. It is thread safe only when running in STA mode called from a multi-threaded COM client that supports STA threading...

Rick Strahl said:
...STA is a good option for multi-threaded FoxPro code, but it’s not always available. In fact, more and more STA support is going away because the era of legacy COM components like FoxPro and VB6 has pretty much disappeared for high-level development and is relegated now to system components which typically can support free threading.

Rick also writes that the COM interface is slow and not recommendable:
Rick Strahl said:
However, things get A LOT slower quickly as soon as you add data access and return object data to the COM client, so the actual raw throughput you can achieve with STA is far outweighed by the CPU overhead of doing anything actually useful inside of your FoxPro code. Running a query and returning a collection of objects of about 80 records for example drops the results down to about 8 req/sec. Call overhead is a nearly useless statistic when it comes to VFP COM objects, so even if free threading were possible and even if it were 10x faster than STA threading, it would have next no effect at all on throughput for most requests that take more than a few milliseconds. STA is the most effective way to get safe and efficient performance.

But these thoughts are about an MTDLL COM server. Rick Strahl also writes about an EXE COM server - which I propose here - as follows:
Rick Strahl said:
running a pool manager of instances so that instances are loaded and then cached rather than having to be restarted each time. I use this approach in West Wind Web Connection, and by removing the overhead of loading a VFP instance each time and keeping VFP’s running state active, raw request throughput performance actually rivals that of DLL server with this approach.

Well, this isn't a pool manager, it's just one "background.process", so a pool of size 1, but it only has a one-time initialization penalty, then you can use it to run as many scripts as you like in parallel. In one of the Tek-Tips threads I mentioned Mandy_crw wanted to avoid the waiting time caused by a button sending an SMS before her VFP form became responsive again. I am sure you could keep a VFP form responsive while some button click code triggers something, but it's not astonishing this unresponsiveness occurs within a VFP application as VFP is pretty much single-threaded, which Rick Strahl also describes:

Rick Strahl said:
Keep this in mind – Visual FoxPro code is single threaded and it can’t and won’t natively branch off to new threads. Further FoxPro code assumes pretty much it’s the only thing running on the machine so it’s not a particularly good citizen when it comes to giving up processor cycles for other things running on the same machine. This means Visual FoxPro will often hog the CPU compared to other more multithread aware applications do. Essentially VFP will only yield when the OS forces it to yield.

As said already other implementations and ideas of multi-processing already exist, like ParallelFox. So what differs here? usual approaches using RUN or Createprocess give you very little access to this parallel running process, you mainly can know a process handle that can be used by some API functions like WaitForSingleObject and further such synchronization methods, but you don't have hands on the process as much as you do with a COM server object reference.

You can easily extend this code any way you like. It's just a starting point, a skeleton. You could implement a worker pool, a task queue, or anything like that not just running one script in parallel, but multiple scripts. It could do workload balancing. And once the main principle is understood, it doesn't need to stay a COM server, too. I just also liked it for another aspect: Starting an EXE COM Server unlike starting a normal VFP built EXE, you keep the ForegroundWindow to what it was. The COM Server _VFP.hwnd does not become the foreground window when you start an EXE COM server.

Let me finish by pointing out how it actually can do parallel code, as that's not as simple as having a new process. The usual way of calling a method in a COM Server still hinders you from parallel execution. Calling a method of a COM server object (in the main process) is not different from calling a method in any other object: Code execution goes into the method, until it returns and so in any normal EXE COM Server instance you still don't execute anything in parallel. You run the COM Server method, which runs in the parallel process of the COM Server, but while it runs the main process waits for the result of the method. So you still run single-threaded, just in two processes of which only one actually executes.

The major ingredient to overcome this is the usage of PostMessage, as described in the Microsft API reference:

The important quote to make from it is the first sentence already:
MS docs said:
Places (posts) a message in the message queue associated with the thread that created the specified window and [highlight #FCE94F]returns without waiting for the thread to process the message[/highlight].
The call PostMessage(Thisform.HWnd, This.ExecuteScriptMessage, 0, 0) within the EXECUTE method will trigger what is set up within the Init method with BindEvent and a registered user-defined message: It triggers the execution of the bound method but does not wait for it to run, so you have what the VFP RAISEEVENT() function promised to do, you actually only raise the execution of the script in the COM Server, without waiting for it to finish, you don't even wait for it to start.

It would be possible to do this within a single EXE, too, but it won't help much, as the VFP runtime still only handles one current method at a time, the event you queue with PostMessage would only be processed when you get back to the READ EVENTS state of VFP and will not run in parallel. But once Execute returns the COM server becomes idle and then does process the message it posted to itself. That's the spark to actually run in parallel. It also has a little penalty of the usual message queue time, but you can do much more in a script than RETURN 42 after sleeping for a second. That obviously just was to demonstrate the prinnciple.

I'll give you some ideas of how a script could look like to do something, also based on code of your main EXE and the classes and classlibraries you have in it, even though none of them are built into the COM Server background.EXE. It's just a matter of knowing how-to, again.

Chriss
 
One of the questions I sense coming up is what to do with script.

To start with, I'd just put this into your major EXE main.prg:
Code:
Public goBackgroundprocess
goBackgroundprocess = Createobject("background.process")
It could also become a member of your goApp application object or a property of _screen. Just make it available throughout all your application to use it whenever it's useful.

The closing code can become part of your exit/quit code:
Code:
goBackgroundprocess.Quit()
Release goBackgroundprocess

The core usage then is jsut the part between instantiation and releasing of this object.

Well, and now that you have goBackgroundprocess available anywhere in your application, what can be done with goBackgroundProcess.script? How can you make use of the code you already have in PRGs and classlibs/classes of your main EXE as nothing of it is included in the background.exe

In short, your imagination and your knowledge of VFP are the only liits, script can of course be any VFP code.

And it does not need to be lengthy to do complex things. Nothing speaks against very short scripts that just execute something you already have in your own EXE. Because VFP offers DO to call PRGs and if you would dive into what DO can do you'd know you can not only DO a PRG or DO a compiled FXP. Yo can actually also do a PRG or FXP that is built into an EXE, just learn your language in all details:
Code:
DO ProgramName1 | ProcedureName [IN ProgramName2] [WITH ParameterList]

That's the syntax of the DO command and in detail, ProgramName1 can already be an EXE or APP, the help topic of DO even states that EXE and APP are prioritized file extensions looked for, if you don't provide the PRG file extension. Besides that little known fact, you can also DO some.prg IN some.EXE, so think of this usage example:

Code:
Text To goBackgroundprocess.Script TextMerge NoShow
   DO some.prg in ("<<Sys(16,0)>>")
EndText
goBackgroundprocess.Execute()

Within your EXE, SYS(16,0) will be the full path and filename of the EXE itself, so you can let background.process run a prg called "some.prg" - of course any prg name you know you build into your EXE.

The other very helpful tool to use is the NewObject function:
Code:
Text To goBackgroundprocess.Script TextMerge NoShow
   Local loObject
   loObject = NewObject("class","classlib.vcx","<<SYS(16,0)>>")
   loObject.Method(..parameters...)
EndText
goBackgroundprocess.Execute()
This way you can create an object of one of your classlibraries without the need to also include it into the background.exe project.

In both of these usages your main EXE does not only work as usual but also is a source for what to execute. So you don't have to put any work in extracting code you want to execute for the script property of the background.process COM server, your scripts can and most often should only be a few lines that enter into what you already have available anyway. Just now it can run in parallel to whatever your main application does.

An idea to do batch processing also is easy to realize: Create a DBF with a script Memo field containing scripts to execute and just run this script:
Code:
Text To goBackgroundprocess.Script TextMerge NoShow
   Use scripts.dbf
   Scan
      ExecuteScript(scripts.script)
   Endscan
EndText
goBackgroundprocess.Execute()
You may put the full path to the DBF into it, but you could also add some methods to the Process class, like a SetPath method to set the current directory for the COM server process.

If you still don't find enough inspiration for what to do in parallel I can't help you. I hope I've shown that you can use this single separate process to do quite anything you want, including to let it execute a batch of scripts, not just one. This already is the most simple implementation of a task queue I can think of, without even extending the COM server class itself, just using it - as simple as it is.

Chriss
 
Hi Chris,

This is EXCELENT. I really like the way you make parameters generic by using the TEXT...ENDTEXT construct along with goBackgroundprocess.Execute().

I recently coded a COM server (for a specific application) to do stuff in order to avoid the dreaded "Visual Foxpro Not Responding" thing... My goApp object has a "Process" property set when the main app is initialized:

Code:
goApp.Process = CREATEOBJECT("RunProcess.ProcessSession")

When I want to run a process, I create an EMPTY parameter object and add properties:

Code:
loParm = CREATEOBJECT("Empty")

ADDPROPERTY(loParm, "Type", 4)
ADDPROPERTY(loParm, "DBName", STRCONV(tcDataBase, 13))
ADDPROPERTY(loParm, "DBPath", STRCONV(lcFilePath, 13))

llSuccess = goApp.DoProcess(loParm)

Here is what the "DoProcess" method looks like in the COM EXE:

Code:
PROCEDURE DoProcess(toParm AS Object) AS Variant
   LOCAL lcTmpFile, lcNewFile, lcKeyPair, lcKeyIV, lcDatabase, lcDataPath, lcFSName, lcFSPath, lcMDFPath, lcLDFPath, lcNewDrive, lcDstFolder, lcSrcFolder, lvReturn

   DO CASE
      CASE toParm.Type = 0
           lcTmpFile = STRCONV(toParm.TmpFile, 14)
           lvReturn  = HASHFILE(lcTmpFile, 2)
           lvReturn  = STRCONV(lvReturn, 15)

      CASE toParm.Type = 1
           lvReturn  = .T.
           lcTmpFile = STRCONV(toParm.TmpFile, 14)
           lcNewFile = STRCONV(toParm.NewFile, 14)
           lcKeyPair = STRCONV(toParm.KeyFile, 14)
           lcKeyIV   = STRCONV(toParm.KeyInit, 14)

           TRY
               DecryptFile(lcTmpFile, lcNewFile, lcKeyPair, 2, 2, 2, 32, 32, lcKeyIV)
           CATCH WHEN .T.
               lvReturn = .F.
           ENDTRY

      CASE toParm.Type = 2
           lvReturn  = .T.
           lcTmpFile = STRCONV(toParm.TmpFile, 14)
           lcNewFile = STRCONV(toParm.NewFile, 14)
           lcKeyPair = STRCONV(toParm.KeyFile, 14)
           lcKeyIV   = STRCONV(toParm.KeyInit, 14)

           TRY
               EncryptFile(lcTmpFile, lcNewFile, lcKeyPair, 2, 2, 2, 32, 32, lcKeyIV)
           CATCH WHEN .T.
               lvReturn = .F.
           ENDTRY

      CASE toParm.Type = 3
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcDataPath = STRCONV(toParm.DBPath, 14)
           lvReturn   = This.BackupDatabase(lcDatabase, lcDataPath, .T.)

      CASE toParm.Type = 4
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcDataPath = STRCONV(toParm.DBPath, 14)
           lvReturn   = This.BackupRestore(lcDatabase, lcDataPath)

      CASE toParm.Type = 5
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcDataPath = STRCONV(toParm.DBPath, 14)
           lcFSName   = STRCONV(toParm.FSName, 14)
           lcFSPath   = STRCONV(toParm.FSPath, 14)
           lcMDFPath  = STRCONV(toParm.MDPath, 14)
           lcLDFPath  = STRCONV(toParm.LDPath, 14)
           lvReturn   = This.BackupRestoreMove(lcDatabase, lcDataPath, lcFSName, lcFSPath, lcMDFPath, lcLDFPath)

      CASE toParm.Type = 6
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcNewDrive = STRCONV(toParm.NDrive, 14)
           lcFSName   = STRCONV(toParm.FSName, 14)
           lcFSPath   = STRCONV(toParm.FSPath, 14)
           lcMDFPath  = STRCONV(toParm.MDPath, 14)
           lvReturn   = This.BackupRestoreMoveBAK(lcDatabase, lcNewDrive, lcFSName, lcFSPath, lcMDFPath)

      CASE toParm.Type = 7
           lcDstFolder = STRCONV(toParm.DstFolder, 14)
           lcSrcFolder = STRCONV(toParm.SrcFolder, 14)
           lvReturn    = This.MoveToFolder(lcDstFolder, lcSrcFolder)

      CASE toParm.Type = 8
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcDataPath = STRCONV(toParm.DBPath, 14)
           lvReturn   = This.BackupDatabase(lcDatabase, lcDataPath, .F.)

      CASE toParm.Type = 9
           lcDatabase = STRCONV(toParm.DBName, 14)
           lcDataPath = STRCONV(toParm.DBPath, 14)
           lvReturn   = This.BackupVerify(lcDatabase, lcDataPath)

   ENDCASE

   DOEVENTS
   RETURN lvReturn
   SYS(1104)
   CLEAR ALL

ENDPROC

As you can see, the server parses the parameter object, then calls a method for each CASE. Your generic parameter approach is much better - much more flexable. Good job!
 
Almost forgot,

I don't think the VFP Quit command works with COM EXEs. Also, Some clown may get curious and kill the COM EXE in the Task Manager. Here is a TIP on how to handle that:

The goApp.DoProcess method looks like this:

Code:
LPARAMETERS toParm AS Object
LOCAL lvReturn, llFailed

TRY
    lvReturn = This.Process.DoProcess(toParm)
CATCH WHEN .T.
    llFailed = .T.
ENDTRY

IF llFailed
   This.Process = CREATEOBJECT("RunProcess.ProcessSession")
   DOEVENTS

   IF TYPE("This.Process") = "O"
      lvReturn = This.Process.DoProcess(toParm)
   ENDIF
ENDIF

RETURN lvReturn
 
Hi vernspace,

I don't really have a parameter for the execution, All you want to do has to be in the script. But of course, you can put in anything into your script code by textmerge operation before you set the script property. If that's what you mean, well, at first I also had a specific parameterization of a method AddTask into which you would pass in a script, an object and a propert name or method of that object for a callback. Then I simplified that to the script property and the properties for receiving the last error and last return value.

I have a QUIT in the destroy, because without it the background.exe stays resident, the task manager still shows the background.exe. The quit method is what you actually call from the client to indicate you want to quit. In the event of a system shutdown, I just call the QUIT command, not the quit() method. That could be better, true. But in the destroy I really mean the QUIT command to quit the EXE itself.

If you remove that QUIT from destroy, it could make a second CreateObject("background.process") faster. But the way I intend to initialize it once at the start of an application and only quit it at the end of that application I don't need background.exe to stay resident. nd obviously it helps the development, if you can build background.exe without needing to manually kill the background.exe in task manager.

It's in no way necessary to call This.Quit from destroy. If you turn on coverage profiling in the Quit method, you'll see the call of the Quit() method usually only makes one call of Sys(3098,This), which reduces the object ref count to 0. That makes goBackgroundprocess a dangling object reference, but still does not cause the Destroy() to run. Only the RELEASE of the goBackgroundProcess causes the destroy event to happen.

If you would not call the Quit() method but just RELEASE goBackgroundProcess in the main executable, I think you get along, too. The quit actually only is there to get rid of additional reference counts, the destroy is there to destroy the EXE itself, too. I don't want to get stuck in an endless mutual calling/raising of Destroy() and Quit(). If destroy executes SYS(3098,This) was already 0 and will stay 0. That may cause the Destroy() event, which then calls Quit(), etc. I may experiment with how that works out and monitor it with coverage profiling.

All in all I wanted a Quit() method you can call from your main process, not a Quit() method that is caused by a system shutdown. A system shutdown will kill any process anyway. But indeed you also can call a method with ON SHUTDOWN. It can't be THIS.Quit(), as ON SHUTDOWN runs out of context of THIS, but you could create an internal goProcess=This in the Init() and do ON SHUTDOWN goProcess.Quit().

But mainly the Quit() method it should be just as the Quit() method of word.application or other automation servers Quit() methods.

Thank you for the code resurrecting the COM server object when it's killed by a user in the task manager. I see that you would do that on the client side to ensure goBackgroundprocess is a valid reference. So it would be
Code:
Try
   goBackgroundProcess.IsAlive = .T.[s][/s]
Catch && if goBackgroundProcess is a zombie object, create it new:
   goBackgroundProcess = CreateObject("background.process")
Finally
   goBackgroundProcess.Script = ...
   goBackgroundProcess.Execute()
Endtry

Chriss
 
The IsAlive property needs to be added to the class, of course...

Code:
Define Class Process As Form OlePublic
   Hidden ExecuteScriptMessage
   IsAlive= .T.
   Script = ''
   ...

Chriss
 
Let me now summarize:

1. The use of one or several secondary processes to execute something in parallel is often demanded.
2. Possible ways are an MTDLL, a separate process, but also a COM server. With pros and cons, see Rick Strahl's article
3. A COM server present to execute something is avoiding the cost of starting an EXE repeatedly, so the performance is quite as good as a multithreading solution with the benefits of free threading, no need for STA mode. A COM server also has an interface to the background processs with multiple methods, for example, to set the default path. This interface does not need to stay the few properties and the Execute method, you're free to extend this as you like.
4. The actual spark of parallel execution is not just having a new processid with the COM server instance, but making use of the Windows message/event queue and the PostMessage API function that puts a message on this queue without waiting for it to be processed. The COM Server is based on a form to have its own HWND, by the way, that's the target of a message. and last not least the creation of the COM server instance from the main process naturally enables to have the necessary permissions to send messages to it, as it is a child process.
5. Keeping the COM Server executable usable with a defense mechanism against manually ending the background.exe process is possible, if you make use of TRY..CATCH. The CATCH part will be triggered if your object reference has become invalid and the catch branch can be used to recreate that object and then use it. Thanks to vernspace for that idea.

I also know some would use the WM_COPYDATA message, which could be used to not only trigger the execution of a COM server method. The copied data could also be the script and/or the parameters. I preferred the simple Script property, as you might also want to execute the same script multiple times without needing to pass it in again and again. Aside from the overhead of memory allocation and deallocation needed for WM_COPYDATA. Also, the Init() and Execute() methods of the COM Server class encapsulate the registering and using of the user-defined Windows message as the mechanism to spark parallel execution. You could, of course, also post the same message from the client side already and not need the Execute method, but I like the full encapsulation of this mechanism, as that means you also can switch to another solution in the future, without changing anything in the usage.

Chriss
 
Chris,

There is a security issue with your code: The "Script" property and the Execute() method are both PUBLIC interfaces. Theoretically, a hacker could exploit this. I call this "COM Injection".
 
Well, a hacker programming VFP can program VFP anyway. You can't inject scripts just like you can't inject SQL if you don't execute user input. While background.exe runs you don't have the reference to it unless you created it. And then again, you could execute VFP code anyway.

If you're concerned such a COM server registered on a machine can be used to execute VFP code, well, you should already be concerned if a VFP runtime is installed. I don't see a reason to harden the security of this, but you could do things MS does with powershell scripts: Only run signed code and add cryptographic checks.

I think there also are simpler ways of securing this. I know COM classes may not be creatable for everyone, though I'd have to dig up how that works. A cryptograhic verification layer would of course turn down performance, as every script signature needs to be verified before it's run.

I know usual security mechanisms are whitelisting of known executables restricting anything else to run. So any rogue EXE or also script that could potentially use that COM server to execute any VFP code would be rejected to run, that would be a strict restriction.


Chriss
 
In the end, if you're only usage of this is for a few scripts, you can change it so you have these as code within the COM server instead of using the script property. So you can limit this to only do what you have programmed, like anything else. Then you're back to only let in parameters you can easier check for valid ranges.

Chriss
 
In this day and age, all programmers (any language) need to develope the discipline to harden their applications as much as possible - any programmer who disagrees with this thinking should consider another profession.
 
I gave enough argumentation why this is no concern here, but teach me how you would use it as a way to sneak in code that should not be executed.

If you can program in VFP, you can also build an EXE and excute that. No hardening of this COM server prevents that. And as a VFP runtime is present, your EXE works, unless the company is too inept to appy group policies, a whitelisting of allowed executables or anything like that.

Lets say I program a validation of a signature, easy to do with a keypair that only allows the owner of a private key to sign his code and verify it with the public key. Now I give you the necessary steps to do that and you allow your users to write code you sign for them so it executes, then the security is broken by wrong usage. As any injection weakness it is prevented by not allowing to execute user input.

You could not allow a databse server as it has a known interface to send SQL to it, you could not allow much more.

If you allow macro execution of VBA, for example, that's the fault, not that you can create a background.process object and use it.

Again, if you can program a VFP script you also have the means to make it an executable and run it. And if you can write something that formats the hard drive, the problem is not that a program can be programmed, but that the users have the permission to run it.

So consider me an unprofessional developer, as I reject to add that verification here. You are not forbidden to do that for your concerns. But to me you're also considered deaf to argumentations that aree very valid in themselves.

I already gave another very valid solution to not use this public script property and run ExecScript(this.script), but simply program whatever you need to run in parallel in methods you trigger by windows messages. You can extend the init to register multiple such messages and also add multiple methods to trigger them. You can check for the eligibility of the current user in the Init, there are thousands of ides.

But if I teach you TRUNCATE empties a database table you also don't ask of me to teach that you have to harden your database so not everyone can execute any SQL. You're responsible for the security of your database server, the logins and permissions and the granted privileges on tables, not me. And I don't take that as a must-have in code that demonstrates a principle.

If you go that far, then please go back to any of your posts and add code that prevents this to be used by anybody that's not eligible to use it.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top