【问题标题】:Is there a better API for the thing I want我想要的东西有更好的API吗
【发布时间】:2016-07-22 10:25:15
【问题描述】:

考虑以下 2 个 sn-ps:

  1. 这就是我所追求的:

数据库.h:

template<class T, class T2> class __declspec(dllexport) Database
{
protected:
    struct Impl;
    Impl *pimpl;
    virtual int GetTableListFromDb(std::vector<T2> &errorMsg) = 0;
public:
    virtual ~Database() = 0;
    Impl &GetTableVector() { return *pimpl; };
    virtual int Connect(T *selectedDSN, std::vector<T2> &errorMsg) = 0;
    virtual int Disconnect(std::vector<T2> &errorMsg) = 0;
};

template<class T, class T2> struct Database<T, T2>::Impl
{
    std::vector<Table> m_tables;
};

template<class T, class T2> Database<T, T2>::~Database()
{
    delete pimpl;
}

在 sqlite_db.h 中:

template <class T, class T2> class __declspec(dllexport) SQLiteDatabase : public Database<T, T2>
{
public:
    SQLiteDatabase();
    virtual ~SQLiteDatabase();
    virtual int Connect(T *selectedDSN, std::vector<T2> &errorMsg);
    virtual int Disconnect(std::vector<T2> &errorMsg);
protected:
    void GetErrorMessage(int code, T2 &errorMsg);
    virtual int GetTableListFromDb(std::vector<T2> &errorMsg);
private:
    sqlite3 *m_db;
};

在 odbc_db.h 中:

template<class T, class T2> class __declspec(dllexport) ODBCDatabase : public Database<T, T2>
{
public:
    ODBCDatabase();
    virtual ~ODBCDatabase();
    virtual int Connect(T *selectedDSN, std::vector<T2 *> &errorMsg);
    virtual int Disconnect(std::vector<T2> &errorMsg);
protected:
    int GetErrorMessage(std::vector<T> &errorMsg);
    virtual int GetTableListFromDb(std::vector<T2> &errorMsg);
private:
    SQLHENV m_env;
    SQLHDBC m_hdbc;
    SQLHSTMT m_hstmt;
    SQLHWND m_handle;
};

以及用法 - 在某些 dll 中:

extern "C" __declspec(dllexport) Database *my_dllfunc()
{
    Database<T, T2> *db = NULL;
    if( <cond1> )
    {
        db = new SQLiteDatabase<const char *, wstring>();
    }
    if( <cond2> )
    {
        db = new ODBCDatabase<SQLWCHAR *, SQLWCHAR*>();
    }
    db->Connect();
}

在主应用程序中:

class CMainFrame
{
public:
    CMainFrame();
    ~CmainFrame();
    void Foo();
private:
    Database *m_pdb;
}

CMainFrame::CMainFrame()
{
    m_pdb = NULL;
}

CMainFrame::~CMainFrame()
{
    delete m_pdb;
    m_pdbc = NULL;
}

void CMainFrame::Foo()
{
    HMODULE instance = ::LoadLibrary( "my_dll" );
    MYFUNC func = (MYFUNC)::GetProcInstance( "my_dllfunc", instance );
    m_pdb = func();
}

但是,由于模板变量不存在,此设计似乎无法编译。

  1. 这是我想避免的:

在database.h中:

class __declspec(dllexport) Database
{
protected:
    struct Impl;
    Impl *pimpl;
    virtual int GetTableListFromDb(std::vector<std::wstring> &errorMsg);
    virtual int GetTableListFromDb(std::vector<SQLWCHAR *> &errorMsg);
public:
    virtual ~Database() = 0;
    Impl &GetTableVector() { return *pimpl; };
    virtual int Connect(const char *selectedDSN, std::vector<std::wstring> &errorMsg);
    virtual int Connect(SQLWCHAR *selectedDSN, std::vector<SQLWCHAR *> &errorMsg);
    virtual int Disconnect(std::vector<T2> &errorMsg) = 0;
};

在 sqlite_db.h 中:

class __declspec(dllexport) SQLiteDatabase : public Database
{
public:
    SQLiteDatabase();
    virtual ~SQLiteDatabase();
    virtual int Connect(const char *selectedDSN, std::vector<std::wstring> &errorMsg);
    virtual int Disconnect(std::vector<std::wstring> &errorMsg);
protected:
    void GetErrorMessage(int code, std::wstring &errorMsg);
    virtual int GetTableListFromDb(std::vector<std::wstring> &errorMsg);
private:
    sqlite3 *m_db;
};

在 odbc_db 中:

