📜 ⬆️ ⬇️

Development of cross-platform modular applications in C ++ with the library wxWidgets

Introduction



For a long time I have not been writing articles on development, although I really like the process of writing and allows me to bring my thoughts in order.

And now, there is an opportunity now to talk about the developments that have appeared lately. I hope someone this text greatly simplify life and give impetus to the conquest of new peaks.
')
This time we will talk about creating cross-platform applications with C ++ plugins using the wxWidgets library. Considered to be the operating systems Windows, Linux and OS X, as the most popular.

As usual, the first part will be an overview, in order to lower the entry threshold for readers. Some of the information from the first part will seem obvious (especially as regards the toolkit), but, nevertheless, I consider it necessary, because for beginners the information from the first part will allow organizing the development process with minimal effort.
Next - a phased analysis of the test application code with explanations.

Tools


wxWidgets


First we need:

The wxWidgets library in source code. I use the most recent version of SVN. They are, of course, not without bugs, but they implemented functionality, which is usually not enough in official releases.

Source code can be found here: http://svn.wxwidgets.org/svn/wx/wxWidgets/trunk

You can read more about the library building process here: http://habrahabr.ru/post/123588/

The difference in the build process, compared with the above article, is only that you need to use the DLL Debug configuration DLL Debug and DLL Release instead of Debug and Release . In addition, it is imperative that in the settings of all projects included in the wxWidgets distribution kit, the Multi-Threaded Debug DLL Multi-Threaded DLL values ​​are specified in the C/C++ -> Code Generation -> Runtime Library parameter. It is with “DLL” at the end. In this case, we will have wxWidgets compiled in the form of dynamic libraries and with dynamic CRT.

When building DLL Debug configurations, DLL Debug and DLL Release can be such that not all libraries will be assembled the first time. All this because of problems with specifying dependencies. If not assembled, run the assembly again. Usually 2-3 iterations are enough to get a complete set of dynamic libraries.

I also recall that to work with wxWidgets, you must have the environment variable %WXWIN% (for Windows), which points to the folder with the wxWidgets source codes. For Linux and OS X, just configure && make && make install .

Parameters for configure:


CMake


In order to facilitate the work on creating project files for different platforms on different working machines with different settings, we will use the CMake project generation system, about which, by the way, there are several quite good overview articles on Habré, for example:

In general, CMake is a tool with which we can generate Visual Studio (Windows), Makefile / CodeBlocks (Linux), Makefile / XCode (OS X) project files with correct paths to source codes and third-party libraries on different machines. which will allow us to get rid of a fairly large amount of extra work on setting up the assembly.

Download CMake here: http://www.cmake.org/cmake/resources/software.html

If you compiled wxWidgets (Linux, OS X) with debugging information, and then you want to install the Release version, you need to make make uninstall for the Debug version and manually delete the files

If the above files are not manually deleted, the settings from the Debug version will be used for the Release version of the library. The application will be assembled, but will not start.

You should also bear in mind the fact that if you installed the Debug-version of wxWidgets, then on Linux and OS X you will most likely be able to build only the Debug-version of the application. The same goes for the release version. And all because CMake takes the compilation and linking parameters from the wx-config script, which by default gives the parameters for one current configuration. Or for Debug separately, or separately for Release.

Visual C ++ (Windows)


We will use Visual C ++ 2012 to build wxWidgets and our application from source codes in Windows. Express edition will also work. This means that all development tools, including the IDE and the compiler, will be free.

For those who are in the tank, the link to the free Visual C ++ 2012: http://www.microsoft.com/visualstudio/rus/products/visual-studio-express-products

DialogBlocks


To create a user interface, in order not to write everything by hand, I recommend using the DialogBlocks application. So, yes, it is paid, but there is a free trial version, which is enough to create simple forms. Although again, no one bothers to write everything by hand (by the way, this is even a good thing for educational purposes and clearly has a positive effect on the understanding of the code).

Download DialogBlocks here: http://www.anthemion.co.uk/dialogblocks/download.htm

Start


Folder structure


I understand that the taste and color of the felt-tip pens are different and imposing their directory structure is a thankless task, but after several years of work we have come to a certain structure in the company that has worked well on fairly complex projects and is quite simple to understand. Therefore, in this article we will use it.


Main CMakeList


The main CMake script contains general parameters and settings for all projects, as well as a description of some common variables.

