// rev. 2023-10-07 ======================================================================================================================= === HOW TO INCLUDE THE LIBRARY INTO YOUR PROJECT === ======================================================================================================================= [A] For using ONLY utilities like file i/o, locks, C array wrappers, multithreading, shared memory: #include "bmdx_cpiomt.h" This header is self-contained. [B] For using ONLY multipart vectors and/or hashed associative arrays: #include "vecm_hashx.h" This header is self-contained. [C=A+B] To combine classes bmdx_cpiomt.h and vecm_hashx.h: #include "bmdx_config.h" This includes the headers in such way that vecm_hashx.h starts using cpiomt's lock classes for certain operations. [D=full] Complete library API: #include "bmdx_main.h" The project must be compiled/linked with bmdx_main.cpp. ======================================================================================================================= ==== COMPILER NOTES BY PLATFORM ==== === COMPILER INVOCATION EXAMPLES === ===== TESTED COMPILER VERSIONS ===== ======================================================================================================================= The below notes help ensuring near-identical behavior of an application on the variety of operating systems and compilers. FORMAT: OS name and version 1. 2. 3... - notes ----------------------------------------------------- compiler name and version, target architecture compiler invocation example: executable, optimize, static link to std. lib., no debug info compiler invocation example: executable, no optimization, dynamic link to std. lib., no debug info compiler invocation example: shared library, optimize, static link to std. lib., no debug info compiler invocation example: shared library, no optimization, dynamic link to std. lib., no debug info ================================== Common notes (important!). 1. In the examples, private module symbols binding is forced everywhere if possible. This mode is recommended for any programs, using custom shared libraries. Otherwise, all custom shared libraries, used by the application, must be assembled together with main executable, or at least exactly with the same compiler type and version. Otherwise, uncontrolled automatic calling static functions in non-compatible module (shared library or executable), different from the current one, in almost 100% leads to program failure. This problem is compiler-specific, de-facto not a standard, and not related to BMDX library. 2. Avoid using dynamic_cast in the program that uses shared libraries, on objects, taken from another binary module (i.e. shared library or executable). Otherwise frequently leads to program crash. This problem is independent on compiler type and version matching. 3. For most of POSIX platforms, few compiler switches are highly recommended for complete BMDX builds. The recommendation is expressed in self-explanatory qmake language: !*win32*:*g++*|*clang* { !*bsd* { LIBS += -ldl } *linux*:equals(TEMPLATE,app) { QMAKE_LFLAGS += -Wl,-E } # for executables *linux*|*bsd* { QMAKE_LFLAGS += -Wl,-Bsymbolic } *linux*|*bsd*:equals(TEMPLATE,lib) { QMAKE_CXXFLAGS += -fPIC } # for shared libraries } NOTE If the main executable does not use cross-module features of BMDX (does not load shared libraries that also use BMDX), the following switches may be omitted: -Wl,-E -Wl,-Bsymbolic ================================== Linux Ubuntu 18.04 1. (common) Private module symbols binding is recommended for linker: -Wl,-Bsymbolic 2. (common) If shared library needs to get some symbols from main executable (via dlsym), use -Wl,-E 3. (common) If possible, prefer GCC (g++) - the main system compiler. 4. (compiler-specific) It's recommended to link with C, C++ std. libs. statically. GCC: -static-libgcc -static-libstdc++ Clang: -static-libgcc 5. (BMDX) The library may be compiled with optimization 0..2. 6. (BMDX, compiler-specific) Default (without -Bsymbolic) symbols binding is compiler-dependent: GCC: DOES share symbols between binary modules. Clang: DOES NOT share symbols between binary modules. This may lead to interop. problems between binary modules from different compiler types. 6.1. Do not use "-static" option when building executable and shared libraries. 6.2. If the default behavior (no -Bsymbolic) is required, use: -Dbmdx_cmti_stdstring_mode=0 to force BMDX std strings copying by value in all cases (instead of references). 6.3. Also, without -Bsymbolic, private module symbols binding in unity::mod-->dlopen call may be enabled with -Dbmdx_mod_load_def_flags=1 (enables RTLD_DEEPBIND on shared library load, see also man dlopen). This works well only with GCC. Automatic choice, based on compiler type, works better: -Dbmdx_mod_load_def_flags=2 ----------------------------------------------------- Clang 6.0.0 (6.0.0-1ubuntu2, tags/RELEASE_600/final), x86_64-pc-linux-gnu Clang 10.0.0, x86_64-unknown-linux-gnu clang -static-libgcc -Wl,-E -m64 -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread clang -Wl,-E -m64 -Wl,-Bsymbolic -O0 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread clang -static-libgcc -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread clang -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O0 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread ----------------------------------------------------- GCC 7.3.0 (Ubuntu 7.3.0-16ubuntu3), x86_64-linux-gnu g++ -static-libgcc -static-libstdc++ -Wl,-E -m64 -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -ldl -lpthread g++ -Wl,-E -m64 -Wl,-Bsymbolic -O0 -o test1 test1.cpp bmdx_main.cpp -ldl -lpthread g++ -static-libgcc -static-libstdc++ -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -ldl -lpthread g++ -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O0 -o test1.so test1.cpp bmdx_main.cpp -ldl -lpthread For 32-bit executable: use -m32 instead of -m64 ================================== Linux Ubuntu 22.04 All compiler notes are same as for Ubuntu 18.04. Tested compilers: gcc version 12.1.0 (Ubuntu 12.1.0-2ubuntu1~22.04), target: x86_64-linux-gnu Ubuntu clang version 15.0.6, target: x86_64-pc-linux-gnu ================================== macOS 10.15.6 1. (common) Do not link C, C++ libs. statically into program. This produces faulty binaries. 2. (common) If possible, prefer Clang - the main system compiler. 3. (BMDX) The library may be compiled with optimization 0..2. 4. (BMDX, compiler-specific note) Linking via both GCC and Clang uses different default rules for binding and visibility. Certain symbols (esp. static variables and inline functions) are shared between modules in unexpected manner. BMDX is resistant to these differences. Its objects, when crossing binary module boundaries, behave exactly as on all other platforms. Additional notes on using compiler options for symbols binding: 4.1. -fvisibility-inlines-hidden: suppresses inline functions "aliasing"; so that the code in shared library executes its own inline functions instead of mapping (as non-inline) into main executable. 4.2. In GCC (g++), if inlines are hidden, do not enable optimization level >= 2. 4.3. -fvisibility=hidden: any cross-module calls to module, compiled with this option, will not operate (dlsym fails because the module does not export symbols). ----------------------------------------------------- Apple clang version 12.0.0 (clang-1200.0.32.27), x86_64-apple-darwin19.6.0 clang -m64 -O2 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread clang -shared -Wall -m64 -O2 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread ----------------------------------------------------- GCC 5.1.0, x86_64-apple-darwin14.4.0 a) specify appropriate path to system headers, e.g. xcodedir=/Users/admin/Xcode.app sdkdir=MacOSX.sdk platformdir=/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs incl1="-idirafter$xcodedir/$platformdir/$sdkdir/usr/include" b) automatically detect the path incl1="-isysroot`xcrun --show-sdk-path`" g++ -m64 $incl1 -O2 -o test1 test1.cpp bmdx_main.cpp -ldl -lpthread g++ -shared -Wall -m64 $incl1 -O2 -o test1.so test1.cpp bmdx_main.cpp -ldl -lpthread ================================== macOS 13.2 All compiler notes are same as for macOS 10.15.6. Tested compilers: Apple clang version 14.0.0 (clang-1400.0.29.202), Target: x86_64-apple-darwin22.3.0 gcc version 12.2.0 (Homebrew GCC 12.2.0), Target: x86_64-apple-darwin22 ================================== iOS 1. Read OS X / macOS notes first. ----------------------------------------------------- XCode 12.2, / Apple clang version 12.0.0 XCode 14.2 (14C18) / Apple clang version 14.0.0 Specific options (in build settings): executable: Symbols Hidden By Default = No, Dead Code Stripping = No executable, shared library: Warnings / Implicit conversion to 32 bit type = No Explanation: by default, extern "C" symbols are hidden in the executable, but not in the shared libraries. ================================== Windows 8.1 x64, ver. 6.3, build 9600 Windows 10 x64, ver. 21H2, build 19044.1415 Compiler-related notes. 1. MSVC, Intel C++ 1.1. It's recommended to minimize dynamic library dependencies: -MT NOTE This mode does not require additional mt.exe call for manifest embedding. 1.2. It's recommended to treat warnings as errors: -WX 2. MinGW32, MinGW-w64 2.1. It's recommended to minimize dynamic library dependencies: -static -static-libgcc -static-libstdc++ 2.2. When creating a DLL, DLL symbols must be correctly exported to interoperate with other modules: -Wl,--add-stdcall-alias 3. bcc64 The compiler has certain amount of bugs. To create modules that can freely interoperate, use the factually working -tR, -tD, -tM switches combination, as shown in the below examples. ----------------------------------------------------- clang 10.0.0, i386-pc-windows-msvc, x86_64-pc-windows-msvc http://clang.org clang -static -m64 -Xclang -flto-visibility-public-std -O2 -o test1.exe test1.cpp bmdx_main.cpp clang -m64 -Xclang -flto-visibility-public-std -O0 -o test1.exe test1.cpp bmdx_main.cpp clang -static -shared -Wall -m64 -Xclang -flto-visibility-public-std -O2 -o test1.dll test1.cpp bmdx_main.cpp clang -shared -Wall -m64 -Xclang -flto-visibility-public-std -O0 -o test1.dll test1.cpp bmdx_main.cpp For 32-bit executable: use -m32 instead of -m64 ----------------------------------------------------- MinGW-w64 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project) http://mingw-w64.org g++ -static -static-libgcc -static-libstdc++ -O2 -o test1.exe test1.cpp bmdx_main.cpp g++ -O0 -o test1.exe test1.cpp bmdx_main.cpp g++ -static -static-libgcc -static-libstdc++ -shared -Wl,--add-stdcall-alias -Wall -O2 -o test1.dll test1.cpp bmdx_main.cpp g++ -shared -Wl,--add-stdcall-alias -Wall -O0 -o test1.dll test1.cpp bmdx_main.cpp ----------------------------------------------------- MinGW-32 4.8.1 (GCC) http://mingw.org g++ -static -static-libgcc -static-libstdc++ -O2 -o test1.exe test1.cpp bmdx_main.cpp g++ -O0 -o test1.exe test1.cpp bmdx_main.cpp g++ -static -static-libgcc -static-libstdc++ -shared -Wl,--add-stdcall-alias -Wall -O2 -o test1.dll test1.cpp bmdx_main.cpp g++ -shared -Wl,--add-stdcall-alias -Wall -O0 -o test1.dll test1.cpp bmdx_main.cpp ----------------------------------------------------- MSVC 19.28.29515.1 (VS 2019), x86, x64 MSVC 19.10.25019 (VS 2017), x86, x64 set _base=c:\vs2019\ide\VC\Tools\MSVC\14.28.29515 set _base=c:\vs2017\VC\Tools\MSVC\14.10.25017 set libpath1=c:\winsdk10\Lib\10.0.10586.0 set libpath2=%_base%\lib rem === 32-bit target === set libpath_all=-libpath:%libpath2%\x86 -libpath:%libpath1%\um\x86 -libpath:%libpath1%\ucrt\x86 set path=%_base%\bin\HostX86\x86;%path% rem === 64-bit target === rem set libpath_all=-libpath:%libpath2%\x64 -libpath:%libpath1%\um\x64 -libpath:%libpath1%\ucrt\x64 rem set path=%_base%\bin\HostX64\x64;%path% set incl1="%_base%\include" cl -nologo -EHsc -WX -I%incl1% -MT -O2 -Fetest1.exe test1.cpp bmdx_main.cpp -link %libpath_all% cl -nologo -EHsc -WX -I%incl1% -MD -Od -Fetest1.exe test1.cpp bmdx_main.cpp -link %libpath_all% -manifest cl -nologo -EHsc -WX -I%incl1% -MT -O2 -Fetest1.dll -LD test1.cpp bmdx_main.cpp -link %libpath_all% cl -nologo -EHsc -WX -I%incl1% -MD -Od -Fetest1.dll -LD test1.cpp bmdx_main.cpp -link %libpath_all% -manifest ----------------------------------------------------- MSVC 18.00.21005.1 (VS 2013), x86, x64 MSVC 15.00.21022.08 (VS 2008), x86 rem === MSVC 18 === set _base=C:\Program Files (x86)\Microsoft Visual Studio 12.0 rem === MSVC 15 === rem set _base=c:\vc2008 rem === 32-bit target === set path=%_base%\VC\bin;%_base%\Common7\IDE;%_base%\Common7\Tools;%path% call vcvars32.bat rem === 64-bit target === rem set path=%_base%\VC\bin\amd64;%_base%\Common7\IDE;%_base%\Common7\Tools;%path% rem call vcvars64.bat set incl1="%_base%\include" cl -nologo -EHsc -WX -I%incl1% -MT -O2 -Fetest1.exe test1.cpp bmdx_main.cpp cl -nologo -EHsc -WX -I%incl1% -MD -Od -Fetest1.exe test1.cpp bmdx_main.cpp -link -manifest mt.exe -manifest test1.exe.manifest -outputresource:test1.exe;1 cl -nologo -EHsc -WX -I%incl1% -MT -O2 -Fetest1.dll -LD test1.cpp bmdx_main.cpp cl -nologo -EHsc -WX -I%incl1% -MD -Od -Fetest1.dll -LD test1.cpp bmdx_main.cpp -link -manifest mt.exe -manifest test1.dll.manifest -outputresource:test1.dll;2 ----------------------------------------------------- Intel C++ 15.0.3.208 (build 20150407), IA-32, x64 icl -nologo -EHsc -WX -MT -fast -Fetest1.exe test1.cpp bmdx_main.cpp icl -nologo -EHsc -WX -MD -Od -Fetest1.exe test1.cpp bmdx_main.cpp -link -manifest mt.exe -manifest test1.exe.manifest -outputresource:test1.exe;1 icl -nologo -EHsc -WX -MT -fast -Fetest1.dll -LD test1.cpp bmdx_main.cpp icl -nologo -EHsc -WX -MD -Od -Fetest1.dll -LD test1.cpp bmdx_main.cpp -link -manifest mt.exe -manifest test1.dll.manifest -outputresource:test1.dll;2 ----------------------------------------------------- bcc64 version 5.0.2 (LLVM 5.0.2), x86_64-pc-windows-elf rem === Executable, static RTL, optimized === bcc64 -tM -O2 -o test1.exe test1.cpp bmdx_main.cpp rem === Executable, dynamic RTL === bcc64 -tR -tM -O0 -o test1.exe test1.cpp bmdx_main.cpp rem === DLL, static RTL, optimized === bcc64 -tM -tD -Wall -O2 -o test1.dll test1.cpp bmdx_main.cpp rem === DLL, dynamic RTL === bcc64 -tR -tM -tD -Wall -O0 -o test1.dll test1.cpp bmdx_main.cpp ================================== Android ----------------------------------------------------- Android Studio 3.0.1. Target architectures: x86, arm64-v8a Specific options, additional libraries (in app/build.gradle): minSdkVersion 21 cppFlags "-std=c++11 -frtti -fexceptions -O0" CMakeLists.txt (example line for building native library): target_link_libraries( native-lib ${log-lib} -latomic ) Optimization levels tested: -O0, -O2. ================================== Solaris 11.1 1. Recommended linker binding mode - do not share symbols between binary modules: -xldscope=symbolic 2. It's recommended to treat warnings as errors: -xwe 3. The library is tested with global template instances linking (this is the default): -instances=global ----------------------------------------------------- CC: Sun C++ 5.12 SunOS_i386 2011/11/16 CC -Bstatic -m64 -xldscope=symbolic -xwe -O3 -o test1 test1.cpp bmdx_main.cpp CC -Bdynamic -m64 -xldscope=symbolic -xwe -o test1 test1.cpp bmdx_main.cpp CC -Bstatic -shared -m64 -xldscope=symbolic -xwe -O3 -o test1.so test1.cpp bmdx_main.cpp CC -Bdynamic -shared -m64 -xldscope=symbolic -xwe -o test1.so test1.cpp bmdx_main.cpp ================================== FreeBSD 10.1 1. The library may be compiled with optimization: 0..2 for Clang, 0..1 for GCC (level 2 is not valid - exception handling is faulty). 2. The only reliable linker binding mode for multimodule programs - do not share symbols between binary modules: -Wl,-Bsymbolic 3. For GCC, it's recommended to link with C, C++ std. libs. statically: -static-libgcc -static-libstdc++ 4. If shared library needs to import symbols from main executable (via dlsym), use -Wl,-E for that executable. ----------------------------------------------------- Clang 3.4.1 (tags/RELEASE_34/dot1-final 208032) 20140512, x86_64-unknown-freebsd10.1 clang -Wl,-E -m64 -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -lpthread clang -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -lpthread ----------------------------------------------------- GCC 4.8.3, x86_64-portbld-freebsd10.1 comp=/usr/local/lib/gcc48 lib1=$comp lib2=/usr/local/lib incl1=$comp/include/c++ incl2=$incl1/x86_64-portbld-freebsd10.1 g++ -static-libgcc -static-libstdc++ -Wl,-E -m64 -fno-use-linker-plugin -Wl,-Bsymbolic -I$incl1 -I$incl2 -O1 -o test1 test1.cpp bmdx_main.cpp -L$lib1 -L$lib2 -lpthread g++ -Wl,-E -m64 -fno-use-linker-plugin -Wl,-Bsymbolic -I$incl1 -I$incl2 -O0 -o test1 test1.cpp bmdx_main.cpp -L$lib1 -L$lib2 -lpthread g++ -static-libgcc -static-libstdc++ -shared -fPIC -Wall -m64 -fno-use-linker-plugin -Wl,-Bsymbolic -I$incl1 -I$incl2 -O1 -o test1.so test1.cpp bmdx_main.cpp -L$lib1 -L$lib2 -lpthread g++ -shared -fPIC -Wall -m64 -fno-use-linker-plugin -Wl,-Bsymbolic -I$incl1 -I$incl2 -O0 -o test1.so test1.cpp bmdx_main.cpp -L$lib1 -L$lib2 -lpthread ================================== FreeBSD 12.1 1. All notes, made for FreeBSD 10.1, apply here too. 2. Warning. System compiler (Clang) fails to catch C++ exceptions, thrown by another compiler's binary module. Tested between Clang-GCC. To avoid problems in application that loads shared libraries from different compilers, use exceptionless part of BMDX API (e.g. objPtr instead of objRef, and so on). ----------------------------------------------------- clang version 8.0.1 (tags/RELEASE_801/final 366581), x86_64-unknown-freebsd12.1 clang -Wl,-E -m64 -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -lpthread clang -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -lpthread ----------------------------------------------------- gcc version 9.3.0 (FreeBSD Ports Collection), x86_64-portbld-freebsd12.1 g++ -static-libgcc -static-libstdc++ -Wl,-E -m64 -Wl,-Bsymbolic -O1 -o test1 test1.cpp bmdx_main.cpp -lpthread g++ -Wl,-E -m64 -Wl,-Bsymbolic -O0 -o test1 test1.cpp bmdx_main.cpp -lpthread g++ -static-libgcc -static-libstdc++ -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O1 -o test1.so test1.cpp bmdx_main.cpp -lpthread g++ -shared -fPIC -Wall -m64 -Wl,-Bsymbolic -O0 -o test1.so test1.cpp bmdx_main.cpp -lpthread ================================== Raspberry Pi 4 Model B / Ubuntu 20.04 All compiler notes are same as for Ubuntu 18.04. ----------------------------------------------------- gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1), aarch64-linux-gnu g++ -static-libgcc -static-libstdc++ -Wl,-E -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -ldl -lpthread g++ -Wl,-E -Wl,-Bsymbolic -O0 -o test1 test1.cpp bmdx_main.cpp -ldl -lpthread g++ -static-libgcc -static-libstdc++ -shared -fPIC -Wall -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -ldl -lpthread g++ -shared -fPIC -Wall -Wl,-Bsymbolic -O0 -o test1.so test1.cpp bmdx_main.cpp -ldl -lpthread ----------------------------------------------------- clang version 12.0.0-3ubuntu1~20.04.5, aarch64-unknown-linux-gnu clang++-12 -static-libgcc -m64 -Wl,-E -Wno-psabi -Wl,-Bsymbolic -O2 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread -latomic clang++-12 -m64 -Wl,-E -Wno-psabi -Wl,-Bsymbolic -O0 -o test1 test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread -latomic clang++-12 -static-libgcc -m64 -shared -fPIC -Wall -Wno-psabi -Wl,-Bsymbolic -O2 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread -latomic clang++-12 -m64 -shared -fPIC -Wall -Wno-psabi -Wl,-Bsymbolic -O0 -o test1.so test1.cpp bmdx_main.cpp -lstdc++ -lm -ldl -lpthread -latomic ======================================================================================================================= === BRIEF SUMMARY ON CROSS-MODULE CLASSES === ======================================================================================================================= This section presents classes, whose objects may be passed between from binary module A to binary module B of the same process by pointer or reference (as such, or wrapped), and called in module B directly. Some of them are completely protected from compiler differences, some have certain limitations. 1. Containers. 1.1. Arrays and queues. struct carray_t, carray_r_t, cpparray_t, arrayref_t, vnnqueue_t. carray_t: wrapper around C-style array. Based on _carray_base_t. Assumes that elements do not require procedural copy construction or destruction. carray_t object may be passed between binary modules. If the original module uses malloc/free common to all modules, carray_t object may be safely destroyed in another module even after the original module has been unloaded. carray_r_t: carray_t with place reserving. It should be used instead of std::basic_string when it's necessary to access and manipulate (e.g. append to) a contiguous string of primitive values (bytes, integers, floats etc.) in more than one binary module. Sizing factor in automatic place reserving is 2. cpparray_t: wrapper around C-style array. Based on _carray_base_t. The array assumes that its elements require C++-style construction and/or destruction, and that the element, constructed in one binary module, may be safely destroyed in another. E.g. classes like cref_t and unity may be 100% safely used as cpparray_t elements in cross-module context. (NOTE The array remembers (only) memory allocation / deallocation function pointers from original module where it has been created. When particular array instance resizing, copying, destruction may happen in different binary modules, the element constructors and destructor must be compatible or trivial. To cover another case (element constructors, destructor are semi- or incompatible, but the elements are not required to be kept contiguously in memory), cpparray_t or vec2_t may be used instead of cpparray_t.) In other respects, cpparray_t is similar to carray_t. arrayref_t: weak reference to contiguous array of elements. Contains only a pointer and a number of elements. Basically, may be constructed from T*, carray_t, carray_r_t, cpparray_t, basic_string. See also _arrayref_adapter_t. vnnqueue_t: non-locking queue of objects. Cross-module-related behavior is similar to cpparray_t: the queue assumes that its elements require C++-style construction and/or destruction, and that the element, constructed in one binary module, may be safely destroyed in another. E.g. classes like cref_t and unity may be 100% safely used as vnnqueue_t elements in cross-module context. 1.2. struct cref_t Template class for strong/weak shared referencing an object, completely thread-safe. When the contained object is created with cross-module API (iref2, cm_create), the resulting cref_t may be passed between binary modules in any possible way (by value, reference or pointer). By default, the contained object is considered constant, but the client may optionally call non-const functions through additional API (ref_ts, _pnonc_u, operator->). For cross-module calls to the contained object being safe, the client should use with cref_t a) BMDX cross-module classes or b) specially designed custom classes. 1.3. struct unity Polymorphic non-template value: character, number, string, date, array, map, hashlist, object wrapper. May be created/destroyed/copied/modified in different binary modules. Uses built-in compatibility checks and static methods redirection for all wrapped. When unity wraps a user object, the client in any binary module may call it in one of two ways: a) obtain an object pointer (objPtr) and call the wrapped object directly. This works well only if the wrapped object is itself cross-module. b) on the creating side, attach a set of interfaces to the object (objItfsAttach) + declare simple proxy class (o_proxy specialization) for passing the client calls to implementations of that interfaces, which in turn call the wrapped object as necessary, inside its own module. For calling the interface, an wrapped interface pointer must be created (see unity :: pinterface). Interfaces may be used with any kind of wrapped object, even if it's not of a C++ class (e.g. int[]). When the wrapped object is not a subclass of an interface, a specialization of struct o_iimpl may be created, implementing the interface for particular object's type. See bmdx_main.h for details. 1.4. struct vecm, vec2_t, hashx, ordhs_t. Polymorphic vector, template vector, hashed associative array, hashed ordered map - all from vecm_hashx.h. They provide implementation for struct unity's built-in arrays and associative arrays. Cross-module behavior is similar to cref_t in the sense that container's elements must be a) trivial types, b) classes without destructor or with cross-module safe destructor, c) BMDX cross-module classes, like struct unity, carray_t etc., and vecm, vec2_t etc. themselves, d) application-defined classes, specially designed as cross-module. 1.5. struct bmdx_str::flstr_t Fixed length string. Data buffer does not use dynamic allocation. All member functions behave similarly in any binary module. 1.6. struct unity::pcos Interface to in-process common objects storage. One storage hash map { unity, cref_t } is associated with each loaded binary module of the process. The storage in main executable is the default one. The storage is created on first client request. Any binary module may put a value into the storage or get a reference to existing value by its key. References are based on cref_t and designed in such way that shared library, hosting the executable code of an object in the storage, is unloaded anyway after the last such object is destroyed. (The client has only to avoid situations of library self-unloading.) 2. Special objects. Like some of BMDX containers, they use static method tables, to ensure all their methods being called in the binary module where they have been created. 2.1. struct critsec_t, critsec_t::csdata By default critsec_t is a per-type-per-module lock, i.e. it locks in the scope of its own binary module. But, an instance of csdata may be passed to critsec_t constructor, to individually lock any particular object or section of code. While csdata internal structure is compiler-dependent, all system API calls around it (inside both csdata and critsec_t) are made through static method tables, which means that 1) csdata object may be passed by pointer into any binary module of the process, and safely used there, 2) critsec_t itself is completely compiler-independent, i.e. the lock, set in one binary module, may be safely released from within another. 2.2. struct threadctl Thread control object. Based on client implementing virtual function (threadctl::ctx_base::_thread_proc). Base class supports high-level object passing to thread procedure, and its automatic destruction on thread exit (both normal and termination). 2.3. struct threadctl::tid Wraps system-dependent thread handle type. Allows for handle conversion into number and checking for implementation compatibility if passed into another module. 2.4. struct unity::mod_handle Wraps system-dependent shared library handle type. mod_handle may be freely passed between modules, and live longer than module where it's created. (Handle destruction is safe unless the top-level module invoking unity::mod to load library has been itself loaded/unloaded in other way.) 2.5. struct unity::o_api Functions to operate on an object, wrapped (strong- or weak-referenced) into unity object. While o_api is expected to be always created and called locally, it anyway redirects any requested operation into the native module of the referenced unity object. 2.6. struct unity::o_api::critsec_rc Critical section object to protect concurrent operations on o_api and around it. 2.7. struct o_type_info, o_ref_info Information on type and reference count of an object, wrapped (strong- or weak-referenced) into unity object. 2.8. struct i_dispatcher_mt Thread-safe interface to message dispatcher proxy object. Proxy objects (wrapped into unity object) are created with dispatcher_mt::new_proxy. Access: via unity::pinterface. 2.9. struct file_io Wrapper around file handle (FILE). Contains no other data members. Cross-module compatibility of file_io is exactly that of std. C library fread, fwrite, rewind etc. ======================================================================================================================= === KNOWN ISSUES === ======================================================================================================================= 1. Certain compilers delay shared library unloading (i.e. C++ objects static deinitialization) until after main executable deinitialization. This may cause problems with unloading libraries, holding objects of each other in static variables. Ways to minimize chance of problems: a) right before the application exits, clear (in application-specific way) all static variables, containing strong references to cross-module objects. b) use exclusively common objects storage of the main executable (see struct pcos) for continuous storing any objects, shared between binary modules. 2. (Context: POSIX systems only / processctl::launch). The default choice for starting new processes is vfork(), which may be unsafe in certain (very specific) conditions. If this is the case, disallow vfork() use at compile time: -D__bmdx_cfg_allow_vfork=0 See __bmdx_cfg_allow_vfork for details. ======================================================================================================================= === MESSAGE DISPATCHER === ======================================================================================================================= === Definition === BMDX message dispatcher is the specialized communication subsystem, allowing multiple clients (like programmatic objects, CPU threads, processes) to exchange high-level structured information, with optional binary attachments. The message dispatcher is represented by - dispatcher_mt class, - i_dispatcher_mt interface, - underlying implementation (using several dedicated CPU threads). === State of development === Currently implemented: - in-process message exchange (transparently between executable and shared libraries). - inter-process message exchange (based on system API for shared memory). - subscriptions for messages. - dynamic re-configuring the scheme of communications (adding/removing dispatcher threads and slots). Main planned features: - network message exchange (based on TCP) - public interface for adding application-specific message exchange protocols. === Terminology === "dispatcher process", also "dispatcher session" - an instance of dispatcher_mt and all its associated objects and CPU threads. Optionally, one CPU process may create any number of independent dispatcher processes. "dispatcher thread" - an object, consisting of input, output, and internal queues for messages. Messages as kept temporarily, until a) read and popped by the client, b) sent to another dispatcher process, c) dispatcher session ends. Normally, one dispatcher thread should be associated with one application-specific task ("client"), implemented as function, object, or as CPU thread procedure. A dispatcher process contains multiple dispatcher threads, used by their respective clients to send and receive messages. "slot" - named object, 1:1 associated with particular dispatcher thread message queue. It is like input or output port for sending or/and receiving messages by particular client or by multiple clients. There are several types of slots, with different logic and properties. When client A sends a message to client B, the message passes 1) from A's "output" slot, 2) (for non-local exchange only) through intermediate queues, 3) into B's input slot. If B's slot is a queue for subscriptions (qs): 4) an internal thread regularly pops messages from the queue and sends a copy of this message each of the currently subscribed client slots. "address" - specifically structured name of a slot. In the program, it's represented as one-dimensional array of (usually short readable text) keys, or as string representation of such array. Consists of communication type abbreviation, dispatcher process, thread, slot names. "[dispatcher] process name" - non-empty string, uniquely representing dispatcher process on local computer. "[dispatcher] thread name" - non-empty string, uniquely representing dispatcher thread inside the dispatcher process. "client proxy" - opaque, strongly referenced programmatic object, associated with particular dispatcher thread. Supplies i_dispatcher_mt interface, for client making all communication. All client requests (and certain feedback objects) are always associated with client's dispatcher thread, through which they are issued. "local" and "non-local" - describes technical way of message transfer. Also used to distinguish if the sender and the recipient are so close in the system, that the message may be passed immediately. 1. "local" means that both the sender and the recipient are clients of the same dispatcher process (session). Namely, client proxy objects they use, are taken from the same dispatcher_mt object (or the second proxy is obtained from request() to the first proxy, or both clients use interface pointers, taken from the same proxy, which means they work with the same dispatcher thread). For "local" messages: 1) target address starts from "LP" or "LPA". 2) message is delivered immediately. msend() at once returns the result of putting the message into the recipient's queue. 2. "non-local" means that the message must pass through intermediate queue (one or more), with help of internal threads. a) the sender and the recipient may be in different CPU processes. b) the sender and the recipient may be in different dispatcher processes in the same CPU process. c) the sender and the recipient may be in the same dispatcher process, but target address for the message is non-local (starts from "LM"). === Notation === { key; value } - associative array; key, value may be literary values, parameter names, or descriptions of such (element, element ... ) - list or array as programmatic object; elements may be literary values, parameter names, or descriptions of such |element|element ... - list or array as string in the program; each "|" starts new element; see also struct paramline [ ... ] - optional element or part - some value or parameter; the identifier is used to describe the value in text * ? - standard wildcards (any characters, any single character) === Slot types and address structure === Slot name may be one of two kinds: // _ // - one-string name of the slot. // |_|[|part 3[|part 4 ...]] // - multipart (array) name of the slot (showed in string form). Name root must have at least one character. Slot type: see below. Both kinds of slot name are functionally equivalent. Multipart name is designed for complex communications with large number of independent data channels. Slot with one-string name has only one privilege: the client is allowed to send messages from one-string-named slot to any multipart-named slot, whose name root is the same, but not vice versa. See also "Rules of correspondence between sender and receiver slots" below. Address format. // Address is an array, consisting of transfer type, and dispatcher process, thread, slot names. // For different transfer types, different set of names is required. // Below, for each transfer type, template of address is given, for slots with both one-string and multipart names. // (It's easy to see that inside address, one-string and multipart slot names are quite uniform.) // // a) |LP||_ // |LP||_|[|part 3[|part 4 ...]] // // b) |LPA|qs_ // |LPA|qs_|[|part 3[|part 4 ...]] // // c) |LM|||_ // |LM|||_|[|part 3[|part 4 ...]] // // NOTE Above, "process", "thread" means "dispatcher process", "dispatcher thread". // // (a) is for sending the message between two dispatcher threads inside the same one dispatcher process. // (b) same as (a), only the name of dispatcher thread, owning the specified qs (subscription) slot, is not known to sender. // (c) is for sending the message from one dispatcher process to another, using IPC (shared memory). Slot types. // po - output-only pin, message goes directly to the recipient. // pi - input-only pin, messages may be overwritten if not popped in time. // qi - non-blocking input queue for values; any number of messages from one or more sources // are queued and do not overwrite each other. // NOTE When configured by default, capacities of all types of queues are unlimited. // pbo, hbo - command sender pin. Ensures the sequence // 1) command sender sent a command, // 2) command target popped and executed the command, // 3) command target pushed a response, and now is ready for new command, // 4) command sender popped and analyzed the response, and now is ready to issue new command. // pbo can send one command at a time, to any target. // hbo can send commands to multiple targets, one at a time per target. // pbi, qbi - command target pins. // pbi can hold only one command at a time, so it's convenient only for sequential exchange // with single command sender only. // qbi is a queue, to which multiple senders can push their commands independently, without collisions. // The commands are queued, and the client (command target) pops/executes/responds to them one by one. // qs - the queue for delivering the messages to multiple subscribed clients (from multiple senders). Rules of correspondence between sender and receiver slots. // 1. Slot types matching (including transfer direction): // po_* --> pi_*, qi_*, qs_* - send a message with any kind of information. // pbo_*, hbo_* --> pbi_*, qbi_* - send command message. After the recipient receives the command, it must respond. // Until that, the sender cannot issue a new command. // pbi_*, qbi_* --> pbo_*, hbo_* - respond to a command. // (automatic) qs_* --> pi_*, qi_* - messages, sent to qs slot directly or using LPA-type address, // are delivered to subscribers by internal procedure. // 2. Slot with one-string name // may send to any slot with 1) corresponding type, 2) exactly same name root, 3) with or without any additional name parts. // NOTE Bidirectional output slots (pbo, hbo) with particular name, // may issue commands to different target threads with same name root. // In case of hbo, command messages may be sent at once to several targets with same name root; // each command target is expected to reply once; this response is stored individually for the target's address, // on the side of command sender, until the command sender pops it. // 3. Slot with multipart name may send only to slot, whose name parts are exactly equal, // except for type prefix in the first part (po-pi, pbo-pbi etc.). === Message format === Message format on sender side (shown in string form). src = ; trg = ; text = // NAME of the slot (here: sender): see above. // // ADDR - destination slot address: see above. // // T is the client message. // Usually, T is a string, numeric value, or one-dimensional 1-based array. // Also, T may be empty, but "text" key must be present. // If ADDR is local, and anlo_msg flag is set, T may contain any object of set of objects. // // NOTE The sender may put additional entries into the message. // Entry name (hashlist key) must be string >= 2 chars., starting from single '_'. // (The sender should not use keys, starting from letter or "__", for additional entries.) Message format on recipient's side (shown in string form). src = ; trg = ; text = [; bin = ][; src0 = ][; trg0 = ] // T is the sender's message itself. // Usually, T is a string, numeric value, one-dimensional 1-based array, or empty. // If ADDR1 is local address, and anlo_msg flag was set both in mget and in the original msend, // T may contain any object of set of objects. // NOTE In certain applications, the sender may put additional entries into the message. // Entry name (hashlist key) is a string >= 2 chars., starting from single '_'. // (The recipient should not expect additional entries under keys, starting from letter or "__".) // // ADDR1 is the complete sender's address, suitable for responding to the message (if it's necessary). // The address may be of any type (LP, LPA, LM), allowed for the current slot type. // NOTE Except for command inputs (pbi, qbi), if the recipient needs to respond, // it cannot send the response through the same slot from which the original message has been popped. // See also msend comment "Rules of correspondence between sender and receiver slots". // // ADDR2 is the original recipient's address, as specified on the sender's side. // The address may be used to determine if the sender is local, and what names it uses. // // "bin" may appear only if message has binary attachment, but the client did not supply // a container for it (see also mget). // D is a sequence of wide character values in range 0..255. // Each wide character encodes only 1 byte of binary data. // // "src0" appears in messages delivered by subscription. It is the address of the primary message sender. // The address is formatted to be valid from recipient's perspective, as if the message has been received directly from that address. // // "trg0" appears in messages delivered by subscription. It is the address of qs slot, specified by primary sender. // The address is formatted to be valid from recipient's perspective, so that recipient could post messages to the same qs slot. === Implementation notes === On messaging performance and internal locking of dispatcher_mt objects. The current version uses multiple kinds of per-object and per-type locks, and also thread-safe strong and weak references, for 1) read/write/add/remove accessing global sets of names and objects, 2) setting distinct parameters of objects, 3) working with distinct slots on message transfer, etc., 4) automatic memory management, 5) minimizing risk for client binary module unloading during their cross-module objects, like by reference BLOBS, living inside the dispatcher and other binary modules. Certain kinds of operations, when called by the client first time, require system resources acquisition and short dialog with other CPU processes, which is done automatically, but may take noticeable time if there are hundreds or more communicating entities. If this becomes the issue in particular application, certain dispatcher parameters may be set to non-default values, to pre-allocate resources, pre-communicate with other processes, speed up periodic internal operations. E.g. see params. caporig, __thm_lmsc (= 2), __lmsc_peers, __lqsd_dt. Note about performance optimization. By default, the dispatcher does not transfer any cross-module objects, for max. safety. But a) applications, sending large messages or binary data, b) applications, exchanging user-defined objects inside one process, may want to use anlo_msg, anlo_att flags, to selectively disable binary data copying and user-defined objects removal. The objects, passed this way, may reside in 3 locations: 1) inside dispatcher_mt session object, 2) in any application-defined communicating client, 3) in the local (in-process) queue, associated with shared memory (shmqueue_s), before being sent to another process. For (1, 3), dispatcher_mt ensures objects releasing as part of dispatcher session object releasing. This occurs when session object is not used anymore by any side. This works safely only if the client does not forcefully terminate any of communication threads (which would leave proxy object, referring to session object, unreleased). Case (2) is not controlled by the dispatcher, and is pure client's responsibility. Additional kinds of objects that may be used in cross-module context: 4) message and subscription tracking objects. dispatcher_mt holds such objects no longer than until proxy, through which they were supplied, is released. 5) custom allocators for shared queue's incoming messages. Depending on parameters, specified on its setting, custom allocator lives a) until proxy, through which it is set, is released, b) until dispatcher_mt session object is released. Based on all the above, for an object, created in a shared library, and passed through dispatcher, the client must ensure that that library is not unloaded while the object is held by any other module. === Dispatcher configuration: root level === Full configuration, as passed to dispatcher_mt() via cfg arg.: // cfg: { // ; { // "slots"; { ; } // ; // } // ; // } // Slot configuration: { ; } // Value of per-dispatcher_mt, thread, and slot parameter: // scalar, array or associative array, as described in arch_notes.txt / MESSAGE DISPATCHER / Configuration parameters. // All elements are optional. === Configuration parameters / dispatcher thread === disp = If true, this thread is allowed to (technically) mediate in delivering messages from qs slots, configured with "input lpa = true", in all threads. Delivery cycle is executed on client calling request() with rt 8. This parameter is usually not needed, if the internal qs delivery thread is enabled (__thm_lqsd = 1 (dflt.) or 2). === Configuration parameters / slot === qi, qs, qbi (all queues): size = Fixed queue size (capacity). Usage note: size is ignored if any cap* are specified. Allowed values: N >= 3. Default: queue capacity is not limited. capmin = capmax = caporig = Hints for queue capacity limits and initial value. Usage note: capmin, capmax must be either omitted or specified together. caporig may be omitted. If any cap* is specified, size parameter is ignored. Values: capmin: NMIN == -1 - do not shrink automatically. NMIN >= 0 - may shrink only to capacity >= NMIN. Default: 0 capmax: NMAX == -2 - max. capacity is not limited. NMAX == -1 - do not grow more than existing capacity (should be used with caporig > 0). NMAX >= 0 - may shrink only to capacity <= NMAX (do not use 0). Default: -2 caporig: N >= 0 - the initial queue capacity. Default: 0 (should be set > 0 if NMAX == -1) See also: vnnqueue_t::set_cap_hints nmin, nmax; vnnqueue_t::set_rsv. NOTE A queue with fixed size n can be created in two ways: a) size = n b) capmin = -1; capmax = n; caporig = n pi, qi, qs, qbi (all inputs): input all = true - allow all threads to write messages into this slot. false - accept messages only 1) from anyone in slot's own thread, 2) with LPA target address, pointing to this slot. input lpa = true - enable writing per-dispatcher_mt messages (LPA) to this slot. Only one thread in the dispatcher process may have input lpa = true for slot with particular name. This thread becomes servicing thread for LPA-type address of the slot. false - disable LPA messages for this slot. The slot may be addressed only by explicit specifying its name together with its thread name. NOTE "input all = false; input lpa = false" setting may be regarded as creating "private" slot. This prevents casual or intentional reception of unwanted messages by thread client (or clients, if this dispatcher thread is shared between several clients, e.g. has several proxies). pi, qi only: input qsa = [; ][; ] - a set of predefined qs slot addresses (in string form), to which the current slot should be pre-subscribed. On thread/slot creation, for each address from this set, a subscription to target qs slot this is automatically requested (the current slot is added to qs slot output list). For local qs slots, this is made directly. For non-local qs slots, this is made by means of technical message exchange. NOTE Mutual action of "input qsa" and qs slot param. "output". When both qs slot and it subscriber mention each other in their respective "output" and "input qsa" sets, a complete pre-defined subscription appears, without explicit i_dispatcher_mt subscribe() calls. The mutual subscription is fail-safe in sense that casual termination and restart of any of sides (e.g. in different CPU processes) automatically re-makes the other side its correspondent. qs only: delivery = <"immediate" (default) | "disp" | "thread"> "immediate": any message is delivered to each subscriber immediately inside msend() call. (Non-local messages are queued only to be sent out of the dispatcher by internal thread.) "thread": messages are queued inside the qs by msend; pending messages are delivered to subscribers exclusively on the client calling qs owner thread's proxy request() with rt 8, with flag 0x1 set in the integer value of args. "disp": messages are queued inside the qs by msend; pending messages are delivered to subscribers when any of the following occurs: 1) the internal thread for qs delivery (see __thm_lqsd) periodically initiates the delivery. This is enabled by default. 2) the client calls a proxy's request() with rt 8, with flag 0x2 set in the integer value of args. To succeed, that proxy's thread must be configured with disp = true. output = [; ][; ] ... - a set of predefined subscribers addresses (in string form). Allowed slot types: pi, qi. On thread/slot creation, for each address from this set, qs slot automatically links to the subscriber (adds itself to subscriber's input list). For local subscribers, this is made directly. For non-local subscribers, this is made by means of technical message exchange. NOTE Using this parameter alone is enough for pre-subscribing local recipients by configuration. For non-local recipients, their configuration should also be modified: see "input qsa" param. of pi, qi slots. output fixed = true: any thread can call subscribe() to become this qs slot subscriber, or unsubscribe. false: subscribers list cannot change from predefined value. === Configuration parameters / per-dispatcher_mt === Configuration parameters / per-dispatcher_mt / various: __exitmode = Specifies the behavior of ~dispatcher_mt(). M == 0 "detach" ~dispatcher_mt() exits immediately, but DOES NOT end the session. The session is completed in the following stages: 1. (in any order) a) dispatcher_mt object is released, b) all client proxy objects are released. 2. All internal dispatcher_mt threads exit, releasing the remaining session object refs. 3. Session object is automatically deleted. M == 1 "set end flag, exit at once" ~dispatcher_mt() clears the session flag (same as end_session()) and exits immediately. The session is completed like in mode 0, only, in between ~dispatcher_mt() and proxies release, all operations in all proxies objects return "no session" error code. Normally, this should stimulate all client threads to release their proxy object references. NOTE Exit mode 1 causes specific (usually undesired) effect when one dispatcher_mt instance has been just destroyed, and the program at once tries to construct another instance with same process name. Most often, this will fail, because global lock, based on the given process name, is released later than ~dispatcher_mt() of the first instance exits, because internal session object is released only after all associated threads have exited and all client proxy objects are released. M == 2 (dflt.) "set end flag, wait for all clients exited, then exit" ~dispatcher_mt() resets session flag, waits for releasing all proxy objects, all internal objects, stopping all internal threads, then exits. The client threads should release their proxy object references at once when received "no session" error code first time. NOTE When dispatcher_mt instance with exit mode 2 is about to be destroyed, its owning CPU thread must release all dispatcher's proxy objects, owned by that thread, first, and only after that, it should release dispatcher_mt instance. Otherwise deadlock occurs. NOTE ~dispatcher_mt() may hang in exit mode 2, if any client thread does not release its proxy object correctly by any reason (thread has been terminated without normal cleanup, or sleeps for too long, or waits for CPU thread, owning dispatcher_mt instance). __msend_anlo = __mget_anlo = X Per-dispatcher_mt setting for forced disabling binary objects by-reference handling in msend and mget. X == 1, true, or not specified (dflt.): mget/msend behave according to their anlo_msg, anlo_att flags setting in each call. X == 0 or false: mget/msend ignore their anlo_msg, anlo_att flags, and behave as if both flags were 0. X == -1: see X == 1. X out of [-1..1]: bad argument. NOTE "anlo" = "allow non-local objects" NOTE These settings are useful only in the program with multiple shared libraries communicating. Their purpose is to prevent objects, created in binary module A, getting into the dispatcher and/or into the receiving binary module (B), which may be different from A. For binary attachments, prevention means that a copy of attachment is created. Memory for the copy is allocated inside the dispatcher or the receiving module, so that the sending module may be safely unloaded at any time after this copying. Configuration parameters / per-dispatcher_mt / related to internal threads of the dispatcher: __thm_lqsd = __thm_lmsc = __thm_nsc = (not implemented) __thm_cdcc = (not implemented) Sets dedicated thread and operation mode for each kind of message delivery: 1) local qs delivery and other periodic tasks, 2) IPC-based delivery (local machine slot controller, LMSC) 3) (not implemented) all network-based delivery (network slot controller, NSC), 4) (not implemented) all custom driver based delivery (custom driver slot controller, CDCC). X == 2: the thread is activated in full on dispatcher_mt creation. If enabling fails for any reason, whole dispatcher_mt initialization is regarded unsuccessful. X == 1 (dflt.): the thread is half-enabled (may be any of the following: low-priority, long-sleeping, creating only part of the necessary internal objects and connections). If half-enabling fails, whole dispatcher_mt initialization is regarded unsuccessful. If half-enabling succeeds, the full activation is done automatically when the first message is sent or received from IPC or network. X == 0: disables the appropriate kind of communication. (In the current version, it cannot be enabled later by dispatcher_mt instance clients or owner.) If all __thm_* are 0, dispatcher_mt does not create any additional threads, and works only on threads, from which client or owner calls are made. 1. __thm_lqsd = 0: Disables the thread that automatically delivers multi-client messages from qs-type queue to subscribed clients. In this mode, pending messages delivery still may be done via request() by any client thread, configured with disp == true. See also request(). 2. __thm_lmsc = 0: completely disables any IPC activity and any threads that are responsible for that. 3. __thm_nsc = 0: completely disables all socket-based communications, and any threads that are responsible for that. 4. __thm_cdcc = 0: completely disables all communications using custom driver, and any threads that are responsible for that. Any client or owner call, trying to set custom driver instance, will be rejected. X < 0 or __thm_* key is not specified: see X == 1. X > 2: bad argument. Configuration parameters / per-dispatcher_mt / related to local qs delivery: __lqsd_dt = = 0, if < 0 or not specified: 1000)> Sleep time for internal thread, between automatic messages delivery calls, in mcs. Effects on delivery lag for qs-type slots with "delivery = disp". Used only if automatic qs delivery is not disabled (see __thm_lqsd). Configuration parameters / per-dispatcher_mt / related to IPC-based delivery (LMSC): __lmsc_nb_rqum = X: number of bytes for receiving shared queue for user messages. Dflt. value is defined as macro __bmdx_shmfifo_nbytes_dflt. X is automatically limited by min./max. values, defined as macros __lm_slot_controller_nb_rqum_min, __lm_slot_controller_nb_rqum_max. __lmsc_nb_rqinfo = X: number of bytes for receiving shared queue for internal technical messages. Dflt. value is defined as macro __bmdx_shmfifo_nbytes_dflt. X is automatically limited by min./max. values, defined as macros __lm_slot_controller_nb_rqinfo_min, __lm_slot_controller_nb_rqinfo_max. __lmsc_peers = L: a list (array or associative array (keys only)) of peer process names, with which internal communication must be started at once on dispatcher construction. Dflt. L is empty. If a peer is not listed in L, the communication begins automatically on the first message sending between the current dispatcher and the peer (in any direction), which causes some extra delay in msend (of order 150 ms). NOTE If the target system works in stress conditions, it's recommended to explicitly specify the list of peers (__lmsc_peers) in all communicating processes. __lmsc_chsbin = B: boolean value: true: for binary attachments of interprocess messages, calculate/verify checksums on message sending/receival. false (dflt.): do not calculate/verify checksums for binary attachments. See also: msend use_chsbin flag. === Examples of configuration === The below examples are close to BMDX library example projects and automatic tests. They illustrate three main modes of message transfer: one-way (side1 --> side2), command-response sequence (side1 --> side2 --> side1), delivering a message to subscribers (side1 --> intermediary queue -->>> side2..N). Each example shows 1-3 typical variants of communicating sides disposition. This is representative, but not a complete or fixed set of possible configurations. The right choice of the number of dispatcher processes and types of slots for communication is application-dependent. The choice of names for slots, threads and dispatchers is arbitrary, as convenient for the developer. Example 1. One-way data transfer, with optional feedback. Side A sends data to B (po_1 --> qi_1). Side B receives the data, and responds if necessary (po_feedback --> qi_feedback). 1a. Both sides use the same dispatcher thread. (Anyway, they may reside in different CPU threads.) ----------------------------------------------------- cfga = "=|thread1|slots; po_1; qi_feedback"; cfgb = "=|thread1|slots; po_feedback; qi_1"; cfg_full = cfga + "\n" + cfgb; dispatcher_mt disp("disp1", cfg_full); ----------------------------------------------------- Target address for sending data from A to B: "|LP|thread1|qi_1" Target address for feedback: "|LP|thread1|qi_feedback" 1b. Both sides use different dispatcher threads in the same dispatcher process. ----------------------------------------------------- cfga = "=|threadA|slots; po_1; qi_feedback"; cfgb = "=|threadB|slots; po_feedback; qi_1"; cfg_full = cfga + "\n" + cfgb; dispatcher_mt disp("disp1", cfg_full); ----------------------------------------------------- Target address for sending data from A to B: "|LP|threadB|qi_1" Target address for feedback: "|LP|threadA|qi_feedback" 1c. Sides use different dispatcher processes, which may be created in one, or each in its own CPU process. NOTE Dispatcher threads in different dispatcher processes are totally independent, even if their names are equal. ----------------------------------------------------- cfga = "=|threadA|slots; po_1; qi_feedback"; dispatcher_mt disp1("disp1", cfga); ----------------------------------------------------- cfgb = "=|threadB|slots; po_feedback; qi_1"; dispatcher_mt disp2("disp2", cfgb); ----------------------------------------------------- Target address for sending data from A to B: "|LM|disp2|threadB|qi_1" Target address for feedback: "|LM|disp1|threadA|qi_feedback" Example 2. Sequential command-response exchange between two sides, communicating only with each other. Side A sends a command to B (pbo_1 --> pbi_1). Side B receives the command, and anyway responds (pbi_1 --> pbo_1). Side A receives the response from B. Until that, it won't be able to send another command therein. 2a. Both sides use the same dispatcher process. ----------------------------------------------------- cfga = "=|threadA|slots; pbo_1"; cfgb = "=|threadB|slots; pbi_1"; cfg_full = cfga + "\n" + cfgb; dispatcher_mt disp("disp1", cfg_full); ----------------------------------------------------- Target address for command (A --> B): "|LP|threadB|pbi_1" Target address for response (B --> A): "|LP|threadA|pbo_1". It's available in the command message ("trg" key). 2b. Sides use different dispatcher processes. ----------------------------------------------------- cfga = "=|threadA|slots; pbo_1"; dispatcher_mt disp1("disp1", cfga); ----------------------------------------------------- cfgb = "=|threadB|slots; pbi_1"; dispatcher_mt disp2("disp2", cfgb); ----------------------------------------------------- Target address for command (A --> B): "|LM|disp2|threadB|pbi_1" Target address for response (B --> A): "|LM|disp1|threadA|pbo_1". It's available in the command message ("trg" key). Example 3. Message delivery to pre-subscribed sides. Side A sends messages to the intermediary queue (po_1 --> qs_1). The internal CPU thread of the intermediary queue's dispatcher delivers messages from the intermediary queue to sides B and C. 3a. All sides are in the same dispatcher process. ----------------------------------------------------- cfga = "=|threadA|slots; po_1"; cfgb = "=|threadB|slots; qi_1"; cfgc = "=|threadC|slots; qi_1"; cfgqs = "=|intermediary|slots|qs_1|output; |LP|threadB|qi_1; |LP|threadC|qi_1"; cfg_full = cfga + "\n" + cfgb + "\n" + cfgc + "\n" + cfgqs; dispatcher_mt disp("disp1", cfg_full); ----------------------------------------------------- Target address for sending data from A to the intermediary queue: "|LP|intermediary|qs_1" 3b. Sides are such that the resulting communication system combines in- and inter-process delivery. Note that in case of inter-process delivery, both the final recipient and the intermediary queue mention each other in their pre-subscription lists. This way, subscribing occurs independently on which process starts later. ----------------------------------------------------- cfga = "=|threadA|slots; po_1"; dispatcher_mt disp1("disp1", cfga); ----------------------------------------------------- cfgb = "=|threadB|slots|qi_1|input qsa; |LM|disp3|intermediary|qs_1"; dispatcher_mt disp2("disp2", cfgb); ----------------------------------------------------- cfgc = "=|threadC|slots; qi_1"; cfgqs = "=|intermediary|slots|qs_1|output; |LM|disp2|threadB|qi_1; |LP|threadC|qi_1"; dispatcher_mt disp3("disp3", cfgc + "\n" + cfgqs); ----------------------------------------------------- Target address for sending data from A to the intermediary queue: "|LM|disp3|intermediary|qs_1" Example 4. Correspondence between single-part and multipart slot names. cfg = "\n =|sender|slots; po_1; |po_1|A; pbo_1; hbo_1; |pbo_1|A; |hbo_1|A; " "\n =|intermediary|slots; qs_1; |qs_1|A; " "\n =|intermediary|slots|qs_1|output; |LP|receiver|pi_1; |LP|receiver|pi_1|A; |LP|receiver|qi_1; |LP|receiver|qi_1|A; |LP|receiver|qi_1|A|B|C; " "\n =|intermediary|slots|\\|qs_1\\|A|output; |LP|receiver|pi_1|A; |LP|receiver|qi_1|A; " "\n =|receiver|slots; pi_1; |pi_1|A; qi_1; |qi_1|A; |qi_1|A|B|C; pbi_1; |pbi_1|A; qbi_1; |qbi_1|A; " ; Valid correspondence: po_1 --> pi_1, |pi_1|A, qi_1, |qi_1|A, |qi_1|A|B|C, qs_1, |qs_1|A |po_1|A --> |pi_1|A, |qi_1|A, |qs_1|A pbo_1, hbo_1 <--> pbi_1, |pbi_1|A, qbi_1, |qbi_1|A |pbo_1|A, |hbo_1|A <--> |pbi_1|A, |qbi_1|A Some cases of invalid correspondence: |po_1|A --x--> pi_1, qi_1, qs_1, |qi_1|A|B|C |pbo_1|A, |hbo_1|A <--x--> pbi_1, qbi_1 === Examples of the simplest communication programs === 1. Low-level plain bytearray transfer. Uses only shmqueue_s from bmdx_cpiomt.h. SERVER #include "bmdx_cpiomt.h" #include int main (int, char**) { try { std::cout << bmdx_shm::shmqueue_s("handshake").mget_str(20000) << "\n"; } catch (...) {} return 0; } CLIENT #include "bmdx_cpiomt.h" int main (int, char**) { bmdx_shm::shmqueue_s q("handshake"); return !(q.msend("Hello, server!", 20000) == 2 && q.lqwait_out(1000) == 0); } NOTE For more than one client, started when server is absent, all clients but one (random) will exit, based on concurrency. 2. High-level message transfer. Uses whole BMDX library. SERVER #include "bmdx_main.h" #include int main (int, char**) { using namespace bmdx; dispatcher_mt disp("server", "=|y|slots|qi_1"); unity prx; disp.new_proxy(prx, "y"); unity msg; try { while (1 != prx.pinterface()->mget("qi_1", msg)) { sleep_mcs(10000); } } catch (...) {} std::cout << msg.vcstr() << "\n"; return msg.isEmpty() ? 1 : 0; } CLIENT #include "bmdx_main.h" int main (int, char**) { using namespace bmdx; dispatcher_mt disp("client", "=|x|slots|po_1"); unity prx; disp.new_proxy(prx, "x"); int res = 0; try { while (-2 == (res = prx.pinterface()->msend("src=po_1; trg=|LM|server|y|qi_1; text=Hello, server!"))) { sleep_mcs(10000); } } catch (...) {} return res == 1 ? 0 : 1; }