gnucash master: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Mon Nov 28 15:27:47 EST 2016


Updated	 via  https://github.com/Gnucash/gnucash/commit/e592e00e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/abb66016 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/06af7d79 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/46ce3f37 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/eace6250 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/90a95204 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/52683403 (commit)
	from  https://github.com/Gnucash/gnucash/commit/dd4b8a10 (commit)



commit e592e00e10bd1cee31de0e21c68ce66e6069b97c
Merge: dd4b8a1 abb6601
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Nov 28 12:26:26 2016 -0800

    Third interim merge of 'c++-backend'


commit abb66016bc8ef1b2d5c5eaafa7020bfe48d55bb6
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Nov 28 10:27:09 2016 -0800

    Convert QofBackend to a C++ class and the backend class hierarchy into C++.
    
    Getting rid of all of the casting and different flavors of backend pointers
    and adopting the C++ member-function calling conventions.

diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp
index cbd251e..5e00451 100644
--- a/src/backend/dbi/gnc-backend-dbi.cpp
+++ b/src/backend/dbi/gnc-backend-dbi.cpp
@@ -111,31 +111,8 @@ static QofLogModule log_module = G_LOG_DOMAIN;
 
 static void adjust_sql_options (dbi_conn connection);
 static bool save_may_clobber_data (dbi_conn conn, const std::string& dbname);
-static void init_sql_backend (GncDbiBackend* dbi_be);
 
-static bool conn_test_dbi_library (dbi_conn conn, QofBackend* qof_be);
-
-template <DbType T> void gnc_dbi_session_begin(QofBackend* qof_be,
-                                               QofSession* session,
-                                               const char* book_id,
-                                               gboolean ignore_lock,
-                                               gboolean create, gboolean force);
-template <DbType Type> QofBackend*
-new_backend ()
-{
-    auto dbi_be = new GncDbiBackend(nullptr, nullptr);
-    assert (dbi_be != nullptr);
-
-    QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(dbi_be);
-    qof_backend_init (qof_be);
-
-    qof_be->session_begin = gnc_dbi_session_begin<Type>;
-    init_sql_backend (dbi_be);
-
-    return qof_be;
-}
-
-template <DbType T>
+template <DbType Type>
 class QofDbiBackendProvider : public QofBackendProvider
 {
 public:
@@ -146,7 +123,10 @@ public:
     QofDbiBackendProvider(QofDbiBackendProvider&&) = delete;
     QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete;
     ~QofDbiBackendProvider () = default;
-    QofBackend* create_backend(void) { return new_backend<T>(); }
+    QofBackend* create_backend(void)
+    {
+        return new GncDbiBackend<Type>(nullptr, nullptr);
+    }
     bool type_check(const char* type) { return true; }
 };
 
@@ -235,14 +215,13 @@ set_options(dbi_conn conn, const PairVec& options)
 /**
  * Sets standard db options in a dbi_conn.
  *
- * @param qof_be QOF backend
  * @param conn dbi_conn connection
  * @param uri UriStrings containing the needed paramters.
  * @return TRUE if successful, FALSE if error
  */
-static bool
-set_standard_connection_options (QofBackend* qof_be, dbi_conn conn,
-                                 const UriStrings& uri)
+template <DbType Type> bool
+GncDbiBackend<Type>::set_standard_connection_options (dbi_conn conn,
+                                                const UriStrings& uri)
 
 {
     gint result;
@@ -266,7 +245,7 @@ set_standard_connection_options (QofBackend* qof_be, dbi_conn conn,
     }
     catch (std::runtime_error& err)
     {
-        qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+        set_error (ERR_BACKEND_SERVER_ERR);
         return false;
     }
 
@@ -277,8 +256,7 @@ template <DbType Type> void error_handler(void* conn, void* data);
 void error_handler(void* conn, void* data);
 
 template <DbType Type> dbi_conn
-conn_setup (QofBackend* qof_be, PairVec& options,
-            UriStrings& uri)
+GncDbiBackend<Type>::conn_setup (PairVec& options, UriStrings& uri)
 {
     const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" :
                          Type == DbType::DBI_MYSQL ? "mysql" : "pgsql");
@@ -295,13 +273,13 @@ conn_setup (QofBackend* qof_be, PairVec& options,
     if (conn == nullptr)
     {
         PERR ("Unable to create %s dbi connection", dbstr);
-        qof_backend_set_error (qof_be, ERR_BACKEND_BAD_URL);
+        set_error (ERR_BACKEND_BAD_URL);
 	return nullptr;
     }
 
-    dbi_conn_error_handler (conn, error_handler<Type>, qof_be);
+    dbi_conn_error_handler (conn, error_handler<Type>, this);
     if (!uri.m_dbname.empty() &&
-        !set_standard_connection_options(qof_be, conn, uri))
+        !set_standard_connection_options(conn, uri))
     {
         dbi_conn_close(conn);
         return nullptr;
@@ -314,7 +292,7 @@ conn_setup (QofBackend* qof_be, PairVec& options,
         catch (std::runtime_error& err)
         {
             dbi_conn_close(conn);
-            qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+            set_error (ERR_BACKEND_SERVER_ERR);
             return nullptr;
         }
     }
@@ -322,12 +300,12 @@ conn_setup (QofBackend* qof_be, PairVec& options,
     return conn;
 }
 
-static bool
-create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db)
+template <DbType Type>bool
+GncDbiBackend<Type>::create_database(dbi_conn conn, const char* db)
 {
     const char *dbname;
     const char *dbcreate;
-    if (type == DbType::DBI_MYSQL)
+    if (Type == DbType::DBI_MYSQL)
     {
         dbname = "mysql";
         dbcreate = "CREATE DATABASE %s CHARACTER SET utf8";
@@ -345,7 +323,7 @@ create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db)
     }
     catch (std::runtime_error& err)
     {
-        qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+        set_error (ERR_BACKEND_SERVER_ERR);
         return false;
     }
 
@@ -353,19 +331,19 @@ create_database(DbType type, QofBackend *qof_be, dbi_conn conn, const char* db)
     if (result < 0)
     {
         PERR ("Unable to connect to %s database", dbname);
-        qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+        set_error(ERR_BACKEND_SERVER_ERR);
         return false;
     }
-    if (type == DbType::DBI_MYSQL)
+    if (Type == DbType::DBI_MYSQL)
         adjust_sql_options(conn);
     auto dresult = dbi_conn_queryf (conn, dbcreate, db);
     if (dresult == nullptr)
     {
         PERR ("Unable to create database '%s'\n", db);
-        qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+        set_error (ERR_BACKEND_SERVER_ERR);
         return false;
     }
-    if (type == DbType::DBI_PGSQL)
+    if (Type == DbType::DBI_PGSQL)
     {
         const char *alterdb = "ALTER DATABASE %s SET "
             "standard_conforming_strings TO on";
@@ -380,26 +358,23 @@ template <> void
 error_handler<DbType::DBI_SQLITE> (dbi_conn conn, void* user_data)
 {
     const char* msg;
-    GncDbiBackend *dbi_be = static_cast<decltype(dbi_be)>(user_data);
+    GncDbiBackend<DbType::DBI_SQLITE> *dbi_be =
+        static_cast<decltype(dbi_be)>(user_data);
     int errnum = dbi_conn_error (conn, &msg);
     PERR ("DBI error: %s\n", msg);
     if (dbi_be->connected())
-        dbi_be->set_error (ERR_BACKEND_MISC, 0, false);
+        dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
 }
 
 template <> void
-gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
-                                          QofSession* session,
-                                          const char* book_id,
-                                          gboolean ignore_lock,
-                                          gboolean create, gboolean force)
+GncDbiBackend<DbType::DBI_SQLITE>::session_begin(QofSession* session,
+                                                 const char* book_id,
+                                                 bool ignore_lock,
+                                                 bool create, bool force)
 {
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-    const char* msg = nullptr;
     gboolean file_exists;
     PairVec options;
 
-    g_return_if_fail (qof_be != nullptr);
     g_return_if_fail (session != nullptr);
     g_return_if_fail (book_id != nullptr);
 
@@ -414,9 +389,9 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
     file_exists = g_file_test (filepath.c_str(), ftest);
     if (!create && !file_exists)
     {
-        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (qof_be, "Sqlite3 file %s not found",
-                                 filepath.c_str());
+        set_error (ERR_FILEIO_FILE_NOT_FOUND);
+        std::string msg{"Sqlite3 file "};
+        set_message (msg + filepath + " not found");
         PWARN ("Sqlite3 file %s not found", filepath.c_str());
         LEAVE("Error");
 	return;
@@ -424,14 +399,14 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
 
     if (create && !force && file_exists)
     {
-        qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS);
-        msg = "Might clobber, no force";
+        set_error (ERR_BACKEND_STORE_EXISTS);
+        auto msg = "Might clobber, no force";
         PWARN ("%s", msg);
         LEAVE("Error");
 	return;
     }
 
-    dbi_be->connect(nullptr);
+    connect(nullptr);
     /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
     options.push_back(std::make_pair("host", "localhost"));
     auto dirname = g_path_get_dirname (filepath.c_str());
@@ -441,7 +416,7 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
     if (basename != nullptr) g_free (basename);
     if (dirname != nullptr) g_free (dirname);
     UriStrings uri;
-    auto conn = conn_setup<DbType::DBI_SQLITE>(qof_be, options, uri);
+    auto conn = conn_setup(options, uri);
     if (conn == nullptr)
     {
         LEAVE("Error");
@@ -454,12 +429,12 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
     {
         dbi_conn_close(conn);
         PERR ("Unable to connect to %s: %d\n", book_id, result);
-        qof_backend_set_error (qof_be, ERR_BACKEND_BAD_URL);
+        set_error (ERR_BACKEND_BAD_URL);
         LEAVE("Error");
 	return;
     }
 
-    if (!conn_test_dbi_library(conn, qof_be))
+    if (!conn_test_dbi_library(conn))
     {
         if (create && !file_exists)
         {
@@ -477,8 +452,8 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
 
     try
     {
-        dbi_be->connect(new GncDbiSqlConnection(DbType::DBI_SQLITE,
-                                            qof_be, conn, ignore_lock));
+        connect(new GncDbiSqlConnection(DbType::DBI_SQLITE,
+                                            this, conn, ignore_lock));
     }
     catch (std::runtime_error& err)
     {
@@ -497,7 +472,8 @@ gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qof_be,
 template <> void
 error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
 {
-    GncDbiBackend* dbi_be = static_cast<decltype(dbi_be)>(user_data);
+    GncDbiBackend<DbType::DBI_MYSQL>* dbi_be =
+        static_cast<decltype(dbi_be)>(user_data);
     const char* msg;
 
     auto err_num = dbi_conn_error (conn, &msg);
@@ -531,18 +507,18 @@ error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
     if (err_num == 2006)       // Server has gone away
     {
         PINFO ("DBI error: %s - Reconnecting...\n", msg);
-        dbi_be->set_error (ERR_BACKEND_CONN_LOST, 1, true);
+        dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
         dbi_be->retry_connection(msg);
     }
     else if (err_num == 2003)       // Unable to connect
     {
-        dbi_be->set_error (ERR_BACKEND_CANT_CONNECT, 1, true);
+        dbi_be->set_dbi_error (ERR_BACKEND_CANT_CONNECT, 1, true);
         dbi_be->retry_connection (msg);
     }
     else                            // Any other error
     {
         PERR ("DBI error: %s\n", msg);
-        dbi_be->set_error (ERR_BACKEND_MISC, 0, FALSE);
+        dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, FALSE);
     }
 }
 
@@ -610,16 +586,13 @@ adjust_sql_options (dbi_conn connection)
 }
 
 
-template <DbType T> void
-gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
-                             const char* book_id, gboolean ignore_lock,
-                             gboolean create, gboolean force)
+template <DbType Type> void
+GncDbiBackend<Type>::session_begin (QofSession* session, const char* book_id,
+                                    bool ignore_lock, bool create, bool force)
 {
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
     GncDbiTestResult dbi_test_result = GNC_DBI_PASS;
     PairVec options;
 
-    g_return_if_fail (qof_be != nullptr);
     g_return_if_fail (session != nullptr);
     g_return_if_fail (book_id != nullptr);
 
@@ -630,7 +603,7 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
      where username, password and port are optional) */
     UriStrings uri(book_id);
 
-    if (T == DbType::DBI_PGSQL)
+    if (Type == DbType::DBI_PGSQL)
     {
         if (uri.m_portnum == 0)
             uri.m_portnum = PGSQL_DEFAULT_PORT;
@@ -642,31 +615,31 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
         uri.m_dbname = std::string{lcname};
         g_free(lcname);
     }
-    dbi_be->connect(nullptr);
+    connect(nullptr);
 
-    auto conn = conn_setup<T>(qof_be, options, uri);
+    auto conn = conn_setup(options, uri);
     if (conn == nullptr)
     {
         LEAVE("Error");
         return;
     }
 
-    dbi_be->set_exists(true); //May be unset in the error handler.
+    m_exists = true; //May be unset in the error handler.
     auto result = dbi_conn_connect (conn);
     if (result == 0)
     {
-        if (T == DbType::DBI_MYSQL)
+        if (Type == DbType::DBI_MYSQL)
             adjust_sql_options (conn);
-        if(!conn_test_dbi_library(conn, qof_be))
+        if(!conn_test_dbi_library(conn))
         {
             dbi_conn_close(conn);
             LEAVE("Error");
             return;
         }
         if (create && !force && save_may_clobber_data (conn,
-                                                       uri.quote_dbname(T)))
+                                                       uri.quote_dbname(Type)))
         {
-            qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS);
+            set_error (ERR_BACKEND_STORE_EXISTS);
             PWARN ("Databse already exists, Might clobber it.");
             dbi_conn_close(conn);
             LEAVE("Error");
@@ -677,10 +650,10 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
     else
     {
 
-        if (dbi_be->exists())
+        if (m_exists)
         {
             PERR ("Unable to connect to database '%s'\n", uri.dbname());
-            qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+            set_error (ERR_BACKEND_SERVER_ERR);
             dbi_conn_close(conn);
             LEAVE("Error");
             return;
@@ -688,45 +661,46 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
 
         if (create)
         {
-            if (!create_database(T, qof_be, conn, uri.quote_dbname(T).c_str()))
+            if (!create_database(conn, uri.quote_dbname(Type).c_str()))
             {
                 dbi_conn_close(conn);
                 LEAVE("Error");
                 return;
             }
-            conn = conn_setup<T>(qof_be, options, uri);
+            conn = conn_setup(options, uri);
             result = dbi_conn_connect (conn);
             if (result < 0)
             {
                 PERR ("Unable to create database '%s'\n", uri.dbname());
-                qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
+                set_error (ERR_BACKEND_SERVER_ERR);
                 dbi_conn_close(conn);
                 LEAVE("Error");
                 return;
             }
-            if (T == DbType::DBI_MYSQL)
+            if (Type == DbType::DBI_MYSQL)
                 adjust_sql_options (conn);
-            if (!conn_test_dbi_library(conn, qof_be))
+            if (!conn_test_dbi_library(conn))
             {
-                if (T == DbType::DBI_PGSQL)
+                if (Type == DbType::DBI_PGSQL)
                     dbi_conn_select_db (conn, "template1");
                 dbi_conn_queryf (conn, "DROP DATABASE %s",
-                                 uri.quote_dbname(T).c_str());
+                                 uri.quote_dbname(Type).c_str());
                 dbi_conn_close(conn);
                 return;
             }
         }
         else
         {
-            qof_backend_set_error (qof_be, ERR_BACKEND_NO_SUCH_DB);
-            qof_backend_set_message (qof_be, "Database %s not found", uri.dbname());
+            set_error(ERR_BACKEND_NO_SUCH_DB);
+            std::string msg{"Database "};
+            set_message(msg + uri.dbname() + " not found");
         }
     }
 
-    dbi_be->connect(nullptr);
+    connect(nullptr);
     try
     {
-        dbi_be->connect(new GncDbiSqlConnection(T, qof_be, conn, ignore_lock));
+        connect(new GncDbiSqlConnection(Type, this, conn, ignore_lock));
     }
     catch (std::runtime_error& err)
     {
@@ -745,7 +719,8 @@ gnc_dbi_session_begin (QofBackend* qof_be, QofSession* session,
 template<> void
 error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
 {
-    GncDbiBackend* dbi_be = static_cast<decltype(dbi_be)>(user_data);
+    GncDbiBackend<DbType::DBI_PGSQL>* dbi_be =
+        static_cast<decltype(dbi_be)>(user_data);
     const char* msg;
 
     (void)dbi_conn_error (conn, &msg);
@@ -764,7 +739,7 @@ error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
             return;
         }
         PINFO ("DBI error: %s - Reconnecting...\n", msg);
-        dbi_be->set_error (ERR_BACKEND_CONN_LOST, 1, true);
+        dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
         dbi_be->retry_connection(msg);
     }
     else if (g_str_has_prefix (msg, "connection pointer is NULL") ||
@@ -776,7 +751,7 @@ error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
                                   ERR_BACKEND_CANT_CONNECT);
         else
         {
-            dbi_be->set_error(ERR_BACKEND_CANT_CONNECT, 1, true);
+            dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true);
             dbi_be->retry_connection (msg);
         }
     }
@@ -784,38 +759,28 @@ error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
     {
         PERR ("DBI error: %s\n", msg);
         if (dbi_be->connected())
-            dbi_be->set_error (ERR_BACKEND_MISC, 0, false);
+            dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
     }
 }
 
 /* ================================================================= */
 
-static void
-gnc_dbi_session_end (QofBackend* qof_be)
+template <DbType Type> void
+GncDbiBackend<Type>::session_end ()
 {
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-
-    g_return_if_fail (dbi_be != nullptr);
-
     ENTER (" ");
 
-    dbi_be->finalize_version_info ();
-    dbi_be->connect(nullptr);
+    finalize_version_info ();
+    connect(nullptr);
 
     LEAVE (" ");
 }
 
-static void
-gnc_dbi_destroy_backend (QofBackend* qof_be)
+template <DbType Type>
+GncDbiBackend<Type>::~GncDbiBackend()
 {
-    g_return_if_fail (qof_be != nullptr);
-
     /* Stop transaction logging */
     xaccLogSetBaseName (nullptr);
-
-    qof_backend_destroy (qof_be);
-
-    g_free (qof_be);
 }
 
 /* ================================================================= */
@@ -829,41 +794,38 @@ gnc_dbi_destroy_backend (QofBackend* qof_be)
  * then the database will be loaded read-only. A resave will update
  * both values to match this version of Gnucash.
  */
-void
-gnc_dbi_load (QofBackend* qof_be,  QofBook* book, QofBackendLoadType loadType)
+template <DbType Type> void
+GncDbiBackend<Type>::load (QofBook* book, QofBackendLoadType loadType)
 {
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-
-    g_return_if_fail (qof_be != nullptr);
     g_return_if_fail (book != nullptr);
 
-    ENTER ("dbi_be=%p, book=%p", dbi_be, book);
+    ENTER ("dbi_be=%p, book=%p", this, book);
 
     if (loadType == LOAD_TYPE_INITIAL_LOAD)
     {
 
         // Set up table version information
-        dbi_be->init_version_info ();
-        assert (dbi_be->m_book == nullptr);
-        dbi_be->create_tables();
+        init_version_info ();
+        assert (m_book == nullptr);
+        create_tables();
     }
 
-    dbi_be->load(book, loadType);
+    GncSqlBackend::load(book, loadType);
 
-    if (GNUCASH_RESAVE_VERSION > dbi_be->get_table_version("Gnucash"))
+    if (GNUCASH_RESAVE_VERSION > get_table_version("Gnucash"))
     {
         /* The database was loaded with an older database schema or
          * data semantics. In order to ensure consistency, the whole
          * thing needs to be saved anew. */
-        qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_OLD);
+        set_error(ERR_SQL_DB_TOO_OLD);
     }
-    else if (GNUCASH_RESAVE_VERSION < dbi_be->get_table_version("Gnucash-Resave"))
+    else if (GNUCASH_RESAVE_VERSION < get_table_version("Gnucash-Resave"))
     {
         /* Worse, the database was created with a newer version. We
          * can't safely write to this database, so the user will have
          * to do a "save as" to make one that we can write to.
          */
-        qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_NEW);
+        set_error(ERR_SQL_DB_TOO_NEW);
     }
 
 
@@ -894,17 +856,14 @@ save_may_clobber_data (dbi_conn conn, const std::string& dbname)
  * no errors. If there are errors, drop the new tables and restore the
  * originals.
  *
- * @param qof_be: QofBackend for the session.
  * @param book: QofBook to be saved in the database.
  */
-void
-gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
+template <DbType Type> void
+GncDbiBackend<Type>::safe_sync (QofBook* book)
 {
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-    auto conn = dynamic_cast<GncDbiSqlConnection*>(dbi_be->m_conn);
+    auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
 
     g_return_if_fail (conn != nullptr);
-    g_return_if_fail (dbi_be != nullptr);
     g_return_if_fail (book != nullptr);
 
     ENTER ("book=%p, primary=%p", book, m_book);
@@ -921,12 +880,11 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
         set_error (ERR_BACKEND_SERVER_ERR);
         set_message("Failed to drop indexes");
         LEAVE ("Failed to drop indexes");
-            return;
-        }
+        return;
     }
 
-    dbi_be->sync_all(book);
-    if (qof_backend_check_error (qof_be))
+    sync(m_book);
+    if (check_error())
     {
         conn->table_operation (TableOpType::rollback);
         LEAVE ("Failed to create new database tables");
@@ -936,63 +894,6 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
     LEAVE ("book=%p", m_book);
 }
 /* ================================================================= */
-static void
-gnc_dbi_begin_edit (QofBackend* qof_be, QofInstance* inst)
-{
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-
-    g_return_if_fail (dbi_be != nullptr);
-    g_return_if_fail (inst != nullptr);
-
-    dbi_be->begin_edit(inst);
-}
-
-static void
-gnc_dbi_rollback_edit (QofBackend* qof_be, QofInstance* inst)
-{
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-
-    g_return_if_fail (dbi_be != nullptr);
-    g_return_if_fail (inst != nullptr);
-
-    dbi_be->rollback_edit(inst);
-}
-
-static void
-gnc_dbi_commit_edit (QofBackend* qof_be, QofInstance* inst)
-{
-    GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be);
-
-    g_return_if_fail (dbi_be != nullptr);
-    g_return_if_fail (inst != nullptr);
-
-    dbi_be->commit_edit(inst);
-}
-
-/* ================================================================= */
-
-static void
-init_sql_backend (GncDbiBackend* dbi_be)
-{
-    QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(dbi_be);
-
-    qof_be->session_end = gnc_dbi_session_end;
-    qof_be->destroy_backend = gnc_dbi_destroy_backend;
-
-    qof_be->load = gnc_dbi_load;
-
-    /* The gda backend treats accounting periods transactionally. */
-    qof_be->begin = gnc_dbi_begin_edit;
-    qof_be->commit = gnc_dbi_commit_edit;
-    qof_be->rollback = gnc_dbi_rollback_edit;
-
-    /* The SQL/DBI backend doesn't need to be synced until it is
-     * configured for multiuser access. */
-    qof_be->sync = gnc_dbi_safe_sync_all;
-    qof_be->safe_sync = gnc_dbi_safe_sync_all;
-    /* CoA Export function not implemented for the SQL backend. */
-    qof_be->export_fn = nullptr;
-}
 
 /*
  * Checks to see whether the file is an sqlite file or not
@@ -1254,8 +1155,8 @@ dbi_library_test (dbi_conn conn)
     return retval;
 }
 
-static bool
-conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be)
+template <DbType Type> bool
+GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
 {
     auto result = dbi_library_test (conn);
     switch (result)
@@ -1264,15 +1165,13 @@ conn_test_dbi_library(dbi_conn conn, QofBackend* qof_be)
             break;
 
         case GNC_DBI_FAIL_SETUP:
-            qof_backend_set_error (qof_be, ERR_SQL_DBI_UNTESTABLE);
-            qof_backend_set_message (qof_be,
-                                     "DBI library large number test incomplete");
+            set_error(ERR_SQL_DBI_UNTESTABLE);
+            set_message ("DBI library large number test incomplete");
             break;
 
         case GNC_DBI_FAIL_TEST:
-            qof_backend_set_error (qof_be, ERR_SQL_BAD_DBI);
-            qof_backend_set_message (qof_be,
-                                     "DBI library fails large number test");
+            set_error (ERR_SQL_BAD_DBI);
+            set_message ("DBI library fails large number test");
             break;
     }
     return result == GNC_DBI_PASS;
diff --git a/src/backend/dbi/gnc-backend-dbi.hpp b/src/backend/dbi/gnc-backend-dbi.hpp
index 796c825..5d02732 100644
--- a/src/backend/dbi/gnc-backend-dbi.hpp
+++ b/src/backend/dbi/gnc-backend-dbi.hpp
@@ -84,14 +84,22 @@ enum class DbType
 /**
  * Implementations of GncSqlBackend.
  */
+struct UriStrings;
+
+template <DbType Type>
 class GncDbiBackend : public GncSqlBackend
 {
 public:
     GncDbiBackend(GncSqlConnection *conn, QofBook* book) :
         GncSqlBackend(conn, book), m_exists{false} {}
+    ~GncDbiBackend();
+    void session_begin(QofSession*, const char*, bool, bool, bool) override;
+    void session_end() override;
+    void load(QofBook*, QofBackendLoadType) override;
+    void safe_sync(QofBook*) override;
     bool connected() const noexcept { return m_conn != nullptr; }
     /** FIXME: Just a pass-through to m_conn: */
-    void set_error(int error, unsigned int repeat,  bool retry) noexcept
+    void set_dbi_error(int error, unsigned int repeat,  bool retry) noexcept
     {
         m_conn->set_error(error, repeat, retry);
     }
@@ -102,15 +110,14 @@ public:
     /*-----*/
     bool exists() { return m_exists; }
     void set_exists(bool exists) { m_exists = exists; }
-    friend void gnc_dbi_load(QofBackend*, QofBook*, QofBackendLoadType);
-    friend void gnc_dbi_safe_sync_all(QofBackend*, QofBook*);
 private:
+    dbi_conn conn_setup(PairVec& options, UriStrings& uri);
+    bool conn_test_dbi_library(dbi_conn conn);
+    bool set_standard_connection_options(dbi_conn conn, const UriStrings& uri);
+    bool create_database(dbi_conn conn, const char* db);
     bool m_exists;         // Does the database exist?
 };
 
-
-void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book);
-
 /* external access required for tests */
 std::string adjust_sql_options_string(const std::string&);
 
diff --git a/src/backend/dbi/gnc-dbisqlconnection.cpp b/src/backend/dbi/gnc-dbisqlconnection.cpp
index e39dc8f..b3f56d4 100644
--- a/src/backend/dbi/gnc-dbisqlconnection.cpp
+++ b/src/backend/dbi/gnc-dbisqlconnection.cpp
@@ -124,7 +124,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
         std::string err{"SQL Backend failed to obtain a transaction: "};
         err += errstr;
         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
-        qof_backend_set_message (m_qbe, err.c_str());
+        m_qbe->set_message (err.c_str());
         return false;
     }
     dbi_result_free(result);
@@ -147,7 +147,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
         if (!result)
         {
             qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
-            qof_backend_set_message (m_qbe, "Failed to delete lock record");
+            m_qbe->set_message("Failed to delete lock record");
             result = dbi_conn_query (m_conn, "ROLLBACK");
             if (result)
                 dbi_result_free (result);
@@ -165,7 +165,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
     if (!result)
     {
         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
-        qof_backend_set_message (m_qbe, "Failed to create lock record");
+        m_qbe->set_message("Failed to create lock record");
         result = dbi_conn_query (m_conn, "ROLLBACK");
         if (result)
             dbi_result_free (result);
@@ -179,7 +179,7 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
         qof_backend_set_error(m_qbe, ERR_BACKEND_SERVER_ERR);
         std::string err{"Failed to commit transaction: "};
         err += errstr;
-        qof_backend_set_message(m_qbe, err.c_str());
+        m_qbe->set_message(err.c_str());
         return false;
     }
     dbi_result_free (result);
@@ -189,8 +189,6 @@ GncDbiSqlConnection::lock_database (bool ignore_lock)
 void
 GncDbiSqlConnection::unlock_database ()
 {
-    GncDbiBackend* qe = reinterpret_cast<decltype(qe)>(m_qbe);
-
     if (m_conn == nullptr) return;
     g_return_if_fail (dbi_conn_error (m_conn, nullptr) == 0);
 
@@ -225,7 +223,7 @@ GncDbiSqlConnection::unlock_database ()
             if (!result)
             {
                 PERR ("Failed to delete the lock entry");
-                qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
+                m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
                 result = dbi_conn_query (m_conn, "ROLLBACK");
                 if (result)
                 {
@@ -262,7 +260,7 @@ GncDbiSqlConnection::unlock_database ()
         result = nullptr;
     }
     PWARN ("Unable to get a lock on LOCK, so failed to clear the lock entry.");
-    qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
+    m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
 }
 
 GncDbiSqlConnection::~GncDbiSqlConnection()
diff --git a/src/backend/sql/gnc-sql-backend.cpp b/src/backend/sql/gnc-sql-backend.cpp
index 9dd8064..6447746 100644
--- a/src/backend/sql/gnc-sql-backend.cpp
+++ b/src/backend/sql/gnc-sql-backend.cpp
@@ -80,10 +80,8 @@ static EntryVec version_table
 };
 
 GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) :