build / CMakeLists.txt

 cmake_minimum_required(VERSION 2.6.0) # We will generate both Debug and Release project files at the same time # for Windows and OS X if(WIN32 OR APPLE) set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) set(LIB_SUFFIX "") endif(WIN32 OR APPLE) # For Linux we will need to execute CMake twice in order to generate # Debug and Release versions of Makefiles if(UNIX AND NOT APPLE) set(LINUX ON) set(LIB_SUFFIX /${CMAKE_BUILD_TYPE}) endif(UNIX AND NOT APPLE) set(PROJECT_NAME wxModularHost) project(${PROJECT_NAME}) # If there are any additional CMake modules (eg module which searches # for OpenCV or for DirectShow libs), then CMake should start searching # for them in current folder set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) if(APPLE) set(OS_BASE_NAME Mac) set(CMAKE_OSX_SYSROOT "macosx10.6") endif(APPLE) if(LINUX) set(OS_BASE_NAME Linux) endif(LINUX) if(WIN32) set(OS_BASE_NAME Win) endif(WIN32) # Here we specify the list of wxWidgets libs which we will use in our project set(wxWidgets_USE_LIBS base core adv aui net gl xml propgrid html) # Here we specify that we need DLL version of wxWidgets libs and dynamic CRT # This is a MUST for applications with plugins. Both app and DLL plugin MUST # use the same instance of wxWidgets and the same event loop. set(BUILD_SHARED_LIBS 1) # Find wxWidgets library on current PC # You should have %WXWIN% environment variable which should point to the # directory where wxWidgets source code is placed. # wxWidgets libs MUST be compiled for both Debug and Release versions find_package(wxWidgets REQUIRED) # For some reason CMake generates wrong list of definitions. # Each item should start with /D but it does not. # We need to fix that manually set(wxWidgets_DEFINITIONS_TEMP) foreach(DEFINITION ${wxWidgets_DEFINITIONS}) if(NOT ${DEFINITION} MATCHES "/D.*") set(DEFINITION "/D${DEFINITION}") endif() set(wxWidgets_DEFINITIONS_TEMP ${wxWidgets_DEFINITIONS_TEMP} ${DEFINITION}) endforeach(${DEFINITION}) set(wxWidgets_DEFINITIONS ${wxWidgets_DEFINITIONS_TEMP}) # Here we add some definitions which prevent Visual Studio from # generating tons of warnings about unsecure function calls. # See http://msdn.microsoft.com/en-us/library/ttcz0bys.aspx if(WIN32) set(wxWidgets_DEFINITIONS ${wxWidgets_DEFINITIONS}; /D_CRT_SECURE_NO_DEPRECATE; /D_CRT_NONSTDC_NO_DEPRECATE; /D_UNICODE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4996") endif(WIN32) # Since we are going to use wxWidgets in all subrojects, # it's OK to create the variable which will contain # common preprocessor definitions. This variable will be # used in subprojects. set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS}; ${wxWidgets_DEFINITIONS}) # Variable which points to root folder of our source code set(PROJECT_ROOT_DIR ${PROJECT_SOURCE_DIR}/..) # If any ThirdParty libraries are going to be # used in our project then it would be better to put # them into separate subfolder. We will create # the variable which points to this subfolder. set(THIRD_PARTY_DIR ${PROJECT_ROOT_DIR}/ThirdParty) set(BASE_INCLUDE_DIRECTORIES ${PROJECT_ROOT_DIR}/include) # Add wxWidgets include paths to the list of # include directories for all projects. include_directories(${wxWidgets_INCLUDE_DIRS}) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D__WXDEBUG__=1" ) # Now we can include all our subprojects. # CMake will generate project files for them add_subdirectory (../wxModularHost ../../wxModularHost/${OS_BASE_NAME}${LIB_SUFFIX}) 


Scripts for generating projects


For ease of use, CMake is better to use shell or batch scripts. This will save some time on routine operations such as calling CMake and setting environment variables.

Windows (cm.bat)

For convenience, it is better to use separate batch scripts to create Visual Studio projects for x86 and x64, as well as one common script that will determine under which platform the application is built:

 rem @echo off IF "%1" == "" GOTO NO_PARAMS IF "%1" == "x86" GOTO CMAKE_86 IF "%1" == "86" GOTO CMAKE_86 IF "%1" == "x64" GOTO CMAKE_64 IF "%1" == "64" GOTO CMAKE_64 ECHO %1 ECHO "Nothing to do" GOTO End :CMAKE_86 ECHO "Configuring for x86" cm86.bat GOTO End :CMAKE_64 ECHO "Configuring for x64" cm64.bat GOTO End :NO_PARAMS ECHO "No parameters specified" IF EXIST "%ProgramW6432%" GOTO CMAKE_64 GOTO CMAKE_86 :End 


Windows (cm86.bat)

 rmdir /S /Q Win mkdir Win cd Win cmake ../ -G "Visual Studio 11" cd .. 


Windows (cm64.bat)

 rmdir /S /Q Win mkdir Win cd Win cmake ../ -G "Visual Studio 11 Win64" cd .. 


Linux (cmLinux.sh)


 #!/bin/bash echo OS Type: $OSTYPE # ---------------------------------- # build Debug configuration makefile # ---------------------------------- echo building Debug configuration makefile echo directory "LinuxDebug" rm -dr "LinuxDebug" mkdir "LinuxDebug" cd "LinuxDebug" cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:STRING=Debug ../ cd .. # ---------------------------------- # build Release configuration makefile # ---------------------------------- echo building Release configuration makefile echo directory "LinuxRelease" rm -dr "LinuxRelease" mkdir "LinuxRelease" cd "LinuxRelease" cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE:STRING=Release ../ cd .. 


Minimal wxWidgets-application with CMake


To get started, we need an application template to which we will add functionality. Let's create a simple application consisting of an application class (for example, wxModularHostApp) and a main form class (for example, MainFrame).

If you use DialogBlocks, then, in addition to a pair of h / cpp files for each class, we get another .rc file with a description of application resources.

I will not give the code. An example can be taken from previous articles or from the %WXWIN%\samples\minimal folder

Now you can proceed to create CMake-script.

wxModularHost / CMakeLists.txt

 set(SRCS MainFrame.cpp wxModularHostApp.cpp) set(HEADERS MainFrame.h wxModularHostApp.h) set(INCLUDE_DIRECTORIES ${BASE_INCLUDE_DIRECTORIES}) if(WIN32) set(SRCS ${SRCS} wxModularHost.rc) set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS}; /D_USRDLL; /DwxUSE_NO_MANIFEST=1; /D__STDC_CONSTANT_MACROS) endif(WIN32) set(LIBS ${wxWidgets_LIBRARIES}) set(EXECUTABLE_NAME wxModularHost) add_definitions(${PREPROCESSOR_DEFINITIONS}) include_directories(${INCLUDE_DIRECTORIES}) if(WIN32) set(EXECUTABLE_TYPE WIN32) endif(WIN32) if(APPLE) set(MACOSX_BUNDLE YES) set(EXECUTABLE_TYPE MACOSX_BUNDLE) endif(APPLE) if(LINUX) set(EXECUTABLE_TYPE "") endif(LINUX) set(PROJECT_FILES ${SRCS} ${HFILES}) add_executable(${EXECUTABLE_NAME} ${EXECUTABLE_TYPE} ${PROJECT_FILES}) set(EXE_DIR bin) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${EXE_DIR}${LIB_SUFFIX}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION}) target_link_libraries(${EXECUTABLE_NAME} ${LIBS}) 