class __declspec(dllexport) ODBCDatabase : public Database
{
public:
    ODBCDatabase();
    virtual ~ODBCDatabase();
    virtual int Connect(SQLWCHAR *selectedDSN, std::vector<SQLWCHAR *> &errorMsg);
    virtual int Disconnect(std::vector<SQLWCHAR *> &errorMsg);
protected:
    int GetErrorMessage(std::vector<SQLWCHAR *> &errorMsg);
    virtual int GetTableListFromDb(std::vector<SQLWCHAR *> &errorMsg);
private:
    SQLHENV m_env;
    SQLHDBC m_hdbc;
    SQLHSTMT m_hstmt;
    SQLHWND m_handle;
};

用法 - 大概是这样的(?):

在某些 dll 中:

extern "C" __declspec(dllexport) 数据库 *my_dllfunc() { 数据库 *db; 如果( ) db = 新 SQLiteDatabase(); 如果( ) db = 新的 ODBCDatabase(); }

在主应用程序中:

class CMainFrame
{
public:
    CMainFrame();
    ~CmainFrame();
    void Foo();
private:
    Database *m_pdb;
}

CMainFrame::CMainFrame()
{
    m_pdb = NULL;
}

CMainFrame::~CMainFrame()
{
    delete m_pdb;
    m_pdbc = NULL;
}

void CMainFrame::Foo()
{
    HMODULE instance = ::LoadLibrary( "my_dll" );
    MYFUNC func = (MYFUNC)::GetProcInstance( "my_dllfunc", instance );
    m_pdb = func();
}

除了“数据库”类对于每个需要的函数都变得不必要地臃肿,然后我能够重现 m_pdb 破坏崩溃,只是不能使用简单的代码。

我在我的 GUI 中使用 wxWidgets,它可以在那里重现。

希望你现在看到我在寻找什么。

看起来像我在 1 中输入的内容。即使在 C++11 中也是不可能的。那么,现在的问题就变成了 - 可以使用什么 API 来实现这一点?

基本上,我正在寻找一组类来处理使用不同 API 的不同数据库。

作为一个例子,我使用了 SQLite 和 ODBC。

TIA!

[编辑]

API #2 不起作用,因为:

  1. 类数据库位于 .dll/.a 库中(即静态链接)。
  2. SQLiteDatabase 和 ODBCDatabase 类位于其自己的 .dll/.so/.dylib 内(即动态链接)。它们链接到 database_dll。
  3. 将 database_dll 链接到主应用程序没有多大意义。因此,Database 类成为具有一堆纯虚函数的 Database 类接口。这意味着现在尝试实现 SQLiteDatabase,我需要在 odbc32.lib 中进行链接,这完全没有必要。
  4. 我正在使用 wxWidgets 来实现 GUI。这是 C++ 库,不像 MFC 那样使用 malloc()/free() 进行内存管理。因此,尝试在 DLL 中分配内存并在主应用程序中释放内存会崩溃。我有一个包含 MFC 和 wx 的完整代码——第一个有效,第二个无效。我正在使用 MSVC 2010 MFC 版本和最新的稳定 wx 版本 - 3.1。

所以从第 2 点开始的所有 API 都将不起作用。

[/编辑]

[编辑2]

静态 DLL:

class Database
{
public:
    Database();
    ~Database();
    int Connect();
    int Disconnect();
};

动态 DLL1:

class SQLiteDatabase : public Database
{
public:
    SQLiteDatabase();
    ~SQLiteDatabase();
    int Connect();
    int Disconnect();
};

动态 DLL2:

class ODBCDatabase : public Database
{
public:
    ODBCDatabase();
    ~ODBCDatabase();
    int Connect();
    int Disconnect();
};

这是我追求的界面。静态库Database类代码只能在头文件中,不要显式链接到主应用程序。SQLite和ODBC操作的不同数据类型如何处理?

我实际上正在寻找比这更好的东西:

class __declspec(dllexport) Database
{
public:
    Database();
    ~Database();
    int Connect(const char *dbName, std::vector<std::wstring> &errorMsg) = 0;
    int Connect(SQLWCHAR *dbName, std::vector<SQLWCHAR *> &errorMsg) = 0;
// possibly more Connect() overwrites
    int Disconnect() = 0;
};

而且这个接口很丑,因为所有那些 Connect() 函数都需要维护。

此外,现在 SQLite 接口现在必须引入不应依赖的 odbc 库,因为必须在任何地方实现第二个纯虚拟 Connect()。

那么还有什么更好的吗?

[/编辑]

[编辑3]

我实施了 xvan 的建议,现在一切都可以编译了 - 至少在带有 MSVC 2010 的 Windows 上。不幸的是,在运行程序时它会崩溃。

它的编写方式是我在 DLL 中分配内存,然后将指针传回主应用程序。然后主应用程序应该管理指针并将其发送到另一个 dll 或自己使用它。当应用程序结束时,应调用 Disconnect() 函数并销毁指针(通过析构函数释放内存)。

