create dll in qt

Creating dll seems an easy task in Qt. In Qt creator,  click File/New File or Project/Projects/Library/C++ Library, then simply choose a name, you will create a dll project. Click “build”, you will see the .dll generated in the debug or release directory. After building the .dll, you can use it in your main application. Your main application can explicitly load the dll at runtime or implicitly link to the dll at compiling time. Either way, the process is straightforward. In the explicit method, you can use QLibrary::load to load the dll,    use QLibrary::resolve to get the address of an exported function, then call that function. If you’re familiar with Windows API, you must know these functions are the packaging of the API LoadLibrary and GetProcAddress. In the implicit method, you need to import the lib of the dll in your main application, then you can use the functions/classes in the dll as if they are defined in your main program. If you dig further, you’ll find more powerful libs as to loading dlls and plugins such as QPluginLoader.

Unfortunately, all the simplicity hides a conspiracy. If you look at the files Qt creator generates for the dll project, you’ll find two header files:dllclass.h and dllclass_global.h. The cpp file (dllclass.cpp) includes the  dllclass.h. dllclass.h is also supposed to be included in other files (e.g., your main app) that use the dll. Normally, a dll project has only one header file that is included by both the implementation file of dll project and the related files in other projects using the dll. Why Qt generates an extra header file:dllclass_global.h? Open that file, you’ll find it contains two Qt specific symbols: Q_DECL_EXPORT and Q_DECL_IMPORT. Moreover, it also includes another Qt internal header file <QtCore/qglobal.h>. Note that dllclass.h includes dllclass_global.h and dllclass.h will be used by both the producer and the consumer of the dll, which means all parties related to the dll will have to use Qt as their development tool. This is ridiculous.

We should understand why we need a dll. Some guys like to separate their program into several components and each component is put in an independent dll. This is not the typical scenarios  dll is used for. The typical scenario dll is used in is: if you want others to extend the functionality of your application but do not want to disclose your source code to them, others who extend your application may not want you to get their source code, either. You and he/she can use dll to cooperate with each other in this case. The developer of dll only needs to deliver the dll file to make the extended app work.  No exposure or exchange of source code,even the header file. But you and the dll developer must have agreement on the functionality of the dll, which is called interface. The interface is usually represented in a header file, but not always. It can also be represented in a document or any text. The best part of dll is the isolation of the main program and the dll. The main program and the dll can even be developed using different programming languages. This is why I call the Qt solution to dll a conspiracy, because it requires both parties use the same programming language(C), and even the same programming framework(Qt).  This behavior is rather a marketing behavior than a tech solution.

We need to tweak the header file a little so that the developers of dll and main app do not need Qt any longer.

#ifndef DLLCLASS_H
#define DLLCLASS_H

//#include "dllclass_global.h"
#if defined(DLLCLASS_LIBRARY)
#  define DLLCLASSSHARED_EXPORT __declspec(dllexport)
#else
#  define DLLCLASSSHARED_EXPORT __declspec(dllimport)
#endif


class DLLCLASSSHARED_EXPORT DllClass
{

public:
    DllClass();
    virtual ~DllClass();
    virtual bool fun(char *);
};

extern "C" DLLCLASSSHARED_EXPORT DllClass * getDllClass();
typedef DllClass* (*GetDllClass)();

#endif // DLLCLASS_H

First, we comment the “#include “dllclass_global.h” line  and delete the extra header file dllclass_global.h. Then, we redefine DLLCLASSSHARED_EXPORT as __declspec(dllexport) or __declspec(dllimportport) instead of Q_DECL_EXPORT or Q_DECL_IMPORT. In fact Q_DECL_EXPORT is defined as __declspec(dllexport), and Q_DECL_IMPORT is defined as __declspec(dllimportport) in Qt. The introduction of DLLCLASSSHARED_EXPORT makes it possible to keep one version of dllclass.h that can be used by both the dll developer and the main app developer. If you are developing the dll, you need to define the macro DLLCLASS_LIBRARY before the header file. If you are writing the code for the main app, you do not need to define anything before including the header file. The removal of dllclass_global.h and the changes to dllclass.h make the interface independent of the Qt framework. Now any C++ programmer can use the header file as the interface to develop the dll/main app. You may notice that we use char * instead of QString for the function parameter. This is also helpful in getting rid of the dependency of Qt.

To use the DllClass in your main app, you should not use “new DllClass” because you did not implement the DllClass in your main app despite the fact that you have included the header file. So instancing a DllClass object directly in the main app will produce the error like:

error: LNK2019: unresolved external symbol “__declspec(dllimport) public: bool __thiscall DllClass::fun(char *)” (__imp_?fun@DllClass@@QAE_NPAD@Z) referenced in function …

However, no implementation of a class does not interfere with calling the member function using a class pointer:

DllClass *p;
p->fun((char*)"hello");

This is where the virtual keyword before the member functions plays its role. Without the virtual keyword, the calling of a member function compiles to the calling of the function address. Since the member function is not defined(implemented), its address can not be determined and the linking would fail with “unresolved external symbol”. With the virtual keyword, the function call will be compiled to like “(*(p+offset of fun in vtable))(…)”. The offsets of virtual functions are determined at compiling time solely with the header file(the declaration of the class), thus the code for the function call can be successfully generated.  The only problem is p is a wild pointer now. We should create the DllClass object in dll and pass its pointer to the main app. We are able to instance the DllClass object in dll because we implement the class in that dll. This is why we implement and export a function getDllClass() in dll. This function is responsible for creating the DllClass object(new DllClass). In main program, we use  QLibrary::load and QLibrary::resolve to get and call that function to obtain the pointer to a real DllClass object. We will use that pointer to call the member functions of DllClass.

QLibrary mylib("mydll.dll");

if (!mylib.load())
{
    return;
}

GetDllClass  getDllClass = (GetDllClass)mylib.resolve("getGetDllClass");
if(!GetDllClass)
{
    return;
}

DllClass *dllclass = getDllClass();
dllclass->fun((char*)"hello");

To store the address of getGetDllClass, we also define a type GetDllClass in the header file.

Posted in

Leave a Reply