Precompiled Headers


To speed up the compilation process, it is possible to use precompiled headers ( http://en.wikipedia.org/wiki/Precompiled_header ).

To implement this feature, we need two files:
include / stdwx.h

 #ifndef _STDWX_H_ #define _STDWX_H_ #if defined(WIN32) || defined(WINDOWS) #include <windows.h> #include <winnt.h> #define PLUGIN_EXPORTED_API WXEXPORT #else #define PLUGIN_EXPORTED_API extern "C" #endif // SYSTEM INCLUDES // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #include "wx/wx.h" #include <wx/cmdline.h> #include <wx/config.h> #include <wx/defs.h> #include <wx/dir.h> #include <wx/display.h> #include <wx/dynlib.h> #include <wx/dynload.h> #include <wx/fileconf.h> #include <wx/filename.h> #include <wx/frame.h> #include <wx/glcanvas.h> #include <wx/hashmap.h> #include <wx/image.h> #include <wx/imaglist.h> #include <wx/intl.h> #include <wx/list.h> #include <wx/notebook.h> #include <wx/stdpaths.h> #include <wx/sstream.h> #include <wx/thread.h> #include <wx/treebook.h> #include <wx/wfstream.h> #include <wx/wupdlock.h> #include <wx/textfile.h> #include <wx/socket.h> #include <wx/mimetype.h> #include <wx/ipc.h> #endif 


include / stdwx.cpp

 #include "stdwx.h" 


In addition to the C ++ source files, we also need to teach CMake to add the necessary rules to the Visual Studio project for working with precompiled headers. For this we will help a special module. I do not remember where it came from, but it seems from here ( http://public.kitware.com/Bug/file_download.php?file_id=901&type=bug ). The source code of the CMake module to support precompiled headers can be found here: https://github.com/T-Rex/wxModularApp/blob/master/build/PCHSupport.cmake .

This module should be included in build / CmakeLists.txt as follows:

build / CMakeLists.txt

 cmake_minimum_required(VERSION 2.6.0) include(PCHSupport.cmake) ... 


After connecting precompiled headers to the project, the first line in all .CPP files of the project should be the line

 #include "stdwx.h" 


Simplest plugin without GUI


Library with base classes


To develop, in fact, the plugin, and in order for the application to skillfully load the plugins of the desired type, it is necessary to do the preparatory work. We need to create a library that will contain the base abstract class of the plugin, the functionality of which we will have to implement in each specific plugin. Also, our main application will contain pointers of this type, which were created inside libraries and refer to specific plug-in implementations.

Those. as a result, we should learn a library containing data types used in both plugins and in the main application.

wxNonGuiPluginBase / Declarations.h

 #ifndef _DECLARATIONS_H #define _DECLARATIONS_H #if defined(__WXMSW__) #ifdef DEMO_PLUGIN_EXPORTS #define DEMO_API __declspec(dllexport) #else #define DEMO_API __declspec(dllimport) #endif #else #define DEMO_API #endif #endif // _DECLARATIONS_H 


wxNonGuiPluginBase / wxNonGuiPluginBase.h

 #pragma once #include "Declarations.h" class DEMO_API wxNonGuiPluginBase : public wxObject { DECLARE_ABSTRACT_CLASS(wxNonGuiPluginBase) public: wxNonGuiPluginBase(); virtual ~wxNonGuiPluginBase(); virtual int Work() = 0; }; typedef wxNonGuiPluginBase * (*CreatePlugin_function)(); typedef void (*DeletePlugin_function)(wxNonGuiPluginBase * plugin); 


The Declarations.h file contains the definition of the DEMO_API macro, which indicates wxNonGuiPluginBase class exported from us or imported. This is done with the help of dllexport/dllimport (see http://msdn.microsoft.com/en-us/library/3y1sfaz2(v=vs.90).aspx) depending on the presence of the preprocessor directive DEMO_PLUGIN_EXPORTS . When building the wxNonGuiPluginBase library, we specify DEMO_PLUGIN_EXPORTS in the list of preprocessor directives, and when building plug-ins that depend on the wxNonGuiPluginBase library and when building the main application, we do not specify. Thus, for the wxNonGuiPluginBase project, the DEMO_API value will contain the dllexport attribute, and for all other projects, the dllimport value.

wxNonGuiPluginBase / wxNonGuiPluginBase.cpp

 #include "stdwx.h" #include "wxNonGuiPluginBase.h" IMPLEMENT_ABSTRACT_CLASS(wxNonGuiPluginBase, wxObject) wxNonGuiPluginBase::wxNonGuiPluginBase() { } wxNonGuiPluginBase::~wxNonGuiPluginBase() { } 


wxNonGuiPluginBase / CMakeLists.txt

 set (SRCS wxNonGuiPluginBase.cpp) set (HEADERS Declarations.h wxNonGuiPluginBase.h) set(LIBRARY_NAME wxNonGuiPluginBase) if(WIN32) # Only for Windows: # we add additional preprocessor definitons set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS}; /D_USRDLL;/DDEMO_PLUGIN_EXPORTS;/D__STDC_CONSTANT_MACROS) endif(WIN32) # Add 2 files for precompiled headers set(SRCS ${SRCS} ${HEADERS} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) # Set preprocessor definitions add_definitions(${PREPROCESSOR_DEFINITIONS}) # Set include directories include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES}) # Set library search paths link_directories(${LINK_DIRECTORIES}) # Setup the project name and assign the source files for this project add_library(${LIBRARY_NAME} SHARED ${SRCS}) #Setup the output folder set(DLL_DIR bin) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}${LIB_SUFFIX}) set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION}) # Set additional dependencies target_link_libraries(${LIBRARY_NAME} ${wxWidgets_LIBRARIES}) # Setup precompiled headers set_precompiled_header(${LIBRARY_NAME} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) 


As mentioned earlier, the macro PREPROCESSOR_DEFINITIONS contains the declaration of the macro DEMO_PLUGIN_EXPORTS , which is used in the file Definitions.h

First plugin


In the plugin, we need to make a class derived from wxNonGuiPluginBase , implement the working functionality in it, and also make exported functions to create an instance of the class and to delete it. These functions will be called by the main application.

SampleNonGuiPlugin / SampleNonGuiPlugin.h

 #pragma once #include <wxNonGuiPluginBase.h> class SampleNonGuiPlugin : public wxNonGuiPluginBase { DECLARE_DYNAMIC_CLASS(SampleNonGuiPlugin) public: SampleNonGuiPlugin(); virtual ~SampleNonGuiPlugin(); virtual int Work(); }; 


SampleNonGuiPlugin / SampleNonGuiPlugin.cpp

 #include "stdwx.h" #include "SampleNonGuiPlugin.h" IMPLEMENT_DYNAMIC_CLASS(SampleNonGuiPlugin, wxObject) SampleNonGuiPlugin::SampleNonGuiPlugin() { } SampleNonGuiPlugin::~SampleNonGuiPlugin() { } int SampleNonGuiPlugin::Work() { return 10; } 


SampleNonGuiPlugin / SampleNonGuiPlugin.def

 LIBRARY "SampleNonGuiPlugin" EXPORTS CreatePlugin=CreatePlugin DeletePlugin=DeletePlugin 


SampleNonGuiPlugin / SampleNonGuiPluginExports.cpp

 #include "stdwx.h" #include <wxNonGuiPluginBase.h> #include "SampleNonGuiPlugin.h" PLUGIN_EXPORTED_API wxNonGuiPluginBase * CreatePlugin() { return new SampleNonGuiPlugin; } PLUGIN_EXPORTED_API void DeletePlugin(wxNonGuiPluginBase * plugin) { wxDELETE(plugin); } 


SampleNonGuiPlugin / CMakeLists.txt

 set (SRCS SampleNonGuiPlugin.cpp SampleNonGuiPluginExports.cpp) set (HEADERS SampleNonGuiPlugin.h) set(LIBRARY_NAME SampleNonGuiPlugin) if(WIN32) set(SRCS ${SRCS} ${LIBRARY_NAME}.def) set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS) set(LINK_DIRECTORIES ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName)) set(DEMO_LIBS wxNonGuiPluginBase.lib) endif(WIN32) set(SRCS ${SRCS} ${HEADERS} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) add_definitions(${PREPROCESSOR_DEFINITIONS}) include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES} ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase) link_directories(${LINK_DIRECTORIES}) add_library(${LIBRARY_NAME} SHARED ${SRCS}) set(DLL_DIR bin) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR}/plugins) set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION}) target_link_libraries(${LIBRARY_NAME} ${DEMO_LIBS} ${wxWidgets_LIBRARIES}) add_dependencies(${LIBRARY_NAME} wxNonGuiPluginBase) set_precompiled_header(${LIBRARY_NAME} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) 


