-2

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]

Igor
  • 5,620
  • 11
  • 51
  • 103
  • 1
    You seem to be confused about how templates work. You can't have a templated member variable (`m_p`). Either `CMainFrame` needs to be a template, or `m_p` should be a regular member variable, pointing to some supertype of both `Derived1` and `Derived2` (i.e. `Base *m_p;` according to your description). – Thomas Apr 02 '16 at 17:10
  • @Thomas, Like I wrote, making the CMainFrame class templatized still produce an error. Also, I wish Derived1 and Derived2 were operated on the same data type. ;-) – Igor Apr 02 '16 at 17:28
  • Like I said, if `Derived1` and `Derived2` both inherit (publically) from `Base`, just declare `Base *m_p;` and assign like `m_p = new Derived1<..., ...>();`. – Thomas Apr 02 '16 at 17:30
  • 1
    Have you successfully created a template class before trying this? I'm confused at exactly what is confusing you. Do a [MCVE] of the case where you create an entire template class and get a confusing problem. – Yakk - Adam Nevraumont Apr 02 '16 at 17:38
  • @Thomas, Yes, they are publicly inherited from Base.However 'const char * != SQLWCHAR *' on any platform/library I know of. And so the Base will have multiple functions taking different parameters. And so both Derived1 and Derived2 will depend on ODBC library in situation where only one should. Maybe there is a solution in C++11? – Igor Apr 02 '16 at 17:58
  • @Thomas, please see the edit for what I'm looking for and why the simple solution will not work. – Igor Apr 02 '16 at 19:42
  • @Yakk, Please see the edit for the interface I'm after and the explanation why the other won't work. – Igor Apr 02 '16 at 19:43
  • Member template variables didn't exist when you asked the previous question, and they still don't exist now. You don't want to define any templates for this task. You are asking the wrong question. Here's an example of a good question: I have these different databases *show the APIs* how do I fit them in a common framework, given that they work with different incompatible strings? – n. m. could be an AI Apr 02 '16 at 21:30
  • @n.m., question edited. I kept the original sets of code to show what I already tried and to show what I'm looking for. And I guess you are right - I _was_ asking the wrong question. Sorry about that. – Igor Apr 02 '16 at 23:32
  • Farrrr too much code here. [MCVE] – Lightness Races in Orbit Apr 02 '16 at 23:40
  • 1
    You are still having the XY problem. You are not, I repeat, not after templates of absolutely any kind. I suggest you drop this question and try to ask a new one, **without writing a single line of code**. Show APIs you are trying to interface with, but do not post any code of your own. – n. m. could be an AI Apr 02 '16 at 23:40
  • 1
    @n.m., here is the thing: I'm not trying to interface with anything. My goal is to write a set of pluggable libraries (.dll/.so/.dylib) for use with different DBMS API and check them against wxWidgets library. However those libraries should not depend wx,only on standard C++ classes. – Igor Apr 03 '16 at 00:14
  • n.m. is right. You say that the first chunk of code is what you're after, but it's a so-far failed means to an end. What is that end? Based on what you've said, it's to have a Database pointer that can point to an SQLiteDatabase or and ODCBDatabase. That doesn't require a template, just inheritance and virtual functions. – Topological Sort Apr 03 '16 at 02:57
  • "for use with different DBMS API". So you are given a set of DBMS APIs like sqlite and odbc. These are the things you interface with. – n. m. could be an AI Apr 03 '16 at 04:17
  • @WillBriggs, Please see my edit - I explain why the API #2 will not work from C++ design POV and from the run-time POV. – Igor Apr 03 '16 at 04:20
  • You **really** need to provide a minimal example. That said, this won't work because your exported classes use different template parameters. So they have different parents.. This can only be solved if you define common interface. – xvan Apr 03 '16 at 04:55
  • @xvan, And how do I do that? The minimal example will not really be minimal, but I will try. – Igor Apr 03 '16 at 06:26
  • @xvan, I put in a minimal interface API. Can you fill in the blanks? Or you can just say that this is not possible, because those DBMS'es do use different char types. – Igor Apr 03 '16 at 06:36

1 Answers1

0

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();
    }