-    qof_be {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
-            nullptr, nullptr, nullptr, nullptr, ERR_BACKEND_NO_ERR, nullptr, 0,
-            nullptr}, m_conn{conn}, m_book{book}, m_loading{false},
-        m_in_query{false}, m_is_pristine_db{false}
+    QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false},
+    m_in_query{false}, m_is_pristine_db{false}
 {
     if (conn != nullptr)
         connect (conn);
@@ -188,15 +186,15 @@ GncSqlBackend::add_columns_to_table(const std::string& table_name,
 void
 GncSqlBackend::update_progress() const noexcept
 {
-    if (qof_be.percentage != nullptr)
-        (qof_be.percentage) (nullptr, 101.0);
+    if (m_percentage != nullptr)
+        (m_percentage) (nullptr, 101.0);
 }
 
 void
 GncSqlBackend::finish_progress() const noexcept
 {
-    if (qof_be.percentage != nullptr)
-        (qof_be.percentage) (nullptr, -1.0);
+    if (m_percentage != nullptr)
+        (m_percentage) (nullptr, -1.0);
 }
 
 void
@@ -441,7 +439,7 @@ GncSqlBackend::write_schedXactions()
 #pragma GCC diagnostic warning "-Wformat-nonliteral"
 
 void
-GncSqlBackend::sync_all(QofBook* book)
+GncSqlBackend::sync(QofBook* book)
 {
     g_return_if_fail (book != NULL);
 
@@ -500,8 +498,7 @@ GncSqlBackend::sync_all(QofBook* book)
     }
     else
     {
-        if (!qof_backend_check_error (&qof_be))
-            qof_backend_set_error (&qof_be, ERR_BACKEND_SERVER_ERR);
+        set_error (ERR_BACKEND_SERVER_ERR);
         is_ok = m_conn->rollback_transaction ();
     }
     finish_progress();
@@ -512,7 +509,7 @@ GncSqlBackend::sync_all(QofBook* book)
 /* Routines to deal with the creation of multiple books. */
 
 void
-GncSqlBackend::begin_edit (QofInstance* inst)
+GncSqlBackend::begin(QofInstance* inst)
 {
     g_return_if_fail (inst != NULL);
 
@@ -521,7 +518,7 @@ GncSqlBackend::begin_edit (QofInstance* inst)
 }
 
 void
-GncSqlBackend::rollback_edit(QofInstance* inst)
+GncSqlBackend::rollback(QofInstance* inst)
 {
     g_return_if_fail (inst != NULL);
 
@@ -546,7 +543,7 @@ GncSqlBackend::get_object_backend(const std::string& type) const noexcept
  * type and call its commit handler
  */
 void
-GncSqlBackend::commit_edit (QofInstance* inst)
+GncSqlBackend::commit (QofInstance* inst)
 {
     sql_backend be_data;
     gboolean is_dirty;
@@ -557,7 +554,7 @@ GncSqlBackend::commit_edit (QofInstance* inst)
 
     if (qof_book_is_readonly(m_book))
     {
-        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        set_error (ERR_BACKEND_READONLY);
         (void)m_conn->rollback_transaction ();
         return;
     }
diff --git a/src/backend/sql/gnc-sql-backend.hpp b/src/backend/sql/gnc-sql-backend.hpp
index 1ad4608..31a908f 100644
--- a/src/backend/sql/gnc-sql-backend.hpp
+++ b/src/backend/sql/gnc-sql-backend.hpp
@@ -62,11 +62,41 @@ typedef enum
  *
  * Main SQL backend structure.
  */
-class GncSqlBackend
+class GncSqlBackend : public QofBackend
 {
 public:
     GncSqlBackend(GncSqlConnection *conn, QofBook* book);
     virtual ~GncSqlBackend() = default;
+    /**
+     * Load the contents of an SQL database into a book.
+     *
+     * @param book Book to be loaded
+     */
+    void load(QofBook*, QofBackendLoadType) override;
+    /**
+     * Save the contents of a book to an SQL database.
+     *
+     * @param book Book to be saved
+     */
+    void sync(QofBook*) override;
+    /**
+     * An object is about to be edited.
+     *
+     * @param inst Object being edited
+     */
+    void begin(QofInstance*) override;
+    /**
+     * Object editting is complete and the object should be saved.
+     *
+     * @param inst Object being edited
+     */
+    void commit(QofInstance*) override;
+    /**
+     * Object editing has been cancelled.
+     *
+     * @param inst Object being edited
+     */
+    void rollback(QofInstance*) override;
     /** Connect the backend to a GncSqlConnection.
      * Sets up version info. Calling with nullptr clears the connection and
      * destroys the version info.
@@ -158,36 +188,6 @@ public:
     uint_t get_table_version(const std::string& table_name) const noexcept;
     bool set_table_version (const std::string& table_name, uint_t version) noexcept;
     /**
-     * Load the contents of an SQL database into a book.
-     *
-     * @param book Book to be loaded
-     */
-    void load(QofBook*, QofBackendLoadType);
-    /**
-     * Save the contents of a book to an SQL database.
-     *
-     * @param book Book to be saved
-     */
-    void sync_all(QofBook*);
-    /**
-     * An object is about to be edited.
-     *
-     * @param inst Object being edited
-     */
-    void begin_edit(QofInstance*);
-    /**
-     * Object editting is complete and the object should be saved.
-     *
-     * @param inst Object being edited
-     */
-    void commit_edit(QofInstance*);
-    /**
-     * Object editing has been cancelled.
-     *
-     * @param inst Object being edited
-     */
-    void rollback_edit(QofInstance*);
-    /**
      * Register a commodity to be committed after loading is complete.
      *
      * Necessary to save corrections made while loading.
@@ -241,7 +241,6 @@ public:
     void finish_progress() const noexcept;
 
 protected:
-    QofBackend qof_be;           /**< QOF backend. Not a pointer, nor really a member */
     GncSqlConnection* m_conn;  /**< SQL connection */
     QofBook* m_book;           /**< The primary, main open book */
     bool m_loading;        /**< We are performing an initial load */
diff --git a/src/backend/sql/test/utest-gnc-backend-sql.cpp b/src/backend/sql/test/utest-gnc-backend-sql.cpp
index cd7107b..9e8154c 100644
--- a/src/backend/sql/test/utest-gnc-backend-sql.cpp
+++ b/src/backend/sql/test/utest-gnc-backend-sql.cpp
@@ -34,6 +34,17 @@ extern "C"
 
 static const gchar* suitename = "/backend/sql/gnc-backend-sql";
 void test_suite_gnc_backend_sql (void);
+
+class GncMockSqlBackend : public GncSqlBackend
+{
+public:
+    GncMockSqlBackend(GncSqlConnection* conn, QofBook* book) :
+        GncSqlBackend(conn, book) {}
+    void session_begin(QofSession*, const char*, bool, bool, bool) override {}
+    void session_end() override {}
+    void safe_sync(QofBook* book) override { sync(book); }
+};
+
 class GncMockSqlConnection;
 
 class GncMockSqlResult : public GncSqlResult
@@ -265,7 +276,7 @@ test_gnc_sql_commit_edit (void)
     guint dirty_called = 0;
     GncMockSqlConnection conn;
     const char* msg1 =
-        "[GncSqlBackend::commit_edit()] Unknown object type 'null'\n";
+        "[GncSqlBackend::commit()] Unknown object type 'null'\n";
     GLogLevelFlags loglevel = static_cast<decltype (loglevel)>
                               (G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL);
     const char* logdomain = "gnc.backend.sql";
@@ -281,7 +292,8 @@ test_gnc_sql_commit_edit (void)
 
     qof_object_initialize ();
     auto book = qof_book_new();
-    GncSqlBackend sql_be (&conn, book);
+    auto sql_be = new GncMockSqlBackend
+        (&conn, book);
     inst  = static_cast<decltype (inst)> (g_object_new (QOF_TYPE_INSTANCE, NULL));
     qof_instance_init_data (inst, QOF_ID_NULL, book);
     qof_book_set_dirty_cb (book, test_dirty_cb, &dirty_called);
@@ -291,7 +303,7 @@ test_gnc_sql_commit_edit (void)
     g_assert (qof_instance_get_dirty_flag (inst));
     g_assert (qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 1);
-    sql_be.commit_edit (inst);
+    sql_be->commit(inst);
     g_assert (!qof_instance_get_dirty_flag (inst));
     g_assert (!qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 0);
@@ -302,7 +314,7 @@ test_gnc_sql_commit_edit (void)
     g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book)));
     g_assert (qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 1);
-    sql_be.commit_edit (QOF_INSTANCE (book));
+    sql_be->commit(QOF_INSTANCE (book));
     g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book)));
     g_assert (qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 1);
@@ -313,7 +325,7 @@ test_gnc_sql_commit_edit (void)
     g_assert (qof_instance_get_dirty_flag (QOF_INSTANCE (book)));
     g_assert (qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 1);
-    sql_be.commit_edit(QOF_INSTANCE (book));
+    sql_be->commit(QOF_INSTANCE (book));
     g_assert (!qof_instance_get_dirty_flag (QOF_INSTANCE (book)));
     g_assert (!qof_book_session_not_saved (book));
     g_assert_cmpint (dirty_called, == , 0);
@@ -322,6 +334,7 @@ test_gnc_sql_commit_edit (void)
     g_log_remove_handler (logdomain, hdlr1);
     g_object_unref (inst);
     g_object_unref (book);
+    delete sql_be;
 }
 /* handle_and_term
 static void
diff --git a/src/backend/xml/gnc-backend-xml.cpp b/src/backend/xml/gnc-backend-xml.cpp
index 7c4088b..d3e2cb2 100644
--- a/src/backend/xml/gnc-backend-xml.cpp
+++ b/src/backend/xml/gnc-backend-xml.cpp
@@ -108,42 +108,11 @@ struct QofXmlBackendProvider : public QofBackendProvider
     QofXmlBackendProvider(QofXmlBackendProvider&&) = delete;
     QofXmlBackendProvider operator=(QofXmlBackendProvider&&) = delete;
     ~QofXmlBackendProvider () = default;
-    QofBackend* create_backend(void);
+    QofBackend* create_backend(void) { return new GncXmlBackend; }
     bool type_check(const char* type);
 
 };
 
-
-static void
-xml_session_begin (QofBackend* qof_be, QofSession* session,
-                   const char* book_id, gboolean ignore_lock,
-                   gboolean create, gboolean force)
-{
-    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
-
-    ENTER (" ");
-    xml_be->session_begin(session, book_id, ignore_lock, create, force);
-    LEAVE (" ");
-    return;
-}
-
-/* ================================================================= */
-
-static void
-xml_session_end (QofBackend* qof_be)
-{
-    GncXmlBackend* xml_be = (GncXmlBackend*)qof_be;
-    ENTER (" ");
-    xml_be->session_end();
-    LEAVE (" ");
-}
-
-static void
-xml_destroy_backend (QofBackend* qof_be)
-{
-    delete reinterpret_cast<GncXmlBackend*>(qof_be);
-}
-
 bool
 QofXmlBackendProvider::type_check (const char *uri)
 {
@@ -201,86 +170,8 @@ det_exit:
     return result;
 }
 
-static void
-xml_sync_all (QofBackend* qof_be, QofBook* book)
-{
-    GncXmlBackend* xml_be = reinterpret_cast<decltype(xml_be)>(qof_be);
-    xml_be->sync(book);
-    ENTER ("book=%p, xml_be->m_book=%p", book, xml_be->get_book());
-
-    LEAVE ("book=%p", book);
-}
-
-
-static void
-xml_begin_edit (QofBackend* qof_be, QofInstance* inst)
-{
-    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
-    xml_be->begin(inst);
-}
-
-static void
-xml_rollback_edit (QofBackend* qof_be, QofInstance* inst)
-{
-
-    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
-    xml_be->rollback(inst);
-}
-
-/* ---------------------------------------------------------------------- */
-
-
-/* Load financial data from a file into the book, automatically
-   detecting the format of the file, if possible.  Return FALSE on
-   error, and set the error parameter to indicate what went wrong if
-   it's not NULL.  This function does not manage file locks in any
-   way. */
-
-static void
-gnc_xml_be_load_from_file (QofBackend* qof_be, QofBook* book,
-                           QofBackendLoadType loadType)
-{
-    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
-    xml_be->load(book, loadType);
-}
-
-/* ---------------------------------------------------------------------- */
-
-
-
-static void
-gnc_xml_be_write_accounts_to_file (QofBackend* qof_be, QofBook* book)
-{
-    auto datafile = ((GncXmlBackend*)qof_be)->get_filename();
-    gnc_book_write_accounts_to_xml_file_v2 (qof_be, book, datafile);
-}
-
 /* ================================================================= */
 