DEF-file is included in the list of source code files is not accidental. Without its use, the names of the exported functions will be decorated and the application will not be able to get a pointer to these functions from the DLL. You can read about using DEF files and exporting functions from a DLL here:


Plugin control module


So, at the moment we have a host application and a minimal plugin. Now you need to implement the loading and use of the plugin in the application. With a view to universality, it is better to select the code that will be engaged in searching, loading and unloading plug-ins from memory into a separate class, and even better into a separate library.

Remember once again the implementation of our plugins:

Based on these requirements, we can come to the following conclusions:

Based on these requirements and conclusions, we implement the plug-in management class and containers:
wxModularCore / wxModularCore.h

 #pragma once #include <wxNonGuiPluginBase.h> // We need to know which DLL produced the specific plugin object. WX_DECLARE_HASH_MAP(wxNonGuiPluginBase*, wxDynamicLibrary*, wxPointerHash, wxPointerEqual, wxNonGuiPluginToDllDictionary); // We also need to keep the list of loaded DLLs WX_DECLARE_LIST(wxDynamicLibrary, wxDynamicLibraryList); // And separate list of loaded plugins for faster access. WX_DECLARE_LIST(wxNonGuiPluginBase, wxNonGuiPluginBaseList); class wxModularCoreSettings; class wxModularCore { public: wxModularCore(); virtual ~wxModularCore(); virtual wxString GetPluginsPath(bool forceProgramPath) const; virtual wxString GetPluginExt(); bool LoadPlugins(bool forceProgramPath); bool UnloadPlugins(); const wxNonGuiPluginBaseList & GetNonGuiPlugins() const; void Clear(); private: bool LoadNonGuiPlugins(const wxString & pluginsDirectory); bool UnloadNonGuiPlugins(); bool RegisterNonGuiPlugin(wxNonGuiPluginBase * plugin); bool UnRegisterNonGuiPlugin(wxNonGuiPluginBase * plugin); wxDynamicLibraryList m_DllList; wxNonGuiPluginToDllDictionary m_MapNonGuiPluginsDll; wxNonGuiPluginBaseList m_NonGuiPlugins; wxModularCoreSettings * m_Settings; }; 


Consider the code in detail:

