diff --git a/applications/test/dynamicLibrary/Make/files b/applications/test/dynamicLibrary/Make/files
new file mode 100644
index 0000000000000000000000000000000000000000..54ed5e99bcf6c9dce5c30c4a3512c7fcad2dd96c
--- /dev/null
+++ b/applications/test/dynamicLibrary/Make/files
@@ -0,0 +1,3 @@
+Test-dynamicLibrary.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-dynamicLibrary
diff --git a/applications/test/dynamicLibrary/Make/options b/applications/test/dynamicLibrary/Make/options
new file mode 100644
index 0000000000000000000000000000000000000000..75c7356f1c6d09cf219b7d3357cdc94afc8d45e5
--- /dev/null
+++ b/applications/test/dynamicLibrary/Make/options
@@ -0,0 +1,2 @@
+EXE_INC =
+EXE_LIBS =
diff --git a/applications/test/dynamicLibrary/Test-dynamicLibrary.C b/applications/test/dynamicLibrary/Test-dynamicLibrary.C
new file mode 100644
index 0000000000000000000000000000000000000000..ee7009b96180a811b609b4ab5ddfcea9d7e4cca5
--- /dev/null
+++ b/applications/test/dynamicLibrary/Test-dynamicLibrary.C
@@ -0,0 +1,143 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Application
+    Test-dynamicLibrary
+
+Description
+    Test loading/unloading of libraries
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "profiling.H"
+#include "DynamicList.H"
+
+using namespace Foam;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+int main(int argc, char *argv[])
+{
+    argList::addNote("Low-level test of library load/unload");
+
+    profiling::disable(); // No profiling output
+    argList::noBanner();
+    argList::noParallel();
+    argList::removeOption("case");
+    argList::removeOption("noFunctionObjects");
+    argList::addBoolOption("no-close", "Skip dlclose");
+    argList::addBoolOption("quiet", "Disable verbosity");
+
+    argList::addArgument("lib...");
+    argList::noMandatoryArgs();  // Arguments are optional
+
+    argList args(argc, argv, false, true);
+
+    const bool noClose = args.found("no-close");
+    const bool verbose = !args.found("quiet");
+
+    //- Pointers to the loaded libraries
+    DynamicList<void*> libPtrs_;
+
+    //- Names of loaded libraries, or of libraries to be loaded
+    DynamicList<fileName> libNames_;
+
+    label nbad = 0;
+    wordHashSet loaded;
+
+    for (int argi = 1; argi < args.size(); ++argi)
+    {
+        const fileName libName(fileName::validate(args[argi]));
+
+        if (libName.empty())
+        {
+            continue;
+        }
+
+        void* ptr = Foam::dlOpen(libName, false);
+
+        if (!ptr)
+        {
+            ++nbad;
+        }
+        else
+        {
+            libPtrs_.append(ptr);
+            libNames_.append(libName);
+
+            if (verbose)
+            {
+                const word addr(Foam::name(ptr));
+
+                if (loaded.insert(addr))
+                {
+                    InfoErr << "Can load " << libName << nl;
+                }
+                else
+                {
+                    InfoErr << "Already loaded " << libName << nl;
+                }
+            }
+        }
+    }
+
+    if (!noClose)
+    {
+        forAllReverse(libPtrs_, i)
+        {
+            void* ptr = libPtrs_[i];
+
+            if (ptr == nullptr)
+            {
+                libNames_[i].clear();
+                continue;
+            }
+
+            const bool ok = Foam::dlClose(ptr);
+
+            if (verbose)
+            {
+                if (ok)
+                {
+                    InfoErr << "Closed ";
+                }
+                else
+                {
+                    InfoErr << "Failed closing ";
+                }
+
+                InfoErr
+                    << libNames_[i]
+                    << " with handle " << Foam::name(ptr) << nl;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/db/Time/Time.C b/src/OpenFOAM/db/Time/Time.C
index cccda67da8c3dbffc214083c0e21c819cd2c2e10..dbc1c8214c3f2bf12ee98f248c005bc8790b0a3e 100644
--- a/src/OpenFOAM/db/Time/Time.C
+++ b/src/OpenFOAM/db/Time/Time.C
@@ -471,7 +471,7 @@ Foam::Time::Time
 
     if (enableLibs)
     {
-        libs_.open(controlDict_, "libs");
+        libs_.open("libs", controlDict_);
     }
 
     // Explicitly set read flags on objectRegistry so anything constructed
@@ -553,12 +553,9 @@ Foam::Time::Time
     // Libraries
     //
     // * enable by default unless '-no-libs' option was used
-    if (!args.found("no-libs"))
+    if (enableLibs && !args.found("no-libs"))
     {
-        if (enableLibs)
-        {
-            libs_.open(controlDict_, "libs");
-        }
+        libs_.open("libs", controlDict_);
     }
 
     // Explicitly set read flags on objectRegistry so anything constructed
@@ -634,7 +631,7 @@ Foam::Time::Time
 
     if (enableLibs)
     {
-        libs_.open(controlDict_, "libs");
+        libs_.open("libs", controlDict_);
     }
 
 
@@ -708,7 +705,7 @@ Foam::Time::Time
 
     if (enableLibs)
     {
-        libs_.open(controlDict_, "libs");
+        libs_.open("libs", controlDict_);
     }
 
     setMonitoring(); // for profiling etc
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.C b/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.C
index 72a1f92778f3851d0c946ea32caf3f07c5ec7314..2fd1c3c78b65adf79927f7838edb466accf2815a 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.C
+++ b/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -145,18 +145,13 @@ Foam::functionEntries::codeStream::getFunction
 
         if (isA<baseIOdictionary>(topDict))
         {
-            // Cached access to dl libs. Guarantees clean up upon destruction
-            // of Time.
-            dlLibraryTable& dlLibs = libs(parentDict);
-            if (dlLibs.open(libPath, false))
-            {
-                lib = dlLibs.findLibrary(libPath);
-            }
+            // Cached access to libs, with cleanup upon termination
+            lib = libs(parentDict).open(libPath, false);
         }
         else
         {
             // Uncached opening of libPath. Do not complain if cannot be loaded
-            lib = dlOpen(libPath, false);
+            lib = Foam::dlOpen(libPath, false);
         }
     }
 
@@ -299,14 +294,13 @@ Foam::functionEntries::codeStream::getFunction
 
         if (isA<baseIOdictionary>(topDict))
         {
-            // Cached access to dl libs. Guarantees clean up upon destruction
-            // of Time.
-            dlLibraryTable& dlLibs = libs(parentDict);
-
+            // Cached access to libs, with cleanup upon termination
             DebugPout
                 << "Opening cached dictionary:" << libPath << endl;
 
-            if (!dlLibs.open(libPath, false))
+            lib = libs(parentDict).open(libPath, false);
+
+            if (!lib)
             {
                 FatalIOErrorInFunction(parentDict)
                     << "Failed loading library " << libPath << nl
@@ -314,8 +308,6 @@ Foam::functionEntries::codeStream::getFunction
                     << " in system/controlDict?"
                     << exit(FatalIOError);
             }
-
-            lib = dlLibs.findLibrary(libPath);
         }
         else
         {
@@ -323,7 +315,7 @@ Foam::functionEntries::codeStream::getFunction
             DebugPout
                 << "Opening uncached dictionary:" << libPath << endl;
 
-            lib = dlOpen(libPath, true);
+            lib = Foam::dlOpen(libPath, true);
         }
     }
 
@@ -346,7 +338,7 @@ Foam::functionEntries::codeStream::getFunction
     streamingFunctionType function =
         reinterpret_cast<streamingFunctionType>
         (
-            dlSym(lib, dynCode.codeName())
+            Foam::dlSym(lib, dynCode.codeName())
         );
 
 
diff --git a/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.C b/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.C
index c4429222cbde5c5cc7675eeb4a6ac78096ac9855..0f6cc586d93c29c617ff98c0454f1348c052e0f2 100644
--- a/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.C
+++ b/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.C
@@ -98,12 +98,7 @@ void* Foam::codedBase::loadLibrary
 {
     // Avoid compilation by loading an existing library
 
-    void* handle =
-    (
-        !libPath.empty() && libs().open(libPath, false)
-      ? libs().findLibrary(libPath)
-      : nullptr
-    );
+    void* handle = libs().open(libPath, false);
 
     if (!handle)
     {
@@ -115,25 +110,9 @@ void* Foam::codedBase::loadLibrary
     // Manual execution of code after loading.
     // This is mandatory for codedBase.
 
-    void* rawSymbol = dlSymFind(handle, funcName);
+    const bool ok = libs().loadHook(handle, funcName, false);
 
-    if (rawSymbol)
-    {
-        loaderType fun = reinterpret_cast<loaderType>(rawSymbol);
-
-        if (fun)
-        {
-            (*fun)(true);    // force load
-        }
-        else
-        {
-            FatalIOErrorInFunction(context.dict())
-                << "Failed symbol lookup " << funcName.c_str() << nl
-                << "from " << libPath << nl
-                << exit(FatalIOError);
-        }
-    }
-    else
+    if (!ok)
     {
         FatalIOErrorInFunction(context.dict())
             << "Failed symbol lookup " << funcName.c_str() << nl
@@ -160,12 +139,7 @@ void Foam::codedBase::unloadLibrary
     const dynamicCodeContext& context
 ) const
 {
-    void* handle =
-    (
-        !libPath.empty() && libs().open(libPath, false)
-      ? libs().findLibrary(libPath)
-      : nullptr
-    );
+    void* handle = libs().open(libPath, false);
 
     if (!handle)
     {
@@ -175,23 +149,13 @@ void Foam::codedBase::unloadLibrary
     // Manual execution of code before unloading.
     // This is mandatory for codedBase.
 
-    void* rawSymbol = dlSymFind(handle, funcName);
+    const bool ok = libs().unloadHook(handle, funcName, false);
 
-    if (rawSymbol)
+    if (!ok)
     {
-        loaderType fun = reinterpret_cast<loaderType>(rawSymbol);
-
-        if (fun)
-        {
-            (*fun)(false);    // force unload
-        }
-        else
-        {
-            FatalIOErrorInFunction(context.dict())
-                << "Failed symbol lookup " << funcName.c_str() << nl
-                << "from " << libPath << nl
-                << exit(FatalIOError);
-        }
+        IOWarningInFunction(context.dict())
+            << "Failed looking up symbol " << funcName << nl
+            << "from " << libPath << nl;
     }
 
     if (!libs().close(libPath, false))
@@ -389,7 +353,7 @@ void Foam::codedBase::updateLibrary
     unloadLibrary
     (
         oldLibPath_,
-        dynamicCode::libraryBaseName(oldLibPath_),
+        dlLibraryTable::basename(oldLibPath_),
         context
     );
 
diff --git a/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.H b/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.H
index e0bae03e592adce16a1a7c62a5e4b69c574ef180..8f276553b26e52b7f754855a15255b959db651f9 100644
--- a/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.H
+++ b/src/OpenFOAM/db/dynamicLibrary/codedBase/codedBase.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -75,13 +75,6 @@ class codedBase
         mutable fileName oldLibPath_;
 
 
-    // Data Types
-
-        //- Global loader/unloader function type
-        //  Called with true on load, false on unload.
-        typedef void (*loaderType)(bool);
-
-
     // Private Member Functions
 
         //- Load specified library and execute funcName(true)
diff --git a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.C b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.C
index 3fb1c9943769fc4a7176b8d3cc5c2723d6142126..54b3feca21b10ddee05fe480b6e454afe4c3008a 100644
--- a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.C
+++ b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.C
@@ -29,7 +29,15 @@ License
 #include "dlLibraryTable.H"
 #include "OSspecific.H"
 #include "IOstreams.H"
-#include "int.H"
+
+// Could be constexpr in the header if required
+#ifdef __APPLE__
+    #define EXT_SO  "dylib"
+#elif defined _WIN32
+    #define EXT_SO  "dll"
+#else
+    #define EXT_SO  "so"
+#endif
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -38,6 +46,115 @@ namespace Foam
     defineTypeNameAndDebug(dlLibraryTable, 0);
 }
 
+std::unique_ptr<Foam::dlLibraryTable> Foam::dlLibraryTable::global_(nullptr);
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+Foam::word Foam::dlLibraryTable::basename(const fileName& libPath)
+{
+    word libName(libPath.nameLessExt());
+    libName.removeStart("lib");  // Remove leading 'lib' from name
+    return libName;
+}
+
+
+Foam::word Foam::dlLibraryTable::fullname(word libName)
+{
+    if (libName.empty())
+    {
+        return libName;
+    }
+
+    // Add leading 'lib' and trailing '.so'
+    return "lib" + libName.ext(EXT_SO);
+}
+
+
+Foam::dlLibraryTable& Foam::dlLibraryTable::libs()
+{
+    if (!global_)
+    {
+        global_.reset(new dlLibraryTable{});
+    }
+
+    return *global_;
+}
+
+
+bool Foam::dlLibraryTable::functionHook
+(
+    const bool load,
+    void* handle,
+    const std::string& funcName,
+    const bool verbose,
+    const std::string& context
+)
+{
+    if (!handle || funcName.empty())
+    {
+        return false;
+    }
+
+    bool ok = false;
+
+    void* symbol = Foam::dlSymFind(handle, funcName);
+
+    if (symbol)
+    {
+        // Execute loader/unloader code
+        try
+        {
+            loaderType fun = reinterpret_cast<loaderType>(symbol);
+
+            if (fun)
+            {
+                (*fun)(load);
+                ok = true;
+            }
+        }
+        catch (...)
+        {}
+    }
+
+    if (verbose && !ok)
+    {
+        auto& err = WarningInFunction
+            << "Failed symbol lookup " << funcName.c_str() << nl;
+
+        if (!context.empty())
+        {
+            err << "from " << context.c_str() << nl;
+        }
+    }
+
+    return ok;
+}
+
+
+bool Foam::dlLibraryTable::loadHook
+(
+    void* handle,
+    const std::string& funcName,
+    const bool verbose,
+    const std::string& context
+)
+{
+    return functionHook(true, handle, funcName, verbose, context);
+}
+
+
+bool Foam::dlLibraryTable::unloadHook
+(
+    void* handle,
+    const std::string& funcName,
+    const bool verbose,
+    const std::string& context
+)
+{
+    return functionHook(false, handle, funcName, verbose, context);
+}
+
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
@@ -84,14 +201,39 @@ Foam::dlLibraryTable::dlLibraryTable
 
 Foam::dlLibraryTable::dlLibraryTable
 (
+    std::initializer_list<fileName> libNames,
+    bool verbose
+)
+{
+    dlLibraryTable::open(libNames, verbose);
+}
+
+
+Foam::dlLibraryTable::dlLibraryTable
+(
+    const word& libsEntry,
     const dictionary& dict,
-    const word& libsEntry
+    bool verbose
 )
 {
-    dlLibraryTable::open(dict, libsEntry);
+    fileNameList libNames;
+    dict.readIfPresent(libsEntry, libNames);
+    dlLibraryTable::open(libNames, verbose);
 }
 
 
+Foam::dlLibraryTable::dlLibraryTable
+(
+    const dictionary& dict,
+    const word& libsEntry,
+    bool verbose
+
+)
+:
+    dlLibraryTable(libsEntry, dict, verbose)
+{}
+
+
 // * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
 
 Foam::dlLibraryTable::~dlLibraryTable()
@@ -132,6 +274,30 @@ Foam::label Foam::dlLibraryTable::size() const
 }
 
 
+Foam::List<Foam::fileName> Foam::dlLibraryTable::loaded() const
+{
+    List<fileName> list(libNames_.size());
+
+    label nLoaded = 0;
+
+    forAll(libNames_, i)
+    {
+        void* ptr = libPtrs_[i];
+        const fileName& libName = libNames_[i];
+
+        if (ptr != nullptr && !libName.empty())
+        {
+            list[nLoaded] = libName;
+            ++nLoaded;
+        }
+    }
+
+    list.resize(nLoaded);
+
+    return list;
+}
+
+
 void Foam::dlLibraryTable::clear(bool verbose)
 {
     label nLoaded = 0;
@@ -231,12 +397,14 @@ bool Foam::dlLibraryTable::open(bool verbose)
 
     forAll(libPtrs_, i)
     {
+        void* ptr = libPtrs_[i];
         const fileName& libName = libNames_[i];
 
-        if (libPtrs_[i] == nullptr && !libName.empty())
+        if (ptr == nullptr && !libName.empty())
         {
             ++nCand;
-            void* ptr = openLibrary(libName, verbose);
+
+            ptr = openLibrary(libName, verbose);
 
             if (ptr)
             {
@@ -260,6 +428,7 @@ void* Foam::dlLibraryTable::open
     bool verbose
 )
 {
+    // Handles empty name silently
     void* ptr = openLibrary(libName, verbose);
 
     if (ptr)
@@ -278,7 +447,34 @@ bool Foam::dlLibraryTable::open
     bool verbose
 )
 {
-    label nOpen = 0;
+    decltype(libNames.size()) nOpen = 0;
+
+    for (const fileName& libName : libNames)
+    {
+        const label index = libNames_.find(libName);
+
+        if (index >= 0 && libPtrs_[index] != nullptr)
+        {
+            // Already known and opened
+            ++nOpen;
+        }
+        else if (dlLibraryTable::open(libName, verbose))
+        {
+            ++nOpen;
+        }
+    }
+
+    return nOpen && nOpen == libNames.size();
+}
+
+
+bool Foam::dlLibraryTable::open
+(
+    std::initializer_list<fileName> libNames,
+    bool verbose
+)
+{
+    decltype(libNames.size()) nOpen = 0;
 
     for (const fileName& libName : libNames)
     {
@@ -307,13 +503,19 @@ bool Foam::dlLibraryTable::close
 {
     const label index = libNames_.rfind(libName);
 
-    if (index < 0)
+    if (index < 0 || libName.empty())
     {
         return false;
     }
 
     void* ptr = libPtrs_[index];
 
+    if (ptr == nullptr)
+    {
+        libNames_[index].clear();
+        return false;
+    }
+
     DebugInFunction
         << "Closing " << libName
         << " with handle " << Foam::name(ptr) << nl;
@@ -323,7 +525,23 @@ bool Foam::dlLibraryTable::close
     libPtrs_[index] = nullptr;
     libNames_[index].clear();
 
-    if (!ok && verbose)
+    if (ok)
+    {
+        // From man dlopen(3)
+        // ...
+        // a dynamically loaded shared object is not deallocated until
+        // dlclose() has been called on it as many times as dlopen()
+        // has succeeded on it.
+
+        // Handle aliased library names
+        for (label idx = 0; (idx = libPtrs_.find(ptr, idx)) >= 0; ++idx)
+        {
+            (void) Foam::dlClose(ptr);
+            libPtrs_[idx] = nullptr;
+            libNames_[idx].clear();
+        }
+    }
+    else if (verbose)
     {
         WarningInFunction
             << "Could not close " << libName << endl;
@@ -337,7 +555,7 @@ void* Foam::dlLibraryTable::findLibrary(const fileName& libName)
 {
     const label index = libNames_.rfind(libName);
 
-    if (index < 0)
+    if (index < 0 || libName.empty())
     {
         return nullptr;
     }
@@ -348,24 +566,27 @@ void* Foam::dlLibraryTable::findLibrary(const fileName& libName)
 
 bool Foam::dlLibraryTable::open
 (
+    const word& libsEntry,
     const dictionary& dict,
-    const word& libsEntry
+    bool verbose
 )
 {
     fileNameList libNames;
-    dict.readIfPresent(libsEntry, libNames);
+    return
+    (
+        dict.readIfPresent(libsEntry, libNames)
+     && dlLibraryTable::open(libNames, verbose)
+    );
+}
 
-    label nOpen = 0;
 
-    for (const fileName& libName : libNames)
-    {
-        if (dlLibraryTable::open(libName))  // verbose = true
-        {
-            ++nOpen;
-        }
-    }
-
-    return nOpen && nOpen == libNames.size();
+bool Foam::dlLibraryTable::open
+(
+    const dictionary& dict,
+    const word& libsEntry
+)
+{
+    return dlLibraryTable::open(libsEntry, dict, true); // verbose = true
 }
 
 
@@ -381,7 +602,7 @@ Foam::Ostream& Foam::operator<<
 
     os << token::BEGIN_LIST << nl;
 
-    // Lengths of pointers/names are guaranteed interally to be identical
+    // Lengths of pointers/names are guaranteed internally to be identical
     forAll(tbl.pointers(), i)
     {
         const void* ptr = tbl.pointers()[i];
diff --git a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.H b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.H
index b48f02f32257710e10a6af9a5e0ad175dbe96bbf..a89df8f90854e65b8c53437bbaf9d561ce8e44fe 100644
--- a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.H
+++ b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTable.H
@@ -30,6 +30,10 @@ Class
 Description
     A table of dynamically loaded libraries.
 
+SeeAlso
+    Foam::dlOpen
+    Foam::dlClose
+
 SourceFiles
     dlLibraryTable.C
     dlLibraryTableTemplates.C
@@ -40,7 +44,9 @@ SourceFiles
 #define dlLibraryTable_H
 
 #include "DynamicList.H"
+#include "fileName.H"
 #include "InfoProxy.H"
+#include <memory>
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -57,15 +63,35 @@ Ostream& operator<<(Ostream& os, const InfoProxy<dlLibraryTable>& info);
 
 class dlLibraryTable
 {
+    // Static Data
+
+        //- Global singleton of dynamic libraries
+        static std::unique_ptr<dlLibraryTable> global_;
+
+
     // Private Data
 
+        //- Pointers to the loaded libraries
         DynamicList<void*> libPtrs_;
 
+        //- Names of loaded libraries, or of libraries to be loaded
         DynamicList<fileName> libNames_;
 
 
     // Private Member Functions
 
+        //- Load/unload hook.
+        //  \return true if the function was found and executed
+        static bool functionHook
+        (
+            const bool load,    //!< true = on-load, false = on-unload
+            void* handle,       //!< library handle
+            const std::string& funcName,
+            const bool verbose,
+            const std::string& context
+        );
+
+
         //- Open specified library name and return pointer.
         //  Warning messages, but no additional side-effects.
         void* openLibrary(const fileName& libName, bool verbose);
@@ -73,9 +99,17 @@ class dlLibraryTable
 
 public:
 
-    // Declare name of the class and its debug switch
+    // Public Data Types
+
+        //- Global loader/unloader function type (C-linkage)
+        //  Called with true on load, false on unload.
+        typedef void (*loaderType)(bool);
+
+
+    //- Declare name of the class and its debug switch
     ClassName("dlLibraryTable");
 
+
     // Generated Methods
 
         //- Default construct
@@ -96,22 +130,82 @@ public:
 
     // Constructors
 
-        //- Open specified libraries. Ignores duplicate names.
+        //- Open specified libraries, warn by default if problems occur
+        //  Ignores duplicate names.
         explicit dlLibraryTable
         (
             const UList<fileName>& libNames,
             bool verbose = true
         );
 
-        //- Open all libraries listed in the 'libsEntry' entry in the
-        //- given dictionary. Verbose = true.
-        dlLibraryTable(const dictionary& dict, const word& libsEntry);
+        //- Open specified libraries, warn by default if problems occur
+        //  Ignores duplicate names.
+        explicit dlLibraryTable
+        (
+            std::initializer_list<fileName> libNames,
+            bool verbose = true
+        );
+
+        //- Open libraries listed in 'libsEntry' entry in the dictionary,
+        //- warn by default if problems occur
+        dlLibraryTable
+        (
+            const word& libsEntry,
+            const dictionary& dict,
+            bool verbose = true
+        );
+
+        //- Open libraries listed in 'libsEntry' entry in the dictionary,
+        //- warn by default if problems occur
+        dlLibraryTable
+        (
+            const dictionary& dict,
+            const word& libsEntry,
+            bool verbose = true
+        );
 
 
     //- Destructor. Closes all libraries loaded by the table.
     ~dlLibraryTable();
 
 
+    // Static Member Functions
+
+        //- Library basename without leading 'lib' or trailing '.so'
+        static word basename(const fileName& libPath);
+
+        //- Library fullname, prefix with 'lib', suffix with '.so'
+        //  \note the suffix is system-dependent
+        static word fullname(word libName);
+
+        //- Table of global libraries
+        static dlLibraryTable& libs();
+
+        //- Low-level interface to execute global "void funcName(true)"
+        //- from the library, typically for additional loading.
+        //  If called, it should be the first step after opening a library.
+        //  \return true if the function was found and executed
+        static bool loadHook
+        (
+            void* handle,
+            const std::string& funcName,
+            const bool verbose = false,
+            const std::string& context = "" //!< Calling context for warnings
+        );
+
+        //- Low-level interface to execute global "void funcName(false)"
+        //- from the library, typically for unloading.
+        //  If called, it should be the last step before closing a library.
+        //  \return true if the function was found and executed
+        static bool unloadHook
+        (
+            void* handle,
+            const std::string& funcName,
+            const bool verbose = false,
+            const std::string& context = "" //!< Calling context for warnings
+        );
+
+
     // Member Functions
 
         //- True if there are no libraries loaded by the table
@@ -120,6 +214,9 @@ public:
         //- The number of libraries loaded by the table
         label size() const;
 
+        //- Names of the libraries in use
+        List<fileName> loaded() const;
+
         //- Names of the libraries in use, or requested
         const UList<fileName>& names() const
         {
@@ -147,21 +244,41 @@ public:
         //- These names will normally have been added with the append() method.
         bool open(bool verbose = true);
 
-        //- Open the named library, optionally warn if problems occur
+        //- Open the named library, warn by default if problems occur.
+        //  An empty name is a silent no-op and always returns nullptr.
+        //  \return a pointer to the library opened, or nullptr on failure.
         void* open(const fileName& libName, bool verbose = true);
 
-        //- Open the named libraries, optionally warn if problems occur
+        //- Open the named libraries, warn by default if problems occur.
         //  Ignores duplicate names.
         bool open(const UList<fileName>& libNames, bool verbose = true);
 
+        //- Open the named libraries, warn by default if problems occur.
+        //  Ignores duplicate names.
+        bool open
+        (
+            std::initializer_list<fileName> libNames,
+            bool verbose = true
+        );
+
         //- Close the named library, optionally warn if problems occur
+        //  Using an empty name is a no-op and always returns false.
         bool close(const fileName& libName, bool verbose = true);
 
         //- Find the handle of the named library
+        //  Using an empty name is a no-op and always returns nullptr.
         void* findLibrary(const fileName& libName);
 
-        //- Open all libraries listed in the 'libsEntry' entry in the
-        //- given dictionary.
+        //- Open libraries listed in the 'libsEntry' entry in the dictionary.
+        bool open
+        (
+            const word& libsEntry,
+            const dictionary& dict,
+            bool verbose = true
+        );
+
+        //- Open libraries listed in the 'libsEntry' entry in the dictionary.
+        //  Verbose = true
         bool open(const dictionary& dict, const word& libsEntry);
 
         //- Open all libraries listed in the 'libsEntry' entry in the
@@ -172,7 +289,8 @@ public:
         (
             const dictionary& dict,
             const word& libsEntry,
-            const TablePtr& tablePtr
+            const TablePtr& tablePtr,
+            bool verbose = true
         );
 
 
diff --git a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTableTemplates.C b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTableTemplates.C
index ff39cebf170b7391c2ac17ab5fea5f3a15aa9c88..8c558acf8be8f57b1fc58a4cecdf06738ac55c5e 100644
--- a/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTableTemplates.C
+++ b/src/OpenFOAM/db/dynamicLibrary/dlLibraryTable/dlLibraryTableTemplates.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,7 +28,6 @@ License
 
 #include "dlLibraryTable.H"
 #include "dictionary.H"
-#include "fileNameList.H"
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
@@ -37,23 +36,24 @@ bool Foam::dlLibraryTable::open
 (
     const dictionary& dict,
     const word& libsEntry,
-    const TablePtr& tablePtr
+    const TablePtr& tablePtr,
+    bool verbose
 )
 {
-    fileNameList libNames;
+    List<fileName> libNames;
     dict.readIfPresent(libsEntry, libNames);
 
     label nOpen = 0;
 
     for (const fileName& libName : libNames)
     {
-        const label nEntries = (tablePtr ? tablePtr->size() : 0);
+        const label nEntries = (tablePtr ? tablePtr->size() : -1);
 
-        if (dlLibraryTable::open(libName))
+        if (dlLibraryTable::open(libName, verbose))
         {
             ++nOpen;
 
-            if (debug && (!tablePtr || tablePtr->size() <= nEntries))
+            if (debug && tablePtr != nullptr && tablePtr->size() <= nEntries)
             {
                 WarningInFunction
                     << "library " << libName
@@ -61,12 +61,6 @@ bool Foam::dlLibraryTable::open
                     << nl << endl;
             }
         }
-        else
-        {
-            WarningInFunction
-                << "Could not open library " << libName
-                << nl << endl;
-        }
     }
 
     return nOpen && nOpen == libNames.size();
diff --git a/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.C b/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.C
index d5bc74826662c1aaf21502443ff102de7d5b07c7..d2d2098488e05e5090d6e7ae3ab71f34f7eb35a4 100644
--- a/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.C
+++ b/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,6 +28,7 @@ License
 
 #include "dynamicCode.H"
 #include "dynamicCodeContext.H"
+#include "dlLibraryTable.H"
 #include "argList.H"
 #include "stringOps.H"
 #include "Fstream.H"
@@ -37,15 +38,6 @@ License
 #include "dictionary.H"
 #include "foamVersion.H"
 
-#ifdef __APPLE__
-    #define EXT_SO  ".dylib"
-#elif defined _WIN32
-    #define EXT_SO  ".dll"
-#else
-    #define EXT_SO  ".so"
-#endif
-
-
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 int Foam::dynamicCode::allowSystemOperations
@@ -103,14 +95,6 @@ void Foam::dynamicCode::checkSecurity
 }
 
 
-Foam::word Foam::dynamicCode::libraryBaseName(const fileName& libPath)
-{
-    word libName(libPath.nameLessExt());
-    libName.removeStart("lib");  // Remove leading 'lib' from name
-    return libName;
-}
-
-
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 void Foam::dynamicCode::copyAndFilter
@@ -328,13 +312,13 @@ Foam::fileName Foam::dynamicCode::codeRelPath() const
 
 Foam::fileName Foam::dynamicCode::libPath() const
 {
-    return codeRoot_/libSubDir_/"lib" + codeName_ + EXT_SO;
+    return codeRoot_/libSubDir_/dlLibraryTable::fullname(codeName_);
 }
 
 
 Foam::fileName Foam::dynamicCode::libRelPath() const
 {
-    return codeRelPath()/libSubDir_/"lib" + codeName_ + EXT_SO;
+    return codeRelPath()/libSubDir_/dlLibraryTable::fullname(codeName_);
 }
 
 
@@ -514,16 +498,10 @@ bool Foam::dynamicCode::wmakeLibso() const
     //   cmd[0] = stringOps::expand("$WM_PROJECT_DIR/wmake/wmake");
 
     // This can take a bit longer, so report that we are starting wmake
+    // Even with details turned off, we want some feedback
 
-    if (Foam::infoDetailLevel > 0)
-    {
-        Info<< "Invoking wmake libso " << this->codePath().c_str() << endl;
-    }
-    else
-    {
-        // Even with details turned off, we want some feedback
-        Serr<< "Invoking wmake libso " << this->codePath().c_str() << endl;
-    }
+    OSstream& os = (Foam::infoDetailLevel > 0 ? Info : Serr);
+    os  << "Invoking wmake libso " << this->codePath().c_str() << endl;
 
     if (Foam::system(cmd) == 0)
     {
diff --git a/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.H b/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.H
index 486ae3ee17833418e645e599674923d847fc09e4..438219b505ada462f50c95cb2e02eefa62211cd6 100644
--- a/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.H
+++ b/src/OpenFOAM/db/dynamicLibrary/dynamicCode/dynamicCode.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -47,7 +47,7 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class dynamicCodeContext;
 class ISstream;
 class OSstream;
@@ -60,10 +60,12 @@ class SHA1Digest;
 class dynamicCode
 {
 public:
+
     typedef Tuple2<fileName, string> fileAndContent;
 
 private:
-    // Private data
+
+    // Private Data
 
         //- Root for dynamic code compilation
         fileName codeRoot_;
@@ -104,7 +106,7 @@ private:
 
 protected:
 
-    // Static data members
+    // Static Data Members
 
         //- Root of the LIB target for Make/files
         static const char* const libTargetRoot;
@@ -151,7 +153,7 @@ protected:
 
 public:
 
-    // Static data members
+    // Static Data Members
 
         //- Name of the code template environment variable
         //  Used to located the codeTemplateName
@@ -170,15 +172,12 @@ public:
         //- Check security for creating dynamic code
         static void checkSecurity(const char* title, const dictionary&);
 
-        //- Return the library basename without leading 'lib' or trailing '.so'
-        static word libraryBaseName(const fileName& libPath);
-
 
     // Constructors
 
         //- Construct for a specified code name and code directory name
         //  Defaults to using the code name for the code directory name
-        dynamicCode
+        explicit dynamicCode
         (
             const word& codeName,
             const word& codeDirName = ""