Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
248 views
in Technique[技术] by (71.8m points)

c++ - Is there a better API for the thing I want

Consider following 2 snippets:

  1. This is what I'm after:

database.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;
}

In 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;
};

In 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;
};

And the usage - in some 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();
}

And in the main application:

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();
}

However, it looks like this design will not compile as template variable do not exist.

  1. This is what I would like to avoid:

In 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;
};

In 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;
};

In 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;
};

Usage - probably something like this (?):

In some dll:

extern "C" __declspec(dllexport) Database *my_dllfunc() { Database *db; if( ) db = new SQLiteDatabase(); if( ) db = new ODBCDatabase(); }

In main application:

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();
}

except that the "Database" class becomes needlessly bloated for every needed function and then there is a crash on m_pdb destruction which I'm able to reproduce, just not with the simple code.

I'm using wxWidgets for my GUI and it is reproducible there.

Hopefully now you see what I'm looking for.

It looks like what I put in 1`. is not possible, even in C++11. So, now, the question becomes - what API can be used for implementing this?

Basically I am looking for the set of classes to work with different databases that uses different API.

As an example here I used SQLite and ODBC.

TIA!

[EDIT]

The API #2 won't work, because:

  1. class Database is inside .dll/.a library (i.e. statically linked).
  2. classes SQLiteDatabase and ODBCDatabase are inside its own .dll/.so/.dylib (i.e. dynamically linked). They are linked to the database_dll.
  3. Linking to the database_dll to main application does not make much sense. Therefore the Database class becomes Database class-interface with a bunch of pure virtual functions. Which means that now trying to implement SQLiteDatabase, I will need to link in odbc32.lib, which is completely unnecessary.
  4. I'm using wxWidgets to implement GUI. This is C++ library and does not use malloc()/free() for memory management like MFC does. Therefore trying to allocate memory inside DLL and free inside the main app will crash. I have a complete code with both MFC and wx - first one works, second - isn't. I'm using MSVC 2010 MFC version and latest stable wx version - 3.1.

So all in all the API from point 2 will not work.

[/EDIT]

[EDIT2]

Static DLL:

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

Dynamic DLL1:

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

Dynamic DLL2:

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

This is the interface I am after. The static library Database class code should be in header file only, so not to explicitly link to main application.And how do I deal with different data types SQLite and ODBC operates?

I'm actually looking for something better than this:

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;
};

and this interface is ugly, since all those Connect() functions needs to be maintained.

Moreover, now SQLite interface will now have to bring in the odbc library, which it shouldn't depend on, because the second pure virtual Connect() has to be implemented everywhere.

So is there something better?

[/EDIT]

[EDIT3]

I impolemented xvan' suggestion and now everything compiles - at least on Windows with MSVC 2010. Unfortunately, when running the program it is crashes.

The way it is written is I allocate memory inside DLL and then pass the pointer back to the main application. Then main application should manage the pointer and send it to another dll or use it itself. When the application ends, the Disconnect() function should be called and the pointer should be destroyed (memory deallocated thru the destructor).

Now the problem with this approach is that as soon as I bring the pointer inside the main application I no longer have an information about what this pointer is. Meaning that in the MSVC debugger if I open the pointer inside the main application I no longer see the line with the derived class and so deleting the object crashes.

I thought that maybe I can push the pointer back to the dll where it was allocated, but that did crash as well, since the object loose the hierarchy and is not able to keep it.

So is there any way to make it work without allocating memory inside the main application?

[/EDIT3]

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You need to define a common interface, then implement the appropriate translation functions from and to that interface:

The following code is untested.

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();
    }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...