wxModularCore/wxModularCore.cpp

 #include "stdwx.h" #include "wxModularCore.h" #include "wxModularCoreSettings.h" #include <wx/listimpl.cpp> WX_DEFINE_LIST(wxDynamicLibraryList); WX_DEFINE_LIST(wxNonGuiPluginBaseList); wxModularCore::wxModularCore() :m_Settings(new wxModularCoreSettings) { // This will allow to delete all objects from this list automatically m_DllList.DeleteContents(true); } wxModularCore::~wxModularCore() { Clear(); wxDELETE(m_Settings); } void wxModularCore::Clear() { UnloadPlugins(); // TODO: Add the code which resets the object to initial state } bool wxModularCore::LoadPlugins(bool forceProgramPath) { wxString pluginsRootDir = GetPluginsPath(forceProgramPath); wxFileName fn; fn.AssignDir(pluginsRootDir); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); fn.AppendDir(wxT("plugins")); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); if (!fn.DirExists()) return false; return LoadNonGuiPlugins(fn.GetFullPath()); } bool wxModularCore::UnloadPlugins() { return UnloadNonGuiPlugins(); } bool wxModularCore::LoadNonGuiPlugins(const wxString & pluginsDirectory) { wxFileName fn; fn.AssignDir(pluginsDirectory); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); fn.AppendDir(wxT("nongui")); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); if (!fn.DirExists()) return false; if(!wxDirExists(fn.GetFullPath())) return false; wxString wildcard = wxString::Format(wxT("*.%s"), GetPluginExt().GetData()); wxArrayString pluginPaths; wxDir::GetAllFiles(fn.GetFullPath(), &pluginPaths, wildcard); for(size_t i = 0; i < pluginPaths.GetCount(); ++i) { wxString fileName = pluginPaths[i]; wxDynamicLibrary * dll = new wxDynamicLibrary(fileName); if (dll->IsLoaded()) { wxDYNLIB_FUNCTION(CreatePlugin_function, CreatePlugin, *dll); if (pfnCreatePlugin) { wxNonGuiPluginBase* plugin = pfnCreatePlugin(); RegisterNonGuiPlugin(plugin); m_DllList.Append(dll); m_MapNonGuiPluginsDll[plugin] = dll; } else wxDELETE(dll); } } return true; } bool wxModularCore::UnloadNonGuiPlugins() { bool result = true; wxNonGuiPluginBase * plugin = NULL; while (m_NonGuiPlugins.GetFirst() && (plugin = m_NonGuiPlugins.GetFirst()->GetData())) { result &= UnRegisterNonGuiPlugin(plugin); } return result; } wxString wxModularCore::GetPluginsPath(bool forceProgramPath) const { wxString path; if (m_Settings->GetStoreInAppData() && !forceProgramPath) path = wxStandardPaths::Get().GetConfigDir(); else path = wxPathOnly(wxStandardPaths::Get().GetExecutablePath()); return path; } wxString wxModularCore::GetPluginExt() { return #if defined(__WXMSW__) wxT("dll"); #else wxT("so"); #endif } bool wxModularCore::RegisterNonGuiPlugin(wxNonGuiPluginBase * plugin) { m_NonGuiPlugins.Append(plugin); return true; } bool wxModularCore::UnRegisterNonGuiPlugin(wxNonGuiPluginBase * plugin) { wxNonGuiPluginBaseList::compatibility_iterator it = m_NonGuiPlugins.Find(plugin); if (it == NULL) return false; do { wxDynamicLibrary * dll = m_MapNonGuiPluginsDll[plugin]; if (!dll) // Probably plugin was not loaded from dll break; wxDYNLIB_FUNCTION(DeletePlugin_function, DeletePlugin, *dll); if (pfnDeletePlugin) { pfnDeletePlugin(plugin); m_NonGuiPlugins.Erase(it); m_MapNonGuiPluginsDll.erase(plugin); return true; } } while (false); // If plugin is not loaded from DLL (eg embedded into executable) wxDELETE(plugin); m_NonGuiPlugins.Erase(it); return true; } const wxNonGuiPluginBaseList & wxModularCore::GetNonGuiPlugins() const { return m_NonGuiPlugins; } 


LoadNonGuiPlugins() , wxDYNLIB_FUNCTION CreatePlugin() . CreatePlugin_function wxNonGuiPluginBase.h.

UnRegisterNonGuiPlugin() , , DeletePlugin() . (, ), .

wxModularCore/wxModularCoreSettings.h

 #pragma once class wxModularCoreSettings { public: wxModularCoreSettings(); wxModularCoreSettings(const wxModularCoreSettings & settings); wxModularCoreSettings & operator = (const wxModularCoreSettings & settings); virtual ~wxModularCoreSettings(); void SetStoreInAppData(const bool & val); bool GetStoreInAppData() const; protected: virtual void CopyFrom(const wxModularCoreSettings & settings); private: bool m_bStoreInAppData; // Should we store data in Application Data folder or in .exe folder }; 


wxModularCore/wxModularCoreSettings.cpp

 #include "stdwx.h" #include "wxModularCoreSettings.h" wxModularCoreSettings::wxModularCoreSettings() : m_bStoreInAppData(false) { } wxModularCoreSettings::wxModularCoreSettings(const wxModularCoreSettings & settings) { CopyFrom(settings); } wxModularCoreSettings & wxModularCoreSettings::operator = (const wxModularCoreSettings & settings) { if (this != &settings) { CopyFrom(settings); } return *this; } wxModularCoreSettings::~wxModularCoreSettings() { } void wxModularCoreSettings::CopyFrom(const wxModularCoreSettings & settings) { m_bStoreInAppData = settings.m_bStoreInAppData; } void wxModularCoreSettings::SetStoreInAppData(const bool & value) { m_bStoreInAppData = value; } bool wxModularCoreSettings::GetStoreInAppData() const { return m_bStoreInAppData; } 