-QofBackend*
-QofXmlBackendProvider::create_backend(void)
-{
-
-    auto xml_be = new GncXmlBackend;
-    auto qof_be = xml_be->get_qof_be();
-    qof_be->session_begin = xml_session_begin;
-    qof_be->session_end = xml_session_end;
-    qof_be->destroy_backend = xml_destroy_backend;
-
-    qof_be->load = gnc_xml_be_load_from_file;
-
-    /* The file backend treats accounting periods transactionally. */
-    qof_be->begin = xml_begin_edit;
-    qof_be->commit = NULL;
-    qof_be->rollback = xml_rollback_edit;
-
-    qof_be->sync = xml_sync_all;
-
-    qof_be->export_fn = gnc_xml_be_write_accounts_to_file;
-
-    return qof_be;
-}
-
 static void
 business_core_xml_init (void)
 {
diff --git a/src/backend/xml/gnc-xml-backend.cpp b/src/backend/xml/gnc-xml-backend.cpp
index 213208b..e63b31b 100644
--- a/src/backend/xml/gnc-xml-backend.cpp
+++ b/src/backend/xml/gnc-xml-backend.cpp
@@ -23,6 +23,7 @@ extern "C"
 #include <windows.h>
 #endif
 #include <errno.h>
+#include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/stat.h>
@@ -48,21 +49,8 @@ extern "C"
 #define FILE_URI_PREFIX "file://"
 static QofLogModule log_module = GNC_MOD_BACKEND;
 
-GncXmlBackend::GncXmlBackend()
-{
-    memset(&qof_be, 0, sizeof(qof_be));
-    qof_backend_init(&qof_be);
-}
-
-GncXmlBackend::~GncXmlBackend()
-{
-    /* Stop transaction logging */
-    xaccLogSetBaseName (NULL);
-    qof_backend_destroy (&qof_be);
-}
-
 bool
-check_path (const char* fullpath, QofBackend* qof_be, bool create)
+GncXmlBackend::check_path (const char* fullpath, bool create)
 {
     struct stat statbuf;
     char* dirname = g_path_get_dirname (fullpath);
@@ -78,9 +66,9 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
     {
         /* Error on stat or if it isn't a directory means we
            cannot find this filename */
-        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (qof_be, "Couldn't find directory for %s",
-                                 fullpath);
+        set_error(ERR_FILEIO_FILE_NOT_FOUND);
+        std::string msg {"Couldn't find directory for "};
+        set_message(msg + fullpath);
         PWARN ("Couldn't find directory for %s", fullpath);
         g_free(dirname);
         return false;
@@ -91,8 +79,9 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
     if ((rc != 0) && (!create))
     {
         /* Error on stat means the file doesn't exist */
-        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (qof_be, "Couldn't find %s", fullpath);
+        set_error(ERR_FILEIO_FILE_NOT_FOUND);
+        std::string msg {"Couldn't find "};
+        set_message(msg + fullpath);
         PWARN ("Couldn't find %s", fullpath);
         g_free(dirname);
         return false;
@@ -105,8 +94,10 @@ check_path (const char* fullpath, QofBackend* qof_be, bool create)
 #endif
         )
     {
-        qof_backend_set_error (qof_be, ERR_FILEIO_UNKNOWN_FILE_TYPE);
-        qof_backend_set_message (qof_be, "Path %s is a directory", fullpath);
+        set_error(ERR_FILEIO_UNKNOWN_FILE_TYPE);
+        std::string msg {"Path "};
+        msg += fullpath;
+        set_message(msg +  " is a directory");
         PWARN ("Path %s is a directory", fullpath);
         g_free(dirname);
         return false;
@@ -123,20 +114,19 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
 
     if (m_fullpath.empty())
     {
-        qof_backend_set_error (&qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (&qof_be, "No path specified");
+        set_error(ERR_FILEIO_FILE_NOT_FOUND);
+        set_message("No path specified");
         return;
     }
     if (create && !force && save_may_clobber_data())
     {
-        qof_backend_set_error (&qof_be, ERR_BACKEND_STORE_EXISTS);
+        set_error(ERR_BACKEND_STORE_EXISTS);
         PWARN ("Might clobber, no force");
         return;
     }
 
-    if (!check_path(m_fullpath.c_str(), &qof_be, create))
+    if (!check_path(m_fullpath.c_str(), create))
         return;
-    qof_be.fullpath = const_cast<char*>(m_fullpath.c_str());
     m_dirname = g_path_get_dirname (m_fullpath.c_str());
 
 
@@ -159,7 +149,7 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
 
         if (force)
         {
-            QofBackendError berror = qof_backend_get_error (&qof_be);
+            QofBackendError berror = get_error();
             if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
             {
                 // Even though we couldn't get the lock, we were told to force
@@ -169,7 +159,7 @@ GncXmlBackend::session_begin(QofSession* session, const char* book_id,
             else
             {
                 // Unknown error. Push it again on the error stack.
-                qof_backend_set_error (&qof_be, berror);
+                set_error(berror);
             }
         }
     }
@@ -181,7 +171,7 @@ GncXmlBackend::session_end()
 {
     if (m_book && qof_book_is_readonly (m_book))
     {
-        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        set_error(ERR_BACKEND_READONLY);
         return;
     }
 
@@ -307,7 +297,7 @@ GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
 
     if (error != ERR_BACKEND_NO_ERR)
     {
-        qof_backend_set_error (&qof_be, error);
+        set_error(error);
     }
 
     /* We just got done loading, it can't possibly be dirty !! */
@@ -329,7 +319,7 @@ GncXmlBackend::sync(QofBook* book)
     if (qof_book_is_readonly (m_book))
     {
         /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        set_error(ERR_BACKEND_READONLY);
         return;
     }
 
@@ -348,6 +338,20 @@ GncXmlBackend::save_may_clobber_data()
 
 }
 
+void
+GncXmlBackend::export_coa(QofBook* book)
+{
+    auto out = fopen(m_fullpath.c_str(), "w");
+    if (out == NULL)
+    {
+        set_error(ERR_FILEIO_WRITE_ERROR);
+        set_message(strerror(errno));
+        return;
+    }
+    gnc_book_write_accounts_to_xml_filehandle_v2(this, book, out);
+    fclose(out);
+}
+
 bool
 GncXmlBackend::write_to_file (bool make_backup)
 {
@@ -358,7 +362,7 @@ GncXmlBackend::write_to_file (bool make_backup)
     if (m_book && qof_book_is_readonly (m_book))
     {
         /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        set_error(ERR_BACKEND_READONLY);
         LEAVE ("");
         return FALSE;
     }
@@ -374,8 +378,8 @@ GncXmlBackend::write_to_file (bool make_backup)
 
     if (!mktemp (tmp_name))
     {
-        qof_backend_set_error (&qof_be, ERR_BACKEND_MISC);
-        qof_backend_set_message (&qof_be, "Failed to make temp file");
+        set_error(ERR_BACKEND_MISC);
+        set_message("Failed to make temp file");
         LEAVE ("");
         return FALSE;
     }
@@ -403,8 +407,8 @@ GncXmlBackend::write_to_file (bool make_backup)
             /* Use the permissions from the original data file */
             if (g_chmod (tmp_name, statbuf.st_mode) != 0)
             {
-                /* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message(&qof_be, "Failed to chmod filename %s", tmp_name ); */
+                /* set_error(ERR_BACKEND_PERM); */
+                /* set_message("Failed to chmod filename %s", tmp_name ); */
                 /* Even if the chmod did fail, the save
                    nevertheless completed successfully. It is
                    therefore wrong to signal the ERR_BACKEND_PERM
@@ -423,8 +427,8 @@ GncXmlBackend::write_to_file (bool make_backup)
                that. */
             if (chown (tmp_name, -1, statbuf.st_gid) != 0)
             {
-                /* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message(&qof_be, "Failed to chown filename %s", tmp_name ); */
+                /* set_error(ERR_BACKEND_PERM); */
+                /* set_message("Failed to chown filename %s", tmp_name ); */
                 /* A failed chown doesn't mean that the saving itself
                 failed. So don't abort with an error here! */
                 PWARN ("unable to chown filename %s: %s",
@@ -439,7 +443,7 @@ GncXmlBackend::write_to_file (bool make_backup)
         }
         if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
         {
-            qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+            set_error(ERR_BACKEND_READONLY);
             PWARN ("unable to unlink filename %s: %s",
                    m_fullpath.empty() ? "(null)" : m_fullpath.c_str(),
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -449,16 +453,16 @@ GncXmlBackend::write_to_file (bool make_backup)
         }
         if (!link_or_make_backup (tmp_name, m_fullpath))
         {
-            qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
-            qof_backend_set_message (&qof_be, "Failed to make backup file %s",
-                                     m_fullpath.empty() ? "NULL" : m_fullpath.c_str());
+            set_error(ERR_FILEIO_BACKUP_ERROR);
+            std::string msg{"Failed to make backup file "};
+            set_message(msg + (m_fullpath.empty() ? "NULL" : m_fullpath));
             g_free (tmp_name);
             LEAVE ("");
             return FALSE;
         }
         if (g_unlink (tmp_name) != 0)
         {
-            qof_backend_set_error (&qof_be, ERR_BACKEND_PERM);
+            set_error(ERR_BACKEND_PERM);
             PWARN ("unable to unlink temp filename %s: %s",
                    tmp_name ? tmp_name : "(null)",
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -492,7 +496,7 @@ GncXmlBackend::write_to_file (bool make_backup)
                 be_err = ERR_BACKEND_MISC;
                 break;
             }
-            qof_backend_set_error (&qof_be, be_err);
+            set_error(be_err);
             PWARN ("unable to unlink temp_filename %s: %s",
                    tmp_name ? tmp_name : "(null)",
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -501,9 +505,9 @@ GncXmlBackend::write_to_file (bool make_backup)
         else
         {
             /* Use a generic write error code */
-            qof_backend_set_error (&qof_be, ERR_FILEIO_WRITE_ERROR);
-            qof_backend_set_message (&qof_be, "Unable to write to temp file %s",
-                                     tmp_name ? tmp_name : "NULL");
+            set_error(ERR_FILEIO_WRITE_ERROR);
+            std::string msg{"Unable to write to temp file "};
+            set_message(msg + (tmp_name ? tmp_name : "NULL"));
         }
         g_free (tmp_name);
         LEAVE ("");
@@ -519,8 +523,8 @@ copy_file (const std::string& orig, const std::string& bkup)
     constexpr size_t buf_size = 1024;
     char buf[buf_size];
     int flags = 0;
-    ssize_t count_write;
-    ssize_t count_read;
+    ssize_t count_write = 0;
+    ssize_t count_read = 0;
 
 
 #ifdef G_OS_WIN32
@@ -542,7 +546,7 @@ copy_file (const std::string& orig, const std::string& bkup)
 
     do
     {
-        auto count_read = read (orig_fd, buf, buf_size);
+        count_read = read (orig_fd, buf, buf_size);
         if (count_read == -1 && errno != EINTR)
         {
             close (orig_fd);
@@ -602,7 +606,7 @@ GncXmlBackend::link_or_make_backup (const std::string& orig,
 
         if (!copy_success)
         {
-            qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
+            set_error(ERR_FILEIO_BACKUP_ERROR);
             PWARN ("unable to make file backup from %s to %s: %s",
                    orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) : "");
             return false;
@@ -626,7 +630,7 @@ GncXmlBackend::get_file_lock ()
     if (!rc)
     {
         /* oops .. file is locked by another user  .. */
-        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        set_error(ERR_BACKEND_LOCKED);
         return false;
     }
 
@@ -648,7 +652,7 @@ GncXmlBackend::get_file_lock ()
             be_err = ERR_BACKEND_LOCKED;
             break;
         }
-        qof_backend_set_error (&qof_be, be_err);
+        set_error(be_err);
         return false;
     }
 
@@ -693,7 +697,7 @@ GncXmlBackend::get_file_lock ()
         }
 
         /* Otherwise, something else is wrong. */
-        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        set_error(ERR_BACKEND_LOCKED);
         g_unlink (linkfile.str().c_str());
         close (m_lockfd);
         g_unlink (m_lockfile.c_str());
@@ -704,9 +708,9 @@ GncXmlBackend::get_file_lock ()
     if (rc)
     {
         /* oops .. stat failed!  This can't happen! */
-        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
-        qof_backend_set_message (&qof_be, "Failed to stat lockfile %s",
-                                 m_lockfile.c_str());
+        set_error(ERR_BACKEND_LOCKED);
+        std::string msg{"Failed to stat lockfile "};
+        set_message(msg + m_lockfile);
         g_unlink (linkfile.str().c_str());
         close (m_lockfd);
         g_unlink (m_lockfile.c_str());
@@ -715,7 +719,7 @@ GncXmlBackend::get_file_lock ()
 
     if (statbuf.st_nlink != 2)
     {
-        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        set_error(ERR_BACKEND_LOCKED);
         g_unlink (linkfile.str().c_str());
         close (m_lockfd);
         g_unlink (m_lockfile.c_str());
diff --git a/src/backend/xml/gnc-xml-backend.hpp b/src/backend/xml/gnc-xml-backend.hpp
index e49486f..2710115 100644
--- a/src/backend/xml/gnc-xml-backend.hpp
+++ b/src/backend/xml/gnc-xml-backend.hpp
@@ -26,28 +26,26 @@ extern "C"
 #include <string>
 #include <qof-backend.hpp>
 
-class GncXmlBackend
+class GncXmlBackend : public QofBackend
 {
 public:
-    GncXmlBackend();
+    GncXmlBackend() = default;
     GncXmlBackend(const GncXmlBackend&) = delete;
     GncXmlBackend operator=(const GncXmlBackend&) = delete;
     GncXmlBackend(const GncXmlBackend&&) = delete;
     GncXmlBackend operator=(const GncXmlBackend&&) = delete;
-    ~GncXmlBackend();
+    ~GncXmlBackend() = default;
     void session_begin(QofSession* session, const char* book_id,
-                       bool ignore_lock, bool create, bool force);
-    void session_end();
-    void load(QofBook* book, QofBackendLoadType loadType);
+                       bool ignore_lock, bool create, bool force) override;
+    void session_end() override;
+    void load(QofBook* book, QofBackendLoadType loadType) override;
     /* The XML backend isn't able to do anything with individual instances. */
-    void begin(QofInstance* inst) {}
-    void commit(QofInstance* inst) {}
-    void rollback(QofInstance* inst) {}
-    void sync(QofBook* book);
-    QofBackend* get_qof_be() { return &qof_be; }
+    void export_coa(QofBook*) override;
+    void sync(QofBook* book) override;
+    void safe_sync(QofBook* book) override { sync(book); } // XML sync is inherently safe.
     const char * get_filename() { return m_fullpath.c_str(); }
     QofBook* get_book() { return m_book; }
-    
+
 private:
     bool save_may_clobber_data();
     bool get_file_lock();
@@ -56,10 +54,9 @@ private:
     bool write_to_file(bool make_backup);
     void remove_old_files();
     void write_accounts(QofBook* book);
-    QofBackend qof_be;
+    bool check_path(const char* fullpath, bool create);
 
     std::string m_dirname;
-    std::string m_fullpath;  /* Fully qualified path to book */
     std::string m_lockfile;
     std::string m_linkfile;
     int m_lockfd;
diff --git a/src/backend/xml/io-gncxml-v2.cpp b/src/backend/xml/io-gncxml-v2.cpp
index d58e822..c097af8 100644
--- a/src/backend/xml/io-gncxml-v2.cpp
+++ b/src/backend/xml/io-gncxml-v2.cpp
@@ -698,7 +698,6 @@ qof_session_load_from_xml_file_v2_full (
     QofBookFileType type)
 {
     Account* root;
-    QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(xml_be);
     sixtp_gdv2* gd;
     sixtp* top_parser;
     sixtp* main_parser;
@@ -707,7 +706,8 @@ qof_session_load_from_xml_file_v2_full (
     gboolean retval;
     char* v2type = NULL;
 
-    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, qof_be->percentage);
+    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
+                             xml_be->get_percentage());
 
     top_parser = sixtp_new ();
     main_parser = sixtp_new ();
@@ -1340,7 +1340,8 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
         return FALSE;
 
     qof_be = qof_book_get_backend (book);
-    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, qof_be->percentage);
+    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback,
+                             qof_be->get_percentage());
     gd->counter.commodities_total =
         gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
     gd->counter.accounts_total = 1 +
@@ -1386,7 +1387,8 @@ gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
         || !write_counts (out, "commodity", ncom, "account", nacc, NULL))
         return FALSE;
 
-    gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, qof_be->percentage);
+    gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback,
+                             qof_be->get_percentage());
     gd->counter.commodities_total = ncom;
     gd->counter.accounts_total = nacc;
 
@@ -1651,10 +1653,8 @@ gnc_book_write_to_xml_file_v2 (
  * postgress or anything else.
  */
 gboolean
-gnc_book_write_accounts_to_xml_file_v2 (
-    QofBackend* qof_be,
-    QofBook* book,
-    const char* filename)
+gnc_book_write_accounts_to_xml_file_v2 (QofBackend* qof_be, QofBook* book,
+                                        const char* filename)
 {
     FILE* out;
     gboolean success = TRUE;
@@ -1671,7 +1671,7 @@ gnc_book_write_accounts_to_xml_file_v2 (
     if (out && fclose (out))
         success = FALSE;
 
-    if (!success && !qof_backend_check_error (qof_be))
+    if (!success && !qof_be->check_error())
     {
 
         /* Use a generic write error code */
diff --git a/src/backend/xml/test/test-dom-converters1.cpp b/src/backend/xml/test/test-dom-converters1.cpp
index c577f9a..5c1d44b 100644
--- a/src/backend/xml/test/test-dom-converters1.cpp
+++ b/src/backend/xml/test/test-dom-converters1.cpp
@@ -32,12 +32,12 @@ extern "C"
 
 #include "test-stuff.h"
 #include "test-engine-stuff.h"
-#include "test-file-stuff.h"
 #include "cashobjects.h"
 #include "gnc-engine.h"
 #include "gnc-commodity.h"
 }
 
+#include "test-file-stuff.h"
 #include "gnc-xml-helper.h"
 #include "sixtp.h"
 #include "sixtp-parsers.h"
diff --git a/src/backend/xml/test/test-save-in-lang.cpp b/src/backend/xml/test/test-save-in-lang.cpp
index 04214d0..c9af56e 100644
--- a/src/backend/xml/test/test-save-in-lang.cpp
+++ b/src/backend/xml/test/test-save-in-lang.cpp
@@ -32,11 +32,12 @@ extern "C"
 
 #include "test-stuff.h"
 #include "test-engine-stuff.h"
-#include "test-file-stuff.h"
 
 #include "gnc-engine.h"
 #include "TransLog.h"
 }
+
+#include "test-file-stuff.h"
 #include "io-gncxml-v2.h"
 
 const char* possible_envs[] =
diff --git a/src/backend/xml/test/test-string-converters.cpp b/src/backend/xml/test/test-string-converters.cpp
index 8cbf726..66b7572 100644
--- a/src/backend/xml/test/test-string-converters.cpp
+++ b/src/backend/xml/test/test-string-converters.cpp
@@ -26,9 +26,9 @@ extern "C"
 
 #include "test-stuff.h"
 #include "test-engine-stuff.h"
-#include "test-file-stuff.h"
 }
 
+#include "test-file-stuff.h"
 #include "sixtp-dom-parsers.h"
 #include "sixtp-dom-generators.h"
 
diff --git a/src/backend/xml/test/test-xml-transaction.cpp b/src/backend/xml/test/test-xml-transaction.cpp
index 9247fda..bfe87f7 100644
--- a/src/backend/xml/test/test-xml-transaction.cpp
+++ b/src/backend/xml/test/test-xml-transaction.cpp
@@ -52,7 +52,7 @@ extern "C"
 #include "../sixtp-parsers.h"
 #include "../sixtp-dom-parsers.h"
 #include "../io-gncxml-gen.h"
-#include <test-file-stuff.h>
+#include "test-file-stuff.h"
 
 static QofBook* book;
 
diff --git a/src/engine/test/utest-Transaction.cpp b/src/engine/test/utest-Transaction.cpp
index 0e06a34..5982ba8 100644
--- a/src/engine/test/utest-Transaction.cpp
+++ b/src/engine/test/utest-Transaction.cpp
@@ -86,47 +86,49 @@ typedef struct
     Account *gains_acc;
 } GainsFixture;
 
-typedef struct
-{
-    QofBackend be;
-    gchar last_call[12];
-    QofBackendError result_err;
-} MockBackend;
-
-static void
-mock_backend_set_error (MockBackend *mbe, QofBackendError err)
-{
-    mbe->result_err = err;
-}
-
-static void
-mock_backend_rollback (QofBackend *be, QofInstance *foo)
+class MockBackend : public QofBackend
 {
-    MockBackend *mbe = (MockBackend *)be;
-    g_strlcpy (mbe->last_call, "rollback", sizeof (mbe->last_call));
-    mbe->be.last_err = mbe->result_err;
-}
-
-static MockBackend*
-mock_backend_new (void)
-{
-    MockBackend *mbe = g_new0 (MockBackend, 1);
-    mbe->be.rollback = mock_backend_rollback;
-    memset (mbe->last_call, 0, sizeof (mbe->last_call));
-    return mbe;
-}
+public:
+    MockBackend() : QofBackend(), m_last_call{"Constructor"},
+                    m_result_err{ERR_BACKEND_NO_ERR} {}
+    void session_begin(QofSession*, const char*, bool, bool, bool) override {
+        m_last_call = "session_begin";
+    }
+    void session_end() override {
+        m_last_call = "session_end";
+    }
+    void load(QofBook*, QofBackendLoadType) override {
+        m_last_call = "load";
+    }
+    void sync(QofBook*) override {
+        m_last_call = "sync";
+    }
+    void safe_sync(QofBook*) override {
+        m_last_call = "safe_sync";
+    }
+    void rollback(QofInstance*) override {
+        set_error(m_result_err);
+        m_last_call = "rollback";
+    }
+    void inject_error(QofBackendError err) {
+        m_result_err = err;
+    }
+    std::string m_last_call;
+private:
+    QofBackendError m_result_err;
+};
 
 static void
 setup (Fixture *fixture, gconstpointer pData)
 {
     QofBook *book = qof_book_new ();
-    MockBackend *mbe = mock_backend_new ();
+    MockBackend *mbe = new MockBackend;
     Transaction *txn;
     Timespec entered = gnc_dmy2timespec (20, 4, 2012);
     Timespec posted = gnc_dmy2timespec (21, 4, 2012);
     auto frame = new KvpFrame ();
 
-    qof_book_set_backend (book, (QofBackend*)mbe);
+    qof_book_set_backend (book, mbe);
     auto split1 = xaccMallocSplit (book);
     auto split2 = xaccMallocSplit (book);
     txn = xaccMallocTransaction (book);
@@ -211,14 +213,14 @@ static void
 teardown (Fixture *fixture, gconstpointer pData)
 {
     QofBook *book = qof_instance_get_book (QOF_INSTANCE (fixture->txn));
-    MockBackend *mbe = (MockBackend *)qof_book_get_backend (book);
+    auto mbe = static_cast<MockBackend*>(qof_book_get_backend (book));
 
     test_destroy (fixture->txn);
     test_destroy (fixture->acc1);
     test_destroy (fixture->acc2);
     test_destroy (fixture->curr);
     test_destroy (fixture->comm);
-    g_free (mbe);
+    delete mbe;
     qof_book_destroy(book);
     g_slist_free_full (fixture->hdlrs, test_free_log_handler);
     test_clear_error_list();
@@ -1695,7 +1697,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData)
     KvpFrame *base_frame = NULL;
     auto sig_account = test_signal_new (QOF_INSTANCE (fixture->acc1),
                               GNC_EVENT_ITEM_CHANGED, NULL);
-    MockBackend *mbe = (MockBackend*)qof_book_get_backend (book);
+    auto mbe = static_cast<MockBackend*>(qof_book_get_backend (book));
     auto split_00 = static_cast<Split*>(txn->splits->data);
     auto split_01 = static_cast<Split*>(txn->splits->next->data);
     auto split_02 = xaccMallocSplit (book);
@@ -1743,7 +1745,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData)
                               FALSE, FALSE, FALSE));
     g_assert (xaccSplitEqual (static_cast<Split*>(txn->splits->next->data),
                               split_10, FALSE, FALSE, FALSE));
-    g_assert_cmpstr (mbe->last_call, ==, "rollback");
+    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");
     g_assert_cmpuint (qof_instance_get_editlevel (QOF_INSTANCE (txn)), ==, 0);
     g_assert (qof_instance_get_destroying (txn) == FALSE);
     test_signal_free (sig_account);
@@ -1757,7 +1759,7 @@ test_xaccTransRollbackEdit (Fixture *fixture, gconstpointer pData)
 static void
 test_xaccTransRollbackEdit_BackendErrors (Fixture *fixture, gconstpointer pData)
 {
-    MockBackend *mbe = (MockBackend*)qof_book_get_backend (qof_instance_get_book (fixture->txn));
+    auto mbe = static_cast<MockBackend*>(qof_book_get_backend (qof_instance_get_book (fixture->txn)));
     auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL);
     auto msg = "[xaccTransRollbackEdit()] Rollback Failed.  Ouch!";
     auto check = test_error_struct_new ("gnc.engine", loglevel, msg);
@@ -1765,16 +1767,16 @@ test_xaccTransRollbackEdit_BackendErrors (Fixture *fixture, gconstpointer pData)
                      (GLogFunc)test_checked_handler);
     g_object_ref (fixture->txn);
     xaccTransBeginEdit (fixture->txn);
-    mock_backend_set_error (mbe, ERR_BACKEND_MODIFIED);
+    mbe->inject_error(ERR_BACKEND_MODIFIED);
     xaccTransRollbackEdit (fixture->txn);
     g_assert_cmpint (check->hits, ==, 1);
-    g_assert_cmpstr (mbe->last_call, ==, "rollback");
-    memset (mbe->last_call, 0, sizeof (mbe->last_call));
+    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");
+    mbe->m_last_call.clear();
     xaccTransBeginEdit (fixture->txn);
-    mock_backend_set_error (mbe, ERR_BACKEND_MOD_DESTROY);
+    mbe->inject_error (ERR_BACKEND_MOD_DESTROY);
     xaccTransRollbackEdit (fixture->txn);
     g_assert_cmpint (GPOINTER_TO_INT(fixture->txn->num), ==, 1);
-    g_assert_cmpstr (mbe->last_call, ==, "rollback");
+    g_assert_cmpstr (mbe->m_last_call.c_str(), ==, "rollback");
 
 }
 /* xaccTransIsOpen C: 23 in 7 SCM: 1  Local: 0:0:0
@@ -1941,7 +1943,7 @@ test_xaccTransReverse (Fixture *fixture, gconstpointer pData)
     g_assert (guid_equal (frame->get_slot(TRANS_REVERSED_BY)->get<GncGUID*>(),
                           xaccTransGetGUID (rev)));
 
-    g_assert (qof_instance_is_dirty (QOF_INSTANCE (rev)));
+    g_assert (!qof_instance_is_dirty (QOF_INSTANCE (rev))); //Cleared by commit
     g_assert_cmpint (g_list_length (fixture->txn->splits), ==,
                      g_list_length (rev->splits));
     for (orig_splits = fixture->txn->splits,
diff --git a/src/libqof/qof/qof-backend.cpp b/src/libqof/qof/qof-backend.cpp
index 28feadc..e779fc0 100644
--- a/src/libqof/qof/qof-backend.cpp
+++ b/src/libqof/qof/qof-backend.cpp
@@ -25,17 +25,14 @@
 extern "C"
 {
 
-#include "config.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <regex.h>
-#include <glib.h>
-#include <gmodule.h>
-#include <errno.h>
+#include <config.h>
 #include "qof.h"
 }
 
+#include <string>
+#include <algorithm>
+#include <vector>
+
 #include "qof-backend.hpp"
 
 G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
@@ -47,238 +44,128 @@ G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
  * error handling                                                   *
 \********************************************************************/
 
+GModuleVec QofBackend::c_be_registry{};
+
 void
-qof_backend_set_error (QofBackend *be, QofBackendError err)
+QofBackend::set_error(QofBackendError err)
 {
-    if (!be) return;
-
     /* use stack-push semantics. Only the earliest error counts */
-    if (ERR_BACKEND_NO_ERR != be->last_err) return;
-    be->last_err = err;
+    if (m_last_err != ERR_BACKEND_NO_ERR) return;
+    m_last_err = err;
 }
 
 QofBackendError
-qof_backend_get_error (QofBackend *be)
+QofBackend::get_error()
 {
-    QofBackendError err;
-    if (!be) return ERR_BACKEND_NO_BACKEND;
-
     /* use 'stack-pop' semantics */
-    err = be->last_err;
-    be->last_err = ERR_BACKEND_NO_ERR;
+    auto err = m_last_err;
+    m_last_err = ERR_BACKEND_NO_ERR;
     return err;
 }
 
-gboolean
-qof_backend_check_error (QofBackend *be)
+bool
+QofBackend::check_error()
 {
-    g_return_val_if_fail (be != NULL, TRUE);
-    return be->last_err != ERR_BACKEND_NO_ERR;
+    return m_last_err != ERR_BACKEND_NO_ERR;
 }
 
-gboolean
-qof_backend_can_rollback (QofBackend* be)
+void
+QofBackend::set_message (std::string&& msg)
 {
-    if (be == nullptr) return FALSE;
-    return be->rollback != nullptr;
+    m_error_msg = msg;
 }
 
-void
-qof_backend_rollback_instance (QofBackend* be, QofInstance* inst)
+const std::string&&
+QofBackend::get_message ()
 {
-    if (be == nullptr || be->rollback == nullptr) return;
-    (be->rollback)(be, inst);
+    return std::move(m_error_msg);
 }
 
-void
-qof_backend_set_message (QofBackend *be, const char *format, ...)
+bool
+QofBackend::register_backend(const char* directory, const char* module_name)
 {
-    va_list args;
-    char * buffer;
-
-    if (!be) return;
-
-    /* If there's already something here, free it */
-    if (be->error_msg) g_free(be->error_msg);
-
-    if (!format)
+    if (!g_module_supported ())
     {
-        be->error_msg = NULL;
-        return;
+        PWARN("Modules not supported.");
+        return false;
     }
+    auto fullpath = g_module_build_path (directory, module_name);
+/* Darwin modules can have either .so or .dylib for a suffix */
+    if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) &&
+	g_strcmp0 (G_MODULE_SUFFIX, "so") == 0)
+    {
+	auto modname = g_strdup_printf ("lib%s.dylib", module_name);
+	g_free (fullpath);
+	fullpath = g_build_filename (directory, modname, NULL);
+	g_free (modname);
+    }
+    auto backend = g_module_open (fullpath, G_MODULE_BIND_LAZY);
+    g_free (fullpath);
+    if (!backend)
+    {
+        PINFO ("%s: %s\n", PACKAGE, g_module_error ());
+        return false;
+    }
+    void (*module_init_func)(void);
+    if (g_module_symbol (backend, "qof_backend_module_init",
+                         reinterpret_cast<void**>(&module_init_func)))
+        module_init_func ();
 
-    va_start(args, format);
-    buffer = (char *)g_strdup_vprintf(format, args);
-    va_end(args);
-
-    be->error_msg = buffer;
-}
-
-char *
-qof_backend_get_message (QofBackend *be)
-{
-    char * msg;
-
-    if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND");
-    if (!be->error_msg) return NULL;
-
-    /*
-     * Just return the contents of the error_msg and then set it to
-     * NULL. This is necessary, because the Backends don't seem to
-     * have a destroy_backend function to take care of freeing stuff
-     * up. The calling function should free the copy.
-     * Also, this is consistent with the qof_backend_get_error() popping.
-     */
-
-    msg = be->error_msg;
-    be->error_msg = NULL;
-    return msg;
+    g_module_make_resident (backend);
+    c_be_registry.push_back(backend);
+    return TRUE;
 }
 
-/***********************************************************************/
-/* Get a clean backend */
 void
-qof_backend_init(QofBackend *be)
+QofBackend::release_backends()
 {
-    be->session_begin = NULL;
-    be->session_end = NULL;
-    be->destroy_backend = NULL;
-
-    be->load = NULL;
-
-    be->begin = NULL;
-    be->commit = NULL;
-    be->rollback = NULL;
-
-    be->sync = NULL;
-    be->safe_sync = NULL;
-
-    be->export_fn = NULL;
-
-    be->last_err = ERR_BACKEND_NO_ERR;
-    if (be->error_msg) g_free (be->error_msg);
-    be->error_msg = NULL;
-    be->percentage = NULL;
+    for (auto backend : c_be_registry)
+    {
+        void (*module_finalize_func)(void);
+        if (g_module_symbol(backend, "qof_backend_module_finalize",
+                            reinterpret_cast<void**>(&module_finalize_func)))
+            module_finalize_func();
+    }
 }
-
-void
-qof_backend_destroy(QofBackend *be)
+/***********************************************************************/
+QofBackendError
+qof_backend_get_error (QofBackend* qof_be)
 {
-    g_free(be->error_msg);
-    be->error_msg = NULL;
+    if (qof_be == nullptr) return ERR_BACKEND_NO_ERR;
+    return ((QofBackend*)qof_be)->get_error();
 }
 
 void
-qof_backend_run_begin(QofBackend *be, QofInstance *inst)
+qof_backend_set_error (QofBackend* qof_be, QofBackendError err)
 {
-    if (!be || !inst)
-    {
-        return;
-    }
-    if (!be->begin)
-    {
-        return;
-    }
-    (be->begin) (be, inst);
+    if (qof_be == nullptr) return;
+    ((QofBackend*)qof_be)->set_error(err);
 }
 
 gboolean
-qof_backend_begin_exists(const QofBackend *be)
+qof_backend_can_rollback (QofBackend* qof_be)
 {
-    if (be->begin)
-    {
-        return TRUE;
-    }
-    else
-    {
-        return FALSE;
-    }
+    if (qof_be == nullptr) return FALSE;
+    return true;
 }
 
 void
-qof_backend_run_commit(QofBackend *be, QofInstance *inst)
+qof_backend_rollback_instance (QofBackend* qof_be, QofInstance* inst)
 {
-    if (!be || !inst)
-    {
-        return;
-    }
-    if (!be->commit)
-    {
-        return;
-    }
-    (be->commit) (be, inst);
+    if (qof_be == nullptr) return;
+    ((QofBackend*)qof_be)->rollback(inst);
 }
 
-
-gboolean
-qof_backend_commit_exists(const QofBackend *be)
-{
-    if (!be)
-    {
-        return FALSE;
-    }
-    if (be->commit)
-    {
-        return TRUE;
-    }
-    else
-    {
-        return FALSE;
-    }
-}
-
-static GSList* backend_module_list = NULL;
-
 gboolean
 qof_load_backend_library (const char *directory, const char* module_name)
 {
-    gchar *fullpath;
-    GModule *backend;
-    void (*module_init_func) (void);
-
-    g_return_val_if_fail(g_module_supported (), FALSE);
-    fullpath = g_module_build_path (directory, module_name);
-/* Darwin modules can have either .so or .dylib for a suffix */
-    if (!g_file_test (fullpath, G_FILE_TEST_EXISTS) &&
-	g_strcmp0 (G_MODULE_SUFFIX, "so") == 0)
-    {
-	gchar *modname = g_strdup_printf ("lib%s.dylib", module_name);
-	g_free (fullpath);
-	fullpath = g_build_filename (directory, modname, NULL);
-	g_free (modname);
-    }
-    backend = g_module_open (fullpath, G_MODULE_BIND_LAZY);
-    g_free (fullpath);
-    if (!backend)
-    {
-        g_message ("%s: %s\n", PACKAGE, g_module_error ());
-        return FALSE;
-    }
-    if (g_module_symbol (backend, "qof_backend_module_init",
-			 reinterpret_cast<void**>(&module_init_func)))
-        module_init_func ();
-
-    g_module_make_resident (backend);
-    backend_module_list = g_slist_prepend (backend_module_list, backend);
-    return TRUE;
+    return QofBackend::register_backend(directory, module_name);
 }
 
 void
 qof_finalize_backend_libraries(void)
 {
-    GSList* node;
-    GModule* backend;
-    void (*module_finalize_func) (void);
-
-    for (node = backend_module_list; node != NULL; node = node->next)
-    {
-        backend = (GModule*)node->data;
-
-        if (g_module_symbol(backend, "qof_backend_module_finalize",
-                            reinterpret_cast<void**>(&module_finalize_func)))
-            module_finalize_func();
-
-    }
+    QofBackend::release_backends();
 }
 
 /************************* END OF FILE ********************************/
diff --git a/src/libqof/qof/qof-backend.hpp b/src/libqof/qof/qof-backend.hpp
index 9eb0e65..aebec9a 100644
--- a/src/libqof/qof/qof-backend.hpp
+++ b/src/libqof/qof/qof-backend.hpp
@@ -1,5 +1,6 @@
 /********************************************************************\
- * qofbackend-p.h -- private api for data storage backend           *
+ * qof-backend.hpp Declare QofBackend class                         *
+ * Copyright 2016 John Ralls <jralls at ceridwen.us>                   *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -38,81 +39,26 @@
    @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
 @{ */
 
-#ifndef QOF_BACKEND_P_H
-#define QOF_BACKEND_P_H
-
+#ifndef __QOF_BACKEND_HPP__
+#define __QOF_BACKEND_HPP__
+extern "C"
+{
 #include "qofbackend.h"
 #include "qofbook.h"
 #include "qofinstance-p.h"
 #include "qofquery.h"
 #include "qofsession.h"
+#include <gmodule.h>
+}
 
-/**
- * The backend_new routine sets the functions that will be used
- *    by the backend to perform the actions required by QOF. A
- *    basic minimum is session_begin, session_end, load and
- *    sync. Any unused functions should be set to NULL. If the
- *    backend uses configuration options, backend_new must ensure
- *    that these are set to usable defaults before returning. To use
- *    configuration options, load_config and get_config must also
- *    be defined.
- *
- * The session_begin() routine gives the backend a second initialization
- *    opportunity.  It is suggested that the backend check that
- *    the URL is syntactically correct, and that it is actually
- *    reachable.  This is probably(?) a good time to initialize
- *    the actual network connection.
- *
- *    The 'ignore_lock' argument indicates whether the single-user
- *    lock on the backend should be cleared.  The typical GUI sequence
- *    leading to this is: (1) GUI attempts to open the backend
- *    by calling this routine with FALSE==ignore_lock.  (2) If backend
- *    error'ed BACKEND_LOCK, then GUI asks user what to do. (3) if user
- *    answers 'break & enter' then this routine is called again with
- *    TRUE==ignore_lock.
- *
- *    The 'create_if_nonexistent' argument indicates whether this
- *    routine should create a new 'database', if it doesn't already
- *    exist. For example, for a file-backend, this would create the
- *    file, if it didn't already exist.  For an SQL backend, this
- *    would create the database (the schema) if it didn't already
- *    exist.  This flag is used to implement the 'SaveAs' GUI, where
- *    the user requests to save data to a new backend.
- *
- * The load() routine should load the minimal set of application data
- *    needed for the application to be operable at initial startup.
- *    It is assumed that the application will perform a 'run_query()'
- *    to obtain any additional data that it needs.  For file-based
- *    backends, it is acceptable for the backend to return all data
- *    at load time; for SQL-based backends, it is acceptable for the
- *    backend to return no data.
- *
- *    Thus, for example, the GnuCash postgres backend returned
- *    the account tree, all currencies, and the pricedb, as these
- *    were needed at startup.  It did not have to return any
- *    transactions whatsoever, as these were obtained at a later stage
- *    when a user opened a register, resulting in a query being sent to
- *    the backend.
- *
- *    (Its OK to send over entities at this point, but one should
- *    be careful of the network load; also, its possible that whatever
- *    is sent is not what the user wanted anyway, which is why its
- *    better to wait for the query).
- *
- * The begin() routine is called when the engine is about to
- *    make a change to a data structure. It can provide an advisory
- *    lock on data.
- *
- * The commit() routine commits the changes from the engine to the
- *    backend data storage.
- *
- * The rollback() routine is used to revert changes in the engine
- *    and unlock the backend.
- *
- *    If the second user tries to modify an entity that
- *    the first user deleted, then the backend should set the error
- *    to ERR_BACKEND_MOD_DESTROY from this routine, so that the
- *    engine can properly clean up.
+#include <string>
+#include <algorithm>
+#include <vector>
+/* NOTE: The following comments were musings by the original developer about how
+ * some additional API might work. The compile/free/run_query functions were
+ * implemented for the DBI backend but never put into use; the rest were never
+ * implemented. They're here as something to consider if we ever decide to
+ * implement them.
  *
  * The compile_query() method compiles a QOF query object into
  *    a backend-specific data structure and returns the compiled
@@ -140,22 +86,6 @@
  *    continue functioning even when disconnected from the server:
  *    this is because it will have its local cache of data from which to work.
  *
- * The sync() routine synchronizes the engine contents to the backend.
- *    This should done by using version numbers (hack alert -- the engine
- *    does not currently contain version numbers).
- *    If the engine contents are newer than what is in the backend, the
- *    data is stored to the backend. If the engine contents are older,
- *    then the engine contents are updated.
- *
- *    Note that this sync operation is only meant to apply to the
- *    current contents of the engine. This routine is not intended
- *    to be used to fetch entity data from the backend.
- *
- *    File based backends tend to use sync as if it was called dump.
- *    Data is written out into the backend, overwriting the previous
- *    data. Database backends should implement a more intelligent
- *    solution.
- *
  * The events_pending() routines should return true if there are
  *    external events which need to be processed to bring the
  *    engine up to date with the backend.
@@ -164,10 +94,6 @@
  *    by the events_pending() routine. It should return TRUE if
  *    the engine was changed while engine events were suspended.
  *
- * The last_err member indicates the last error that occurred.
- *    It should probably be implemented as an array (actually,
- *    a stack) of all the errors that have occurred.
- *
  * For support of book partitioning, use special "Book"  begin_edit()
  *    and commit_edit() QOF_ID types.
  *
@@ -242,80 +168,147 @@ typedef enum
     LOAD_TYPE_LOAD_ALL
 } QofBackendLoadType;
 
-struct QofBackend_s
+using GModuleVec = std::vector<GModule*>;
+struct QofBackend
 {
-    void (*session_begin) (QofBackend *be,
-                           QofSession *session,
-                           const char *book_id,
-                           gboolean ignore_lock,
-                           gboolean create,
-                           gboolean force);
-    void (*session_end) (QofBackend *);
-    void (*destroy_backend) (/*@ only @*/ QofBackend *);
-
-    void (*load) (QofBackend *, /*@ dependent @*/ QofBook *, QofBackendLoadType);
-
-    void (*begin) (QofBackend *, QofInstance *);
-    void (*commit) (QofBackend *, QofInstance *);
-    void (*rollback) (QofBackend *, QofInstance *);
-
-    void (*sync) (QofBackend *, /*@ dependent @*/ QofBook *);
-    void (*safe_sync) (QofBackend *, /*@ dependent @*/ QofBook *);
-    /* This is implented only in the XML backend where it exports only a chart
-     * of accounts.
+public:
+    /* For reasons that aren't a bit clear, using the default constructor
+     * sometimes initializes m_last_err incorrectly with Xcode8 and a 32-bit
+     * build unless the initialization is stepped-through in a debugger.
      */
-    void (*export_fn) (QofBackend *, QofBook *);
-    QofBePercentageFunc percentage;
-
-    QofBackendError last_err;
-    char * error_msg;
-
-    gint config_count;
+    QofBackend() :
+        m_percentage{nullptr}, m_fullpath{}, m_last_err{ERR_BACKEND_NO_ERR},
+        m_error_msg{} {}
+    QofBackend(const QofBackend&) = delete;
+    QofBackend(const QofBackend&&) = delete;
+    virtual ~QofBackend() = default;
+/**
+ *    Open the file or connect to the server.
+ *    @param session The QofSession that will control the backend.
+ *    @param book_id The book's string identifier.
+ *    @param ignore_lock indicates whether the single-user lock on the backend
+ *    should be cleared.  The typical GUI sequence leading to this is:
+ *    (1) GUI attempts to open the backend by calling this routine with
+ *    ignore_lock false.
+ *    (2) If backend error'ed BACKEND_LOCK, then GUI asks user what to do.
+ *    (3) if user answers 'break & enter' then this routine is called again with
+ *    ignore_lock true.
+ *    @param create indicates whether this routine should create a new
+ *    'database', if it doesn't already exist. For example, for a file-backend,
+ *    this would create the file, if it didn't already exist.  For an SQL
+ *    backend, this would create the database (the schema) if it didn't already
+ *    exist.  This flag is used to implement the 'SaveAs' GUI, where the user
+ *    requests to save data to a new backend.
+ *
+ *    @param force works with create to force creating a new database even if
+ *    one already exists at the same URI.
+ */
+    virtual void session_begin(QofSession *session, const char* book_id,
+                               bool ignore_lock, bool create, bool force) = 0;
+    virtual void session_end() = 0;
+/**
+ *    Load the minimal set of application data needed for the application to be
+ *    operable at initial startup.  It is assumed that the application will
+ *    perform a 'run_query()' to obtain any additional data that it needs.  For
+ *    file-based backends, it is acceptable for the backend to return all data
+ *    at load time; for SQL-based backends, it is acceptable for the backend to
+ *    return no data.
+ *
+ *    Thus, for example, the old GnuCash postgres backend returned the account
+ *    tree, all currencies, and the pricedb, as these were needed at startup.
+ *    It did not have to return any transactions whatsoever, as these were
+ *    obtained at a later stage when a user opened a register, resulting in a
+ *    query being sent to the backend. The current DBI backend on the other hand
+ *    loads the entire database into memory.
+ *
+ *    (Its OK to send over entities at this point, but one should
+ *    be careful of the network load; also, its possible that whatever
+ *    is sent is not what the user wanted anyway, which is why its
+ *    better to wait for the query).
+ */
+    virtual void load (QofBook*, QofBackendLoadType) = 0;
+/**
+ *    Called when the engine is about to make a change to a data structure. It
+ *    could provide an advisory lock on data, but no backend does this.
+ */
+    virtual void begin(QofInstance*) {}
+/**
+ *    Commits the changes from the engine to the backend data storage.
+ */
+    virtual void commit (QofInstance*) {}
+/**
+ *    Revert changes in the engine and unlock the backend.
+ */
+    virtual void rollback(QofInstance*) {}
+/**
+ *    Synchronizes the engine contents to the backend.
+ *    This should done by using version numbers (hack alert -- the engine
+ *    does not currently contain version numbers).
+ *    If the engine contents are newer than what is in the backend, the
+ *    data is stored to the backend. If the engine contents are older,
+ *    then the engine contents are updated.
+ *
+ *    Note that this sync operation is only meant to apply to the
+ *    current contents of the engine. This routine is not intended
+ *    to be used to fetch entity data from the backend.
+ *
+ *    File based backends tend to use sync as if it was called dump.
+ *    Data is written out into the backend, overwriting the previous
+ *    data. Database backends should implement a more intelligent
+ *    solution.
+ */
+    virtual void sync(QofBook *) = 0;
+/** Perform a sync in a way that prevents data loss on a DBI backend.
+ */
+    virtual void safe_sync(QofBook *) = 0;
+/**   Extract the chart of accounts from the current database and create a new
+ *   database with it. Implemented only in the XML backend at present.
+ */
+    virtual void export_coa(QofBook *) {}
+/** Set the error value only if there isn't already an error already.
+ */
+    void set_error(QofBackendError err);
+/** Retrieve the currently-stored error and clear it.
+ */
+    QofBackendError get_error();
+/** Report if there is an error.
+ */
+    bool check_error();
+/** Set a descriptive message that can be displayed to the user when there's an
+ * error.
+ */
+    void set_message(std::string&&);
+/** Retrieve and clear the stored error message.
+ */
+    const std::string&& get_message();
+/** Store and retrieve a backend-specific function for determining the progress
+ * in completing a long operation, for use with a progress meter.
+ */
+    void set_percentage(QofBePercentageFunc pctfn) { m_percentage = pctfn; }
+    QofBePercentageFunc get_percentage() { return m_percentage; }
+/** Retrieve the backend's storage URI.
+ */
+    std::string get_uri() { return m_fullpath; }
+/**
+ * Class methods for dynamically loading the several backends and for freeing
+ * them at shutdown.
+ */
+    static bool register_backend(const char*, const char*);
+    static void release_backends();
+protected:
+    QofBePercentageFunc m_percentage;
     /** Each backend resolves a fully-qualified file path.
      * This holds the filepath and communicates it to the frontends.
      */
-    char * fullpath;
+    std::string m_fullpath;
+private:
+    static GModuleVec c_be_registry;
+    QofBackendError m_last_err;
+    std::string m_error_msg;
 };
 
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-/** The qof_backend_set_message() assigns a string to the backend error message.
- */
-void qof_backend_set_message(QofBackend *be, const char *format, ...);
-
-/** The qof_backend_get_message() pops the error message string from
- *  the Backend.  This string should be freed with g_free().
- */
-char * qof_backend_get_message(QofBackend *be);
-
-void qof_backend_init(QofBackend *be);
-void qof_backend_destroy(QofBackend *be);
-
-/** Allow backends to see if the book is open
-
- at return 'y' if book is open, otherwise 'n'.
-*/
-gchar qof_book_get_open_marker(const QofBook *book);
-
-/** get the book version
-
-used for tracking multiuser updates in backends.
-
- at return -1 if no book exists, 0 if the book is
-new, otherwise the book version number.
-*/
-gint32 qof_book_get_version (const QofBook *book);
-
-void qof_book_set_version (QofBook *book, gint32 version);
-
 /* @} */
 /* @} */
 /* @} */
-#ifdef __cplusplus
-}
-#endif
 
-#endif /* QOF_BACKEND_P_H */
+#endif /* __QOF_BACKEND_HPP__ */
diff --git a/src/libqof/qof/qofbackend.h b/src/libqof/qof/qofbackend.h
index 1fa87c9..98e9a9a 100644
--- a/src/libqof/qof/qofbackend.h
+++ b/src/libqof/qof/qofbackend.h
@@ -127,15 +127,8 @@ typedef enum
     ERR_RPC_FAILED,               /**< Operation failed */
     ERR_RPC_NOT_ADDED,            /**< object not added */
 } QofBackendError;
-/** \brief Pseudo-object providing an interface between the
- * engine and a persistant data store (e.g. a server, a database,
- * or a file).
- *
- * There are no backend functions that are 'public' to users of the
- * engine.  The backend can, however, report errors to the GUI & other
- * front-end users.
- */
-    typedef struct QofBackend_s QofBackend;
+
+typedef struct QofBackend QofBackend;
 
     /* The following functions are used in C files. */
 /** Get the last backend error. */
@@ -143,7 +136,7 @@ typedef enum
 /** Set the error on the specified QofBackend. */
     void qof_backend_set_error (QofBackend*, QofBackendError);
 
-/* Temporary wrapper so that we don't have to expose qofbackend-p.h to Transaction.c */
+/* Temporary wrapper so that we don't have to expose qof-backend.hpp to Transaction.c */
     gboolean qof_backend_can_rollback (QofBackend*);
     void qof_backend_rollback_instance (QofBackend*, QofInstance*);
 
@@ -173,30 +166,7 @@ typedef void (*QofBePercentageFunc) (/*@ null @*/ const char *message, double pe
 
 #ifdef __cplusplus
 }
-
-/** @name Allow access to the begin routine for this backend. */
-//@{
-
-void qof_backend_run_begin(QofBackend *be, QofInstance *inst);
-
-gboolean qof_backend_begin_exists(const QofBackend *be);
-
-void qof_backend_run_commit(QofBackend *be, QofInstance *inst);
-
-gboolean qof_backend_commit_exists(const QofBackend *be);
-//@}
-
-/** Report if the backend is in an error state.
- *  Since get_error resets the error state, its use for branching as the backend
- *  bubbles back up to the session would make the session think that there was
- *  no error.
- * \param be The backend being tested.
- * \return TRUE if the backend has an error set.
- */
-gboolean qof_backend_check_error (QofBackend *be);
-
 #endif
-
 #endif /* QOF_BACKEND_H */
 /** @} */
 /** @} */
