Results 1 to 32 of 32

Thread: VB6 MultiProcessing (StdExe-IPC via SharedMemory)

Hybrid View

  1. #1
    Fanatic Member
    Join Date
    Aug 2016
    Posts
    733

    Question Re: VB6 MultiProcessing (StdExe-IPC via SharedMemory)

    Quote Originally Posted by Schmidt View Post
    This demonstrates, how one can implement robust, asynchronous Workers in VB6 -
    by using a (cross-process) SharedMemory-approach (which is one of the common IPC-mechanisms).

    For convenient usage, the more complex stuff (the MemoryMapping- as well as the CreateProcess-APIs),
    are encapsulated in two generic "Drop-In"-Classes (cIPCMaster and cIPCWorker).

    Those Classes can be used either "split-up" (in separate VB6-Projects for Master and Worker) -
    but also "all-in-one" (when the same Project - or Process - is used to act as Master and Worker both).

    The latter case (using an "all-in-one"-Project), is the most convenient for developing/testing.
    Here the Worker-Process is "shelled" against the same ExeName as the Main- (or Master-) Project,
    using a simple "forking" in Sub Main() ... (as shown below from the content of Demo1 modMain.bas):
    Code:
    Option Explicit
    
    Sub Main() 'Process-Startup-Forking
      If Len(Trim$(Command$)) = 0 Then 'no Cmd-Arg was passed (it was started as the Master-Process)
        fMaster.Show '... so we simply startup the Main-Form
        
      Else 'it was started as a WorkerProcess (pass the CommandLine-Arg into the WorkerRoutine)
        EnterWorkerLoop New cIPCWorker, Trim$(Command$)
      End If
    End Sub
    Above, the blue-colored call represents the "master-fork-path" to your normal "GUI-Project-Code"
    (entered when no Commandline-Param was passed to your Executable).

    And as the magenta-colored routine-name (in the "worker-fork-path") suggests, the upstarting worker is not fired up
    like in an old "classic WebServer-CGI-call" (where the Process exits, after only a single Job was performed).

    Instead the current mechanism is implemented in a way, that the WorkerProcess is fired up once -
    and then enters an "IDLE-loop" (waiting for Jobs, provided by the Master-Process later).
    This way one of the disadvantages of MultiProcessing (the higher Startup-Costs, compared to MultiThreading) is avoided.

    The advantages of doing MultiProcessing instead of threading are:
    - no typelibs, no extra-Dlls are needed
    - in the IDE (after compiling the same Project), the asynchronous workers will behave the same way as in the compiled binary
    - a hard terminate of a worker is possible in a stable and "residue-free" manner (though graceful termination-support is of course built-in)
    - the communication between Master and Worker(s) happens in an absolute "non-blocking" way

    To explain the last point above a bit more... "non-blocking" means, that neither Post- or SendMessage-calls are involved
    (as in VB6-OleBased-Communications between "threaded Apartments", where Events, raised from the Workers will block the Main-Thread) -
    nor are there other mechanisms in play like Mutexes or CriticalSections, which are normally used in conjunction with shared memory...

    Instead the Demo shows, how "state-machine-based" communication (using the shared mem-area) can be implemented.

    The approach is extremely robust, completely IDE- and crash-safe, and "cleans up after itself" under any circumstances:
    - upstarted Worker-Processes will automatically close, when the Master-Class goes out of scope
    - you can even use the IDE-stop-button, whilst asynchronous workers are "deep within a Job" (worker-processes will autoclose nevertheless)

    There is also not a single thing, which is "forbidden to use" in the workers (like in many of the threading-approaches for VB6)...

    The Zip below comes with 3 Demo-Folders (a simple one to get up to speed - and two "medium-difficult" ones for MandelBrot-rendering).

    Ok, here is, what the MandelBrot-Demos will produce (using two Workers, independent from the Main-App):


    And here's the Demo-Zip: IPCSharedMem.zip

    Have fun...

    Olaf
    If I want to pass string data to the shared memory from the main process, how does the working process know the length of the shared string when reading data?


    Public Type RecordType

    str As String * 100 ------Must use a fixed-length string

    End Type

    Now I think of this method to solve this problem

    '====add
    Private Declare Function lstrcpyn _
    Lib "kernel32" _
    Alias "lstrcpynA" (ByVal lpString1 As Any, _
    ByVal lpString2 As Any, _
    ByVal iMaxLength As Long) As Long

    Private Declare Function lstrlen _
    Lib "kernel32" _
    Alias "lstrlenA" (ByVal lpString As Any) As Long
    Private str As String

    Private Sub cmdStart_Click()
    Label2.Caption = ""

    str = "hello:Schmidt" '
    'M.UserDataWrite StrPtr(str), lstrlen(str)
    lstrcpyn M.UserDataPtr, ByVal str, lstrlen(str) + 1
    'MsgBox lstrlen(M.UserDataPtr)
    M.JobState = jobPrepared


    Private Sub DoJob(W As cIPCWorker)

    Dim i As Long

    Dim tmpString As String

    tmpString = Space(lstrlen(W.UserDataPtr))
    lstrcpyn ByVal tmpString, W.UserDataPtr, lstrlen(W.UserDataPtr) + 1

    MsgBox tmpString

    For i = 1 To 100
    W.JobProgress = i / 100
    W.Wait 50 '<- simulate some workload (instead of doing some real stuff)

    If W.MasterWantsToClose Or W.JobState <> wjobProcessing Then Exit Sub 'early exit
    Next
    str = Replace(tmpString, "hello", "thanks")

    lstrcpyn W.UserDataPtr, ByVal str, lstrlen(str) + 1
    W.JobState = wjobFinished 'if the worker-routine reached this point, switch to the next state

    End Sub
    Demo1 Send string.zip

    Do you have other methods?
    Last edited by xxdoc123; Jun 4th, 2018 at 12:38 AM.

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Jun 2013
    Posts
    7,454

    Re: VB6 MultiProcessing (StdExe-IPC via SharedMemory)

    Quote Originally Posted by xxdoc123 View Post
    If I want to pass string data to the shared memory from the main process, how does the working process know the length of the shared string when reading data?

    Now I think of this method to solve this problem...

    Do you have other methods?
    There is no need to apply a (non-unicode-capable) API for that.

    I'd simply:
    - define all Strings of a Shared-Memory-Area as fixed-length-string-members of an UDT
    - then later always read (or write) the entire UDT-content from (or to) the Shared-Area via: UserDataWrite VarPtr(MyUDT), LenB(MyUDT)
    - and for "InBetween" happening changes of the UDTs FixedLength-StringMembers I'd use a simple "Property-Helper" like shown below:

    Code:
    Option Explicit
    
    Private Type tMyUDT
      S As String * 100
    End Type
    
    Private Sub Form_Load()
      Dim MyUDT As tMyUDT
      Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print the S-member content from a fresh initialized UDT
      
      FLS(MyUDT.S) = "123": Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print it after assigning "123"
      FLS(MyUDT.S) = "":    Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print it again, after assigning ""
    End Sub
    
    Private Property Let FLS(FLSmember As String, RHS As String)
      If Len(FLSmember) <= Len(RHS) Then Err.Raise 7
      FLSmember = RHS & vbNullChar
    End Property
    Private Property Get FLS(FLSmember As String) As String
      Dim Pos As Long: Pos = InStr(FLSmember, vbNullChar)
      If Pos > 0 Then FLS = Left$(FLSmember, Pos - 1)
    End Property
    Olaf

  3. #3
    Fanatic Member
    Join Date
    Aug 2016
    Posts
    733

    Re: VB6 MultiProcessing (StdExe-IPC via SharedMemory)

    Quote Originally Posted by Schmidt View Post
    There is no need to apply a (non-unicode-capable) API for that.

    I'd simply:
    - define all Strings of a Shared-Memory-Area as fixed-length-string-members of an UDT
    - then later always read (or write) the entire UDT-content from (or to) the Shared-Area via: UserDataWrite VarPtr(MyUDT), LenB(MyUDT)
    - and for "InBetween" happening changes of the UDTs FixedLength-StringMembers I'd use a simple "Property-Helper" like shown below:

    Code:
    Option Explicit
    
    Private Type tMyUDT
      S As String * 100
    End Type
    
    Private Sub Form_Load()
      Dim MyUDT As tMyUDT
      Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print the S-member content from a fresh initialized UDT
      
      FLS(MyUDT.S) = "123": Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print it after assigning "123"
      FLS(MyUDT.S) = "":    Debug.Print Len(FLS(MyUDT.S)), FLS(MyUDT.S) 'print it again, after assigning ""
    End Sub
    
    Private Property Let FLS(FLSmember As String, RHS As String)
      If Len(FLSmember) <= Len(RHS) Then Err.Raise 7
      FLSmember = RHS & vbNullChar
    End Property
    Private Property Get FLS(FLSmember As String) As String
      Dim Pos As Long: Pos = InStr(FLSmember, vbNullChar)
      If Pos > 0 Then FLS = Left$(FLSmember, Pos - 1)
    End Property
    Olaf
    thanks .

    I have a suggestion to add a length shared variable in the two class module. You can directly return the length

    Private Type tShared 'a convenience-UDT, sitting at the top of the shared mem-area
    UserDataLen As Long
    MasterHwnd As Long
    JobProgress As Single
    JobPreparedHPSec As Double
    JobStartedHPSec As Double
    JobFinishedHPSec As Double
    JobState As eJobState
    UserWriteDataLen As Long '
    End Type
    Public Sub UserDataWrite(ByVal pSrc As Long, _
    ByVal ByteLen As Long, _
    Optional ByVal Offs As Long)

    If BaseAddr Then
    RtlMoveMemory ByVal UserDataPtr + Offs, ByVal pSrc, ByteLen
    T.UserWriteDataLen = ByteLen
    WriteInternalType T
    End If
    End Sub
    I can know the length of the currently written data, and if it needs to be read, I can initialize the length of the variable.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width