wxModularCore/CMakeLists.txt

 set (SRCS wxModularCore.cpp wxModularCoreSettings.cpp) set (HEADERS wxModularCore.h wxModularCoreSettings.h) set(LIBRARY_NAME wxModularCore) if(WIN32) set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D__STDC_CONSTANT_MACROS) set(LINK_DIRECTORIES ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName)) set(DEMO_LIBS wxNonGuiPluginBase.lib) endif(WIN32) set(SRCS ${SRCS} ${HEADERS} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) add_definitions(${PREPROCESSOR_DEFINITIONS}) include_directories(${INCLUDE_DIRECTORIES} ${BASE_INCLUDE_DIRECTORIES} ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase) link_directories(${LINK_DIRECTORIES}) add_library(${LIBRARY_NAME} STATIC ${SRCS}) set(DLL_DIR bin) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR}) set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION}) target_link_libraries(${LIBRARY_NAME} ${DEMO_LIBS} ${wxWidgets_LIBRARIES}) add_dependencies(${LIBRARY_NAME} wxNonGuiPluginBase) set_precompiled_header(${LIBRARY_NAME} ${PROJECT_ROOT_DIR}/include/stdwx.h ${PROJECT_ROOT_DIR}/include/stdwx.cpp) 


wxModularCore CMakeLists.txt:

build/CMakeLists.txt

 ... add_subdirectory (../wxModularCore ../../wxModularCore/${OS_BASE_NAME}${LIB_SUFFIX}) ... 


GUI


, , , .

- wxModularCore :

wxModularHost/wxModularHostApp.h

 ... class wxModularHostApp: public wxApp { void TestNonGuiPlugins(); ... wxModularCore * m_PluginManager; ... }; 