现在这种方法的问题是,只要我将指针带入主应用程序,我就不再知道该指针是什么。这意味着在 MSVC 调试器中,如果我在主应用程序中打开指针,我将不再看到派生类所在的行,因此删除对象会崩溃。

我想也许我可以将指针推回到分配它的 dll 中,但这也确实崩溃了,因为对象失去了层次结构并且无法保留它。

那么有什么方法可以让它在不分配内存的情况下在主应用程序内工作?

[/EDIT3]

【问题讨论】:

  • 您似乎对模板的工作方式感到困惑。您不能有模板化的成员变量 (m_p)。 CMainFrame 需要是一个模板,或者m_p 应该是一个常规成员变量,指向Derived1Derived2 的某个超类型(即Base *m_p;,根据您的描述)。
  • @Thomas,就像我写的那样,使 CMainFrame 类模板化仍然会产生错误。另外,我希望 Derived1 和 Derived2 对相同的数据类型进行操作。 ;-)
  • 您在尝试之前是否成功创建了模板类?我对究竟是什么让你感到困惑感到困惑。对您创建整个模板类并遇到令人困惑的问题的情况进行minimal reproducible example
  • 您仍然遇到 XY 问题。你不是,我重复一遍,绝对不是任何类型的模板。我建议您放弃这个问题并尝试提出一个新问题,无需编写任何代码。显示您尝试与之交互的 API,但不要发布您自己的任何代码。
  • @n.m.,事情是这样的:我不想与任何东西交互。我的目标是编写一组可插入库 (.dll/.so/.dylib) 以用于不同的 DBMS API,并根据 wxWidgets 库检查它们。然而,这些库不应该依赖 wx,只依赖于标准 C++ 类。

标签: c++ templates inheritance


【解决方案1】:

您需要定义一个通用接口,然后在该接口之间实现适当的转换功能:

以下代码未经测试。

class Database
{
public:
    virtual ~Database()=0;
    virtual int Connect(std::wstring,std::vector<std::wstring> &) =0;
    virtual int Disconnect()=0;
};


class SQLiteDatabase : public Database
{
public:
    SQLiteDatabase();
    ~SQLiteDatabase();
    int Connect(std::wstring,std::vector<std::wstring> &);
    int Disconnect();
};



int SQLiteDatabase::Connect(std::wstring dbName,std::vector<std::wstring> &errMsg)
{
   std::string sqlite_dbName(dbName.begin(), dbName.end());
   std::vector<std::wstring> &  sqlite_errMsg = errMsg;

   /* Call real connect*/
   real_connect( sqlite_dbName.c_str(), sqlite_errMsg);
   /*              */

   return errMsg.size();
}


class ODBCDatabase : public Database
{
    int SQLiteDatabase::Connect(std::wstring dbName,std::v
public:
    ODBCDatabase();
    ~ODBCDatabase();
    int Connect(std::wstring,std::vector<std::wstring> &);
    int Disconnect();
};


int ODBCDatabase::Connect(std::wstring dbName,std::vector<std::wstring> &errMsg){
    SQLWCHAR * odbc_dbName = dbName.c_str();
    std::vector<SQLWCHAR *> & odbc_errMsg;

    /* Call real connect*/
    real_connect(odbc_dbName, odbc_errMsg);

    /*Not sure if this'll work, but if it doesn't, manually copy strings from odbc_errMsg to errMsg*/
    errMsg = std::move( std::vector<std::wstring>(odbc_errMsg.begin(), odbc_errMsg.end()));
    return  errMsg.size();
    }

【讨论】:

  • 我还没有尝试过,但是: 1. SQLite 使用 char * UTF-8,从 std::wstring 转换是非常有问题的。参见例如stackoverflow.com/questions/4804298/…。 2. ODBC 代码可能适用于 Windows,但不适用于 Linux (unixODBC),其中 SQLWCHAR 只是 unsigned int *。而且我还没有看过 Mac (iODBC)。所以看到这一切,我不妨放弃这个想法,只定义非常基本的数据库类,并在实现中做所有事情。这肯定会奏效。 ;-) 像这样:
  • 表的构造函数、析构函数和向量。
  • 转换为字符串会起作用,非 ascii 名称不会找到 db。如果 SQLWCHAR 不是 wchar_t 的别名,请使用 #if 来选择正确的转换。
  • 1.你是什​​么意思“非ASCII名称找不到数据库”? SQLite 使用 UTF-8 编码的字符 *。 2. 使用 unixODBC,SQLWCHAR 不是 wchar_t 而是 unsigned int。我在我的 Linux Makefile 中定义了一个 UNICODE。
  • 1) 不知道它是 utf8。使用转换库。 2) 这就是我告诉使用#if 预处理器的原因。如果 sqlwchar 不是 wchar_t,则需要处理转换。
猜你喜欢
  • 2019-06-19
  • 2021-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多