xvan
  • 4,554
  • 1
  • 22
  • 37
  • I didn't try it yet, but: 1. SQLite uses char * UTF-8, and converting from std::wstring is very problematic. See e.g. http://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string. 2. ODBC code might work on Windows, but not on Linux (unixODBC), where SQLWCHAR is just unsigned int *. And I didn't look at Mac (iODBC) yet. So looking at all this, I may just as well abandon this idea and just define very basic Database class and do everything inside implementation. That will work for sure. ;-) Something like this: – Igor Apr 03 '16 at 15:30
  • constructor, destructor and vector of tables. – Igor Apr 03 '16 at 15:31
  • The conversion to string will work, a non ascii name wouldn't find a db. If SQLWCHAR is not an alias for wchar_t make a perpocessor #if to choose the proper conversion. – xvan Apr 03 '16 at 16:10
  • 1. What do you mean "non-ASCII name wouldn't find a db"? SQLite uses UTF-8 encoded char *. 2. With unixODBC SQLWCHAR is not wchar_t but unsigned int. And I have a UNICODE defined in my Linux Makefile. – Igor Apr 03 '16 at 19:15
  • 1) didn't knew it was utf8. Use a conversion library. 2) That's why i told to use the #if preprocessor. If sqlwchar is not wchar_t, you'll need to handle the conversion. – xvan Apr 03 '16 at 19:24
  • 1. Know any that will convert std::wstring to UTF-8 char *? 2. I don't know if SQLWCHAR is wchar_t on Windows. ;-) – Igor Apr 03 '16 at 19:51
  • 1) Check [this](http://stackoverflow.com/questions/4358870/convert-wstring-to-string-encoded-in-utf-8), 2) You'll need to test it, [Mingw](http://www.rpi.edu/dept/cis/software/g77-mingw32/include/sqltypes.h) uses wchar_t, but [this](http://stackoverflow.com/questions/7548825/what-are-the-functional-differences-between-iodbc-and-unixodbc) points to the `short int` implementation. – xvan Apr 03 '16 at 20:03
  • 1. I'm looking at wstring_convert<> solution. However it gives me error on MSVC 2010: std::wstring_convert<_Codecvt>::to_bytes(wchar_t)' : cannot convert parameter 1 from 'std::wstring *' to 'wchar_t'. Is it work only on 2012+? Or it doesn't work even there? – Igor Apr 04 '16 at 00:10
  • Tested with 2012, it works, you may try with [c++ build tools](https://blogs.msdn.microsoft.com/vcblog/2015/11/02/announcing-visual-c-build-tools-2015-standalone-c-tools-for-build-environments/) if you don't want to upgrade visual studio. – xvan Apr 04 '16 at 20:41
  • its not that I don't want to upgrade - I can't. I got this version from my college for free but going back just to get the next version - that'd be ridiculous. And pay MS $$$$ for that would be even more. I may try to look at the link you pushed later. – Igor Apr 05 '16 at 16:00
  • Did you check my link? It's a no cost stand alone official 2015 compiler. – xvan Apr 05 '16 at 16:03
  • yes, I did. But I actually fixed the error with my 2010 version. Now when I run the program it still crashes on the Database destructor. Do I have to release memory in the same dll it had been allocated? Or do everything in the main application? The pointer before destructor appears to be the same and all variables are holding the data. – Igor Apr 06 '16 at 11:39
  • To avoid issues, it's easier to allocate and deallocate in the same dll. If this is not possible, you could pass an allocator function to delegate all allocations. – xvan Apr 06 '16 at 12:41
  • please see my latest edit to the question. I first try my current approach - allocate in dll, free in main app - but that fails. Then I tried to pass the pointer to the function in the same dll which should do the de-allocation - but that failed as well. ANything I can do to overcome this obstacle? – Igor Apr 07 '16 at 14:06
  • Placing this as a comment, as it's probably the wrong advice, but you could add a `free(){delete this}` method – xvan Apr 07 '16 at 14:19
  • add it where? And where to call it? Also, it looks like that when I assign the pointer back to the main application, I can't call any function on it - inheritance is lost. So maybe my idea of creating the pointer inside dll but managing it thru the main application is bad? I can probably try to override new/delete inside the Database class, but that would be bad idea - it is an interface after all, i.e. no implementation. – Igor Apr 07 '16 at 15:59
  • What do you mean that inheritance is lost? Don't you have a `Database` pointer with a virtual destructor? – xvan Apr 07 '16 at 17:03
  • I do. But I don't have a reference to {SQLite|ODBC}Database. Like I wrote, when I expand the pointer in the MSVC debugger I don't see a line referring to those classes. – Igor Apr 07 '16 at 17:46
  • You don't need those references. That's the point of having a virtual destructor. – xvan Apr 07 '16 at 17:52
  • 2 questions: 1. Why it is crashing? 2. When I open the pointer right after allocation "Database *pdb = new SQLiteDatabase();" way, I can click the "+" sign to the left of the variable name in the Watch window and see the "SQLiteDatabase" line right under the pointer. In the main application I loose this line, which means the pointer does not know what type it is. Only the Database interface. – Igor Apr 07 '16 at 19:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/108561/discussion-between-xvan-and-igor). – xvan Apr 07 '16 at 19:25
  • I'm at work and not in front of that machine. Will be home in 1h30min. We will pick up then, OK? – Igor Apr 07 '16 at 20:01
  • Not real time chat, just to remove all this from the comments – xvan Apr 07 '16 at 20:20