wxModularHost/wxModularHostApp.cpp

 void wxModularHostApp::Init() { ////@begin wxModularHostApp member initialisation m_PluginManager = new wxModularCore; ////@end wxModularHostApp member initialisation } 


:

wxModularHost/wxModularHostApp.cpp

 bool wxModularHostApp::OnInit() { ... TestNonGuiPlugins(); MainFrame* mainWindow = new MainFrame( NULL ); mainWindow->Show(true); return true; } /* * Cleanup for wxModularHostApp */ int wxModularHostApp::OnExit() { wxDELETE(m_PluginManager); ////@begin wxModularHostApp cleanup return wxApp::OnExit(); ////@end wxModularHostApp cleanup } void wxModularHostApp::TestNonGuiPlugins() { if(m_PluginManager) { if(m_PluginManager->LoadPlugins(true)) { for(wxNonGuiPluginBaseList::Node * node = m_PluginManager->GetNonGuiPlugins().GetFirst(); node; node = node->GetNext()) { wxNonGuiPluginBase * plugin = node->GetData(); if(plugin) { wxLogDebug(wxT("Non-GUI plugin returns %i"), plugin->Work()); } } } } } 


TestNonGuiPlugins() LoadPlugins() wxModularCore , , Work() (, wxNonGuiPluginBase, ).

GUI-


How to create modules containing only logic, figured out. Now consider an example of a module that wants to create a window:

wxGuiPluginBase / wxGuiPluginBase.h

 #pragma once #include "Declarations.h" class DEMO_API wxGuiPluginBase : public wxObject { DECLARE_ABSTRACT_CLASS(wxGuiPluginBase) public: wxGuiPluginBase(); virtual ~wxGuiPluginBase(); virtual wxString GetName() const = 0; virtual wxString GetId() const = 0; virtual wxWindow * CreatePanel(wxWindow * parent) = 0; }; typedef wxGuiPluginBase * (*CreateGuiPlugin_function)(); typedef void (*DeleteGuiPlugin_function)(wxGuiPluginBase * plugin); 


Public virtual methods:

Plugin implementation based on this interface:

SampleGuiPlugin1 / SampleGuiPlugin1.h

 #pragma once #include <wxGuiPluginBase.h> class SampleGuiPlugin1 : public wxGuiPluginBase { DECLARE_DYNAMIC_CLASS(SampleGuiPlugin1) public: SampleGuiPlugin1(); virtual ~SampleGuiPlugin1(); virtual wxString GetName() const; virtual wxString GetId() const; virtual wxWindow * CreatePanel(wxWindow * parent); }; 


SampleGuiPlugin1 / SampleGuiPlugin1.cpp

 #include "stdwx.h" #include "SampleGuiPlugin1.h" IMPLEMENT_DYNAMIC_CLASS(SampleGuiPlugin1, wxObject) SampleGuiPlugin1::SampleGuiPlugin1() { } SampleGuiPlugin1::~SampleGuiPlugin1() { } wxString SampleGuiPlugin1::GetName() const { return _("GUI Plugin 1"); } wxString SampleGuiPlugin1::GetId() const { return wxT("{4E97DF66-5FBB-4719-AF17-76C1C82D3FE1}"); } wxWindow * SampleGuiPlugin1::CreatePanel(wxWindow * parent) { wxWindow * result= new wxPanel(parent, wxID_ANY); result->SetBackgroundColour(*wxRED); return result; } 


CMakeLists.txt for this plugin is almost the same as the one we wrote for the plugin without a GUI. Differences will be only in the name of the project and in the list of files included in the project.


. GUI , , . . 5-10, . LoadXXXPlugins() , UnloadXXXPlugins() , RegisterXXXPlugin() , UnRegisterXXXPlugin() , - - wxModularCore , , .
wxModularCore/wxModularCore.h

 #pragma once // We need to keep the list of loaded DLLs WX_DECLARE_LIST(wxDynamicLibrary, wxDynamicLibraryList); class wxModularCoreSettings; class wxModularCore { public: wxModularCore(); virtual ~wxModularCore(); virtual wxString GetPluginsPath(bool forceProgramPath) const; virtual wxString GetPluginExt(); virtual bool LoadAllPlugins(bool forceProgramPath) = 0; virtual bool UnloadAllPlugins() = 0; virtual void Clear(); protected: wxDynamicLibraryList m_DllList; wxModularCoreSettings * m_Settings; template<typename PluginType, typename PluginListType> bool RegisterPlugin(PluginType * plugin, PluginListType & list) { list.Append(plugin); return true; } template<typename PluginType, typename PluginListType, typename PluginToDllDictionaryType, typename DeletePluginFunctionType> bool UnRegisterPlugin( PluginType * plugin, PluginListType & container, PluginToDllDictionaryType & pluginMap) { typename PluginListType::compatibility_iterator it = container.Find(plugin); if (it == NULL) return false; do { wxDynamicLibrary * dll = (wxDynamicLibrary *)pluginMap[plugin]; if (!dll) // Probably plugin was not loaded from dll break; wxDYNLIB_FUNCTION(DeletePluginFunctionType, DeletePlugin, *dll); if (pfnDeletePlugin) { pfnDeletePlugin(plugin); container.Erase(it); pluginMap.erase(plugin); return true; } } while (false); // If plugin is not loaded from DLL (eg embedded into executable) wxDELETE(plugin); container.Erase(it); return true; } template<typename PluginType, typename PluginListType, typename PluginToDllDictionaryType, typename DeletePluginFunctionType> bool UnloadPlugins(PluginListType & list, PluginToDllDictionaryType & pluginDictoonary) { bool result = true; PluginType * plugin = NULL; while (list.GetFirst() && (plugin = list.GetFirst()->GetData())) { result &= UnRegisterPlugin<PluginType, PluginListType, PluginToDllDictionaryType, DeletePluginFunctionType>(plugin, list, pluginDictoonary); } return result; } template <typename PluginType, typename PluginListType, typename PluginToDllDictionaryType, typename CreatePluginFunctionType> bool LoadPlugins(const wxString & pluginsDirectory, PluginListType & list, PluginToDllDictionaryType & pluginDictionary, const wxString & subFolder) { wxFileName fn; fn.AssignDir(pluginsDirectory); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); fn.AppendDir(subFolder); wxLogDebug(wxT("%s"), fn.GetFullPath().data()); if (!fn.DirExists()) return false; if(!wxDirExists(fn.GetFullPath())) return false; wxString wildcard = wxString::Format(wxT("*.%s"), GetPluginExt().GetData()); wxArrayString pluginPaths; wxDir::GetAllFiles(fn.GetFullPath(), &pluginPaths, wildcard); for(size_t i = 0; i < pluginPaths.GetCount(); ++i) { wxString fileName = pluginPaths[i]; wxDynamicLibrary * dll = new wxDynamicLibrary(fileName); if (dll->IsLoaded()) { wxDYNLIB_FUNCTION(CreatePluginFunctionType, CreatePlugin, *dll); if (pfnCreatePlugin) { PluginType * plugin = pfnCreatePlugin(); RegisterPlugin(plugin, list); m_DllList.Append(dll); pluginDictionary[plugin] = dll; } else wxDELETE(dll); } } return true; } }; 


wxModularHost/SampleModularCore.h

 #pragma once #include <wxModularCore.h> #include <wxNonGuiPluginBase.h> #include <wxGuiPluginBase.h> // We need to know which DLL produced the specific plugin object. WX_DECLARE_HASH_MAP(wxNonGuiPluginBase*, wxDynamicLibrary*, wxPointerHash, wxPointerEqual, wxNonGuiPluginToDllDictionary); WX_DECLARE_HASH_MAP(wxGuiPluginBase*, wxDynamicLibrary*, wxPointerHash, wxPointerEqual, wxGuiPluginToDllDictionary); // And separate list of loaded plugins for faster access. WX_DECLARE_LIST(wxNonGuiPluginBase, wxNonGuiPluginBaseList); WX_DECLARE_LIST(wxGuiPluginBase, wxGuiPluginBaseList); class SampleModularCore : public wxModularCore { public: virtual ~SampleModularCore(); virtual bool LoadAllPlugins(bool forceProgramPath); virtual bool UnloadAllPlugins(); const wxNonGuiPluginBaseList & GetNonGuiPlugins() const; const wxGuiPluginBaseList & GetGuiPlugins() const; private: wxNonGuiPluginToDllDictionary m_MapNonGuiPluginsDll; wxNonGuiPluginBaseList m_NonGuiPlugins; wxGuiPluginToDllDictionary m_MapGuiPluginsDll; wxGuiPluginBaseList m_GuiPlugins; }; 


wxModularHost/SampleModularCore.cpp

 #include "stdwx.h" #include "SampleModularCore.h" #include <wx/listimpl.cpp> WX_DEFINE_LIST(wxNonGuiPluginBaseList); WX_DEFINE_LIST(wxGuiPluginBaseList); SampleModularCore::~SampleModularCore() { Clear(); } bool SampleModularCore::LoadAllPlugins(bool forceProgramPath) { wxString pluginsRootDir = GetPluginsPath(forceProgramPath); bool result = true; result &= LoadPlugins<wxNonGuiPluginBase, wxNonGuiPluginBaseList, wxNonGuiPluginToDllDictionary, CreatePlugin_function>(pluginsRootDir, m_NonGuiPlugins, m_MapNonGuiPluginsDll, wxT("nongui")); result &= LoadPlugins<wxGuiPluginBase, wxGuiPluginBaseList, wxGuiPluginToDllDictionary, CreateGuiPlugin_function>(pluginsRootDir, m_GuiPlugins, m_MapGuiPluginsDll, wxT("gui")); // You can implement other logic which takes in account // the result of LoadPlugins() calls return true; } bool SampleModularCore::UnloadAllPlugins() { return UnloadPlugins<wxNonGuiPluginBase, wxNonGuiPluginBaseList, wxNonGuiPluginToDllDictionary, DeletePlugin_function>(m_NonGuiPlugins, m_MapNonGuiPluginsDll) && UnloadPlugins<wxGuiPluginBase, wxGuiPluginBaseList, wxGuiPluginToDllDictionary, DeleteGuiPlugin_function>(m_GuiPlugins, m_MapGuiPluginsDll); } const wxNonGuiPluginBaseList & SampleModularCore::GetNonGuiPlugins() const { return m_NonGuiPlugins; } const wxGuiPluginBaseList & SampleModularCore::GetGuiPlugins() const { return m_GuiPlugins; } 


, GUI- .

GUI-


Docking- wxAuiNotebook . wxAuiNotebook:
wxModularHost/MainFrame.cpp
 void MainFrame::AddPagesFromGuiPlugins() { SampleModularCore * pluginManager = wxGetApp().GetPluginManager(); for(wxGuiPluginBaseList::Node * node = pluginManager->GetGuiPlugins().GetFirst(); node; node = node->GetNext()) { wxGuiPluginBase * plugin = node->GetData(); if(plugin) { wxWindow * page = plugin->CreatePanel(m_Notebook); if(page) { m_Notebook->AddPage(page, plugin->GetName()); } } } } 

:

image

GetName() , CreatePanel() .

CMake- Linux


Windows , , RUNTIME_OUTPUT_DIRECTORY . Linux, .. – ( ), LIBRARY_OUTPUT_DIRECTORY . : bin, . , bin. ( ):

SampleGuiPlugin2/CMakeLists.txt
 ... set(DLL_DIR bin) if(LINUX) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}${LIB_SUFFIX}/plugins/nongui) else(LINUX) set(TARGET_LOCATION ${PROJECT_SOURCE_DIR}/${DLL_DIR}/${CMAKE_CFG_INTDIR}/plugins/nongui) get_target_property(RESULT_FULL_PATH ${LIBRARY_NAME} LOCATION) get_filename_component(RESULT_FILE_NAME ${RESULT_FULL_PATH} NAME) endif(LINUX) set_target_properties(${LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${TARGET_LOCATION}) ... if(LINUX) add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${TARGET_LOCATION} COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${LIBRARY_NAME}> ${TARGET_LOCATION}/${RESULT_FILE_NAME} ) endif(LINUX) 


