Sharing a structure between processes

by Richard Russell, October 2014

Most modern PCs have multiple CPU cores, often four or more. If you are creating a computationally-intensive application you may wish to take advantage of this feature, but that is not so easy to do in BBC BASIC. One approach is to code some of the functionality in assembly language (using the built-in assembler) which can be arranged to run in a separate thread, and therefore potentially on a different core. However this requires specialised skills which not all BASIC programmers can be expected to acquire; debugging assembler code can also be difficult.

If you are coding purely in BASIC there is really only one way to take advantage of multiple CPU cores, and that is to run multiple executables, and hence multiple processes - Windows will then automatically allocate the different processes to different cores if available. Depending on the nature of your application these may be multiple copies of the same executable, if it is possible to subdivide the task in this way, or executables compiled from different BASIC sub-programs.

Either way there is very likely to be a need for the multiple processes to communicate with each other, in order to coordinate the work. In Windows there are many possible types of Inter Process Communication (IPC), all of which may be used by BBC BASIC programs with more or less difficulty. One particularly convenient method is the ability to share a data structure between processes; even though your different executables will be running in quite separate address spaces you can arrange that the structure is visible to them all!

This is surprisingly easy to achieve. To do so you should incorporate the following code in the initialisation routines of all the programs that need to share the structure:

        FILE_MAP_WRITE = 2
        PAGE_READWRITE = 4
 
        DIM Shared{member1, member2%, member3&, member4#, etc}
 
        SYS "CreateFileMapping", TRUE, 0, PAGE_READWRITE, 0, DIM(Shared{}), "unique string" TO hMap%
        IF hMap% = 0 ERROR 0, "Couldn't create file-mapping object"
        SYS "MapViewOfFile", hMap%, FILE_MAP_WRITE, 0, 0, DIM(Shared{}) TO pShared%
        IF pShared% = 0 ERROR 0, "Couldn't map view of file"
        !(^Shared{}+4) = pShared%

The string shown as “unique string” must be unique to your application; to guarantee that you don't accidentally use the same string as another program the best choice is a Globally Unique Identifier (GUID).

Note that the structure can contain any valid member types except ordinary (moveable) strings or string arrays (e.g. member$). If you want to share strings use NUL-terminated arrays of characters such as member&(255) which can be accessed as follows:

        Shared.member&() = "Hello world!"
        PRINT Shared.member&()

In your program's 'cleanup' routine include the following code:

        pShared% += 0 : IF pShared% SYS "UnmapViewOfFile", pShared% : pShared% = 0
        hMap% += 0 : IF hMap% SYS "CloseHandle", hMap% : hMap% = 0

It is extremely important that two or more processes don't attempt to write to the same structure member at the same time. The safest way to guarantee that is for each structure member to be writable only by one of your processes, and read-only to all the others.

Also, be aware of the possibility that the data read by one process may be invalid because it is concurrently being written by another process (if it's an aligned 32-bit integer you can probably be assured of an 'atomic' update, but not with larger data types). If this is a concern you can use a mutex to prevent concurrent access.