diff --git a/src/libqof/qof/qofinstance.cpp b/src/libqof/qof/qofinstance.cpp
index 5e923ea..64feeef 100644
--- a/src/libqof/qof/qofinstance.cpp
+++ b/src/libqof/qof/qofinstance.cpp
@@ -958,7 +958,6 @@ gboolean
 qof_begin_edit (QofInstance *inst)
 {
     QofInstancePrivate *priv;
-    QofBackend * be;
 
     if (!inst) return FALSE;
 
@@ -968,9 +967,9 @@ qof_begin_edit (QofInstance *inst)
     if (0 >= priv->editlevel)
         priv->editlevel = 1;
 
-    be = qof_book_get_backend(priv->book);
-    if (be && qof_backend_begin_exists(be))
-        qof_backend_run_begin(be, inst);
+    auto be = qof_book_get_backend(priv->book);
+    if (be)
+        be->begin(inst);
     else
         priv->dirty = TRUE;
 
@@ -1002,7 +1001,6 @@ qof_commit_edit_part2(QofInstance *inst,
                       void (*on_free)(QofInstance *))
 {
     QofInstancePrivate *priv;
-    QofBackend * be;
 
     priv = GET_PRIVATE(inst);
 
@@ -1013,27 +1011,27 @@ qof_commit_edit_part2(QofInstance *inst,
     }
 
     /* See if there's a backend.  If there is, invoke it. */
-    be = qof_book_get_backend(priv->book);
-    if (be && qof_backend_commit_exists(be))
+    auto be = qof_book_get_backend(priv->book);
+    if (be)
     {
         QofBackendError errcode;
 
         /* clear errors */
         do
         {
-            errcode = qof_backend_get_error(be);
+            errcode = be->get_error();
         }
-        while (ERR_BACKEND_NO_ERR != errcode);
+        while (errcode != ERR_BACKEND_NO_ERR);
 
-        qof_backend_run_commit(be, inst);
-        errcode = qof_backend_get_error(be);
-        if (ERR_BACKEND_NO_ERR != errcode)
+        be->commit(inst);
+        errcode = be->get_error();
+        if (errcode != ERR_BACKEND_NO_ERR)
         {
             /* XXX Should perform a rollback here */
             priv->do_free = FALSE;
 
             /* Push error back onto the stack */
-            qof_backend_set_error (be, errcode);
+            be->set_error (errcode);
             if (on_error)
                 on_error(inst, errcode);
             return FALSE;
diff --git a/src/libqof/qof/qofsession.cpp b/src/libqof/qof/qofsession.cpp
index 91e04c6..9939c67 100644
--- a/src/libqof/qof/qofsession.cpp
+++ b/src/libqof/qof/qofsession.cpp
@@ -122,6 +122,7 @@ QofSessionImpl::QofSessionImpl () noexcept
     : m_book {qof_book_new ()},
     m_book_id {},
     m_saving {false},
+    m_last_err {},
     m_error_message {}
 {
     clear_error ();
@@ -157,10 +158,7 @@ QofSessionImpl::destroy_backend () noexcept
     if (backend)
     {
         clear_error ();
-        if (backend->destroy_backend)
-            backend->destroy_backend (backend);
-        else
-            g_free(backend);
+        delete backend;
         qof_book_set_backend (m_book, nullptr);
     }
 }
@@ -218,7 +216,7 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
     * top-level account group out of the backend, and that is a
     * generic, backend-independent operation.
     */
-    QofBackend * be {qof_book_get_backend (oldbook)};
+    auto be (qof_book_get_backend (oldbook));
     qof_book_set_backend (newbook, be);
 
     /* Starting the session should result in a bunch of accounts
@@ -227,12 +225,9 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
     */
     if (be)
     {
-        be->percentage = percentage_func;
-        if (be->load)
-        {
-            be->load (be, newbook, LOAD_TYPE_INITIAL_LOAD);
-            push_error (qof_backend_get_error(be), {});
-        }
+        be->set_percentage(percentage_func);
+        be->load (newbook, LOAD_TYPE_INITIAL_LOAD);
+        push_error (be->get_error(), {});
     }
 
     /* XXX if the load fails, then we try to restore the old set of books;
@@ -262,7 +257,8 @@ QofSessionImpl::load (QofPercentageFunc percentage_func) noexcept
 }
 
 void
-QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, bool force) noexcept
+QofSessionImpl::begin (std::string new_book_id, bool ignore_lock,
+                       bool create, bool force) noexcept
 {
     ENTER (" sess=%p ignore_lock=%d, book-id=%s",
            this, ignore_lock, new_book_id.c_str ());
@@ -313,7 +309,8 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b
     g_free (scheme);
 
     /* No backend was found. That's bad. */
-    if (!qof_book_get_backend (m_book))
+    auto backend = qof_book_get_backend (m_book);
+    if (backend == nullptr)
     {
         m_book_id = {};
         if (ERR_BACKEND_NO_ERR == get_error ())
@@ -324,26 +321,22 @@ QofSessionImpl::begin (std::string new_book_id, bool ignore_lock, bool create, b
     }
 
     /* If there's a begin method, call that. */
-    if (qof_book_get_backend (m_book)->session_begin)
+    backend->session_begin(this, m_book_id.c_str(), ignore_lock, create, force);
+    PINFO ("Done running session_begin on backend");
+    QofBackendError const err {backend->get_error()};
+    auto msg (backend->get_message());
+    if (err != ERR_BACKEND_NO_ERR)
     {
-        auto backend = qof_book_get_backend (m_book);
-        (backend->session_begin) (backend, this, m_book_id.c_str (), ignore_lock, create, force);
-        PINFO ("Done running session_begin on backend");
-        QofBackendError const err {qof_backend_get_error (backend)};
-        char * msg {qof_backend_get_message (backend)};
-        if (err != ERR_BACKEND_NO_ERR)
-        {
-            m_book_id = {};
-            push_error (err, msg ? msg : "");
-            LEAVE (" backend error %d %s", err, msg ? msg : "(null)");
-            return;
-        }
-        if (msg != nullptr)
-        {
-            PWARN("%s", msg);
-            g_free(msg);
-        }
+        m_book_id = {};
+        push_error (err, msg);
+        LEAVE (" backend error %d %s", err, msg.empty() ? "(null)" : msg.c_str());
+        return;
+    }
+    if (!msg.empty())
+    {
+        PWARN("%s", msg.c_str());
     }
+
     LEAVE (" sess=%p book-id=%s", this,  new_book_id.c_str ());
 }
 
@@ -352,10 +345,10 @@ QofSessionImpl::end () noexcept
 {
     ENTER ("sess=%p book_id=%s", this, m_book_id.c_str ());
     auto backend = qof_book_get_backend (m_book);
-    if (backend && backend->session_end)
-        (backend->session_end) (backend);
+    if (backend != nullptr)
+        backend->session_end();
     clear_error ();
-    m_book_id = {};
+    m_book_id.clear();
     LEAVE ("sess=%p book_id=%s", this, m_book_id.c_str ());
 }
 
@@ -368,12 +361,12 @@ QofSessionImpl::clear_error () noexcept
     m_error_message = {};
 
     /* pop the stack on the backend as well. */
-    if (qof_book_get_backend (m_book))
+    if (auto backend = qof_book_get_backend (m_book))
     {
-        QofBackendError err;
+        QofBackendError err = ERR_BACKEND_NO_ERR;
         do
-            err = qof_backend_get_error (qof_book_get_backend (m_book));
-        while (ERR_BACKEND_NO_ERR != err);
+            err = backend->get_error();
+        while (err != ERR_BACKEND_NO_ERR);
     }
 }
 
@@ -388,12 +381,12 @@ QofBackendError
 QofSessionImpl::get_error () noexcept
 {
     /* if we have a local error, return that. */
-    if (ERR_BACKEND_NO_ERR != m_last_err)
+    if (m_last_err != ERR_BACKEND_NO_ERR)
         return m_last_err;
+    auto qof_be = qof_book_get_backend (m_book);
+    if (qof_be == nullptr) return ERR_BACKEND_NO_ERR;
 
-    if (!qof_book_get_backend (m_book)) return ERR_BACKEND_NO_ERR;
-
-    m_last_err = qof_backend_get_error (qof_book_get_backend (m_book));
+    m_last_err = qof_be->get_error();
     return m_last_err;
 }
 
@@ -431,8 +424,9 @@ QofSession::get_backend () const noexcept
 std::string
 QofSessionImpl::get_file_path () const noexcept
 {
-    if (!qof_book_get_backend (m_book)) return nullptr;
-    return qof_book_get_backend (m_book)->fullpath;
+    auto backend = qof_book_get_backend (m_book);
+    if (!backend) return nullptr;
+    return backend->get_uri();
 }
 
 std::string const &
@@ -469,17 +463,14 @@ QofSessionImpl::save (QofPercentageFunc percentage_func) noexcept
     {
         /* if invoked as SaveAs(), then backend not yet set */
         qof_book_set_backend (m_book, backend);
-        backend->percentage = percentage_func;
-        if (backend->sync)
+        backend->set_percentage(percentage_func);
+        backend->sync(m_book);
+        auto err = backend->get_error();
+        if (err != ERR_BACKEND_NO_ERR)
         {
-            (backend->sync)(backend, m_book);
-            QofBackendError err {qof_backend_get_error (backend)};
-            if (ERR_BACKEND_NO_ERR != err)
-            {
-                push_error (err, {});
-                m_saving = false;
-                return;
-            }
+            push_error (err, {});
+            m_saving = false;
+            return;
         }
         /* If we got to here, then the backend saved everything
         * just fine, and we are done. So return. */
@@ -499,17 +490,15 @@ QofSessionImpl::safe_save (QofPercentageFunc percentage_func) noexcept
 {
     auto backend = qof_book_get_backend (m_book);
     if (!backend) return;
-    if (!backend->safe_sync) return;
-    backend->percentage = percentage_func;
-    (backend->safe_sync) (backend, get_book ());
-    auto err = qof_backend_get_error (qof_book_get_backend (m_book));
-    auto msg = qof_backend_get_message (qof_book_get_backend (m_book));
+    backend->set_percentage(percentage_func);
+    backend->safe_sync(get_book ());
+    auto err = backend->get_error();
+    auto msg = backend->get_message();
     if (err != ERR_BACKEND_NO_ERR)
     {
-        m_book_id = {};
-        push_error (err, msg ? msg : "");
+        m_book_id = nullptr;
+        push_error (err, msg);
     }
-    g_free (msg);
 }
 
 void
@@ -517,9 +506,8 @@ QofSessionImpl::ensure_all_data_loaded () noexcept
 {
     auto backend = qof_book_get_backend (m_book);
     if (!backend) return;
-    if (!backend->load) return;
-    backend->load(backend, get_book (), LOAD_TYPE_LOAD_ALL);
-    push_error (qof_backend_get_error (backend), {});
+    backend->load(m_book, LOAD_TYPE_LOAD_ALL);
+    push_error (backend->get_error(), {});
 }
 
 void
@@ -552,7 +540,8 @@ QofSessionImpl::process_events () const noexcept
  * book-closing is implemented.
  */
 bool
-QofSessionImpl::export_session (QofSessionImpl & real_session, QofPercentageFunc percentage_func) noexcept
+QofSessionImpl::export_session (QofSessionImpl & real_session,
+                                QofPercentageFunc percentage_func) noexcept
 {
     auto real_book = real_session.get_book ();
     ENTER ("tmp_session=%p real_session=%p book=%p book_id=%s",
@@ -564,12 +553,11 @@ QofSessionImpl::export_session (QofSessionImpl & real_session, QofPercentageFunc
     auto backend2 = qof_book_get_backend(m_book);
     if (!backend2) return false;
 
-    backend2->percentage = percentage_func;
-    if (!backend2->export_fn) return true;
+    backend2->set_percentage(percentage_func);
 
-    (backend2->export_fn)(backend2, real_book);
-    auto err = qof_backend_get_error(backend2);
-    if (ERR_BACKEND_NO_ERR != err)
+    backend2->export_coa(real_book);
+    auto err = backend2->get_error();
+    if (err != ERR_BACKEND_NO_ERR)
         return false;
     return true;
 }
@@ -749,4 +737,3 @@ qof_session_get_error (QofSession * session)
     if (!session) return ERR_BACKEND_NO_BACKEND;
     return session->get_error();
 }
-
diff --git a/src/libqof/qof/qofutil.cpp b/src/libqof/qof/qofutil.cpp
index 43d32ca..0e988b8 100644
--- a/src/libqof/qof/qofutil.cpp
+++ b/src/libqof/qof/qofutil.cpp
@@ -273,7 +273,7 @@ qof_close(void)
 {
     qof_query_shutdown ();
     qof_object_shutdown ();
-    qof_finalize_backend_libraries();
+    QofBackend::release_backends();
     qof_string_cache_destroy ();
     qof_log_shutdown();
 }
diff --git a/src/libqof/qof/test/test-qofinstance.cpp b/src/libqof/qof/test/test-qofinstance.cpp
index 45fe87d..e9e3c6b 100644
--- a/src/libqof/qof/test/test-qofinstance.cpp
+++ b/src/libqof/qof/test/test-qofinstance.cpp
@@ -41,11 +41,86 @@ static gboolean is_called;
 #define _Q "`"
 #endif
 
+static struct
+{
+    QofInstance *m_inst = nullptr;
+    QofBackend *m_be = nullptr;
+
+    bool m_commit_called = false;
+    bool m_commit_with_err_called = false;
+    bool m_on_error_called = false;
+    bool m_on_free_called = false;
+    bool m_on_done_called = false;
+    QofBackendError m_err = ERR_BACKEND_NO_ERR;
+} commit_test;
+
+
+class MockBackend : public QofBackend
+{
+public:
+    MockBackend() : m_qof_error{ERR_BACKEND_NO_ERR} {
+        commit_test.m_be = this;
+    }
+    void session_begin(QofSession* sess, const char* book_name,
+                       bool ignore_lock, bool create, bool force) override {}
+    void session_end() override {}
+    void load(QofBook*, QofBackendLoadType) override {}
+    void sync(QofBook* book) override {}
+    void safe_sync(QofBook* book) override {}
+    void begin(QofInstance* inst) override {
+        g_assert(inst);
+        g_assert(QOF_IS_INSTANCE(inst));
+        commit_test.m_inst = inst;
+    }
+    void set_error(QofBackendError err) {
+        QofBackend::set_error(err);
+        commit_test.m_err = err;
+    }
+    void commit(QofInstance* inst) override {
+        g_assert( inst );
+        g_assert( QOF_IS_INSTANCE( inst ) );
+        g_assert( commit_test.m_inst == inst );
+        g_assert( commit_test.m_be == this );
+        commit_test.m_commit_called = true;
+        set_error(m_qof_error);
+
+    }
+    void rollback(QofInstance* inst) override {}
+    void inject_error(QofBackendError err) {
+        m_qof_error = err;
+    }
+private:
+    QofBackendError m_qof_error;
+
+};
+
 typedef struct
 {
     QofInstance *inst;
 } Fixture;
 
+static void
+on_error(QofInstance* inst, QofBackendError err) {
+    g_assert( inst );
+    g_assert( QOF_IS_INSTANCE( inst ) );
+    g_assert( commit_test.m_err == err );
+    commit_test.m_on_error_called = true;
+}
+static void
+on_done(QofInstance* inst) {
+    g_assert( inst );
+    g_assert( QOF_IS_INSTANCE( inst ) );
+    g_assert( commit_test.m_inst == inst );
+    commit_test.m_on_done_called = true;
+}
+static void
+on_free(QofInstance* inst) {
+    g_assert( inst );
+    g_assert( QOF_IS_INSTANCE( inst ) );
+    g_assert(commit_test.m_inst == inst );
+    commit_test.m_on_free_called = true;
+}
+
 /* use g_free on error_message after this function been called */
 static gboolean
 fatal_handler ( const char * log_domain,
@@ -423,7 +498,6 @@ mock_backend_begin( QofBackend *be, QofInstance *inst )
 {
     g_assert( be );
     g_assert( inst );
-    g_assert( be->begin == mock_backend_begin );
     g_assert_cmpstr( inst->e_type, == , "test type" );
     is_called = TRUE;
 }
@@ -431,14 +505,12 @@ mock_backend_begin( QofBackend *be, QofInstance *inst )
 static void
 test_instance_begin_edit( Fixture *fixture, gconstpointer pData )
 {
-    QofBackend *be;
     QofBook *book;
     gboolean result;
 
     /* setup */
-    be = g_new0( QofBackend, 1 );
+    auto be = new MockBackend;
     g_assert( be );
-    qof_backend_init( be );
     book = qof_book_new();
     g_assert( book );
     g_assert( QOF_IS_BOOK( book ) );
@@ -467,22 +539,19 @@ test_instance_begin_edit( Fixture *fixture, gconstpointer pData )
 
     g_test_message( "Test when instance's editlevel is <= 0 and backend is set" );
     result = FALSE;
-    is_called = FALSE;
     qof_instance_reset_editlevel( fixture->inst );
     qof_instance_set_dirty_flag( fixture->inst, FALSE );
     qof_instance_set_book( fixture->inst, book );
-    be->begin = mock_backend_begin;
     result = qof_begin_edit( fixture->inst );
     g_assert( result == TRUE );
     g_assert_cmpint( qof_instance_get_editlevel( fixture->inst ), == , 1 );
     g_assert( qof_instance_get_dirty_flag( fixture->inst ) == FALSE );
-    g_assert( is_called );
 
     /* clean up */
     qof_book_set_backend( book, NULL );
     qof_book_destroy( book );
-    qof_backend_destroy( be );
-    g_free( be );
+    delete be;
+
 }
 
 static void
@@ -527,82 +596,16 @@ test_instance_commit_edit( Fixture *fixture, gconstpointer pData )
 
 /* backend commit test start */
 
-static struct
-{
-    gpointer inst;
-    gpointer be;
-
-    gboolean commit_called;
-    gboolean commit_with_err_called;
-    gboolean on_error_called;
-    gboolean on_free_called;
-    gboolean on_done_called;
-    QofBackendError err;
-} commit_test_part2;
-
-static void
-mock_backend_commit( QofBackend *be, QofInstance *inst )
-{
-    g_assert( inst );
-    g_assert( be );
-    g_assert( QOF_IS_INSTANCE( inst ) );
-    g_assert( commit_test_part2.inst == inst );
-    g_assert( commit_test_part2.be == be );
-    commit_test_part2.commit_called = TRUE;
-}
-
-static void
-mock_backend_commit_with_error( QofBackend *be, QofInstance *inst )
-{
-    g_assert( inst );
-    g_assert( be );
-    g_assert( QOF_IS_INSTANCE( inst ) );
-    g_assert( commit_test_part2.inst == inst );
-    g_assert( commit_test_part2.be == be );
-    qof_backend_set_error( be, ERR_BACKEND_NO_HANDLER );
-    commit_test_part2.err = ERR_BACKEND_NO_HANDLER;
-    commit_test_part2.commit_with_err_called = TRUE;
-}
-
-
-static void
-mock_on_error( QofInstance *inst, QofBackendError be_error )
-{
-    g_assert( inst );
-    g_assert( QOF_IS_INSTANCE( inst ) );
-    g_assert( commit_test_part2.err == be_error );
-    commit_test_part2.on_error_called = TRUE;
-}
-
-static void
-mock_on_done( QofInstance *inst )
-{
-    g_assert( inst );
-    g_assert( QOF_IS_INSTANCE( inst ) );
-    g_assert( commit_test_part2.inst == inst );
-    commit_test_part2.on_done_called = TRUE;
-}
-
-static void
-mock_on_free( QofInstance *inst )
-{
-    g_assert( inst );
-    g_assert( QOF_IS_INSTANCE( inst ) );
-    g_assert( commit_test_part2.inst == inst );
-    commit_test_part2.on_free_called = TRUE;
-}
 
 static void
 test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
 {
-    QofBackend *be;
     QofBook *book;
     gboolean result;
 
     /* setup */
-    be = g_new0( QofBackend, 1 );
+    auto be = new MockBackend;
     g_assert( be );
-    qof_backend_init( be );
     book = qof_book_new();
     g_assert( book );
     g_assert( QOF_IS_BOOK( book ) );
@@ -610,13 +613,13 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
 
     /* init */
     result = FALSE;
-    commit_test_part2.commit_called = FALSE;
-    commit_test_part2.commit_with_err_called = FALSE;
-    commit_test_part2.on_error_called = FALSE;
-    commit_test_part2.on_free_called = FALSE;
-    commit_test_part2.on_done_called = FALSE;
-    commit_test_part2.inst = fixture->inst;
-    commit_test_part2.be = be;
+    commit_test.m_commit_called = false;
+    commit_test.m_commit_with_err_called = false;
+    commit_test.m_on_error_called = false;
+    commit_test.m_on_free_called = false;
+    commit_test.m_on_done_called = false;
+    commit_test.m_inst = fixture->inst;
+    commit_test.m_be = be;
     qof_instance_set_dirty_flag( fixture->inst, TRUE );
 
     g_test_message( "Test when instance's backend not set, callbacks not set" );
@@ -626,70 +629,67 @@ test_instance_commit_edit_part2( Fixture *fixture, gconstpointer pData )
     g_assert( result );
     g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
     g_assert( !qof_instance_get_infant( fixture->inst ) );
-    g_assert( !commit_test_part2.commit_called );
-    g_assert( !commit_test_part2.commit_with_err_called );
-    g_assert( !commit_test_part2.on_error_called );
-    g_assert( !commit_test_part2.on_free_called );
-    g_assert( !commit_test_part2.on_done_called );
+    g_assert( !commit_test.m_commit_called );
+    g_assert( !commit_test.m_commit_with_err_called );
+    g_assert( !commit_test.m_on_error_called );
+    g_assert( !commit_test.m_on_free_called );
+    g_assert( !commit_test.m_on_done_called );
 
-    g_test_message( "Test when instance's backend not set, do_free is true" );
+    g_test_message( "Test when instance's backend not set, do_free is false" );
     qof_instance_set_destroying( fixture->inst, TRUE );
-    result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
+    result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
     g_assert( result );
     g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
-    g_assert( !commit_test_part2.commit_called );
-    g_assert( !commit_test_part2.commit_with_err_called );
-    g_assert( !commit_test_part2.on_error_called );
-    g_assert( commit_test_part2.on_free_called );
-    g_assert( !commit_test_part2.on_done_called );
+    g_assert( !commit_test.m_commit_called );
+    g_assert( !commit_test.m_commit_with_err_called );
+    g_assert( !commit_test.m_on_error_called );
+    g_assert( commit_test.m_on_free_called );
+    g_assert( !commit_test.m_on_done_called );
 
     g_test_message( "Test when instance's backend not set, do_free is false" );
     qof_instance_set_destroying( fixture->inst, FALSE );
-    commit_test_part2.on_free_called = FALSE;
-    result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
+    commit_test.m_on_free_called = false;
+    result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
     g_assert( result );
     g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
-    g_assert( !commit_test_part2.commit_called );
-    g_assert( !commit_test_part2.commit_with_err_called );
-    g_assert( !commit_test_part2.on_error_called );
-    g_assert( !commit_test_part2.on_free_called );
-    g_assert( commit_test_part2.on_done_called );
+    g_assert( !commit_test.m_commit_called );
+    g_assert( !commit_test.m_commit_with_err_called );
+    g_assert( !commit_test.m_on_error_called );
+    g_assert( !commit_test.m_on_free_called );
+    g_assert( commit_test.m_on_done_called );
 
     g_test_message( "Test when instance's backend is set, all cb set, no error produced" );
     qof_instance_set_book( fixture->inst, book );
     qof_instance_set_destroying( fixture->inst, FALSE );
-    commit_test_part2.on_done_called = FALSE;
-    be->commit = mock_backend_commit;
-    result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
+    commit_test.m_on_done_called = false;
+    result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
     g_assert( result );
     g_assert( !qof_instance_get_dirty_flag( fixture->inst ) );
-    g_assert( commit_test_part2.commit_called );
-    g_assert( !commit_test_part2.commit_with_err_called );
-    g_assert( !commit_test_part2.on_error_called );
-    g_assert( !commit_test_part2.on_free_called );
-    g_assert( commit_test_part2.on_done_called );
+    g_assert( commit_test.m_commit_called );
+    g_assert( !commit_test.m_commit_with_err_called );
+    g_assert( !commit_test.m_on_error_called );
+    g_assert( !commit_test.m_on_free_called );
+    g_assert( commit_test.m_on_done_called );
 
     g_test_message( "Test when instance's backend is set, all cb set, error produced" );
-    commit_test_part2.commit_called = FALSE;
-    commit_test_part2.on_done_called = FALSE;
-    be->commit = mock_backend_commit_with_error;
+    commit_test.m_commit_called = false;
+    commit_test.m_on_done_called = false;
+    be->inject_error(ERR_BACKEND_NO_HANDLER);
     qof_instance_set_dirty_flag( fixture->inst, TRUE );
     qof_instance_set_destroying( fixture->inst, TRUE );
-    result = qof_commit_edit_part2( fixture->inst, mock_on_error, mock_on_done, mock_on_free );
+    result = qof_commit_edit_part2( fixture->inst, on_error, on_done, on_free );
     g_assert( !result );
     g_assert( qof_instance_get_dirty_flag( fixture->inst ) );
     g_assert( !qof_instance_get_destroying( fixture->inst ) );
-    g_assert( !commit_test_part2.commit_called );
-    g_assert( commit_test_part2.commit_with_err_called );
-    g_assert( commit_test_part2.on_error_called );
-    g_assert( !commit_test_part2.on_free_called );
-    g_assert( !commit_test_part2.on_done_called );
+    g_assert( commit_test.m_commit_called );
+    g_assert( commit_test.m_on_error_called );
+    g_assert( !commit_test.m_on_free_called );
+    g_assert( !commit_test.m_on_done_called );
 
     /* clean up */
     qof_book_set_backend( book, NULL );
     qof_book_destroy( book );
-    qof_backend_destroy( be );
-    g_free( be );
+    delete be;
 }
 
 /* backend commit test end */
diff --git a/src/libqof/qof/test/test-qofsession.cpp b/src/libqof/qof/test/test-qofsession.cpp
index f77a54e..863316d 100644
--- a/src/libqof/qof/test/test-qofsession.cpp
+++ b/src/libqof/qof/test/test-qofsession.cpp
@@ -35,51 +35,51 @@ static bool sync_called {false};
 static bool load_error {true};
 static bool hook_called {false};
 static bool data_loaded {false};
+
+class MockBackend : public QofBackend
+{
+public:
+    MockBackend() = default;
+    MockBackend(const MockBackend&) = delete;
+    MockBackend(const MockBackend&&) = delete;
+    virtual ~MockBackend() = default;
+    void session_begin(QofSession*, const char*, bool, bool, bool) {}
+    void session_end() {}
+    void load(QofBook*, QofBackendLoadType);
+    void sync(QofBook*);
+    void safe_sync(QofBook*);
+    void export_coa(QofBook*);
+};
+
 void example_hook (QofSession & session)
 {
     hook_called = true;
 }
 
-void test_load (QofBackend * be, QofBook *, QofBackendLoadType)
+void MockBackend::load (QofBook *, QofBackendLoadType)
 {
-    if (load_error) be->last_err = ERR_BACKEND_NO_BACKEND;
+    if (load_error) set_error(ERR_BACKEND_NO_BACKEND);
     data_loaded = true;
 }
 
-void test_safe_sync (QofBackend *, QofBook *)
+void MockBackend::safe_sync (QofBook *)
 {
     safe_sync_called = true;
 }
 
-void test_sync (QofBackend *, QofBook *)
+void MockBackend::sync (QofBook *)
 {
     sync_called = true;
 }
 
-void test_export_fn (QofBackend *, QofBook * book)
+void MockBackend::export_coa(QofBook * book)
 {
     exported_book = book;
 }
 
 QofBackend * test_backend_factory ()
 {
-    QofBackend * ret = (QofBackend*) std::malloc (sizeof (QofBackend));
-    ret->session_begin = nullptr;
-    ret->session_end = nullptr;
-    ret->destroy_backend = nullptr;
-    ret->load = &test_load;
-    ret->sync = &test_sync;
-    ret->safe_sync = &test_safe_sync;
-    ret->export_fn = &test_export_fn;
-    ret->error_msg = nullptr;
-    ret->fullpath = nullptr;
-    ret->last_err = ERR_BACKEND_NO_ERR;
-    ret->begin = nullptr;
-    ret->commit = nullptr;
-    ret->rollback = nullptr;
-    ret->percentage = nullptr;
-    ret->config_count = 0;
-    return ret;
+    return new MockBackend;
 }
 
 struct MockProvider : public QofBackendProvider

commit 06af7d794f9493fc41bbcb338d0130ea8339ab3c
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Nov 28 10:24:35 2016 -0800

    Remove need for  gnc_dbi_safe_sync_all to be a friend of GncDbiSqlConnection.
    
    Adds GncDbiSqlConnection::drop_indexes, changes GncDbiSqlConnection::table_operation
    to use a regex-filtered table list from the DbiConn, and converts TableOpType
    to a C++ enum.

diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp
index 8c00ec5..cbd251e 100644
--- a/src/backend/dbi/gnc-backend-dbi.cpp
+++ b/src/backend/dbi/gnc-backend-dbi.cpp
@@ -907,25 +907,20 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
     g_return_if_fail (dbi_be != nullptr);
     g_return_if_fail (book != nullptr);
 
-    ENTER ("book=%p, primary=%p", book, dbi_be->m_book);
-    auto table_list = conn->m_provider->get_table_list (conn->conn(), "");
-    if (!conn->table_operation (table_list, backup))
+    ENTER ("book=%p, primary=%p", book, m_book);
+    if (!conn->table_operation (TableOpType::backup))
     {
-        qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
-        conn->table_operation (table_list, rollback);
+        set_error(ERR_BACKEND_SERVER_ERR);
+        conn->table_operation (TableOpType::rollback);
         LEAVE ("Failed to rename tables");
         return;
     }
-    auto index_list = conn->m_provider->get_index_list (conn->m_conn);
-    for (auto index : index_list)
+    if (!conn->drop_indexes())
     {
-        const char* errmsg;
-        conn->m_provider->drop_index (conn->m_conn, index);
-        if (DBI_ERROR_NONE != dbi_conn_error (conn->m_conn, &errmsg))
-        {
-            qof_backend_set_error (qof_be, ERR_BACKEND_SERVER_ERR);
-            conn->table_operation (table_list, rollback);
-            LEAVE ("Failed to drop indexes %s", errmsg);
+        conn->table_operation (TableOpType::rollback);
+        set_error (ERR_BACKEND_SERVER_ERR);
+        set_message("Failed to drop indexes");
+        LEAVE ("Failed to drop indexes");
             return;
         }
     }
@@ -933,12 +928,12 @@ gnc_dbi_safe_sync_all (QofBackend* qof_be, QofBook* book)
     dbi_be->sync_all(book);
     if (qof_backend_check_error (qof_be))
     {
-        conn->table_operation (table_list, rollback);
+        conn->table_operation (TableOpType::rollback);
         LEAVE ("Failed to create new database tables");
         return;
     }
-    conn->table_operation (table_list, drop_backup);
-    LEAVE ("book=%p", book);
+    conn->table_operation (TableOpType::drop_backup);
+    LEAVE ("book=%p", m_book);
 }
 /* ================================================================= */
 static void
diff --git a/src/backend/dbi/gnc-backend-dbi.hpp b/src/backend/dbi/gnc-backend-dbi.hpp
index 0a571d1..796c825 100644
--- a/src/backend/dbi/gnc-backend-dbi.hpp
+++ b/src/backend/dbi/gnc-backend-dbi.hpp
@@ -49,14 +49,14 @@ class GncSqlRow;
  * @var rollback drop the name table if it exists and rename name_back to name
  * @var drop_backup Drop the backup table
  */
-typedef enum
+enum TableOpType
 {
     drop = 0,
     empty,
     backup,
     rollback,
     drop_backup
-} TableOpType;
+};
 
 /**
  * Return values from conn_test_dbi_library
diff --git a/src/backend/dbi/gnc-dbisqlconnection.cpp b/src/backend/dbi/gnc-dbisqlconnection.cpp
index 8731bdb..e39dc8f 100644
--- a/src/backend/dbi/gnc-dbisqlconnection.cpp
+++ b/src/backend/dbi/gnc-dbisqlconnection.cpp
@@ -28,6 +28,10 @@ extern "C"
 #include <platform.h>
 #include <gnc-locale-utils.h>
 }
+
+#include <string>
+#include <regex>
+
 #include "gnc-dbisqlconnection.hpp"
 
 static QofLogModule log_module = G_LOG_DOMAIN;
@@ -586,16 +590,16 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name,
     dbi_result result = nullptr;
     switch (op)
     {
-    case backup:
+    case TableOpType::backup:
         result = dbi_conn_queryf (m_conn, "ALTER TABLE %s RENAME TO %s",
                                   table_name.c_str(), new_name.c_str());
         break;
-    case rollback:
+    case TableOpType::rollback:
         result = dbi_conn_queryf (m_conn,
                                   "ALTER TABLE %s RENAME TO %s",
                                   new_name.c_str(), table_name.c_str());
         break;
-    case drop_backup:
+    case TableOpType::drop_backup:
         result = dbi_conn_queryf (m_conn, "DROP TABLE %s",
                                   new_name.c_str());
         break;
@@ -630,16 +634,20 @@ GncDbiSqlConnection::table_manage_backup (const std::string& table_name,
  */
 
 bool
-GncDbiSqlConnection::table_operation(const StrVec& table_names,
-                                     TableOpType op) noexcept
+GncDbiSqlConnection::table_operation(TableOpType op) noexcept
 {
-    g_return_val_if_fail (!table_names.empty(), FALSE);
+    static const std::regex backupre (".*_back");
     bool retval{true};
-    for (auto table : table_names)
+    for (auto table : m_provider->get_table_list(m_conn, ""))
     {
         dbi_result result;
-        /* Ignore the lock table */
-        if (table == lock_table)
+        /* Skip the lock table and existing backup tables; the former we don't
+         * want to touch, the latter are handled by table_manage_backup. It
+         * would be nicer to handle this with the get_table_list query, but that
+         * can accept only SQL LIKE patterns (not even regexps) and there's no
+         * way to have a negative one.
+         */
+        if (table == lock_table || std::regex_match(table, backupre))
         {
             continue;
         }
@@ -660,7 +668,7 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names,
                         break;
                 }
             }
-            /* Fall through */
+            /* Fall through to rename the _back tables back.*/
             case backup:
             case drop_backup:
                 result = table_manage_backup (table, op);
@@ -689,6 +697,23 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names,
     return retval;
 }
 
+bool
+GncDbiSqlConnection::drop_indexes() noexcept
+{
+    auto index_list = m_provider->get_index_list (m_conn);
+    for (auto index : index_list)
+    {
+        const char* errmsg;
+        m_provider->drop_index (m_conn, index);
+        if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
+        {
+            PERR("Failed to drop indexes %s", errmsg);
+            return false;
+        }
+    }
+    return true;
+}
+
 std::string
 GncDbiSqlConnection::add_columns_ddl(const std::string& table_name,
                                      const ColVec& info_vec) const noexcept
diff --git a/src/backend/dbi/gnc-dbisqlconnection.hpp b/src/backend/dbi/gnc-dbisqlconnection.hpp
index 466e5d9..05973c8 100644
--- a/src/backend/dbi/gnc-dbisqlconnection.hpp
+++ b/src/backend/dbi/gnc-dbisqlconnection.hpp
@@ -81,12 +81,10 @@ public:
     bool verify() noexcept override;
     bool retry_connection(const char* msg) noexcept override;
     dbi_result table_manage_backup(const std::string& table_name, TableOpType op);
-    bool table_operation (const StrVec& table_name_list,
-                          TableOpType op) noexcept;
+    bool table_operation (TableOpType op) noexcept;
     std::string add_columns_ddl(const std::string& table_name,
                                 const ColVec& info_vec) const noexcept;
-    friend void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book);
-
+    bool drop_indexes() noexcept;
 private:
     QofBackend* m_qbe;
     dbi_conn m_conn;

commit 46ce3f3745dd69dc89f055cb2bf3710c9ba3d6aa
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Nov 27 14:12:43 2016 -0800

    Rename qofbackend-p.h and qofbackend.cpp
    
    To qof-backend.hpp and qof-backend.cpp respectively, to reflect that they
    implement the QofBackend class (which won’t be a class until a future commit).

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0adb5c1..dc5145c 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -482,7 +482,7 @@ src/libqof/qof/gnc-timezone.cpp
 src/libqof/qof/guid.cpp
 src/libqof/qof/kvp_frame.cpp
 src/libqof/qof/kvp-value.cpp
-src/libqof/qof/qofbackend.cpp
+src/libqof/qof/qof-backend.cpp
 src/libqof/qof/qofbook.cpp
 src/libqof/qof/qofchoice.cpp
 src/libqof/qof/qofclass.cpp
diff --git a/src/backend/sql/gnc-sql-backend.hpp b/src/backend/sql/gnc-sql-backend.hpp
index 3146960..1ad4608 100644
--- a/src/backend/sql/gnc-sql-backend.hpp
+++ b/src/backend/sql/gnc-sql-backend.hpp
@@ -27,13 +27,14 @@
 extern "C"
 {
 #include <qof.h>
-#include <qofbackend-p.h>
 #include <Account.h>
 }
 #include <memory>
 #include <exception>
 #include <sstream>
 #include <vector>
+#include <qof-backend.hpp>
+
 class GncSqlColumnTableEntry;
 using GncSqlColumnTableEntryPtr = std::shared_ptr<GncSqlColumnTableEntry>;
 using EntryVec = std::vector<GncSqlColumnTableEntryPtr>;
diff --git a/src/backend/sql/gnc-sql-column-table-entry.hpp b/src/backend/sql/gnc-sql-column-table-entry.hpp
index 26b9271..0dceddf 100644
--- a/src/backend/sql/gnc-sql-column-table-entry.hpp
+++ b/src/backend/sql/gnc-sql-column-table-entry.hpp
@@ -27,7 +27,6 @@
 extern "C"
 {
 #include <qof.h>
-#include "qofbackend-p.h"
 }
 #include <memory>
 #include <vector>
diff --git a/src/backend/xml/gnc-backend-xml.cpp b/src/backend/xml/gnc-backend-xml.cpp
index 6ccd0df..7c4088b 100644
--- a/src/backend/xml/gnc-backend-xml.cpp
+++ b/src/backend/xml/gnc-backend-xml.cpp
@@ -78,7 +78,7 @@ extern "C"
 
 #include <gnc-backend-prov.hpp>
 #include "gnc-backend-xml.h"
-#include <qofbackend-p.h>
+#include <qof-backend.hpp>
 #include "gnc-xml-backend.hpp"
 #include "gnc-xml-helper.h"
 #include "io-gncxml-v2.h"
diff --git a/src/backend/xml/gnc-backend-xml.h b/src/backend/xml/gnc-backend-xml.h
index f82bc25..a9532d0 100644
--- a/src/backend/xml/gnc-backend-xml.h
+++ b/src/backend/xml/gnc-backend-xml.h
@@ -36,7 +36,6 @@ extern "C"
 #endif
 #include <qof.h>
 #include <gmodule.h>
-#include <qofbackend-p.h>
 
 typedef enum
 {
@@ -70,6 +69,6 @@ void qof_backend_module_init (void);
 #endif
 #ifdef __cplusplus
 }
-
+#include <qof-backend.hpp>
 #endif
 #endif /* GNC_BACKEND_XML_H_ */
diff --git a/src/backend/xml/gnc-xml-backend.hpp b/src/backend/xml/gnc-xml-backend.hpp
index ab8c64b..e49486f 100644
--- a/src/backend/xml/gnc-xml-backend.hpp
+++ b/src/backend/xml/gnc-xml-backend.hpp
@@ -21,10 +21,10 @@
 extern "C"
 {
 #include <qof.h>
-#include <qofbackend-p.h>
 }
 
 #include <string>
+#include <qof-backend.hpp>
 
 class GncXmlBackend
 {
diff --git a/src/engine/test/utest-Transaction.cpp b/src/engine/test/utest-Transaction.cpp
index dcdac4d..0e06a34 100644
--- a/src/engine/test/utest-Transaction.cpp
+++ b/src/engine/test/utest-Transaction.cpp
@@ -35,7 +35,6 @@ extern "C"
 #include "../gnc-lot.h"
 #include "../gnc-event.h"
 #include <qof.h>
-#include <qofbackend-p.h>
 
 #ifdef HAVE_GLIB_2_38
 #define _Q "'"
@@ -50,6 +49,7 @@ static const gchar *suitename = "/engine/Transaction";
 void test_suite_transaction ( void );
 }
 
+#include <qof-backend.hpp>
 #include <kvp_frame.hpp>
 
 /* Copied from Transaction.c. Changing these values will break
diff --git a/src/libqof/CMakeLists.txt b/src/libqof/CMakeLists.txt
index 90aa60e..05a03da 100644
--- a/src/libqof/CMakeLists.txt
+++ b/src/libqof/CMakeLists.txt
@@ -14,7 +14,7 @@ SET (gnc_qof_HEADERS
     qof/kvp_frame.hpp
     qof/kvp-value.hpp
     qof/qof.h
-    qof/qofbackend-p.h
+    qof/qof-backend.hpp
     qof/qofbackend.h
     qof/qofbook.h
     qof/qofbookslots.h
@@ -59,7 +59,7 @@ SET (gnc_qof_SOURCES
    qof/guid.cpp
    qof/kvp_frame.cpp
    qof/kvp-value.cpp
-   qof/qofbackend.cpp
+   qof/qof-backend.cpp
    qof/qofbook.cpp
    qof/qofchoice.cpp
    qof/qofclass.cpp
diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am
index 5e83159..c2fec7f 100644
--- a/src/libqof/qof/Makefile.am
+++ b/src/libqof/qof/Makefile.am
@@ -32,7 +32,7 @@ libgnc_qof_la_SOURCES =  \
    guid.cpp            \
    kvp_frame.cpp       \
    kvp-value.cpp       \
-   qofbackend.cpp      \
+   qof-backend.cpp      \
    qofbook.cpp         \
    qofchoice.cpp       \
    qofclass.cpp        \
@@ -61,7 +61,7 @@ qofinclude_HEADERS = \
    kvp_frame.hpp     \
    kvp-value.hpp     \
    qof.h             \
-   qofbackend-p.h    \
+   qof-backend.hpp   \
    qofbackend.h      \
    qofbook.h         \
    qofbookslots.h    \
diff --git a/src/libqof/qof/qofbackend.cpp b/src/libqof/qof/qof-backend.cpp
similarity index 99%
rename from src/libqof/qof/qofbackend.cpp
rename to src/libqof/qof/qof-backend.cpp
index 6613fed..28feadc 100644
--- a/src/libqof/qof/qofbackend.cpp
+++ b/src/libqof/qof/qof-backend.cpp
@@ -34,10 +34,10 @@ extern "C"
 #include <gmodule.h>
 #include <errno.h>
 #include "qof.h"
-#include "qofbackend-p.h"
-
 }
 
+#include "qof-backend.hpp"
+
 G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_BACKEND;
 
 #define QOF_CONFIG_DESC    "desc"
diff --git a/src/libqof/qof/qofbackend-p.h b/src/libqof/qof/qof-backend.hpp
similarity index 100%
rename from src/libqof/qof/qofbackend-p.h
rename to src/libqof/qof/qof-backend.hpp
diff --git a/src/libqof/qof/qofbook.cpp b/src/libqof/qof/qofbook.cpp
index e7701f6..270fc8b 100644
--- a/src/libqof/qof/qofbook.cpp
+++ b/src/libqof/qof/qofbook.cpp
@@ -52,7 +52,7 @@ extern "C"
 
 #include "qof.h"
 #include "qofevent-p.h"
-#include "qofbackend-p.h"
+#include "qofbackend.h"
 #include "qofbook-p.h"
 #include "qofid-p.h"
 #include "qofobject-p.h"
diff --git a/src/libqof/qof/qofinstance.cpp b/src/libqof/qof/qofinstance.cpp
index 9f3f542..5e923ea 100644
--- a/src/libqof/qof/qofinstance.cpp
+++ b/src/libqof/qof/qofinstance.cpp
@@ -41,6 +41,7 @@ extern "C"
 #include "qofid-p.h"
 #include "kvp_frame.hpp"
 #include "qofinstance-p.h"
+#include "qof-backend.hpp"
 
 static QofLogModule log_module = QOF_MOD_ENGINE;
 
diff --git a/src/libqof/qof/qofquery.cpp b/src/libqof/qof/qofquery.cpp
index c6cad34..1fac61c 100644
--- a/src/libqof/qof/qofquery.cpp
+++ b/src/libqof/qof/qofquery.cpp
@@ -33,7 +33,7 @@ extern "C"
 }
 
 #include "qof.h"
-#include "qofbackend-p.h"
+#include "qof-backend.hpp"
 #include "qofbook-p.h"
 #include "qofclass-p.h"
 #include "qofquery-p.h"
diff --git a/src/libqof/qof/qofsession.cpp b/src/libqof/qof/qofsession.cpp
index cf9c31f..91e04c6 100644
--- a/src/libqof/qof/qofsession.cpp
+++ b/src/libqof/qof/qofsession.cpp
@@ -56,7 +56,7 @@ extern "C"
 static QofLogModule log_module = QOF_MOD_SESSION;
 } //extern 'C'
 
-#include "qofbackend-p.h"
+#include "qof-backend.hpp"
 #include "qofsession.hpp"
 #include "gnc-backend-prov.hpp"
 
diff --git a/src/libqof/qof/qofutil.cpp b/src/libqof/qof/qofutil.cpp
index 2881fd7..43d32ca 100644
--- a/src/libqof/qof/qofutil.cpp
+++ b/src/libqof/qof/qofutil.cpp
@@ -32,7 +32,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include "qof.h"
-#include "qofbackend-p.h"
+#include "qof-backend.hpp"
 
 G_GNUC_UNUSED static QofLogModule log_module = QOF_MOD_UTIL;
 
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index f5f6507..5c43d20 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -14,9 +14,7 @@ test_qof_SOURCES = \
 	test-qofbook.c \
 	test-qofinstance.cpp \
 	test-qofobject.c \
-	test-qofsession-old.cpp \
 	test-qof-string-cache.c \
-	test-gnc-guid-old.cpp \
 	${top_srcdir}/src/test-core/unittest-support.c
 
 test_qof_HEADERS = \
diff --git a/src/libqof/qof/test/test-qofinstance.cpp b/src/libqof/qof/test/test-qofinstance.cpp
index 955be9c..45fe87d 100644
--- a/src/libqof/qof/test/test-qofinstance.cpp
+++ b/src/libqof/qof/test/test-qofinstance.cpp
@@ -27,8 +27,8 @@ extern "C"
 #include <glib.h>
 #include <unittest-support.h>
 #include "../qof.h"
-#include "../qofbackend-p.h"
 }
+#include "../qof-backend.hpp"
 #include "../kvp_frame.hpp"
 static const gchar *suitename = "/qof/qofinstance";
 extern "C" void test_suite_qofinstance ( void );
diff --git a/src/libqof/qof/test/test-qofsession.cpp b/src/libqof/qof/test/test-qofsession.cpp
index 7a30cde..f77a54e 100644
--- a/src/libqof/qof/test/test-qofsession.cpp
+++ b/src/libqof/qof/test/test-qofsession.cpp
@@ -25,7 +25,7 @@
 #include <gtest/gtest.h>
 #include "../guid.hpp"
 #include <qofsession.hpp>
-#include "qofbackend-p.h"
+#include <qof-backend.hpp>
 #include <cstdlib>
 #include "../gnc-backend-prov.hpp"
 

commit eace6250075890a13975280cf0d2af0804466ff8
Author: John Ralls <jralls at ceridwen.us>
Date:   Mon Nov 21 09:55:39 2016 -0800

    Extract class GncXmlBackend from gnc-backend-xml.cpp to gnu-xml-backend.cpp.

diff --git a/src/backend/xml/CMakeLists.txt b/src/backend/xml/CMakeLists.txt
index 26f4642..3094bec 100644
--- a/src/backend/xml/CMakeLists.txt
+++ b/src/backend/xml/CMakeLists.txt
@@ -20,6 +20,7 @@ SET (backend_xml_utils_noinst_HEADERS
   gnc-owner-xml-v2.h
   gnc-tax-table-xml-v2.h
   gnc-vendor-xml-v2.h
+  gnc-xml-backend.hpp
   gnc-xml-helper.h
   io-example-account.h
   io-gncxml-gen.h
@@ -57,6 +58,7 @@ SET (backend_xml_utils_SOURCES
   gnc-tax-table-xml-v2.cpp
   gnc-transaction-xml-v2.cpp
   gnc-vendor-xml-v2.cpp
+  gnc-xml-backend.cpp
   gnc-xml-helper.cpp
   io-example-account.cpp
   io-gncxml-gen.cpp
diff --git a/src/backend/xml/Makefile.am b/src/backend/xml/Makefile.am
index e84e589..e64da12 100644
--- a/src/backend/xml/Makefile.am
+++ b/src/backend/xml/Makefile.am
@@ -41,6 +41,7 @@ libgnc_backend_xml_utils_la_SOURCES = \
   gnc-tax-table-xml-v2.cpp \
   gnc-transaction-xml-v2.cpp \
   gnc-vendor-xml-v2.cpp \
+  gnc-xml-backend.cpp \
   gnc-xml-helper.cpp \
   io-example-account.cpp \
   io-gncxml-gen.cpp \
@@ -71,6 +72,7 @@ noinst_HEADERS = \
   gnc-owner-xml-v2.h \
   gnc-tax-table-xml-v2.h \
   gnc-vendor-xml-v2.h \
+  gnc-xml-backend.hpp \
   gnc-xml-helper.h \
   io-example-account.h \
   io-gncxml-gen.h \
diff --git a/src/backend/xml/gnc-backend-xml.cpp b/src/backend/xml/gnc-backend-xml.cpp
index ab9e353..6ccd0df 100644
--- a/src/backend/xml/gnc-backend-xml.cpp
+++ b/src/backend/xml/gnc-backend-xml.cpp
@@ -31,10 +31,6 @@ extern "C"
 {
 #include "config.h"
 
-#include <platform.h>
-#if PLATFORM(WINDOWS)
-#include <windows.h>
-#endif
 
 #include <glib.h>
 #include <glib/gi18n.h>
@@ -43,9 +39,7 @@ extern "C"
 #include <locale.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <sys/stat.h>
 #include <sys/types.h>
-#include <regex.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #else
@@ -73,10 +67,8 @@ extern "C"
 #endif
 
 #include "qof.h"
-#include "TransLog.h"
 #include "gnc-engine.h"
-
-#include "gnc-uri-utils.h"
+#include <gnc-uri-utils.h>
 #include "gnc-prefs.h"
 
 #ifndef HAVE_STRPTIME
@@ -87,6 +79,7 @@ extern "C"
 #include <gnc-backend-prov.hpp>
 #include "gnc-backend-xml.h"
 #include <qofbackend-p.h>
+#include "gnc-xml-backend.hpp"
 #include "gnc-xml-helper.h"
 #include "io-gncxml-v2.h"
 #include "io-gncxml.h"
@@ -105,7 +98,6 @@ extern "C"
 
 static QofLogModule log_module = GNC_MOD_BACKEND;
 
-static gboolean save_may_clobber_data (XmlBackend *xml_be);
 
 struct QofXmlBackendProvider : public QofBackendProvider
 {
@@ -121,291 +113,16 @@ struct QofXmlBackendProvider : public QofBackendProvider
 
 };
 
-/* ================================================================= */
-
-static gboolean
-gnc_xml_be_get_file_lock (XmlBackend* xml_be)
-{
-    struct stat statbuf;
-#ifndef G_OS_WIN32
-    char* pathbuf = NULL, *path = NULL, *tmpbuf = NULL;
-    size_t pathbuf_size = 0;
-#endif
-    int rc;
-    QofBackendError be_err;
-
-    rc = g_stat (xml_be->m_lockfile, &statbuf);
-    if (!rc)
-    {
-        /* oops .. file is locked by another user  .. */
-        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
-        return FALSE;
-    }
-
-    xml_be->m_lockfd = g_open (xml_be->m_lockfile, O_RDWR | O_CREAT | O_EXCL ,
-                         S_IRUSR | S_IWUSR);
-    if (xml_be->m_lockfd < 0)
-    {
-        /* oops .. we can't create the lockfile .. */
-        switch (errno)
-        {
-        case EACCES:
-        case EROFS:
-        case ENOSPC:
-            PWARN ("Unable to create the lockfile %s; may not have write priv",
-                   xml_be->m_lockfile);
-            be_err = ERR_BACKEND_READONLY;
-            break;
-        default:
-            be_err = ERR_BACKEND_LOCKED;
-            break;
-        }
-        qof_backend_set_error ((QofBackend*)xml_be, be_err);
-        return FALSE;
-    }
-
-    /* OK, now work around some NFS atomic lock race condition
-     * mumbo-jumbo.  We do this by linking a unique file, and
-     * then examining the link count.  At least that's what the
-     * NFS programmers guide suggests.
-     * Note: the "unique filename" must be unique for the
-     * triplet filename-host-process, otherwise accidental
-     * aliases can occur.
-     */
-
-    /* apparently, even this code may not work for some NFS
-     * implementations. In the long run, I am told that
-     * ftp.debian.org
-     *  /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
-     * provides a better long-term solution.
-     */
-
-#ifndef G_OS_WIN32
-    pathbuf_size = strlen (xml_be->m_lockfile) + 100;
-    pathbuf = (char*) malloc (pathbuf_size);
-    if (pathbuf == NULL)
-    {
-        return FALSE;
-    }
-    strcpy (pathbuf, xml_be->m_lockfile);
-    path = strrchr (pathbuf, '.');
-    while (snprintf (path, pathbuf_size - (path - pathbuf), ".%lx.%d.LNK",
-                     gethostid (), getpid ())
-           >= static_cast<int> (pathbuf_size - (path - pathbuf)))
-    {
-        pathbuf_size += 100;
-        tmpbuf = (char*) realloc (pathbuf, pathbuf_size);
-        if (tmpbuf == NULL)
-        {
-            free (pathbuf);
-            return FALSE;
-        }
-        else
-        {
-            pathbuf = tmpbuf;
-        }
-    }
-
-    rc = link (xml_be->m_lockfile, pathbuf);
-    if (rc)
-    {
-        /* If hard links aren't supported, just allow the lock. */
-        if (errno == EPERM || errno == ENOSYS
-# ifdef EOPNOTSUPP
-            || errno == EOPNOTSUPP
-# endif
-# ifdef ENOTSUP
-            || errno == ENOTSUP
-# endif
-           )
-        {
-            xml_be->m_linkfile = NULL;
-            free (pathbuf);
-            return TRUE;
-        }
-
-        /* Otherwise, something else is wrong. */
-        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
-        g_unlink (pathbuf);
-        free (pathbuf);
-        close (xml_be->m_lockfd);
-        g_unlink (xml_be->m_lockfile);
-        return FALSE;
-    }
-
-    rc = g_stat (xml_be->m_lockfile, &statbuf);
-    if (rc)
-    {
-        /* oops .. stat failed!  This can't happen! */
-        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
-        qof_backend_set_message ((QofBackend*)xml_be, "Failed to stat lockfile %s",
-                                 xml_be->m_lockfile);
-        g_unlink (pathbuf);
-        free (pathbuf);
-        close (xml_be->m_lockfd);
-        g_unlink (xml_be->m_lockfile);
-        return FALSE;
-    }
-
-    if (statbuf.st_nlink != 2)
-    {
-        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
-        g_unlink (pathbuf);
-        free (pathbuf);
-        close (xml_be->m_lockfd);
-        g_unlink (xml_be->m_lockfile);
-        return FALSE;
-    }
-
-    xml_be->m_linkfile = g_strdup (pathbuf);
-    free (pathbuf);
-
-    return TRUE;
-
-#else /* ifndef G_OS_WIN32 */
-    /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
-       is sufficient for locking. */
-    xml_be->m_linkfile = NULL;
-    return TRUE;
-#endif /* ifndef G_OS_WIN32 */
-}
-
-/* ================================================================= */
-#define XML_URI_PREFIX "xml://"
-#define FILE_URI_PREFIX "file://"
 
 static void
 xml_session_begin (QofBackend* qof_be, QofSession* session,
                    const char* book_id, gboolean ignore_lock,
                    gboolean create, gboolean force)
 {
-    XmlBackend* xml_be = (XmlBackend*) qof_be;
+    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
 
     ENTER (" ");
-
-    /* Make sure the directory is there */
-    xml_be->m_fullpath = gnc_uri_get_path (book_id);
-
-    if (NULL == xml_be->m_fullpath)
-    {
-        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (qof_be, "No path specified");
-        LEAVE ("");
-        return;
-    }
-    if (create && !force && save_may_clobber_data (xml_be))
-    {
-        qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS);
-        LEAVE ("Might clobber, no force");
-        return;
-    }
-
-    xml_be->be.fullpath = xml_be->m_fullpath;
-    xml_be->m_dirname = g_path_get_dirname (xml_be->m_fullpath);
-
-    {
-        struct stat statbuf;
-        int rc;
-
-        /* Again check whether the directory can be accessed */
-        rc = g_stat (xml_be->m_dirname, &statbuf);
-        if (rc != 0
-#if COMPILER(MSVC)
-            || (statbuf.st_mode & _S_IFDIR) == 0
-#else
-            || !S_ISDIR (statbuf.st_mode)
-#endif
-           )
-        {
-            /* Error on stat or if it isn't a directory means we
-               cannot find this filename */
-            qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-            qof_backend_set_message (qof_be, "Couldn't find directory for %s",
-                                     xml_be->m_fullpath);
-            PWARN ("Couldn't find directory for %s", xml_be->m_fullpath);
-            g_free (xml_be->m_fullpath);
-            xml_be->m_fullpath = NULL;
-            g_free (xml_be->m_dirname);
-            xml_be->m_dirname = NULL;
-            LEAVE ("");
-            return;
-        }
-
-        /* Now check whether we can g_stat the file itself */
-        rc = g_stat (xml_be->m_fullpath, &statbuf);
-        if ((rc != 0) && (!create))
-        {
-            /* Error on stat means the file doesn't exist */
-            qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
-            qof_backend_set_message (qof_be, "Couldn't find %s",
-                                     xml_be->m_fullpath);
-            PWARN ("Couldn't find %s", xml_be->m_fullpath);
-            g_free (xml_be->m_fullpath);
-            xml_be->m_fullpath = NULL;
-            g_free (xml_be->m_dirname);
-            xml_be->m_dirname = NULL;
-            LEAVE ("");
-            return;
-        }
-        if (rc == 0
-#if COMPILER(MSVC)
-            && (statbuf.st_mode & _S_IFDIR) != 0
-#else
-            && S_ISDIR (statbuf.st_mode)
-#endif
-           )
-        {
-            qof_backend_set_error (qof_be, ERR_FILEIO_UNKNOWN_FILE_TYPE);
-            qof_backend_set_message (qof_be, "Path %s is a directory",
-                                     xml_be->m_fullpath);
-            PWARN ("Path %s is a directory", xml_be->m_fullpath);
-            g_free (xml_be->m_fullpath);
-            xml_be->m_fullpath = NULL;
-            g_free (xml_be->m_dirname);
-            xml_be->m_dirname = NULL;
-            LEAVE ("");
-            return;
-        }
-    }
-
-
-    /* ---------------------------------------------------- */
-    /* We should now have a fully resolved path name.
-     * Let's start logging */
-    xaccLogSetBaseName (xml_be->m_fullpath);
-    PINFO ("logpath=%s", xml_be->m_fullpath ? xml_be->m_fullpath : "(null)");
-
-    /* And let's see if we can get a lock on it. */
-    xml_be->m_lockfile = g_strconcat (xml_be->m_fullpath, ".LCK", NULL);
-
-    if (!ignore_lock && !gnc_xml_be_get_file_lock (xml_be))
-    {
-        // We should not ignore the lock, but couldn't get it. The
-        // be_get_file_lock() already set the appropriate backend_error in this
-        // case, so we just return here.
-        g_free (xml_be->m_lockfile);
-        xml_be->m_lockfile = NULL;
-
-        if (force)
-        {
-            QofBackendError berror = qof_backend_get_error (qof_be);
-            if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
-            {
-                // Even though we couldn't get the lock, we were told to force
-                // the opening. This is ok because the FORCE argument is
-                // changed only if the caller wants a read-only book.
-            }
-            else
-            {
-                // Unknown error. Push it again on the error stack.
-                qof_backend_set_error (qof_be, berror);
-            }
-        }
-
-        LEAVE ("");
-        return;
-    }
-
+    xml_be->session_begin(session, book_id, ignore_lock, create, force);
     LEAVE (" ");
     return;
 }
@@ -415,204 +132,16 @@ xml_session_begin (QofBackend* qof_be, QofSession* session,
 static void
 xml_session_end (QofBackend* qof_be)
 {
-    XmlBackend* xml_be = (XmlBackend*)qof_be;
+    GncXmlBackend* xml_be = (GncXmlBackend*)qof_be;
     ENTER (" ");
-
-    if (xml_be->m_book && qof_book_is_readonly (xml_be->m_book))
-    {
-        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
-        return;
-    }
-
-    if (xml_be->m_linkfile)
-        g_unlink (xml_be->m_linkfile);
-
-    if (xml_be->m_lockfd > 0)
-        close (xml_be->m_lockfd);
-
-    if (xml_be->m_lockfile)
-    {
-        int rv;
-#ifdef G_OS_WIN32
-        /* On windows, we need to allow write-access before
-           g_unlink() can succeed */
-        rv = g_chmod (xml_be->m_lockfile, S_IWRITE | S_IREAD);
-#endif
-        rv = g_unlink (xml_be->m_lockfile);
-        if (rv)
-        {
-            PWARN ("Error on g_unlink(%s): %d: %s", xml_be->m_lockfile,
-                   errno, g_strerror (errno) ? g_strerror (errno) : "");
-        }
-    }
-
-    g_free (xml_be->m_dirname);
-    xml_be->m_dirname = NULL;
-
-    g_free (xml_be->m_fullpath);
-    xml_be->m_fullpath = NULL;
-
-    g_free (xml_be->m_lockfile);
-    xml_be->m_lockfile = NULL;
-
-    g_free (xml_be->m_linkfile);
-    xml_be->m_linkfile = NULL;
+    xml_be->session_end();
     LEAVE (" ");
 }
 
 static void
 xml_destroy_backend (QofBackend* qof_be)
 {
-    /* Stop transaction logging */
-    xaccLogSetBaseName (NULL);
-
-    qof_backend_destroy (qof_be);
-    g_free (qof_be);
-}
-
-/* ================================================================= */
-/* Write the financial data in a book to a file, returning FALSE on
-   error and setting the error_result to indicate what went wrong if
-   it's not NULL.  This function does not manage file locks in any
-   way.
-
-   If make_backup is true, write out a time-stamped copy of the file
-   into the same directory as the indicated file, with a filename of
-   "file.YYYYMMDDHHMMSS.gnucash" where YYYYMMDDHHMMSS is replaced with the
-   current year/month/day/hour/minute/second. */
-
-/* The variable buf_size must be a compile-time constant */
-#define buf_size 1024
-
-static gboolean
-copy_file (const char* orig, const char* bkup)
-{
-    char buf[buf_size];
-    int orig_fd;
-    int bkup_fd;
-    int flags = 0;
-    ssize_t count_write;
-    ssize_t count_read;
-
-#ifdef G_OS_WIN32
-    flags = O_BINARY;
-#endif
-
-    orig_fd = g_open (orig, O_RDONLY | flags, 0);
-    if (orig_fd == -1)
-    {
-        return FALSE;
-    }
-    bkup_fd = g_open (bkup, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
-    if (bkup_fd == -1)
-    {
-        close (orig_fd);
-        return FALSE;
-    }
-
-    do
-    {
-        count_read = read (orig_fd, buf, buf_size);
-        if (count_read == -1 && errno != EINTR)
-        {
-            close (orig_fd);
-            close (bkup_fd);
-            return FALSE;
-        }
-
-        if (count_read > 0)
-        {
-            count_write = write (bkup_fd, buf, count_read);
-            if (count_write == -1)
-            {
-                close (orig_fd);
-                close (bkup_fd);
-                return FALSE;
-            }
-        }
-    }
-    while (count_read > 0);
-
-    close (orig_fd);
-    close (bkup_fd);
-
-    return TRUE;
-}
-
-/* ================================================================= */
-
-static gboolean
-gnc_int_link_or_make_backup (XmlBackend* xml_be, const char* orig,
-                             const char* bkup)
-{
-    gboolean copy_success = FALSE;
-    int err_ret =
-#ifdef HAVE_LINK
-        link (orig, bkup)
-#else
-        - 1
-#endif
-        ;
-    if (err_ret != 0)
-    {
-#ifdef HAVE_LINK
-        if (errno == EPERM || errno == ENOSYS
-# ifdef EOPNOTSUPP
-            || errno == EOPNOTSUPP
-# endif
-# ifdef ENOTSUP
-            || errno == ENOTSUP
-# endif
-# ifdef ENOSYS
-            || errno == ENOSYS
-# endif
-           )
-#endif
-        {
-            copy_success = copy_file (orig, bkup);
-        }
-
-        if (!copy_success)
-        {
-            qof_backend_set_error ((QofBackend*)xml_be, ERR_FILEIO_BACKUP_ERROR);
-            PWARN ("unable to make file backup from %s to %s: %s",
-                   orig, bkup, g_strerror (errno) ? g_strerror (errno) : "");
-            return FALSE;
-        }
-    }
-
-    return TRUE;
-}
-
-/* ================================================================= */
-
-static QofBookFileType
-gnc_xml_be_determine_file_type (const char* path)
-{
-    gboolean with_encoding;
-    QofBookFileType v2type;
-
-    v2type = gnc_is_xml_data_file_v2 (path, &with_encoding);
-    if (v2type == GNC_BOOK_XML2_FILE)
-    {
-        if (with_encoding)
-        {
-            return GNC_BOOK_XML2_FILE;
-        }
-        else
-        {
-            return GNC_BOOK_XML2_FILE_NO_ENCODING;
-        }
-    }
-    else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
-    {
-        return GNC_BOOK_POST_XML2_0_0_FILE;
-    }
-    else if (v2type == GNC_BOOK_XML1_FILE)
-    {
-        return GNC_BOOK_XML1_FILE;
-    }
-    return GNC_BOOK_NOT_OURS;
+    delete reinterpret_cast<GncXmlBackend*>(qof_be);
 }
 
 bool
@@ -672,449 +201,30 @@ det_exit:
     return result;
 }
 
-static gboolean
-gnc_xml_be_backup_file (XmlBackend* xml_be)
-{
-    gboolean bkup_ret;
-    char* timestamp;
-    char* backup;
-    const char* datafile;
-    struct stat statbuf;
-    int rc;
-
-    datafile = xml_be->m_fullpath;
-
-    rc = g_stat (datafile, &statbuf);
-    if (rc)
-        return (errno == ENOENT);
-
-    if (gnc_xml_be_determine_file_type (datafile) == GNC_BOOK_BIN_FILE)
-    {
-        /* make a more permanent safer backup */
-        const char* back = "-binfmt.bkup";
-        char* bin_bkup = g_new (char, strlen (datafile) + strlen (back) + 1);
-        strcpy (bin_bkup, datafile);
-        strcat (bin_bkup, back);
-        bkup_ret = gnc_int_link_or_make_backup (xml_be, datafile, bin_bkup);
-        g_free (bin_bkup);
-        if (!bkup_ret)
-        {
-            return FALSE;
-        }
-    }
-
-    timestamp = gnc_date_timestamp ();
-    backup = g_strconcat (datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL);
-    g_free (timestamp);
-
-    bkup_ret = gnc_int_link_or_make_backup (xml_be, datafile, backup);
-    g_free (backup);
-
-    return bkup_ret;
-}
-
-/* ================================================================= */
-
-static gboolean
-gnc_xml_be_write_to_file (XmlBackend* xml_be,
-                          QofBook* book,
-                          const gchar* datafile,
-                          gboolean make_backup)
-{
-    QofBackend* qof_be = &xml_be->be;
-    char* tmp_name;
-    struct stat statbuf;
-    int rc;
-    QofBackendError be_err;
-
-    ENTER (" book=%p file=%s", book, datafile);
-
-    if (book && qof_book_is_readonly (book))
-    {
-        /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
-        LEAVE ("");
-        return FALSE;
-    }
-
-    /* If the book is 'clean', recently saved, then don't save again. */
-    /* XXX this is currently broken due to faulty 'Save As' logic. */
-    /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */
-
-    tmp_name = g_new (char, strlen (datafile) + 12);
-    strcpy (tmp_name, datafile);
-    strcat (tmp_name, ".tmp-XXXXXX");
-
-    if (!mktemp (tmp_name))
-    {
-        qof_backend_set_error (qof_be, ERR_BACKEND_MISC);
-        qof_backend_set_message (qof_be, "Failed to make temp file");
-        LEAVE ("");
-        return FALSE;
-    }
-
-    if (make_backup)
-    {
-        if (!gnc_xml_be_backup_file (xml_be))
-        {
-            LEAVE ("");
-            return FALSE;
-        }
-    }
-
-    if (gnc_book_write_to_xml_file_v2 (book, tmp_name,
-                                       gnc_prefs_get_file_save_compressed ()))
-    {
-        /* Record the file's permissions before g_unlinking it */
-        rc = g_stat (datafile, &statbuf);
-        if (rc == 0)
-        {
-            /* We must never chmod the file /dev/null */
-            g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0);
-
-            /* Use the permissions from the original data file */
-            if (g_chmod (tmp_name, statbuf.st_mode) != 0)
-            {
-                /* qof_backend_set_error(qof_be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message(qof_be, "Failed to chmod filename %s", tmp_name ); */
-                /* Even if the chmod did fail, the save
-                   nevertheless completed successfully. It is
-                   therefore wrong to signal the ERR_BACKEND_PERM
-                   error here which implies that the saving itself
-                   failed. Instead, we simply ignore this. */
-                PWARN ("unable to chmod filename %s: %s",
-                       tmp_name ? tmp_name : "(null)",
-                       g_strerror (errno) ? g_strerror (errno) : "");
-#if VFAT_DOESNT_SUCK  /* chmod always fails on vfat/samba fs */
-                /* g_free(tmp_name); */
-                /* return FALSE; */
-#endif
-            }
-#ifdef HAVE_CHOWN
-            /* Don't try to change the owner. Only root can do
-               that. */
-            if (chown (tmp_name, -1, statbuf.st_gid) != 0)
-            {
-                /* qof_backend_set_error(qof_be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message(qof_be, "Failed to chown filename %s", tmp_name ); */
-                /* A failed chown doesn't mean that the saving itself
-                failed. So don't abort with an error here! */
-                PWARN ("unable to chown filename %s: %s",
-                       tmp_name ? tmp_name : "(null)",
-                       strerror (errno) ? strerror (errno) : "");
-#if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
-                /* g_free(tmp_name);
-                return FALSE; */
-#endif
-            }
-#endif
-        }
-        if (g_unlink (datafile) != 0 && errno != ENOENT)
-        {
-            qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
-            PWARN ("unable to unlink filename %s: %s",
-                   datafile ? datafile : "(null)",
-                   g_strerror (errno) ? g_strerror (errno) : "");
-            g_free (tmp_name);
-            LEAVE ("");
-            return FALSE;
-        }
-        if (!gnc_int_link_or_make_backup (xml_be, tmp_name, datafile))
-        {
-            qof_backend_set_error (qof_be, ERR_FILEIO_BACKUP_ERROR);
-            qof_backend_set_message (qof_be, "Failed to make backup file %s",
-                                     datafile ? datafile : "NULL");
-            g_free (tmp_name);
-            LEAVE ("");
-            return FALSE;
-        }
-        if (g_unlink (tmp_name) != 0)
-        {
-            qof_backend_set_error (qof_be, ERR_BACKEND_PERM);
-            PWARN ("unable to unlink temp filename %s: %s",
-                   tmp_name ? tmp_name : "(null)",
-                   g_strerror (errno) ? g_strerror (errno) : "");
-            g_free (tmp_name);
-            LEAVE ("");
-            return FALSE;
-        }
-        g_free (tmp_name);
-
-        /* Since we successfully saved the book,
-         * we should mark it clean. */
-        qof_book_mark_session_saved (book);
-        LEAVE (" successful save of book=%p to file=%s", book, datafile);
-        return TRUE;
-    }
-    else
-    {
-        if (g_unlink (tmp_name) != 0)
-        {
-            switch (errno)
-            {
-            case ENOENT:     /* tmp_name doesn't exist?  Assume "RO" error */
-            case EACCES:
-            case EPERM:
-            case ENOSYS:
-            case EROFS:
-                be_err = ERR_BACKEND_READONLY;
-                break;
-            default:
-                be_err = ERR_BACKEND_MISC;
-                break;
-            }
-            qof_backend_set_error (qof_be, be_err);
-            PWARN ("unable to unlink temp_filename %s: %s",
-                   tmp_name ? tmp_name : "(null)",
-                   g_strerror (errno) ? g_strerror (errno) : "");
-            /* already in an error just flow on through */
-        }
-        else
-        {
-            /* Use a generic write error code */
-            qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
-            qof_backend_set_message (qof_be, "Unable to write to temp file %s",
-                                     tmp_name ? tmp_name : "NULL");
-        }
-        g_free (tmp_name);
-        LEAVE ("");
-        return FALSE;
-    }
-    LEAVE ("");
-    return TRUE;
-}
-
-/* ================================================================= */
-
-/*
- * Clean up any lock files from prior crashes, and clean up old
- * backup and log files.
- */
-
-static void
-gnc_xml_be_remove_old_files (XmlBackend* xml_be)
-{
-    const gchar* dent;
-    GDir* dir;
-    struct stat lockstatbuf, statbuf;
-    time64 now;
-
-    if (g_stat (xml_be->m_lockfile, &lockstatbuf) != 0)
-        return;
-
-    dir = g_dir_open (xml_be->m_dirname, 0, NULL);
-    if (!dir)
-        return;
-
-    now = gnc_time (NULL);
-    while ((dent = g_dir_read_name (dir)) != NULL)
-    {
-        gchar* name;
-
-        /* Ensure we only evaluate GnuCash related files. */
-        if (! (g_str_has_suffix (dent, ".LNK") ||
-               g_str_has_suffix (dent, ".xac") /* old data file extension */ ||
-               g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
-               g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
-            continue;
-
-        name = g_build_filename (xml_be->m_dirname, dent, (gchar*)NULL);
-
-        /* Only evaluate files associated with the current data file. */
-        if (!g_str_has_prefix (name, xml_be->m_fullpath))
-        {
-            g_free (name);
-            continue;
-        }
-
-        /* Never remove the current data file itself */
-        if (g_strcmp0 (name, xml_be->m_fullpath) == 0)
-        {
-            g_free (name);
-            continue;
-        }
-
-        /* Test if the current file is a lock file */
-        if (g_str_has_suffix (name, ".LNK"))
-        {
-            /* Is a lock file. Skip the active lock file */
-            if ((g_strcmp0 (name, xml_be->m_linkfile) != 0) &&
-                /* Only delete lock files older than the active one */
-                (g_stat (name, &statbuf) == 0) &&
-                (statbuf.st_mtime < lockstatbuf.st_mtime))
-            {
-                PINFO ("remove stale lock file: %s", name);
-                g_unlink (name);
-            }
-
-            g_free (name);
-            continue;
-        }
-
-        /* At this point we're sure the file's name is in one of these forms:
-         * <fullpath/to/datafile><anything>.gnucash
-         * <fullpath/to/datafile><anything>.xac
-         * <fullpath/to/datafile><anything>.log
-         *
-         * To be a file generated by GnuCash, the <anything> part should consist
-         * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
-         * regular expression.
-         */
-        {
-            /* Find the start of the date stamp. This takes some pointer
-             * juggling, but considering the above tests, this should always
-             * be safe */
-            regex_t pattern;
-            gchar* stamp_start = name + strlen (xml_be->m_fullpath);
-            gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
-                                                 GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
-            gboolean got_date_stamp = FALSE;
-
-            if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
-                PWARN ("Cannot compile regex for date stamp");
-            else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
-                got_date_stamp = TRUE;
-
-            regfree (&pattern);
-            g_free (expression);
-
-            if (!got_date_stamp) /* Not a gnucash created file after all... */
-            {
-                g_free (name);
-                continue;
-            }
-        }
-
-        /* The file is a backup or log file. Check the user's retention preference
-         * to determine if we should keep it or not
-         */
-        if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
-        {
-            PINFO ("remove stale file: %s  - reason: preference XML_RETAIN_NONE", name);
-            g_unlink (name);
-        }
-        else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
-                 (gnc_prefs_get_file_retention_days () > 0))
-        {
-            int days;
-
-            /* Is the backup file old enough to delete */
-            if (g_stat (name, &statbuf) != 0)
-            {
-                g_free (name);
-                continue;
-            }
-            days = (int) (difftime (now, statbuf.st_mtime) / 86400);
-
-            PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ());
-            if (days >= gnc_prefs_get_file_retention_days ())
-            {
-                PINFO ("remove stale file: %s  - reason: more than %d days old", name, days);
-                g_unlink (name);
-            }
-        }
-        g_free (name);
-    }
-    g_dir_close (dir);
-}
-
 static void
 xml_sync_all (QofBackend* qof_be, QofBook* book)
 {
-    XmlBackend* xml_be = (XmlBackend*) qof_be;
-    ENTER ("book=%p, xml_be->m_book=%p", book, xml_be->m_book);
+    GncXmlBackend* xml_be = reinterpret_cast<decltype(xml_be)>(qof_be);
+    xml_be->sync(book);
+    ENTER ("book=%p, xml_be->m_book=%p", book, xml_be->get_book());
 
-    /* We make an important assumption here, that we might want to change
-     * in the future: when the user says 'save', we really save the one,
-     * the only, the current open book, and nothing else. In any case the plans
-     * for multiple books have been removed in the meantime and there is just one
-     * book, no more.
-     */
-    if (NULL == xml_be->m_book) xml_be->m_book = book;
-    if (book != xml_be->m_book) return;
-
-    if (qof_book_is_readonly (xml_be->m_book))
-    {
-        /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
-        return;
-    }
-
-    gnc_xml_be_write_to_file (xml_be, book, xml_be->m_fullpath, TRUE);
-    gnc_xml_be_remove_old_files (xml_be);
     LEAVE ("book=%p", book);
 }
 
-/* ================================================================= */
-/* Routines to deal with the creation of multiple books.
- * The core design assumption here is that the book
- * begin-edit/commit-edit routines are used solely to write out
- * closed accounting periods to files.  They're not currently
- * designed to do anything other than this. (Although they could be).
- */
-
-static char*
-build_period_filepath (XmlBackend* xml_be, QofBook* book)
-{
-    int len;
-    char* str, *p, *q;
-
-    len = strlen (xml_be->m_fullpath) + GUID_ENCODING_LENGTH + 14;
-    str = g_new (char, len);
-    strcpy (str, xml_be->m_fullpath);
-
-    /* XXX it would be nice for the user if we made the book
-     * closing date and/or title part of the file-name. */
-    p = strrchr (str, G_DIR_SEPARATOR);
-    p++;
-    p = stpcpy (p, "book-");
-    p = guid_to_string_buff (qof_book_get_guid (book), p);
-    p = stpcpy (p, "-");
-    q = strrchr (xml_be->m_fullpath, G_DIR_SEPARATOR);
-    q++;
-    p = stpcpy (p, q);
-    p = stpcpy (p, ".gml");
-
-    return str;
-}
 
 static void
 xml_begin_edit (QofBackend* qof_be, QofInstance* inst)
 {
-    if (0) build_period_filepath (0, 0);
-#if BORKEN_FOR_NOW
-    XmlBackend* xml_be = (XmlBackend*) qof_be;
-    QofBook* book = gp;
-    const char* filepath;
-
-    QofIdTypeConst typ = QOF_INSTANCE (inst)->e_type;
-    if (strcmp (GNC_ID_PERIOD, typ)) return;
-    filepath = build_period_filepath (xml_be, book);
-    PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
-
-    if (NULL == xml_be->m_primary_book)
-    {
-        PERR ("You should have saved the data "
-              "at least once before closing the books!\n");
-    }
-    /* XXX To be anal about it, we should really be checking to see
-     * if there already is a file with this book GncGUID, and disallowing
-     * further progress.  This is because we are not allowed to
-     * modify books that are closed (They should be treated as
-     * 'read-only').
-     */
-#endif
+    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
+    xml_be->begin(inst);
 }
 
 static void
 xml_rollback_edit (QofBackend* qof_be, QofInstance* inst)
 {
-#if BORKEN_FOR_NOW
-    QofBook* book = gp;
 
-    if (strcmp (GNC_ID_PERIOD, typ)) return;
-    PINFO ("book=%p", book);
-#endif
+    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
+    xml_be->rollback(inst);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -1130,94 +240,18 @@ static void
 gnc_xml_be_load_from_file (QofBackend* qof_be, QofBook* book,
                            QofBackendLoadType loadType)
 {
-    QofBackendError error;
-    gboolean rc;
-    XmlBackend* xml_be = (XmlBackend*) qof_be;
-
-    if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
-
-    error = ERR_BACKEND_NO_ERR;
-    xml_be->m_book = book;
-
-    switch (gnc_xml_be_determine_file_type (xml_be->m_fullpath))
-    {
-    case GNC_BOOK_XML2_FILE:
-        rc = qof_session_load_from_xml_file_v2 (xml_be, book, GNC_BOOK_XML2_FILE);
-        if (FALSE == rc)
-        {
-            PWARN ("Syntax error in Xml File %s", xml_be->m_fullpath);
-            error = ERR_FILEIO_PARSE_ERROR;
-        }
-        break;
-
-    case GNC_BOOK_XML2_FILE_NO_ENCODING:
-        error = ERR_FILEIO_NO_ENCODING;
-        PWARN ("No character encoding in Xml File %s", xml_be->m_fullpath);
-        break;
-    case GNC_BOOK_XML1_FILE:
-        rc = qof_session_load_from_xml_file (book, xml_be->m_fullpath);
-        if (FALSE == rc)
-        {
-            PWARN ("Syntax error in Xml File %s", xml_be->m_fullpath);
-            error = ERR_FILEIO_PARSE_ERROR;
-        }
-        break;
-    case GNC_BOOK_POST_XML2_0_0_FILE:
-        error = ERR_BACKEND_TOO_NEW;
-        PWARN ("Version of Xml file %s is newer than what we can read", xml_be->m_fullpath);
-        break;
-    default:
-        /* If file type wasn't known, check errno again to give the
-        user some more useful feedback for some particular error
-        conditions. */
-        switch (errno)
-        {
-        case EACCES: /* No read permission */
-            PWARN ("No read permission to file");
-            error = ERR_FILEIO_FILE_EACCES;
-            break;
-        case EISDIR: /* File is a directory - but on this error we don't arrive here */
-            PWARN ("Filename is a directory");
-            error = ERR_FILEIO_FILE_NOT_FOUND;
-            break;
-        default:
-            PWARN ("File not any known type");
-            error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
-            break;
-        }
-        break;
-    }
-
-    if (error != ERR_BACKEND_NO_ERR)
-    {
-        qof_backend_set_error (qof_be, error);
-    }
-
-    /* We just got done loading, it can't possibly be dirty !! */
-    qof_book_mark_session_saved (book);
+    GncXmlBackend* xml_be = (GncXmlBackend*) qof_be;
+    xml_be->load(book, loadType);
 }
 
 /* ---------------------------------------------------------------------- */
 
-static gboolean
-save_may_clobber_data (XmlBackend *xml_be)
-{
-    struct stat statbuf;
-    if (!xml_be->m_fullpath) return FALSE;
-
-    /* FIXME: Make sure this doesn't need more sophisticated semantics
-     * in the face of special file, devices, pipes, symlinks, etc. */
-    if (g_stat (xml_be->m_fullpath, &statbuf) == 0) return TRUE;
-    return FALSE;
-}
 
 
 static void
 gnc_xml_be_write_accounts_to_file (QofBackend* qof_be, QofBook* book)
 {
-    const gchar* datafile;
-
-    datafile = ((XmlBackend*)qof_be)->m_fullpath;
+    auto datafile = ((GncXmlBackend*)qof_be)->get_filename();
     gnc_book_write_accounts_to_xml_file_v2 (qof_be, book, datafile);
 }
 
@@ -1226,13 +260,9 @@ gnc_xml_be_write_accounts_to_file (QofBackend* qof_be, QofBook* book)
 QofBackend*
 QofXmlBackendProvider::create_backend(void)
 {
-    XmlBackend* xml_be;
-    QofBackend* qof_be;
-
-    xml_be = g_new0 (XmlBackend, 1);
-    qof_be = (QofBackend*) xml_be;
-    qof_backend_init (qof_be);
 
+    auto xml_be = new GncXmlBackend;
+    auto qof_be = xml_be->get_qof_be();
     qof_be->session_begin = xml_session_begin;
     qof_be->session_end = xml_session_end;
     qof_be->destroy_backend = xml_destroy_backend;
@@ -1248,14 +278,6 @@ QofXmlBackendProvider::create_backend(void)
 
     qof_be->export_fn = gnc_xml_be_write_accounts_to_file;
 
-    xml_be->m_dirname = NULL;
-    xml_be->m_fullpath = NULL;
-    xml_be->m_lockfile = NULL;
-    xml_be->m_linkfile = NULL;
-    xml_be->m_lockfd = -1;
-
-    xml_be->m_book = NULL;
-
     return qof_be;
 }
 
diff --git a/src/backend/xml/gnc-backend-xml.h b/src/backend/xml/gnc-backend-xml.h
index c5fa189..f82bc25 100644
--- a/src/backend/xml/gnc-backend-xml.h
+++ b/src/backend/xml/gnc-backend-xml.h
@@ -71,19 +71,5 @@ void qof_backend_module_init (void);
 #ifdef __cplusplus
 }
 
-class XmlBackend
-{
-public:
-    QofBackend be;
-
-    char* m_dirname;
-    char* m_fullpath;  /* Fully qualified path to book */
-    char* m_lockfile;
-    char* m_linkfile;
-    int m_lockfd;
-
-    QofBook* m_book;  /* The primary, main open book */
-};
-
 #endif
 #endif /* GNC_BACKEND_XML_H_ */
diff --git a/src/backend/xml/gnc-xml-backend.cpp b/src/backend/xml/gnc-xml-backend.cpp
new file mode 100644
index 0000000..213208b
--- /dev/null
+++ b/src/backend/xml/gnc-xml-backend.cpp
@@ -0,0 +1,892 @@
+/********************************************************************
+ * gnc-xml-backend.cpp: Implement XML file backend.                 *
+ * Copyright 2016 John Ralls <jralls at ceridwen.us>                   *
+ *                                                                  *
+ * This program 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 this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+extern "C"
+{
+#include <config.h>
+#include <platform.h>
+#if PLATFORM(WINDOWS)
+#include <windows.h>
+#endif
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <regex.h>
+
+#include <gnc-engine.h> //for GNC_MOD_BACKEND
+#include <gnc-uri-utils.h>
+#include <TransLog.h>
+#include <gnc-prefs.h>
+
+}
+
+#include <sstream>
+
+#include "gnc-xml-backend.hpp"
+#include "gnc-backend-xml.h"
+#include "io-gncxml-v2.h"
+#include "io-gncxml.h"
+
+#define XML_URI_PREFIX "xml://"
+#define FILE_URI_PREFIX "file://"
+static QofLogModule log_module = GNC_MOD_BACKEND;
+
+GncXmlBackend::GncXmlBackend()
+{
+    memset(&qof_be, 0, sizeof(qof_be));
+    qof_backend_init(&qof_be);
+}
+
+GncXmlBackend::~GncXmlBackend()
+{
+    /* Stop transaction logging */
+    xaccLogSetBaseName (NULL);
+    qof_backend_destroy (&qof_be);
+}
+
+bool
+check_path (const char* fullpath, QofBackend* qof_be, bool create)
+{
+    struct stat statbuf;
+    char* dirname = g_path_get_dirname (fullpath);
+    /* Again check whether the directory can be accessed */
+    auto rc = g_stat (dirname, &statbuf);
+    if (rc != 0
+#if COMPILER(MSVC)
+        || (statbuf.st_mode & _S_IFDIR) == 0
+#else
+        || !S_ISDIR (statbuf.st_mode)
+#endif
+        )
+    {
+        /* Error on stat or if it isn't a directory means we
+           cannot find this filename */
+        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+        qof_backend_set_message (qof_be, "Couldn't find directory for %s",
+                                 fullpath);
+        PWARN ("Couldn't find directory for %s", fullpath);
+        g_free(dirname);
+        return false;
+    }
+
+    /* Now check whether we can g_stat the file itself */
+    rc = g_stat (fullpath, &statbuf);
+    if ((rc != 0) && (!create))
+    {
+        /* Error on stat means the file doesn't exist */
+        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+        qof_backend_set_message (qof_be, "Couldn't find %s", fullpath);
+        PWARN ("Couldn't find %s", fullpath);
+        g_free(dirname);
+        return false;
+    }
+    if (rc == 0
+#if COMPILER(MSVC)
+        && (statbuf.st_mode & _S_IFDIR) != 0
+#else
+        && S_ISDIR (statbuf.st_mode)
+#endif
+        )
+    {
+        qof_backend_set_error (qof_be, ERR_FILEIO_UNKNOWN_FILE_TYPE);
+        qof_backend_set_message (qof_be, "Path %s is a directory", fullpath);
+        PWARN ("Path %s is a directory", fullpath);
+        g_free(dirname);
+        return false;
+    }
+    return true;
+}
+
+void
+GncXmlBackend::session_begin(QofSession* session, const char* book_id,
+                       bool ignore_lock, bool create, bool force)
+{
+    /* Make sure the directory is there */
+    m_fullpath = gnc_uri_get_path (book_id);
+
+    if (m_fullpath.empty())
+    {
+        qof_backend_set_error (&qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+        qof_backend_set_message (&qof_be, "No path specified");
+        return;
+    }
+    if (create && !force && save_may_clobber_data())
+    {
+        qof_backend_set_error (&qof_be, ERR_BACKEND_STORE_EXISTS);
+        PWARN ("Might clobber, no force");
+        return;
+    }
+
+    if (!check_path(m_fullpath.c_str(), &qof_be, create))
+        return;
+    qof_be.fullpath = const_cast<char*>(m_fullpath.c_str());
+    m_dirname = g_path_get_dirname (m_fullpath.c_str());
+
+
+
+    /* ---------------------------------------------------- */
+    /* We should now have a fully resolved path name.
+     * Let's start logging */
+    xaccLogSetBaseName (m_fullpath.c_str());
+    PINFO ("logpath=%s", m_fullpath.empty() ? "(null)" : m_fullpath.c_str());
+
+    /* And let's see if we can get a lock on it. */
+    m_lockfile = m_fullpath + ".LCK";
+
+    if (!ignore_lock && !get_file_lock())
+    {
+        // We should not ignore the lock, but couldn't get it. The
+        // be_get_file_lock() already set the appropriate backend_error in this
+        // case, so we just return here.
+        m_lockfile.clear();
+
+        if (force)
+        {
+            QofBackendError berror = qof_backend_get_error (&qof_be);
+            if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
+            {
+                // Even though we couldn't get the lock, we were told to force
+                // the opening. This is ok because the FORCE argument is
+                // changed only if the caller wants a read-only book.
+            }
+            else
+            {
+                // Unknown error. Push it again on the error stack.
+                qof_backend_set_error (&qof_be, berror);
+            }
+        }
+    }
+
+}
+
+void
+GncXmlBackend::session_end()
+{
+    if (m_book && qof_book_is_readonly (m_book))
+    {
+        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        return;
+    }
+
+    if (!m_linkfile.empty())
+        g_unlink (m_linkfile.c_str());
+
+    if (m_lockfd > 0)
+        close (m_lockfd);
+
+    if (!m_lockfile.empty())
+    {
+        int rv;
+#ifdef G_OS_WIN32
+        /* On windows, we need to allow write-access before
+           g_unlink() can succeed */
+        rv = g_chmod (m_lockfile.c_str(), S_IWRITE | S_IREAD);
+#endif
+        rv = g_unlink (m_lockfile.c_str());
+        if (rv)
+        {
+            PWARN ("Error on g_unlink(%s): %d: %s", m_lockfile.c_str(),
+                   errno, g_strerror (errno) ? g_strerror (errno) : "");
+        }
+    }
+
+    m_dirname.clear();
+    m_fullpath.clear();
+    m_lockfile.clear();
+    m_linkfile.clear();
+}
+
+static QofBookFileType
+determine_file_type (const std::string& path)
+{
+    gboolean with_encoding;
+    QofBookFileType v2type;
+
+    v2type = gnc_is_xml_data_file_v2 (path.c_str(), &with_encoding);
+    if (v2type == GNC_BOOK_XML2_FILE)
+    {
+        if (with_encoding)
+        {
+            return GNC_BOOK_XML2_FILE;
+        }
+        else
+        {
+            return GNC_BOOK_XML2_FILE_NO_ENCODING;
+        }
+    }
+    else if (v2type == GNC_BOOK_POST_XML2_0_0_FILE)
+    {
+        return GNC_BOOK_POST_XML2_0_0_FILE;
+    }
+    else if (v2type == GNC_BOOK_XML1_FILE)
+    {
+        return GNC_BOOK_XML1_FILE;
+    }
+    return GNC_BOOK_NOT_OURS;
+}
+
+void
+GncXmlBackend::load(QofBook* book, QofBackendLoadType loadType)
+{
+
+    QofBackendError error;
+
+    if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
+
+    error = ERR_BACKEND_NO_ERR;
+    m_book = book;
+
+    int rc;
+    switch (determine_file_type (m_fullpath))
+    {
+    case GNC_BOOK_XML2_FILE:
+        rc = qof_session_load_from_xml_file_v2 (this, book,
+                                                     GNC_BOOK_XML2_FILE);
+        if (rc == FALSE)
+        {
+            PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
+            error = ERR_FILEIO_PARSE_ERROR;
+        }
+        break;
+
+    case GNC_BOOK_XML2_FILE_NO_ENCODING:
+        error = ERR_FILEIO_NO_ENCODING;
+        PWARN ("No character encoding in Xml File %s", m_fullpath.c_str());
+        break;
+    case GNC_BOOK_XML1_FILE:
+        rc = qof_session_load_from_xml_file (book, m_fullpath.c_str());
+        if (rc == FALSE)
+        {
+            PWARN ("Syntax error in Xml File %s", m_fullpath.c_str());
+            error = ERR_FILEIO_PARSE_ERROR;
+        }
+        break;
+    case GNC_BOOK_POST_XML2_0_0_FILE:
+        error = ERR_BACKEND_TOO_NEW;
+        PWARN ("Version of Xml file %s is newer than what we can read",
+               m_fullpath.c_str());
+        break;
+    default:
+        /* If file type wasn't known, check errno again to give the
+        user some more useful feedback for some particular error
+        conditions. */
+        switch (errno)
+        {
+        case EACCES: /* No read permission */
+            PWARN ("No read permission to file");
+            error = ERR_FILEIO_FILE_EACCES;
+            break;
+        case EISDIR: /* File is a directory - but on this error we don't arrive here */
+            PWARN ("Filename is a directory");
+            error = ERR_FILEIO_FILE_NOT_FOUND;
+            break;
+        default:
+            PWARN ("File not any known type");
+            error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
+            break;
+        }
+        break;
+    }
+
+    if (error != ERR_BACKEND_NO_ERR)
+    {
+        qof_backend_set_error (&qof_be, error);
+    }
+
+    /* We just got done loading, it can't possibly be dirty !! */
+    qof_book_mark_session_saved (book);
+}
+
+void
+GncXmlBackend::sync(QofBook* book)
+{
+        /* We make an important assumption here, that we might want to change
+     * in the future: when the user says 'save', we really save the one,
+     * the only, the current open book, and nothing else. In any case the plans
+     * for multiple books have been removed in the meantime and there is just one
+     * book, no more.
+     */
+    if (m_book == nullptr) m_book = book;
+    if (book != m_book) return;
+
+    if (qof_book_is_readonly (m_book))
+    {
+        /* Are we read-only? Don't continue in this case. */
+        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        return;
+    }
+
+    write_to_file (true);
+    remove_old_files();
+}
+
+bool
+GncXmlBackend::save_may_clobber_data()
+{
+    if (m_fullpath.empty())
+        return false;
+    struct stat statbuf;
+    auto rc = g_stat (m_fullpath.c_str(), &statbuf);
+    return rc != 0;
+
+}
+
+bool
+GncXmlBackend::write_to_file (bool make_backup)
+{
+    QofBackendError be_err;
+
+    ENTER (" book=%p file=%s", m_book, m_fullpath.c_str());
+
+    if (m_book && qof_book_is_readonly (m_book))
+    {
+        /* Are we read-only? Don't continue in this case. */
+        qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+        LEAVE ("");
+        return FALSE;
+    }
+
+    /* If the book is 'clean', recently saved, then don't save again. */
+    /* XXX this is currently broken due to faulty 'Save As' logic. */
+    /* if (FALSE == qof_book_session_not_saved (book)) return FALSE; */
+
+
+    auto tmp_name = g_new (char, strlen (m_fullpath.c_str()) + 12);
+    strcpy (tmp_name, m_fullpath.c_str());
+    strcat (tmp_name, ".tmp-XXXXXX");
+
+    if (!mktemp (tmp_name))
+    {
+        qof_backend_set_error (&qof_be, ERR_BACKEND_MISC);
+        qof_backend_set_message (&qof_be, "Failed to make temp file");
+        LEAVE ("");
+        return FALSE;
+    }
+
+    if (make_backup)
+    {
+        if (!backup_file ())
+        {
+            LEAVE ("");
+            return FALSE;
+        }
+    }
+
+    if (gnc_book_write_to_xml_file_v2 (m_book, tmp_name,
+                                       gnc_prefs_get_file_save_compressed ()))
+    {
+        /* Record the file's permissions before g_unlinking it */
+        struct stat statbuf;
+        auto rc = g_stat (m_fullpath.c_str(), &statbuf);
+        if (rc == 0)
+        {
+            /* We must never chmod the file /dev/null */
+            g_assert (g_strcmp0 (tmp_name, "/dev/null") != 0);
+
+            /* Use the permissions from the original data file */
+            if (g_chmod (tmp_name, statbuf.st_mode) != 0)
+            {
+                /* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
+                /* qof_backend_set_message(&qof_be, "Failed to chmod filename %s", tmp_name ); */
+                /* Even if the chmod did fail, the save
+                   nevertheless completed successfully. It is
+                   therefore wrong to signal the ERR_BACKEND_PERM
+                   error here which implies that the saving itself
+                   failed. Instead, we simply ignore this. */
+                PWARN ("unable to chmod filename %s: %s",
+                       tmp_name ? tmp_name : "(null)",
+                       g_strerror (errno) ? g_strerror (errno) : "");
+#if VFAT_DOESNT_SUCK  /* chmod always fails on vfat/samba fs */
+                /* g_free(tmp_name); */
+                /* return FALSE; */
+#endif
+            }
+#ifdef HAVE_CHOWN
+            /* Don't try to change the owner. Only root can do
+               that. */
+            if (chown (tmp_name, -1, statbuf.st_gid) != 0)
+            {
+                /* qof_backend_set_error(&qof_be, ERR_BACKEND_PERM); */
+                /* qof_backend_set_message(&qof_be, "Failed to chown filename %s", tmp_name ); */
+                /* A failed chown doesn't mean that the saving itself
+                failed. So don't abort with an error here! */
+                PWARN ("unable to chown filename %s: %s",
+                       tmp_name ? tmp_name : "(null)",
+                       strerror (errno) ? strerror (errno) : "");
+#if VFAT_DOESNT_SUCK /* chown always fails on vfat fs */
+                /* g_free(tmp_name);
+                return FALSE; */
+#endif
+            }
+#endif
+        }
+        if (g_unlink (m_fullpath.c_str()) != 0 && errno != ENOENT)
+        {
+            qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY);
+            PWARN ("unable to unlink filename %s: %s",
+                   m_fullpath.empty() ? "(null)" : m_fullpath.c_str(),
+                   g_strerror (errno) ? g_strerror (errno) : "");
+            g_free (tmp_name);
+            LEAVE ("");
+            return FALSE;
+        }
+        if (!link_or_make_backup (tmp_name, m_fullpath))
+        {
+            qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
+            qof_backend_set_message (&qof_be, "Failed to make backup file %s",
+                                     m_fullpath.empty() ? "NULL" : m_fullpath.c_str());
+            g_free (tmp_name);
+            LEAVE ("");
+            return FALSE;
+        }
+        if (g_unlink (tmp_name) != 0)
+        {
+            qof_backend_set_error (&qof_be, ERR_BACKEND_PERM);
+            PWARN ("unable to unlink temp filename %s: %s",
+                   tmp_name ? tmp_name : "(null)",
+                   g_strerror (errno) ? g_strerror (errno) : "");
+            g_free (tmp_name);
+            LEAVE ("");
+            return FALSE;
+        }
+        g_free (tmp_name);
+
+        /* Since we successfully saved the book,
+         * we should mark it clean. */
+        qof_book_mark_session_saved (m_book);
+        LEAVE (" successful save of book=%p to file=%s", m_book,
+               m_fullpath.c_str());
+        return TRUE;
+    }
+    else
+    {
+        if (g_unlink (tmp_name) != 0)
+        {
+            switch (errno)
+            {
+            case ENOENT:     /* tmp_name doesn't exist?  Assume "RO" error */
+            case EACCES:
+            case EPERM:
+            case ENOSYS:
+            case EROFS:
+                be_err = ERR_BACKEND_READONLY;
+                break;
+            default:
+                be_err = ERR_BACKEND_MISC;
+                break;
+            }
+            qof_backend_set_error (&qof_be, be_err);
+            PWARN ("unable to unlink temp_filename %s: %s",
+                   tmp_name ? tmp_name : "(null)",
+                   g_strerror (errno) ? g_strerror (errno) : "");
+            /* already in an error just flow on through */
+        }
+        else
+        {
+            /* Use a generic write error code */
+            qof_backend_set_error (&qof_be, ERR_FILEIO_WRITE_ERROR);
+            qof_backend_set_message (&qof_be, "Unable to write to temp file %s",
+                                     tmp_name ? tmp_name : "NULL");
+        }
+        g_free (tmp_name);
+        LEAVE ("");
+        return FALSE;
+    }
+    LEAVE ("");
+    return TRUE;
+}
+
+static bool
+copy_file (const std::string& orig, const std::string& bkup)
+{
+    constexpr size_t buf_size = 1024;
+    char buf[buf_size];
+    int flags = 0;
+    ssize_t count_write;
+    ssize_t count_read;
+
+
+#ifdef G_OS_WIN32
+    flags = O_BINARY;
+#endif
+
+    auto orig_fd = g_open (orig.c_str(), O_RDONLY | flags, 0);
+    if (orig_fd == -1)
+    {
+        return false;
+    }
+    auto bkup_fd = g_open (bkup.c_str(),
+                           O_WRONLY | O_CREAT | O_TRUNC | O_EXCL | flags, 0600);
+    if (bkup_fd == -1)
+    {
+        close (orig_fd);
+        return FALSE;
+    }
+
+    do
+    {
+        auto count_read = read (orig_fd, buf, buf_size);
+        if (count_read == -1 && errno != EINTR)
+        {
+            close (orig_fd);
+            close (bkup_fd);
+            return FALSE;
+        }
+
+        if (count_read > 0)
+        {
+            count_write = write (bkup_fd, buf, count_read);
+            if (count_write == -1)
+            {
+                close (orig_fd);
+                close (bkup_fd);
+                return FALSE;
+            }
+        }
+    }
+    while (count_read > 0);
+
+    close (orig_fd);
+    close (bkup_fd);
+
+    return TRUE;
+}
+
+bool
+GncXmlBackend::link_or_make_backup (const std::string& orig,
+                                    const std::string& bkup)
+{
+    gboolean copy_success = FALSE;
+    int err_ret =
+#ifdef HAVE_LINK
+        link (orig.c_str(), bkup.c_str())
+#else
+        - 1
+#endif
+        ;
+    if (err_ret != 0)
+    {
+#ifdef HAVE_LINK
+        if (errno == EPERM || errno == ENOSYS
+# ifdef EOPNOTSUPP
+            || errno == EOPNOTSUPP
+# endif
+# ifdef ENOTSUP
+            || errno == ENOTSUP
+# endif
+# ifdef ENOSYS
+            || errno == ENOSYS
+# endif
+           )
+#endif
+        {
+            copy_success = copy_file (orig.c_str(), bkup);
+        }
+
+        if (!copy_success)
+        {
+            qof_backend_set_error (&qof_be, ERR_FILEIO_BACKUP_ERROR);
+            PWARN ("unable to make file backup from %s to %s: %s",
+                   orig.c_str(), bkup.c_str(), g_strerror (errno) ? g_strerror (errno) : "");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+GncXmlBackend::get_file_lock ()
+{
+    struct stat statbuf;
+#ifndef G_OS_WIN32
+    char* pathbuf = NULL, *tmpbuf = NULL;
+    size_t pathbuf_size = 0;
+#endif
+    QofBackendError be_err;
+
+    auto rc = g_stat (m_lockfile.c_str(), &statbuf);
+    if (!rc)
+    {
+        /* oops .. file is locked by another user  .. */
+        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        return false;
+    }
+
+    m_lockfd = g_open (m_lockfile.c_str(), O_RDWR | O_CREAT | O_EXCL ,
+                         S_IRUSR | S_IWUSR);
+    if (m_lockfd < 0)
+    {
+        /* oops .. we can't create the lockfile .. */
+        switch (errno)
+        {
+        case EACCES:
+        case EROFS:
+        case ENOSPC:
+            PWARN ("Unable to create the lockfile %s; may not have write priv",
+                   m_lockfile.c_str());
+            be_err = ERR_BACKEND_READONLY;
+            break;
+        default:
+            be_err = ERR_BACKEND_LOCKED;
+            break;
+        }
+        qof_backend_set_error (&qof_be, be_err);
+        return false;
+    }
+
+    /* OK, now work around some NFS atomic lock race condition
+     * mumbo-jumbo.  We do this by linking a unique file, and
+     * then examining the link count.  At least that's what the
+     * NFS programmers guide suggests.
+     * Note: the "unique filename" must be unique for the
+     * triplet filename-host-process, otherwise accidental
+     * aliases can occur.
+     */
+
+    /* apparently, even this code may not work for some NFS
+     * implementations. In the long run, I am told that
+     * ftp.debian.org
+     *  /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
+     * provides a better long-term solution.
+     */
+
+#ifndef G_OS_WIN32
+    auto path = m_lockfile.find_last_of('.');
+    std::stringstream linkfile;
+    if (path != std::string::npos)
+        linkfile << m_lockfile.substr(0, path);
+    else
+        linkfile << m_lockfile;
+    linkfile << "." << gethostid() << "." << getpid() << ".LNK";
+    rc = link (m_lockfile.c_str(), linkfile.str().c_str());
+    if (rc)
+    {
+        /* If hard links aren't supported, just allow the lock. */
+        if (errno == EPERM || errno == ENOSYS
+# ifdef EOPNOTSUPP
+            || errno == EOPNOTSUPP
+# endif
+# ifdef ENOTSUP
+            || errno == ENOTSUP
+# endif
+           )
+        {
+            return true;
+        }
+
+        /* Otherwise, something else is wrong. */
+        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        g_unlink (linkfile.str().c_str());
+        close (m_lockfd);
+        g_unlink (m_lockfile.c_str());
+        return false;
+    }
+
+    rc = g_stat (m_lockfile.c_str(), &statbuf);
+    if (rc)
+    {
+        /* oops .. stat failed!  This can't happen! */
+        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        qof_backend_set_message (&qof_be, "Failed to stat lockfile %s",
+                                 m_lockfile.c_str());
+        g_unlink (linkfile.str().c_str());
+        close (m_lockfd);
+        g_unlink (m_lockfile.c_str());
+        return false;
+    }
+
+    if (statbuf.st_nlink != 2)
+    {
+        qof_backend_set_error (&qof_be, ERR_BACKEND_LOCKED);
+        g_unlink (linkfile.str().c_str());
+        close (m_lockfd);
+        g_unlink (m_lockfile.c_str());
+        return false;
+    }
+
+    m_linkfile = linkfile.str();
+    return true;
+
+#else /* ifndef G_OS_WIN32 */
+    /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
+       is sufficient for locking. */
+    return true;
+#endif /* ifndef G_OS_WIN32 */
+}
+
+bool
+GncXmlBackend::backup_file()
+{
+    struct stat statbuf;
+
+    auto datafile = m_fullpath.c_str();
+
+    auto rc = g_stat (datafile, &statbuf);
+    if (rc)
+        return (errno == ENOENT);
+
+    if (determine_file_type (m_fullpath) == GNC_BOOK_BIN_FILE)
+    {
+        /* make a more permanent safer backup */
+        auto bin_bkup = m_fullpath + "-binfmt.bkup";
+        auto bkup_ret = link_or_make_backup (m_fullpath, bin_bkup);
+        if (!bkup_ret)
+        {
+            return false;
+        }
+    }
+
+    auto timestamp = gnc_date_timestamp ();
+    auto backup = m_fullpath + "." + timestamp + GNC_DATAFILE_EXT;
+    g_free (timestamp);
+
+    return link_or_make_backup (datafile, backup);
+}
+
+/*
+ * Clean up any lock files from prior crashes, and clean up old
+ * backup and log files.
+ */
+
+void
+GncXmlBackend::remove_old_files ()
+{
+    struct stat lockstatbuf, statbuf;
+
+    if (g_stat (m_lockfile.c_str(), &lockstatbuf) != 0)
+        return;
+
+    auto dir = g_dir_open (m_dirname.c_str(), 0, NULL);
+    if (!dir)
+        return;
+
+    auto now = gnc_time (NULL);
+    const char* dent;
+    while ((dent = g_dir_read_name (dir)) != NULL)
+    {
+        gchar* name;
+
+        /* Ensure we only evaluate GnuCash related files. */
+        if (! (g_str_has_suffix (dent, ".LNK") ||
+               g_str_has_suffix (dent, ".xac") /* old data file extension */ ||
+               g_str_has_suffix (dent, GNC_DATAFILE_EXT) ||
+               g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
+            continue;
+
+        name = g_build_filename (m_dirname.c_str(), dent, (gchar*)NULL);
+
+        /* Only evaluate files associated with the current data file. */
+        if (!g_str_has_prefix (name, m_fullpath.c_str()))
+        {
+            g_free (name);
+            continue;
+        }
+
+        /* Never remove the current data file itself */
+        if (g_strcmp0 (name, m_fullpath.c_str()) == 0)
+        {
+            g_free (name);
+            continue;
+        }
+
+        /* Test if the current file is a lock file */
+        if (g_str_has_suffix (name, ".LNK"))
+        {
+            /* Is a lock file. Skip the active lock file */
+            if ((g_strcmp0 (name, m_linkfile.c_str()) != 0) &&
+                /* Only delete lock files older than the active one */
+                (g_stat (name, &statbuf) == 0) &&
+                (statbuf.st_mtime < lockstatbuf.st_mtime))
+            {
+                PINFO ("remove stale lock file: %s", name);
+                g_unlink (name);
+            }
+
+            g_free (name);
+            continue;
+        }
+
+        /* At this point we're sure the file's name is in one of these forms:
+         * <fullpath/to/datafile><anything>.gnucash
+         * <fullpath/to/datafile><anything>.xac
+         * <fullpath/to/datafile><anything>.log
+         *
+         * To be a file generated by GnuCash, the <anything> part should consist
+         * of 1 dot followed by 14 digits (0 to 9). Let's test this with a
+         * regular expression.
+         */
+        {
+            /* Find the start of the date stamp. This takes some pointer
+             * juggling, but considering the above tests, this should always
+             * be safe */
+            regex_t pattern;
+            gchar* stamp_start = name + strlen (m_fullpath.c_str());
+            gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
+                                                 GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
+            gboolean got_date_stamp = FALSE;
+
+            if (regcomp (&pattern, expression, REG_EXTENDED | REG_ICASE) != 0)
+                PWARN ("Cannot compile regex for date stamp");
+            else if (regexec (&pattern, stamp_start, 0, NULL, 0) == 0)
+                got_date_stamp = TRUE;
+
+            regfree (&pattern);
+            g_free (expression);
+
+            if (!got_date_stamp) /* Not a gnucash created file after all... */
+            {
+                g_free (name);
+                continue;
+            }
+        }
+
+        /* The file is a backup or log file. Check the user's retention preference
+         * to determine if we should keep it or not
+         */
+        if (gnc_prefs_get_file_retention_policy () == XML_RETAIN_NONE)
+        {
+            PINFO ("remove stale file: %s  - reason: preference XML_RETAIN_NONE", name);
+            g_unlink (name);
+        }
+        else if ((gnc_prefs_get_file_retention_policy () == XML_RETAIN_DAYS) &&
+                 (gnc_prefs_get_file_retention_days () > 0))
+        {
+            int days;
+
+            /* Is the backup file old enough to delete */
+            if (g_stat (name, &statbuf) != 0)
+            {
+                g_free (name);
+                continue;
+            }
+            days = (int) (difftime (now, statbuf.st_mtime) / 86400);
+
+            PINFO ("file retention = %d days", gnc_prefs_get_file_retention_days ());
+            if (days >= gnc_prefs_get_file_retention_days ())
+            {
+                PINFO ("remove stale file: %s  - reason: more than %d days old", name, days);
+                g_unlink (name);
+            }
+        }
+        g_free (name);
+    }
+    g_dir_close (dir);
+}
diff --git a/src/backend/xml/gnc-xml-backend.hpp b/src/backend/xml/gnc-xml-backend.hpp
new file mode 100644
index 0000000..ab8c64b
--- /dev/null
+++ b/src/backend/xml/gnc-xml-backend.hpp
@@ -0,0 +1,69 @@
+/********************************************************************
+ * gnc-xml-backend.hpp: Declare XML file backend.                 *
+ * Copyright 2016 John Ralls <jralls at ceridwen.us>                   *
+ *                                                                  *
+ * This program 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 this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#ifndef __GNC_XML_BACKEND_HPP__
+#define __GNC_XML_BACKEND_HPP__
+
+extern "C"
+{
+#include <qof.h>
+#include <qofbackend-p.h>
+}
+
+#include <string>
+
+class GncXmlBackend
+{
+public:
+    GncXmlBackend();
+    GncXmlBackend(const GncXmlBackend&) = delete;
+    GncXmlBackend operator=(const GncXmlBackend&) = delete;
+    GncXmlBackend(const GncXmlBackend&&) = delete;
+    GncXmlBackend operator=(const GncXmlBackend&&) = delete;
+    ~GncXmlBackend();
+    void session_begin(QofSession* session, const char* book_id,
+                       bool ignore_lock, bool create, bool force);
+    void session_end();
+    void load(QofBook* book, QofBackendLoadType loadType);
+    /* The XML backend isn't able to do anything with individual instances. */
+    void begin(QofInstance* inst) {}
+    void commit(QofInstance* inst) {}
+    void rollback(QofInstance* inst) {}
+    void sync(QofBook* book);
+    QofBackend* get_qof_be() { return &qof_be; }
+    const char * get_filename() { return m_fullpath.c_str(); }
+    QofBook* get_book() { return m_book; }
+    
+private:
+    bool save_may_clobber_data();
+    bool get_file_lock();
+    bool link_or_make_backup(const std::string& orig, const std::string& bkup);
+    bool backup_file();
+    bool write_to_file(bool make_backup);
+    void remove_old_files();
+    void write_accounts(QofBook* book);
+    QofBackend qof_be;
+
+    std::string m_dirname;
+    std::string m_fullpath;  /* Fully qualified path to book */
+    std::string m_lockfile;
+    std::string m_linkfile;
+    int m_lockfd;
+
+    QofBook* m_book;  /* The primary, main open book */
+};
+#endif // __GNC_XML_BACKEND_HPP__
diff --git a/src/backend/xml/io-gncxml-v2.cpp b/src/backend/xml/io-gncxml-v2.cpp
index 2735238..d58e822 100644
--- a/src/backend/xml/io-gncxml-v2.cpp
+++ b/src/backend/xml/io-gncxml-v2.cpp
@@ -66,6 +66,7 @@ extern "C"
 #endif
 }
 
+#include "gnc-xml-backend.hpp"
 #include "sixtp-parsers.h"
 #include "sixtp-utils.h"
 #include "gnc-xml.h"
@@ -692,7 +693,7 @@ gnc_sixtp_gdv2_new (
 
 static gboolean
 qof_session_load_from_xml_file_v2_full (
-    XmlBackend* xml_be, QofBook* book,
+    GncXmlBackend* xml_be, QofBook* book,
     sixtp_push_handler push_handler, gpointer push_user_data,
     QofBookFileType type)
 {
@@ -793,7 +794,7 @@ qof_session_load_from_xml_file_v2_full (
          * https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more
          * info.
          */
-        gchar* filename = xml_be->m_fullpath;
+         const char* filename = xml_be->get_filename();
         FILE* file;
         gboolean is_compressed = is_gzipped_file (filename);
         file = try_gz_open (filename, "r", is_compressed, FALSE);
@@ -864,7 +865,7 @@ bail:
 }
 
 gboolean
-qof_session_load_from_xml_file_v2 (XmlBackend* xml_be, QofBook* book,
+qof_session_load_from_xml_file_v2 (GncXmlBackend* xml_be, QofBook* book,
                                    QofBookFileType type)
 {
     return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
@@ -2031,7 +2032,7 @@ cleanup_find_ambs:
 
 typedef struct
 {
-    gchar* filename;
+    const char* filename;
     GHashTable* subst;
 } push_data_type;
 
@@ -2168,13 +2169,13 @@ cleanup_push_handler:
 }
 
 gboolean
-gnc_xml2_parse_with_subst (XmlBackend* xml_be, QofBook* book, GHashTable* subst)
+gnc_xml2_parse_with_subst (GncXmlBackend* xml_be, QofBook* book, GHashTable* subst)
 {
     push_data_type* push_data;
     gboolean success;
 
     push_data = g_new (push_data_type, 1);
-    push_data->filename = xml_be->m_fullpath;
+    push_data->filename = xml_be->get_filename();
     push_data->subst = subst;
 
     success = qof_session_load_from_xml_file_v2_full (
diff --git a/src/backend/xml/io-gncxml-v2.h b/src/backend/xml/io-gncxml-v2.h
index 8948b05..ccfb4b3 100644
--- a/src/backend/xml/io-gncxml-v2.h
+++ b/src/backend/xml/io-gncxml-v2.h
@@ -43,7 +43,7 @@ extern "C"
 #include "sixtp.h"
 #include <vector>
 
-class XmlBackend;
+class GncXmlBackend;
 
 /**
  * Struct used to pass in a new data type for XML storage.  This contains
@@ -85,7 +85,7 @@ typedef struct
 } gnc_template_xaction_data;
 
 /** read in an account group from a file */
-gboolean qof_session_load_from_xml_file_v2 (XmlBackend*, QofBook*,
+gboolean qof_session_load_from_xml_file_v2 (GncXmlBackend*, QofBook*,
                                             QofBookFileType);
 
 /* write all book info to a file */
@@ -155,7 +155,7 @@ gint gnc_xml2_find_ambiguous (
  * @param subst hash table with keys and values of type gchar*
  */
 gboolean gnc_xml2_parse_with_subst (
-    XmlBackend* xml_be, QofBook* book, GHashTable* subst);
+    GncXmlBackend* xml_be, QofBook* book, GHashTable* subst);
 #ifdef __cplusplus
 }
 typedef struct

commit 90a95204156ea52196cb9702b127ebcb9ac03c11
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Nov 20 13:17:23 2016 -0800

    Change struct FileBackend to class XmlBackend and rename all variables.
    
    For consistency and clarity: all XmlBackend* are xml_be, all QofBackend*
    are qof_be.

diff --git a/src/backend/xml/gnc-backend-xml.cpp b/src/backend/xml/gnc-backend-xml.cpp
index 40ab3cf..ab9e353 100644
--- a/src/backend/xml/gnc-backend-xml.cpp
+++ b/src/backend/xml/gnc-backend-xml.cpp
@@ -105,7 +105,7 @@ extern "C"
 
 static QofLogModule log_module = GNC_MOD_BACKEND;
 
-static gboolean save_may_clobber_data (FileBackend *bend);
+static gboolean save_may_clobber_data (XmlBackend *xml_be);
 
 struct QofXmlBackendProvider : public QofBackendProvider
 {
@@ -124,7 +124,7 @@ struct QofXmlBackendProvider : public QofBackendProvider
 /* ================================================================= */
 
 static gboolean
-gnc_xml_be_get_file_lock (FileBackend* be)
+gnc_xml_be_get_file_lock (XmlBackend* xml_be)
 {
     struct stat statbuf;
 #ifndef G_OS_WIN32
@@ -134,17 +134,17 @@ gnc_xml_be_get_file_lock (FileBackend* be)
     int rc;
     QofBackendError be_err;
 
-    rc = g_stat (be->lockfile, &statbuf);
+    rc = g_stat (xml_be->m_lockfile, &statbuf);
     if (!rc)
     {
         /* oops .. file is locked by another user  .. */
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
+        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
         return FALSE;
     }
 
-    be->lockfd = g_open (be->lockfile, O_RDWR | O_CREAT | O_EXCL ,
+    xml_be->m_lockfd = g_open (xml_be->m_lockfile, O_RDWR | O_CREAT | O_EXCL ,
                          S_IRUSR | S_IWUSR);
-    if (be->lockfd < 0)
+    if (xml_be->m_lockfd < 0)
     {
         /* oops .. we can't create the lockfile .. */
         switch (errno)
@@ -153,14 +153,14 @@ gnc_xml_be_get_file_lock (FileBackend* be)
         case EROFS:
         case ENOSPC:
             PWARN ("Unable to create the lockfile %s; may not have write priv",
-                   be->lockfile);
+                   xml_be->m_lockfile);
             be_err = ERR_BACKEND_READONLY;
             break;
         default:
             be_err = ERR_BACKEND_LOCKED;
             break;
         }
-        qof_backend_set_error ((QofBackend*)be, be_err);
+        qof_backend_set_error ((QofBackend*)xml_be, be_err);
         return FALSE;
     }
 
@@ -181,13 +181,13 @@ gnc_xml_be_get_file_lock (FileBackend* be)
      */
 
 #ifndef G_OS_WIN32
-    pathbuf_size = strlen (be->lockfile) + 100;
+    pathbuf_size = strlen (xml_be->m_lockfile) + 100;
     pathbuf = (char*) malloc (pathbuf_size);
     if (pathbuf == NULL)
     {
         return FALSE;
     }
-    strcpy (pathbuf, be->lockfile);
+    strcpy (pathbuf, xml_be->m_lockfile);
     path = strrchr (pathbuf, '.');
     while (snprintf (path, pathbuf_size - (path - pathbuf), ".%lx.%d.LNK",
                      gethostid (), getpid ())
@@ -206,7 +206,7 @@ gnc_xml_be_get_file_lock (FileBackend* be)
         }
     }
 
-    rc = link (be->lockfile, pathbuf);
+    rc = link (xml_be->m_lockfile, pathbuf);
     if (rc)
     {
         /* If hard links aren't supported, just allow the lock. */
@@ -219,45 +219,45 @@ gnc_xml_be_get_file_lock (FileBackend* be)
 # endif
            )
         {
-            be->linkfile = NULL;
+            xml_be->m_linkfile = NULL;
             free (pathbuf);
             return TRUE;
         }
 
         /* Otherwise, something else is wrong. */
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
+        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
         g_unlink (pathbuf);
         free (pathbuf);
-        close (be->lockfd);
-        g_unlink (be->lockfile);
+        close (xml_be->m_lockfd);
+        g_unlink (xml_be->m_lockfile);
         return FALSE;
     }
 
-    rc = g_stat (be->lockfile, &statbuf);
+    rc = g_stat (xml_be->m_lockfile, &statbuf);
     if (rc)
     {
         /* oops .. stat failed!  This can't happen! */
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
-        qof_backend_set_message ((QofBackend*)be, "Failed to stat lockfile %s",
-                                 be->lockfile);
+        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
+        qof_backend_set_message ((QofBackend*)xml_be, "Failed to stat lockfile %s",
+                                 xml_be->m_lockfile);
         g_unlink (pathbuf);
         free (pathbuf);
-        close (be->lockfd);
-        g_unlink (be->lockfile);
+        close (xml_be->m_lockfd);
+        g_unlink (xml_be->m_lockfile);
         return FALSE;
     }
 
     if (statbuf.st_nlink != 2)
     {
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_LOCKED);
+        qof_backend_set_error ((QofBackend*)xml_be, ERR_BACKEND_LOCKED);
         g_unlink (pathbuf);
         free (pathbuf);
-        close (be->lockfd);
-        g_unlink (be->lockfile);
+        close (xml_be->m_lockfd);
+        g_unlink (xml_be->m_lockfile);
         return FALSE;
     }
 
-    be->linkfile = g_strdup (pathbuf);
+    xml_be->m_linkfile = g_strdup (pathbuf);
     free (pathbuf);
 
     return TRUE;
@@ -265,7 +265,7 @@ gnc_xml_be_get_file_lock (FileBackend* be)
 #else /* ifndef G_OS_WIN32 */
     /* On windows, there is no NFS and the open(,O_CREAT | O_EXCL)
        is sufficient for locking. */
-    be->linkfile = NULL;
+    xml_be->m_linkfile = NULL;
     return TRUE;
 #endif /* ifndef G_OS_WIN32 */
 }
@@ -275,40 +275,40 @@ gnc_xml_be_get_file_lock (FileBackend* be)
 #define FILE_URI_PREFIX "file://"
 
 static void
-xml_session_begin (QofBackend* be_start, QofSession* session,
+xml_session_begin (QofBackend* qof_be, QofSession* session,
                    const char* book_id, gboolean ignore_lock,
                    gboolean create, gboolean force)
 {
-    FileBackend* be = (FileBackend*) be_start;
+    XmlBackend* xml_be = (XmlBackend*) qof_be;
 
     ENTER (" ");
 
     /* Make sure the directory is there */
-    be->fullpath = gnc_uri_get_path (book_id);
+    xml_be->m_fullpath = gnc_uri_get_path (book_id);
 
-    if (NULL == be->fullpath)
+    if (NULL == xml_be->m_fullpath)
     {
-        qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
-        qof_backend_set_message (be_start, "No path specified");
+        qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+        qof_backend_set_message (qof_be, "No path specified");
         LEAVE ("");
         return;
     }
-    if (create && !force && save_may_clobber_data (be))
+    if (create && !force && save_may_clobber_data (xml_be))
     {
-        qof_backend_set_error (be_start, ERR_BACKEND_STORE_EXISTS);
+        qof_backend_set_error (qof_be, ERR_BACKEND_STORE_EXISTS);
         LEAVE ("Might clobber, no force");
         return;
     }
 
-    be->be.fullpath = be->fullpath;
-    be->dirname = g_path_get_dirname (be->fullpath);
+    xml_be->be.fullpath = xml_be->m_fullpath;
+    xml_be->m_dirname = g_path_get_dirname (xml_be->m_fullpath);
 
     {
         struct stat statbuf;
         int rc;
 
         /* Again check whether the directory can be accessed */
-        rc = g_stat (be->dirname, &statbuf);
+        rc = g_stat (xml_be->m_dirname, &statbuf);
         if (rc != 0
 #if COMPILER(MSVC)
             || (statbuf.st_mode & _S_IFDIR) == 0
@@ -319,30 +319,31 @@ xml_session_begin (QofBackend* be_start, QofSession* session,
         {
             /* Error on stat or if it isn't a directory means we
                cannot find this filename */
-            qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
-            qof_backend_set_message (be_start, "Couldn't find directory for %s",
-                                     be->fullpath);
-            PWARN ("Couldn't find directory for %s", be->fullpath);
-            g_free (be->fullpath);
-            be->fullpath = NULL;
-            g_free (be->dirname);
-            be->dirname = NULL;
+            qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+            qof_backend_set_message (qof_be, "Couldn't find directory for %s",
+                                     xml_be->m_fullpath);
+            PWARN ("Couldn't find directory for %s", xml_be->m_fullpath);
+            g_free (xml_be->m_fullpath);
+            xml_be->m_fullpath = NULL;
+            g_free (xml_be->m_dirname);
+            xml_be->m_dirname = NULL;
             LEAVE ("");
             return;
         }
 
         /* Now check whether we can g_stat the file itself */
-        rc = g_stat (be->fullpath, &statbuf);
+        rc = g_stat (xml_be->m_fullpath, &statbuf);
         if ((rc != 0) && (!create))
         {
             /* Error on stat means the file doesn't exist */
-            qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
-            qof_backend_set_message (be_start, "Couldn't find %s", be->fullpath);
-            PWARN ("Couldn't find %s", be->fullpath);
-            g_free (be->fullpath);
-            be->fullpath = NULL;
-            g_free (be->dirname);
-            be->dirname = NULL;
+            qof_backend_set_error (qof_be, ERR_FILEIO_FILE_NOT_FOUND);
+            qof_backend_set_message (qof_be, "Couldn't find %s",
+                                     xml_be->m_fullpath);
+            PWARN ("Couldn't find %s", xml_be->m_fullpath);
+            g_free (xml_be->m_fullpath);
+            xml_be->m_fullpath = NULL;
+            g_free (xml_be->m_dirname);
+            xml_be->m_dirname = NULL;
             LEAVE ("");
             return;
         }
@@ -354,14 +355,14 @@ xml_session_begin (QofBackend* be_start, QofSession* session,
 #endif
            )
         {
-            qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
-            qof_backend_set_message (be_start, "Path %s is a directory",
-                                     be->fullpath);
-            PWARN ("Path %s is a directory", be->fullpath);
-            g_free (be->fullpath);
-            be->fullpath = NULL;
-            g_free (be->dirname);
-            be->dirname = NULL;
+            qof_backend_set_error (qof_be, ERR_FILEIO_UNKNOWN_FILE_TYPE);
+            qof_backend_set_message (qof_be, "Path %s is a directory",
+                                     xml_be->m_fullpath);
+            PWARN ("Path %s is a directory", xml_be->m_fullpath);
+            g_free (xml_be->m_fullpath);
+            xml_be->m_fullpath = NULL;
+            g_free (xml_be->m_dirname);
+            xml_be->m_dirname = NULL;
             LEAVE ("");
             return;
         }
@@ -371,23 +372,23 @@ xml_session_begin (QofBackend* be_start, QofSession* session,
     /* ---------------------------------------------------- */
     /* We should now have a fully resolved path name.
      * Let's start logging */
-    xaccLogSetBaseName (be->fullpath);
-    PINFO ("logpath=%s", be->fullpath ? be->fullpath : "(null)");
+    xaccLogSetBaseName (xml_be->m_fullpath);
+    PINFO ("logpath=%s", xml_be->m_fullpath ? xml_be->m_fullpath : "(null)");
 
     /* And let's see if we can get a lock on it. */
-    be->lockfile = g_strconcat (be->fullpath, ".LCK", NULL);
+    xml_be->m_lockfile = g_strconcat (xml_be->m_fullpath, ".LCK", NULL);
 
-    if (!ignore_lock && !gnc_xml_be_get_file_lock (be))
+    if (!ignore_lock && !gnc_xml_be_get_file_lock (xml_be))
     {
         // We should not ignore the lock, but couldn't get it. The
         // be_get_file_lock() already set the appropriate backend_error in this
         // case, so we just return here.
-        g_free (be->lockfile);
-        be->lockfile = NULL;
+        g_free (xml_be->m_lockfile);
+        xml_be->m_lockfile = NULL;
 
         if (force)
         {
-            QofBackendError berror = qof_backend_get_error (be_start);
+            QofBackendError berror = qof_backend_get_error (qof_be);
             if (berror == ERR_BACKEND_LOCKED || berror == ERR_BACKEND_READONLY)
             {
                 // Even though we couldn't get the lock, we were told to force
@@ -397,7 +398,7 @@ xml_session_begin (QofBackend* be_start, QofSession* session,
             else
             {
                 // Unknown error. Push it again on the error stack.
-                qof_backend_set_error (be_start, berror);
+                qof_backend_set_error (qof_be, berror);
             }
         }
 
@@ -412,61 +413,61 @@ xml_session_begin (QofBackend* be_start, QofSession* session,
 /* ================================================================= */
 
 static void
-xml_session_end (QofBackend* be_start)
+xml_session_end (QofBackend* qof_be)
 {
-    FileBackend* be = (FileBackend*)be_start;
+    XmlBackend* xml_be = (XmlBackend*)qof_be;
     ENTER (" ");
 
-    if (be->book && qof_book_is_readonly (be->book))
+    if (xml_be->m_book && qof_book_is_readonly (xml_be->m_book))
     {
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY);
+        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
         return;
     }
 
-    if (be->linkfile)
-        g_unlink (be->linkfile);
+    if (xml_be->m_linkfile)
+        g_unlink (xml_be->m_linkfile);
 
-    if (be->lockfd > 0)
-        close (be->lockfd);
+    if (xml_be->m_lockfd > 0)
+        close (xml_be->m_lockfd);
 
-    if (be->lockfile)
+    if (xml_be->m_lockfile)
     {
         int rv;
 #ifdef G_OS_WIN32
         /* On windows, we need to allow write-access before
            g_unlink() can succeed */
-        rv = g_chmod (be->lockfile, S_IWRITE | S_IREAD);
+        rv = g_chmod (xml_be->m_lockfile, S_IWRITE | S_IREAD);
 #endif
-        rv = g_unlink (be->lockfile);
+        rv = g_unlink (xml_be->m_lockfile);
         if (rv)
         {
-            PWARN ("Error on g_unlink(%s): %d: %s", be->lockfile,
+            PWARN ("Error on g_unlink(%s): %d: %s", xml_be->m_lockfile,
                    errno, g_strerror (errno) ? g_strerror (errno) : "");
         }
     }
 
-    g_free (be->dirname);
-    be->dirname = NULL;
+    g_free (xml_be->m_dirname);
+    xml_be->m_dirname = NULL;
 
-    g_free (be->fullpath);
-    be->fullpath = NULL;
+    g_free (xml_be->m_fullpath);
+    xml_be->m_fullpath = NULL;
 
-    g_free (be->lockfile);
-    be->lockfile = NULL;
+    g_free (xml_be->m_lockfile);
+    xml_be->m_lockfile = NULL;
 
-    g_free (be->linkfile);
-    be->linkfile = NULL;
+    g_free (xml_be->m_linkfile);
+    xml_be->m_linkfile = NULL;
     LEAVE (" ");
 }
 
 static void
-xml_destroy_backend (QofBackend* be)
+xml_destroy_backend (QofBackend* qof_be)
 {
     /* Stop transaction logging */
     xaccLogSetBaseName (NULL);
 
-    qof_backend_destroy (be);
-    g_free (be);
+    qof_backend_destroy (qof_be);
+    g_free (qof_be);
 }
 
 /* ================================================================= */
@@ -541,7 +542,7 @@ copy_file (const char* orig, const char* bkup)
 /* ================================================================= */
 
 static gboolean
-gnc_int_link_or_make_backup (FileBackend* be, const char* orig,
+gnc_int_link_or_make_backup (XmlBackend* xml_be, const char* orig,
                              const char* bkup)
 {
     gboolean copy_success = FALSE;
@@ -573,7 +574,7 @@ gnc_int_link_or_make_backup (FileBackend* be, const char* orig,
 
         if (!copy_success)
         {
-            qof_backend_set_error ((QofBackend*)be, ERR_FILEIO_BACKUP_ERROR);
+            qof_backend_set_error ((QofBackend*)xml_be, ERR_FILEIO_BACKUP_ERROR);
             PWARN ("unable to make file backup from %s to %s: %s",
                    orig, bkup, g_strerror (errno) ? g_strerror (errno) : "");
             return FALSE;
@@ -672,7 +673,7 @@ det_exit:
 }
 
 static gboolean
-gnc_xml_be_backup_file (FileBackend* be)
+gnc_xml_be_backup_file (XmlBackend* xml_be)
 {
     gboolean bkup_ret;
     char* timestamp;
@@ -681,7 +682,7 @@ gnc_xml_be_backup_file (FileBackend* be)
     struct stat statbuf;
     int rc;
 
-    datafile = be->fullpath;
+    datafile = xml_be->m_fullpath;
 
     rc = g_stat (datafile, &statbuf);
     if (rc)
@@ -694,7 +695,7 @@ gnc_xml_be_backup_file (FileBackend* be)
         char* bin_bkup = g_new (char, strlen (datafile) + strlen (back) + 1);
         strcpy (bin_bkup, datafile);
         strcat (bin_bkup, back);
-        bkup_ret = gnc_int_link_or_make_backup (be, datafile, bin_bkup);
+        bkup_ret = gnc_int_link_or_make_backup (xml_be, datafile, bin_bkup);
         g_free (bin_bkup);
         if (!bkup_ret)
         {
@@ -706,7 +707,7 @@ gnc_xml_be_backup_file (FileBackend* be)
     backup = g_strconcat (datafile, ".", timestamp, GNC_DATAFILE_EXT, NULL);
     g_free (timestamp);
 
-    bkup_ret = gnc_int_link_or_make_backup (be, datafile, backup);
+    bkup_ret = gnc_int_link_or_make_backup (xml_be, datafile, backup);
     g_free (backup);
 
     return bkup_ret;
@@ -715,12 +716,12 @@ gnc_xml_be_backup_file (FileBackend* be)
 /* ================================================================= */
 
 static gboolean
-gnc_xml_be_write_to_file (FileBackend* fbe,
+gnc_xml_be_write_to_file (XmlBackend* xml_be,
                           QofBook* book,
                           const gchar* datafile,
                           gboolean make_backup)
 {
-    QofBackend* be = &fbe->be;
+    QofBackend* qof_be = &xml_be->be;
     char* tmp_name;
     struct stat statbuf;
     int rc;
@@ -731,7 +732,7 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
     if (book && qof_book_is_readonly (book))
     {
         /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY);
+        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
         LEAVE ("");
         return FALSE;
     }
@@ -746,15 +747,15 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
 
     if (!mktemp (tmp_name))
     {
-        qof_backend_set_error (be, ERR_BACKEND_MISC);
-        qof_backend_set_message (be, "Failed to make temp file");
+        qof_backend_set_error (qof_be, ERR_BACKEND_MISC);
+        qof_backend_set_message (qof_be, "Failed to make temp file");
         LEAVE ("");
         return FALSE;
     }
 
     if (make_backup)
     {
-        if (!gnc_xml_be_backup_file (fbe))
+        if (!gnc_xml_be_backup_file (xml_be))
         {
             LEAVE ("");
             return FALSE;
@@ -774,8 +775,8 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
             /* Use the permissions from the original data file */
             if (g_chmod (tmp_name, statbuf.st_mode) != 0)
             {
-                /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message( be, "Failed to chmod filename %s", tmp_name ); */
+                /* qof_backend_set_error(qof_be, ERR_BACKEND_PERM); */
+                /* qof_backend_set_message(qof_be, "Failed to chmod filename %s", tmp_name ); */
                 /* Even if the chmod did fail, the save
                    nevertheless completed successfully. It is
                    therefore wrong to signal the ERR_BACKEND_PERM
@@ -794,8 +795,8 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
                that. */
             if (chown (tmp_name, -1, statbuf.st_gid) != 0)
             {
-                /* qof_backend_set_error(be, ERR_BACKEND_PERM); */
-                /* qof_backend_set_message( be, "Failed to chown filename %s", tmp_name ); */
+                /* qof_backend_set_error(qof_be, ERR_BACKEND_PERM); */
+                /* qof_backend_set_message(qof_be, "Failed to chown filename %s", tmp_name ); */
                 /* A failed chown doesn't mean that the saving itself
                 failed. So don't abort with an error here! */
                 PWARN ("unable to chown filename %s: %s",
@@ -810,7 +811,7 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
         }
         if (g_unlink (datafile) != 0 && errno != ENOENT)
         {
-            qof_backend_set_error (be, ERR_BACKEND_READONLY);
+            qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
             PWARN ("unable to unlink filename %s: %s",
                    datafile ? datafile : "(null)",
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -818,10 +819,10 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
             LEAVE ("");
             return FALSE;
         }
-        if (!gnc_int_link_or_make_backup (fbe, tmp_name, datafile))
+        if (!gnc_int_link_or_make_backup (xml_be, tmp_name, datafile))
         {
-            qof_backend_set_error (be, ERR_FILEIO_BACKUP_ERROR);
-            qof_backend_set_message (be, "Failed to make backup file %s",
+            qof_backend_set_error (qof_be, ERR_FILEIO_BACKUP_ERROR);
+            qof_backend_set_message (qof_be, "Failed to make backup file %s",
                                      datafile ? datafile : "NULL");
             g_free (tmp_name);
             LEAVE ("");
@@ -829,7 +830,7 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
         }
         if (g_unlink (tmp_name) != 0)
         {
-            qof_backend_set_error (be, ERR_BACKEND_PERM);
+            qof_backend_set_error (qof_be, ERR_BACKEND_PERM);
             PWARN ("unable to unlink temp filename %s: %s",
                    tmp_name ? tmp_name : "(null)",
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -862,7 +863,7 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
                 be_err = ERR_BACKEND_MISC;
                 break;
             }
-            qof_backend_set_error (be, be_err);
+            qof_backend_set_error (qof_be, be_err);
             PWARN ("unable to unlink temp_filename %s: %s",
                    tmp_name ? tmp_name : "(null)",
                    g_strerror (errno) ? g_strerror (errno) : "");
@@ -871,8 +872,8 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
         else
         {
             /* Use a generic write error code */
-            qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR);
-            qof_backend_set_message (be, "Unable to write to temp file %s",
+            qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
+            qof_backend_set_message (qof_be, "Unable to write to temp file %s",
                                      tmp_name ? tmp_name : "NULL");
         }
         g_free (tmp_name);
@@ -891,17 +892,17 @@ gnc_xml_be_write_to_file (FileBackend* fbe,
  */
 
 static void
-gnc_xml_be_remove_old_files (FileBackend* be)
+gnc_xml_be_remove_old_files (XmlBackend* xml_be)
 {
     const gchar* dent;
     GDir* dir;
     struct stat lockstatbuf, statbuf;
     time64 now;
 
-    if (g_stat (be->lockfile, &lockstatbuf) != 0)
+    if (g_stat (xml_be->m_lockfile, &lockstatbuf) != 0)
         return;
 
-    dir = g_dir_open (be->dirname, 0, NULL);
+    dir = g_dir_open (xml_be->m_dirname, 0, NULL);
     if (!dir)
         return;
 
@@ -917,17 +918,17 @@ gnc_xml_be_remove_old_files (FileBackend* be)
                g_str_has_suffix (dent, GNC_LOGFILE_EXT)))
             continue;
 
-        name = g_build_filename (be->dirname, dent, (gchar*)NULL);
+        name = g_build_filename (xml_be->m_dirname, dent, (gchar*)NULL);
 
         /* Only evaluate files associated with the current data file. */
-        if (!g_str_has_prefix (name, be->fullpath))
+        if (!g_str_has_prefix (name, xml_be->m_fullpath))
         {
             g_free (name);
             continue;
         }
 
         /* Never remove the current data file itself */
-        if (g_strcmp0 (name, be->fullpath) == 0)
+        if (g_strcmp0 (name, xml_be->m_fullpath) == 0)
         {
             g_free (name);
             continue;
@@ -937,7 +938,7 @@ gnc_xml_be_remove_old_files (FileBackend* be)
         if (g_str_has_suffix (name, ".LNK"))
         {
             /* Is a lock file. Skip the active lock file */
-            if ((g_strcmp0 (name, be->linkfile) != 0) &&
+            if ((g_strcmp0 (name, xml_be->m_linkfile) != 0) &&
                 /* Only delete lock files older than the active one */
                 (g_stat (name, &statbuf) == 0) &&
                 (statbuf.st_mtime < lockstatbuf.st_mtime))
@@ -964,7 +965,7 @@ gnc_xml_be_remove_old_files (FileBackend* be)
              * juggling, but considering the above tests, this should always
              * be safe */
             regex_t pattern;
-            gchar* stamp_start = name + strlen (be->fullpath);
+            gchar* stamp_start = name + strlen (xml_be->m_fullpath);
             gchar* expression = g_strdup_printf ("^\\.[[:digit:]]{14}(\\%s|\\%s|\\.xac)$",
                                                  GNC_DATAFILE_EXT, GNC_LOGFILE_EXT);
             gboolean got_date_stamp = FALSE;
@@ -1018,10 +1019,10 @@ gnc_xml_be_remove_old_files (FileBackend* be)
 }
 
 static void
-xml_sync_all (QofBackend* be, QofBook* book)
+xml_sync_all (QofBackend* qof_be, QofBook* book)
 {
-    FileBackend* fbe = (FileBackend*) be;
-    ENTER ("book=%p, fbe->book=%p", book, fbe->book);
+    XmlBackend* xml_be = (XmlBackend*) qof_be;
+    ENTER ("book=%p, xml_be->m_book=%p", book, xml_be->m_book);
 
     /* We make an important assumption here, that we might want to change
      * in the future: when the user says 'save', we really save the one,
@@ -1029,18 +1030,18 @@ xml_sync_all (QofBackend* be, QofBook* book)
      * for multiple books have been removed in the meantime and there is just one
      * book, no more.
      */
-    if (NULL == fbe->book) fbe->book = book;
-    if (book != fbe->book) return;
+    if (NULL == xml_be->m_book) xml_be->m_book = book;
+    if (book != xml_be->m_book) return;
 
-    if (qof_book_is_readonly (fbe->book))
+    if (qof_book_is_readonly (xml_be->m_book))
     {
         /* Are we read-only? Don't continue in this case. */
-        qof_backend_set_error ((QofBackend*)be, ERR_BACKEND_READONLY);
+        qof_backend_set_error (qof_be, ERR_BACKEND_READONLY);
         return;
     }
 
-    gnc_xml_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
-    gnc_xml_be_remove_old_files (fbe);
+    gnc_xml_be_write_to_file (xml_be, book, xml_be->m_fullpath, TRUE);
+    gnc_xml_be_remove_old_files (xml_be);
     LEAVE ("book=%p", book);
 }
 
@@ -1053,14 +1054,14 @@ xml_sync_all (QofBackend* be, QofBook* book)
  */
 
 static char*
-build_period_filepath (FileBackend* fbe, QofBook* book)
+build_period_filepath (XmlBackend* xml_be, QofBook* book)
 {
     int len;
     char* str, *p, *q;
 
-    len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
+    len = strlen (xml_be->m_fullpath) + GUID_ENCODING_LENGTH + 14;
     str = g_new (char, len);
-    strcpy (str, fbe->fullpath);
+    strcpy (str, xml_be->m_fullpath);
 
     /* XXX it would be nice for the user if we made the book
      * closing date and/or title part of the file-name. */
@@ -1069,7 +1070,7 @@ build_period_filepath (FileBackend* fbe, QofBook* book)
     p = stpcpy (p, "book-");
     p = guid_to_string_buff (qof_book_get_guid (book), p);
     p = stpcpy (p, "-");
-    q = strrchr (fbe->fullpath, G_DIR_SEPARATOR);
+    q = strrchr (xml_be->m_fullpath, G_DIR_SEPARATOR);
     q++;
     p = stpcpy (p, q);
     p = stpcpy (p, ".gml");
@@ -1078,20 +1079,20 @@ build_period_filepath (FileBackend* fbe, QofBook* book)
 }
 
 static void
-xml_begin_edit (QofBackend* be, QofInstance* inst)
+xml_begin_edit (QofBackend* qof_be, QofInstance* inst)
 {
     if (0) build_period_filepath (0, 0);
 #if BORKEN_FOR_NOW
-    FileBackend* fbe = (FileBackend*) be;
+    XmlBackend* xml_be = (XmlBackend*) qof_be;
     QofBook* book = gp;
     const char* filepath;
 
     QofIdTypeConst typ = QOF_INSTANCE (inst)->e_type;
     if (strcmp (GNC_ID_PERIOD, typ)) return;
-    filepath = build_period_filepath (fbe, book);
+    filepath = build_period_filepath (xml_be, book);
     PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
 
-    if (NULL == fbe->primary_book)
+    if (NULL == xml_be->m_primary_book)
     {
         PERR ("You should have saved the data "
               "at least once before closing the books!\n");
@@ -1106,7 +1107,7 @@ xml_begin_edit (QofBackend* be, QofInstance* inst)
 }
 
 static void
-xml_rollback_edit (QofBackend* be, QofInstance* inst)
+xml_rollback_edit (QofBackend* qof_be, QofInstance* inst)
 {
 #if BORKEN_FOR_NOW
     QofBook* book = gp;
@@ -1126,44 +1127,44 @@ xml_rollback_edit (QofBackend* be, QofInstance* inst)
    way. */
 
 static void
-gnc_xml_be_load_from_file (QofBackend* bend, QofBook* book,
+gnc_xml_be_load_from_file (QofBackend* qof_be, QofBook* book,
                            QofBackendLoadType loadType)
 {
     QofBackendError error;
     gboolean rc;
-    FileBackend* be = (FileBackend*) bend;
+    XmlBackend* xml_be = (XmlBackend*) qof_be;
 
     if (loadType != LOAD_TYPE_INITIAL_LOAD) return;
 
     error = ERR_BACKEND_NO_ERR;
-    be->book = book;
+    xml_be->m_book = book;
 
-    switch (gnc_xml_be_determine_file_type (be->fullpath))
+    switch (gnc_xml_be_determine_file_type (xml_be->m_fullpath))
     {
     case GNC_BOOK_XML2_FILE:
-        rc = qof_session_load_from_xml_file_v2 (be, book, GNC_BOOK_XML2_FILE);
+        rc = qof_session_load_from_xml_file_v2 (xml_be, book, GNC_BOOK_XML2_FILE);
         if (FALSE == rc)
         {
-            PWARN ("Syntax error in Xml File %s", be->fullpath);
+            PWARN ("Syntax error in Xml File %s", xml_be->m_fullpath);
             error = ERR_FILEIO_PARSE_ERROR;
         }
         break;
 
     case GNC_BOOK_XML2_FILE_NO_ENCODING:
         error = ERR_FILEIO_NO_ENCODING;
-        PWARN ("No character encoding in Xml File %s", be->fullpath);
+        PWARN ("No character encoding in Xml File %s", xml_be->m_fullpath);
         break;
     case GNC_BOOK_XML1_FILE:
-        rc = qof_session_load_from_xml_file (book, be->fullpath);
+        rc = qof_session_load_from_xml_file (book, xml_be->m_fullpath);
         if (FALSE == rc)
         {
-            PWARN ("Syntax error in Xml File %s", be->fullpath);
+            PWARN ("Syntax error in Xml File %s", xml_be->m_fullpath);
             error = ERR_FILEIO_PARSE_ERROR;
         }
         break;
     case GNC_BOOK_POST_XML2_0_0_FILE:
         error = ERR_BACKEND_TOO_NEW;
-        PWARN ("Version of Xml file %s is newer than what we can read", be->fullpath);
+        PWARN ("Version of Xml file %s is newer than what we can read", xml_be->m_fullpath);
         break;
     default:
         /* If file type wasn't known, check errno again to give the
@@ -1189,7 +1190,7 @@ gnc_xml_be_load_from_file (QofBackend* bend, QofBook* book,
 
     if (error != ERR_BACKEND_NO_ERR)
     {
-        qof_backend_set_error (bend, error);
+        qof_backend_set_error (qof_be, error);
     }
 
     /* We just got done loading, it can't possibly be dirty !! */
@@ -1199,25 +1200,25 @@ gnc_xml_be_load_from_file (QofBackend* bend, QofBook* book,
 /* ---------------------------------------------------------------------- */
 
 static gboolean
-save_may_clobber_data (FileBackend *bend)
+save_may_clobber_data (XmlBackend *xml_be)
 {
     struct stat statbuf;
-    if (!bend->fullpath) return FALSE;
+    if (!xml_be->m_fullpath) return FALSE;
 
     /* FIXME: Make sure this doesn't need more sophisticated semantics
      * in the face of special file, devices, pipes, symlinks, etc. */
-    if (g_stat (bend->fullpath, &statbuf) == 0) return TRUE;
+    if (g_stat (xml_be->m_fullpath, &statbuf) == 0) return TRUE;
     return FALSE;
 }
 
 
 static void
-gnc_xml_be_write_accounts_to_file (QofBackend* be, QofBook* book)
+gnc_xml_be_write_accounts_to_file (QofBackend* qof_be, QofBook* book)
 {
     const gchar* datafile;
 
-    datafile = ((FileBackend*)be)->fullpath;
-    gnc_book_write_accounts_to_xml_file_v2 (be, book, datafile);
+    datafile = ((XmlBackend*)qof_be)->m_fullpath;
+    gnc_book_write_accounts_to_xml_file_v2 (qof_be, book, datafile);
 }
 
 /* ================================================================= */
@@ -1225,37 +1226,37 @@ gnc_xml_be_write_accounts_to_file (QofBackend* be, QofBook* book)
 QofBackend*
 QofXmlBackendProvider::create_backend(void)
 {
-    FileBackend* gnc_be;
-    QofBackend* be;
+    XmlBackend* xml_be;
+    QofBackend* qof_be;
 
-    gnc_be = g_new0 (FileBackend, 1);
-    be = (QofBackend*) gnc_be;
-    qof_backend_init (be);
+    xml_be = g_new0 (XmlBackend, 1);
+    qof_be = (QofBackend*) xml_be;
+    qof_backend_init (qof_be);
 
-    be->session_begin = xml_session_begin;
-    be->session_end = xml_session_end;
-    be->destroy_backend = xml_destroy_backend;
+    qof_be->session_begin = xml_session_begin;
+    qof_be->session_end = xml_session_end;
+    qof_be->destroy_backend = xml_destroy_backend;
 
-    be->load = gnc_xml_be_load_from_file;
+    qof_be->load = gnc_xml_be_load_from_file;
 
     /* The file backend treats accounting periods transactionally. */
-    be->begin = xml_begin_edit;
-    be->commit = NULL;
-    be->rollback = xml_rollback_edit;
+    qof_be->begin = xml_begin_edit;
+    qof_be->commit = NULL;
+    qof_be->rollback = xml_rollback_edit;
 
-    be->sync = xml_sync_all;
+    qof_be->sync = xml_sync_all;
 
-    be->export_fn = gnc_xml_be_write_accounts_to_file;
+    qof_be->export_fn = gnc_xml_be_write_accounts_to_file;
 
-    gnc_be->dirname = NULL;
-    gnc_be->fullpath = NULL;
-    gnc_be->lockfile = NULL;
-    gnc_be->linkfile = NULL;
-    gnc_be->lockfd = -1;
+    xml_be->m_dirname = NULL;
+    xml_be->m_fullpath = NULL;
+    xml_be->m_lockfile = NULL;
+    xml_be->m_linkfile = NULL;
+    xml_be->m_lockfd = -1;
 
-    gnc_be->book = NULL;
+    xml_be->m_book = NULL;
 
-    return be;
+    return qof_be;
 }
 
 static void
diff --git a/src/backend/xml/gnc-backend-xml.h b/src/backend/xml/gnc-backend-xml.h
index 2f021cf..c5fa189 100644
--- a/src/backend/xml/gnc-backend-xml.h
+++ b/src/backend/xml/gnc-backend-xml.h
@@ -56,24 +56,6 @@ typedef enum
     GNC_BOOK_POST_XML2_0_0_FILE
 } QofBookFileType;
 
-struct FileBackend_struct
-{
-    QofBackend be;
-
-    char* dirname;
-    char* fullpath;  /* Fully qualified path to book */
-    char* lockfile;
-    char* linkfile;
-    int lockfd;
-
-    QofBook* book;  /* The primary, main open book */
-};
-
-typedef struct FileBackend_struct FileBackend;
-
-// This is now a static inside the module
-//QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void);
-
 /** Initialization function which can be used when this module is
  * statically linked into the application. */
 void gnc_module_init_backend_xml (void);
@@ -88,5 +70,20 @@ void qof_backend_module_init (void);
 #endif
 #ifdef __cplusplus
 }
+
+class XmlBackend
+{
+public:
+    QofBackend be;
+
+    char* m_dirname;
+    char* m_fullpath;  /* Fully qualified path to book */
+    char* m_lockfile;
+    char* m_linkfile;
+    int m_lockfd;
+
+    QofBook* m_book;  /* The primary, main open book */
+};
+
 #endif
 #endif /* GNC_BACKEND_XML_H_ */
diff --git a/src/backend/xml/io-gncxml-v2.cpp b/src/backend/xml/io-gncxml-v2.cpp
index 614c4fd..2735238 100644
--- a/src/backend/xml/io-gncxml-v2.cpp
+++ b/src/backend/xml/io-gncxml-v2.cpp
@@ -692,12 +692,12 @@ gnc_sixtp_gdv2_new (
 
 static gboolean
 qof_session_load_from_xml_file_v2_full (
-    FileBackend* fbe, QofBook* book,
+    XmlBackend* xml_be, QofBook* book,
     sixtp_push_handler push_handler, gpointer push_user_data,
     QofBookFileType type)
 {
     Account* root;
-    QofBackend* be = &fbe->be;
+    QofBackend* qof_be = reinterpret_cast<decltype(qof_be)>(xml_be);
     sixtp_gdv2* gd;
     sixtp* top_parser;
     sixtp* main_parser;
@@ -706,7 +706,7 @@ qof_session_load_from_xml_file_v2_full (
     gboolean retval;
     char* v2type = NULL;
 
-    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
+    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, qof_be->percentage);
 
     top_parser = sixtp_new ();
     main_parser = sixtp_new ();
@@ -793,7 +793,7 @@ qof_session_load_from_xml_file_v2_full (
          * https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more
          * info.
          */
-        gchar* filename = fbe->fullpath;
+        gchar* filename = xml_be->m_fullpath;
         FILE* file;
         gboolean is_compressed = is_gzipped_file (filename);
         file = try_gz_open (filename, "r", is_compressed, FALSE);
@@ -864,10 +864,10 @@ bail:
 }
 
 gboolean
-qof_session_load_from_xml_file_v2 (FileBackend* fbe, QofBook* book,
+qof_session_load_from_xml_file_v2 (XmlBackend* xml_be, QofBook* book,
                                    QofBookFileType type)
 {
-    return qof_session_load_from_xml_file_v2_full (fbe, book, NULL, NULL, type);
+    return qof_session_load_from_xml_file_v2_full (xml_be, book, NULL, NULL, type);
 }
 
 /***********************************************************************/
@@ -1257,21 +1257,21 @@ static void
 write_budget (QofInstance* ent, gpointer data)
 {
     xmlNodePtr node;
-    struct file_backend* be = static_cast<decltype (be)> (data);
+    struct file_backend* file_be = static_cast<decltype (file_be)> (data);
 
     GncBudget* bgt = GNC_BUDGET (ent);
 
-    if (ferror (be->out))
+    if (ferror (file_be->out))
         return;
 
     node = gnc_budget_dom_tree_create (bgt);
-    xmlElemDump (be->out, NULL, node);
+    xmlElemDump (file_be->out, NULL, node);
     xmlFreeNode (node);
-    if (ferror (be->out) || fprintf (be->out, "\n") < 0)
+    if (ferror (file_be->out) || fprintf (file_be->out, "\n") < 0)
         return;
 
-    be->gd->counter.budgets_loaded++;
-    sixtp_run_callback (be->gd, "budgets");
+    file_be->gd->counter.budgets_loaded++;
+    sixtp_run_callback (file_be->gd, "budgets");
 }
 
 gboolean
@@ -1328,7 +1328,7 @@ write_v2_header (FILE* out)
 gboolean
 gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
 {
-    QofBackend* be;
+    QofBackend* qof_be;
     sixtp_gdv2* gd;
     gboolean success = TRUE;
 
@@ -1338,8 +1338,8 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
         || !write_counts (out, "book", 1, NULL))
         return FALSE;
 
-    be = qof_book_get_backend (book);
-    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
+    qof_be = qof_book_get_backend (book);
+    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, qof_be->percentage);
     gd->counter.commodities_total =
         gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
     gd->counter.accounts_total = 1 +
@@ -1364,7 +1364,7 @@ gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
  * This function is called by the "export" code.
  */
 gboolean
-gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book,
+gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* qof_be, QofBook* book,
                                               FILE* out)
 {
     gnc_commodity_table* table;
@@ -1385,7 +1385,7 @@ gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book,
         || !write_counts (out, "commodity", ncom, "account", nacc, NULL))
         return FALSE;
 
-    gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, be->percentage);
+    gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, qof_be->percentage);
     gd->counter.commodities_total = ncom;
     gd->counter.accounts_total = nacc;
 
@@ -1651,7 +1651,7 @@ gnc_book_write_to_xml_file_v2 (
  */
 gboolean
 gnc_book_write_accounts_to_xml_file_v2 (
-    QofBackend* be,
+    QofBackend* qof_be,
     QofBook* book,
     const char* filename)
 {
@@ -1662,7 +1662,7 @@ gnc_book_write_accounts_to_xml_file_v2 (
 
     /* Try to write as much as possible */
     if (!out
-        || !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out)
+        || !gnc_book_write_accounts_to_xml_filehandle_v2 (qof_be, book, out)
         || !write_emacs_trailer (out))
         success = FALSE;
 
@@ -1670,11 +1670,11 @@ gnc_book_write_accounts_to_xml_file_v2 (
     if (out && fclose (out))
         success = FALSE;
 
-    if (!success && !qof_backend_check_error (be))
+    if (!success && !qof_backend_check_error (qof_be))
     {
 
         /* Use a generic write error code */
-        qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR);
+        qof_backend_set_error (qof_be, ERR_FILEIO_WRITE_ERROR);
     }
 
     return success;
@@ -2168,17 +2168,17 @@ cleanup_push_handler:
 }
 
 gboolean
-gnc_xml2_parse_with_subst (FileBackend* fbe, QofBook* book, GHashTable* subst)
+gnc_xml2_parse_with_subst (XmlBackend* xml_be, QofBook* book, GHashTable* subst)
 {
     push_data_type* push_data;
     gboolean success;
 
     push_data = g_new (push_data_type, 1);
-    push_data->filename = fbe->fullpath;
+    push_data->filename = xml_be->m_fullpath;
     push_data->subst = subst;
 
     success = qof_session_load_from_xml_file_v2_full (
-                  fbe, book, (sixtp_push_handler) parse_with_subst_push_handler,
+                  xml_be, book, (sixtp_push_handler) parse_with_subst_push_handler,
                   push_data, GNC_BOOK_XML2_FILE);
 
     if (success)
diff --git a/src/backend/xml/io-gncxml-v2.h b/src/backend/xml/io-gncxml-v2.h
index 564e4f7..8948b05 100644
--- a/src/backend/xml/io-gncxml-v2.h
+++ b/src/backend/xml/io-gncxml-v2.h
@@ -43,6 +43,7 @@ extern "C"
 #include "sixtp.h"
 #include <vector>
 
+class XmlBackend;
 
 /**
  * Struct used to pass in a new data type for XML storage.  This contains
@@ -84,7 +85,7 @@ typedef struct
 } gnc_template_xaction_data;
 
 /** read in an account group from a file */
-gboolean qof_session_load_from_xml_file_v2 (FileBackend*, QofBook*,
+gboolean qof_session_load_from_xml_file_v2 (XmlBackend*, QofBook*,
                                             QofBookFileType);
 
 /* write all book info to a file */
@@ -154,7 +155,7 @@ gint gnc_xml2_find_ambiguous (
  * @param subst hash table with keys and values of type gchar*
  */
 gboolean gnc_xml2_parse_with_subst (
-    FileBackend* fbe, QofBook* book, GHashTable* subst);
+    XmlBackend* xml_be, QofBook* book, GHashTable* subst);
 #ifdef __cplusplus
 }
 typedef struct
diff --git a/src/gnome-utils/assistant-xml-encoding.c b/src/gnome-utils/assistant-xml-encoding.c
index 5f20b10..adf7ee7 100644
--- a/src/gnome-utils/assistant-xml-encoding.c
+++ b/src/gnome-utils/assistant-xml-encoding.c
@@ -35,8 +35,27 @@
 #include "gnc-uri-utils.h"
 #include "gnc-module.h"
 #include "gnc-ui.h"
-#include "io-gncxml-v2.h"
 
+/* The following are copied from src/backend/xml/io-gncxml2-v2.h as a temporary
+ * measure to enable this to compile in the face of making changing struct
+ * FileBackend into C++ class XmlBackend, which can't be exposed to this C
+ * file. A future commit will separate the session code from the UI code in this
+ * file.
+ */
+typedef struct
+{
+    GQuark encoding;
+    gchar* utf8_string;
+} conv_type;
+
+extern gint gnc_xml2_find_ambiguous (const gchar* filename,
+                                     GList* encodings,
+                                     GHashTable** unique,
+                                     GHashTable** ambiguous,
+                                     GList** impossible);
+
+extern gboolean gnc_xml2_parse_with_subst (QofBackend* xml_be, QofBook* book,
+                                           GHashTable* subst);
 /* NOTE: This file uses the term "encoding" even in places where it is not
  * accurate. Please ignore that. Encodings occur in different forms:
  * - as descriptive string, as in the list of system encodings
@@ -1041,7 +1060,7 @@ gxi_parse_file (GncXmlImportData *data)
 {
     QofSession *session = NULL;
     QofBook *book;
-    FileBackend *backend;
+    QofBackend *backend;
     QofBackendError io_err = ERR_BACKEND_NO_ERR;
     gchar *message = NULL;
     gboolean success = FALSE;
@@ -1091,7 +1110,7 @@ gxi_parse_file (GncXmlImportData *data)
 
     qof_session_pop_error (session);
     book = qof_session_get_book (session);
-    backend = (FileBackend*) qof_book_get_backend (book);
+    backend = qof_book_get_backend (book);
 
     gxi_update_progress_bar (_("Parsing file..."), 0.0);
     success = gnc_xml2_parse_with_subst (backend, book, data->subst);

commit 526834037cf7dfb5ab0cafc8212da9239abdb167
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Nov 17 16:47:57 2016 -0800

    Remove tabs from io-gncxml-v2.h.

diff --git a/src/backend/xml/io-gncxml-v2.h b/src/backend/xml/io-gncxml-v2.h
index 0457c8c..564e4f7 100644
--- a/src/backend/xml/io-gncxml-v2.h
+++ b/src/backend/xml/io-gncxml-v2.h
@@ -159,15 +159,15 @@ gboolean gnc_xml2_parse_with_subst (
 }
 typedef struct
 {
-    int		version;	/* backend version number */
-    const char *	type_name;	/* The XML tag for this type */
-
-    sixtp *	(*create_parser) (void);
-    gboolean	(*add_item)(sixtp_gdv2 *, gpointer obj);
-    int	      (*get_count) (QofBook *);
-    gboolean	(*write) (FILE*, QofBook*);
-    void		(*scrub) (QofBook *);
-    gboolean	(*ns) (FILE*);
+    int         version;        /* backend version number */
+    const char *        type_name;      /* The XML tag for this type */
+
+    sixtp *     (*create_parser) (void);
+    gboolean    (*add_item)(sixtp_gdv2 *, gpointer obj);
+    int       (*get_count) (QofBook *);
+    gboolean    (*write) (FILE*, QofBook*);
+    void                (*scrub) (QofBook *);
+    gboolean    (*ns) (FILE*);
 } GncXmlDataType_t;
 
 void gnc_xml_register_backend(GncXmlDataType_t&);



Summary of changes:
 po/POTFILES.in                                     |    2 +-
 src/backend/dbi/gnc-backend-dbi.cpp                |  338 +++---
 src/backend/dbi/gnc-backend-dbi.hpp                |   23 +-
 src/backend/dbi/gnc-dbisqlconnection.cpp           |   59 +-
 src/backend/dbi/gnc-dbisqlconnection.hpp           |    6 +-
 src/backend/sql/gnc-sql-backend.cpp                |   27 +-
 src/backend/sql/gnc-sql-backend.hpp                |   66 +-
 src/backend/sql/gnc-sql-column-table-entry.hpp     |    1 -
 src/backend/sql/test/utest-gnc-backend-sql.cpp     |   23 +-
 src/backend/xml/CMakeLists.txt                     |    2 +
 src/backend/xml/Makefile.am                        |    2 +
 src/backend/xml/gnc-backend-xml.cpp                | 1094 +-------------------
 src/backend/xml/gnc-backend-xml.h                  |   20 +-
 src/backend/xml/gnc-xml-backend.cpp                |  896 ++++++++++++++++
 src/backend/xml/gnc-xml-backend.hpp                |   66 ++
 src/backend/xml/io-gncxml-v2.cpp                   |   57 +-
 src/backend/xml/io-gncxml-v2.h                     |   23 +-
 src/backend/xml/test/test-dom-converters1.cpp      |    2 +-
 src/backend/xml/test/test-save-in-lang.cpp         |    3 +-
 src/backend/xml/test/test-string-converters.cpp    |    2 +-
 src/backend/xml/test/test-xml-transaction.cpp      |    2 +-
 src/engine/test/utest-Transaction.cpp              |   86 +-
 src/gnome-utils/assistant-xml-encoding.c           |   25 +-
 src/libqof/CMakeLists.txt                          |    4 +-
 src/libqof/qof/Makefile.am                         |    4 +-
 src/libqof/qof/qof-backend.cpp                     |  171 +++
 src/libqof/qof/{qofbackend-p.h => qof-backend.hpp} |  305 +++---
 src/libqof/qof/qofbackend.cpp                      |  284 -----
 src/libqof/qof/qofbackend.h                        |   36 +-
 src/libqof/qof/qofbook.cpp                         |    2 +-
 src/libqof/qof/qofinstance.cpp                     |   25 +-
 src/libqof/qof/qofquery.cpp                        |    2 +-
 src/libqof/qof/qofsession.cpp                      |  133 ++-
 src/libqof/qof/qofutil.cpp                         |    4 +-
 src/libqof/qof/test/Makefile.am                    |    2 -
 src/libqof/qof/test/test-qofinstance.cpp           |  244 ++---
 src/libqof/qof/test/test-qofsession.cpp            |   46 +-
 37 files changed, 1869 insertions(+), 2218 deletions(-)
 create mode 100644 src/backend/xml/gnc-xml-backend.cpp
 create mode 100644 src/backend/xml/gnc-xml-backend.hpp
 create mode 100644 src/libqof/qof/qof-backend.cpp
 rename src/libqof/qof/{qofbackend-p.h => qof-backend.hpp} (62%)
 delete mode 100644 src/libqof/qof/qofbackend.cpp



More information about the gnucash-changes mailing list