Linux :

SampleGuiPlugin2/CMakeLists.txt
 ... if(WIN32) set(SRCS ${SRCS} ${LIBRARY_NAME}.def) set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS) set(LINK_DIRECTORIES ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName)) set(DEMO_LIBS wxNonGuiPluginBase.lib) endif(WIN32) if(LINUX) set(DEMO_LIBS wxNonGuiPluginBase) endif(LINUX) ... 


, . ( ).
, . , , , . ldd:

 ldd libSampleGuiPlugin2.so | grep wxSampleGuiPluginBase 


, , CMake RPATH , :

SampleGuiPlugin2/CMakeLists.txt
 if(WIN32) set(SRCS ${SRCS} ${LIBRARY_NAME}.def) set(PREPROCESSOR_DEFINITIONS ${PREPROCESSOR_DEFINITIONS};/D_USRDLL;/D__STDC_CONSTANT_MACROS) set(LINK_DIRECTORIES ${PROJECT_ROOT_DIR}/wxNonGuiPluginBase/${OS_BASE_NAME}${LIB_SUFFIX}/$(ConfigurationName)) set(DEMO_LIBS wxNonGuiPluginBase.lib) endif(WIN32) if(LINUX) set(DEMO_LIBS wxNonGuiPluginBase) SET(CMAKE_SKIP_BUILD_RPATH FALSE) SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) SET(CMAKE_INSTALL_RPATH ".:./../../") endif(LINUX) 


Since plugins/gui, wxGuiPluginBase , CMakeLists.txt

CMake- OS X


, Linux, OS X . OS X , install_name_tool.
CMakeLists.txt, :

SampleGuiPlugin2/CMakeLists.txt
 if(APPLE) FOREACH(DEP_LIB ${DEMO_LIBS}) get_filename_component(ABS_ROOT_DIR ${PROJECT_ROOT_DIR} ABSOLUTE) set(LIBNAME_FULL "${ABS_ROOT_DIR}/${DEP_LIB}/${OS_BASE_NAME}${LIB_SUFFIX}/$(CONFIGURATION)/lib${DEP_LIB}.dylib") add_custom_command(TARGET ${LIBRARY_NAME} POST_BUILD COMMAND install_name_tool -change "${LIBNAME_FULL}" "@executable_path/../Frameworks/lib${DEP_LIB}.dylib" $<TARGET_FILE:${LIBRARY_NAME}>) ENDFOREACH(DEP_LIB) endif(APPLE) 


CMake-

wxModularHost/CMakeLists.txt
 if(APPLE) FOREACH(DEP_LIB ${DEMO_LIBS_SHARED}) get_filename_component(ABS_ROOT_DIR ${PROJECT_ROOT_DIR} ABSOLUTE) set(LIBNAME_FULL "${ABS_ROOT_DIR}/${DEP_LIB}/${OS_BASE_NAME}${LIB_SUFFIX}/$(CONFIGURATION)/lib${DEP_LIB}.dylib") add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD COMMAND install_name_tool -change "${LIBNAME_FULL}" "@executable_path/../Frameworks/lib${DEP_LIB}.dylib" $<TARGET_FILE:${EXECUTABLE_NAME}>) ENDFOREACH(DEP_LIB) endif(APPLE) 


In conclusion


- , Windows OS X Makefile CMake. , - .
, , GitHub: https://github.com/T-Rex/wxModularApp

PS: wxWidgets (3.0), CMake ( , 2.9.x 3.0. 2.9: svn.wxwidgets.org/svn/wx/wxWidgets/tags/WX_2_9_5

Source: https://habr.com/ru/post/203322/


All Articles