#+INCLUDE: theme/style.org
#+TITLE: C Wrappers for C++ Libraries and Interoperability
#+DESCRIPTION: cpp/c++ interoperability ffi shim qt5 widgets gui library wrapper julia dlang
#+STARTUP: content

* C Wrappers for C++ Libraries and Interoperability
** Overview

One of the main advantages of the C language is the the possibility of
calling libraries written in C from other programming languages via
FFI (Foreign Function Interface), which allows calling C functions
from C shared libraries. Unlike C, calling C++ libraries directly via
FFI is not feasible as C++ lacks a standard ABI - Application Binary
interface. A solution for this issue is to create a C wrapper for the
C++ library using _opaque pointer_, _incomplete types_ and _extern C_
annotation.

** Calling C++ from C

This section presents how to call a statically linked C++ cod from C.

 + Complete code:  [[https://gist.github.com/caiorss/c236aedbbb514717ad26e856654bb063][Example GIST - calling C++ from C]]

File: *cppcode.hpp*
 + Header file that can be used by a C or C++ code. All code between
   #ifndef __cplusplus ... #edif is ignored by a C compiler.

#+BEGIN_SRC cpp
  #ifndef _CODECPP_
  #define _CODECPP_

  #ifdef __cplusplus
    #define EXPORT_C extern "C"
  #else
    #define EXPORT_C
  #endif

  //============ C++ Only Header =================//
  #ifdef __cplusplus  // Enabled only for C++ compilers
  #include <iostream>

  class Runstat
  {
      /// Sum of sequence processed
      double m_sum;
      /// Sum of squares processed
      double m_sumsq;
      /// Size of sequence processed
      size_t m_N;
  public:
      Runstat();
      Runstat(Runstat const&)            = delete;
      Runstat& operator=(Runstat const&) = delete;
      ~Runstat();
      void   add(double x);
      void   reset();
      size_t size() const;
      double mean() const;
      /// Standard deviation
      double sdev() const;
  };

  #endif //-- End of __cplusplus definition //


  // ========== C-interface for std::string container
  typedef  void* hString;

  EXPORT_C hString string_new();
  EXPORT_C hString string_new1 (const char* text);
  EXPORT_C hString string_copy (hString self);
  EXPORT_C void    string_del  (hString self);
  EXPORT_C void    string_add  (hString self, const char* text);
  EXPORT_C void    string_disp (hString, const char* name);


  //============ C-interface for class Runstat ============//

  // Opaque pointer type alias for C-lang
  typedef void* pStat;

  EXPORT_C pStat   Runstat_new();
  EXPORT_C void    Runstat_del (pStat self);
  EXPORT_C void    Runstat_add (pStat self, double x);
  EXPORT_C double  Runstat_mean(pStat self);
  EXPORT_C double  Runstat_sdev(pStat self);
  EXPORT_C size_t  Runstat_size(pStat self);

  #endif
#+END_SRC

File: *cppcode.cpp*
 + Implementation of classes and C interface functions.

Implementation of class RunStat member functions:

#+BEGIN_SRC cpp
   ... ... ... ...

  Runstat::Runstat()
  {
      std::cout << " [TRACE] Object created Ok." << std::endl;
      m_sum = 0.0;
      m_sumsq = 0.0;
      m_N = 0;
  }

  Runstat::~Runstat()
  {
      std::cout << " [TRACE] Object deleted OK" << std::endl;
  }

  void
  Runstat::add(double x)
  {
      m_sum += x;
      m_sumsq += x * x;
      m_N++;
  }

  ...... ....   ...... ....   ...... ....

  Runstat::~Runstat()
  {
      std::cout << " [TRACE] Object deleted OK" << std::endl;
  }

  ...... ....   ...... ....   ...... ....

  void
  Runstat::add(double x)
  {
      m_sum += x;
      m_sumsq += x * x;
      m_N++;
  }

 ... ...  ... ...  ... ...  ... ...
#+END_SRC

Implementation of the C-interface functions for class std::string.

#+BEGIN_SRC cpp
  //--------- C-Interface for class std::string ------------------//

  hString string_new()
  {
      return new std::string{};
  }

  hString string_new1(const char* text)
  {
      return new std::string(text);
  }

  // Copy constructor
  hString string_copy(hString self)
  {
      std::string* p = reinterpret_cast<std::string*>(self);
      return new std::string(*p);
  }

  void string_del(hString self)
  {
      delete reinterpret_cast<std::string*>(self);
  }

  void string_add(hString self, const char* text)
  {
      auto p = reinterpret_cast<std::string*>(self);
      ,*p = *p + text;
  }

  void string_disp(hString self, const char* name)
  {
      auto p = reinterpret_cast<std::string*>(self);
      std::cout << name << " / std::string{ " << *p << "} " << std::endl;
  }
#+END_SRC

Implementation of the C-interface functions for class RunStat.

#+BEGIN_SRC cpp
  //---------- C-Interface for class Runstat ---------------------//

  pStat Runstat_new()
  {
      return new (std::nothrow) Runstat();
  }

  void Runstat_del(pStat self)
  {
      delete reinterpret_cast<Runstat*>(self);
  }

  void Runstat_add(pStat self, double x)
  {
      auto p = reinterpret_cast<Runstat*>(self);
      p->add(x);
  }

  double Runstat_mean(pStat self)
  {
      Runstat* p = reinterpret_cast<Runstat*>(self);
      return p->mean();
  }

  double Runstat_sdev(pStat self)
  {
      Runstat* p = reinterpret_cast<Runstat*>(self);
      return p->sdev();
  }


  size_t Runstat_size(pStat self)
  {
      Runstat* p = reinterpret_cast<Runstat*>(self);
      return p->size();
  }
#+END_SRC

 *File: main.c*
  + C client code that uses C-interface functions export by the C++ file cppcode.

#+BEGIN_SRC C
  #include <stdio.h>
  #include <stdlib.h>

  #include "codecpp.hpp"

  int main()
  {
      printf("\n == EXPERIMENT 1 - std::string C-wrapper ======\n");
      hString str = string_new1("A C++ string in C");
      string_disp(str, "str");
      hString str2 = string_copy(str);
      string_add(str, " - hello world");
      string_disp(str, "str");
      string_disp(str2, "str2");
      string_del(str);
      string_del(str2);

      printf("\n == EXPERIMENT 2 - Class Runstat ======\n");
      pStat obj = Runstat_new();
      Runstat_add(obj, 10.0);
      Runstat_add(obj, 4.0);
      Runstat_add(obj, 25.0);
      Runstat_add(obj, 16.0);

      printf(" Number of Elements processed = %zu \n", Runstat_size(obj));
      printf(" Mean = %.5f \n", Runstat_mean(obj));
      printf(" Sdev = %.5f \n", Runstat_sdev(obj));

      Runstat_add(obj, -50.0);
      Runstat_add(obj, 80.0);
      printf(" Mean = %.5f \n", Runstat_mean(obj));
      printf(" Sdev = %.5f \n", Runstat_sdev(obj));
      // Delete C++ Object
      Runstat_del(obj);
      return 0;
  }
#+END_SRC

 *File: CMakeLists.txt*

#+BEGIN_SRC cpp
  cmake_minimum_required(VERSION 3.9)
  project( CallCppFromC)

  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_VERBOSE_MAKEFILE ON)

  add_executable(main main.c codecpp.cpp codecpp.hpp)
#+END_SRC

Manual compilation:

#+BEGIN_SRC sh
  # Generate object code codecpp.o
  $ g++ codecpp.cpp -o codecpp.o -c -std=c++1z -Wall -g

  # Generate object-code main.o
  $ gcc main.c -o main.o -c -g

  # Link => generate main.bin
  $ g++ main.o codecpp.o -o main.bin
#+END_SRC

Program output:

#+BEGIN_SRC sh
  ./main.bin

   == EXPERIMENT 1 - std::string C-wrapper ======
  str / std::string{ A C++ string in C}
  str / std::string{ A C++ string in C - hello world}
  str2 / std::string{ A C++ string in C}

   == EXPERIMENT 2 - Class Runstat ======
   [TRACE] Object created Ok.
   Number of Elements processed = 4
   Mean = 13.75000
   Sdev = 8.95824
   Mean = 14.16667
   Sdev = 41.69612
   [TRACE] Object deleted OK
#+END_SRC

** Calling Qt5 Widgets library from many languages 
*** Overview

The technique used in this sample code is the _hourglass-design
pattern_, that exposes C++ classes and methods through a thin C-89 API
for ensuring better ABI stability, allowing C++ client codes built
with any other C++ compiler to link against the library and making the
it friendlier to other programming languages via FFI -
foreign-function interfaces or linking against C-compatible
functions. In this pattern, classes are only accessible to client code
as opaque types or void pointers and member functions (class methods)
are only accessible through functions without name mangling, that are
annoted with _extern "C"_. A C++ SDK (Software Development Kit) wrapping
the thin C-89 API can also be provided for making the library usage
easier by C++ client codes.  This sample code provides a C wrapper to
the QT5 Widgets GUI library. The hourglass design-patter is not
exclusive to C++, it can also be applied other programming languages
such as Rust, D - DLang-language, Nim, Pascal and Zig as those
languages allow exposing C-compatible functions without name mangling
via a extern "C" like annotations.


This C-wrapper allows calling Qt5 widgets library from C, Python,
Common Lisp, Nim, Rust, Julia language and also D-language.

Hourglass Design pattern Schema for a C++ library: 

#+BEGIN_SRC text 

        SDK means Software Development Kit 
                                                                      C++ library with stable ABI 
        +-------------------+                                         +------------------------+
        |                   |      Library                 Library    | Library's Private C++  |
        | C++ client code   +<<<- (C++ SDK) -(+)----<<-- (C-89 API) --+ Implementation         |
        |                   |     header-only            extern "C"   | no exposed             |
        +-------------------+    library wrapper             |        +------------------------+
                                                             |
        +---------------------+                             \ / 
        | Client code in      |                              |
        | higher level        |       FFI Foreign-Function   | 
        | language            +<<<--- Interface Wrapper <<---/               
        | (C#, Python, Ruby)  |       or Linkage against     |
        +---------------------+       the  C-89 API          |
                                                             |
        +---------------------+                              +
        | Rust Client Code    |                             /
        |                     +<<<-- (Rust SDK) <<-----<<<-/   
        |                     |
        +---------------------+                       
#+END_SRC          

This technique can be used for many purposes, including: 

  * Build C++ libraries that can be distributed as pre-compiled
    binaries and can be used with any other C++ compiler as long as
    the client code only uses the C-89 thin API (Application
    Programming Interface).
    
  * Shipping proprietary, closed source libraries, that can be linked
    against by C++ source code built with any other compiler without
    any ABI issues as long as the client code only links against the
    C-89 thin API.
    
  * Making it easier for client code written in high level languages,
    such as Python, Julia, Ruby or C# (CSharp) to use the library
    through a FFI - Foreign Function Interface or linkage against the
    C89 thin API.

  * Create C-bindings to already existing C++ libraries for allowing
    them to be used via FFI - Foreign-Function Interfaces.

  * Implement C libraries in C++, Rust, D (Dlang), Nim or Zig
    programming languages.


Known uses of the hourglass technique:

  1. [[https://github.com/tensorflow/tensorflow/search?q=extern+%2522C%2522][Tensorflow library for Machine Learning]]
     + Leading machine learning library with bindings to many
       languages including Python, JavaScript, C++, Java, Julia,
       Matlab, R, Ruby, Rust and so on.
     + Note: Despite Tensorflow be implemented in C++, the library
       only exposes a C-89 interface.
     + See: https://www.tensorflow.org/api_docs
       
  2. [[https://github.com/TileDB-Inc/TileDB/search?q=extern+%2522C%2522][TileDB]]
     + Embeddable database that can store time-series, geospatial
       data, arrays, matrices, sparse matrices and tensors (multi
       dimensional arrays).

  3. [[https://github.com/randombit/botan/tree/master/src/lib/ffi][Botan C++ TLS crytography library]]
     + C++ library alternative to OpenSSL for TLS (Transport Layer
       Security) or SSL (Socket Layer Security) for encryption of data
       at transit of TCP or UDP network protocols. This library has
       C-89 API and bindings for Python, Ruby, Java, Rust, Haskell and
       Odin programming languages. Note that the [[https://github.com/randombit/botan/blob/master/src/python/botan2.py][Python binding]] uses
       ctypes for loading the C-89 thin API symbols from the botan
       shared library. 
       
  4. [[https://github.com/albertodemichelis/squirrel/search?q=extern+%2522C%2522][Squirrel]]
     + An embedded scripting language, for adding scripting capabilities
       to C or C++ applications, written in C++, but only exposed as
       a C-89 library. 


Futhere reading: 

 + [[https://www.slideshare.net/StefanusDuToit/cpp-con-2014-hourglass-interfaces-for-c-apis][CPpCon2014 - Hourglass Interfaces for C++ APIs]] 


 *Documentation of features used in this experiment*

QT5 Documentation for widgets classes used in the sample code: 

  + [[https://doc.qt.io/qt-5/qobject.html][QObject Class | Qt Core 5.15.0]]

  + [[https://doc.qt.io/qt-5/qwidget.html][QWidget Class | Qt Widgets 5.15.0]]

  + [[https://doc.qt.io/qt-5/qapplication.html][QApplication Class | Qt Widgets 5.15.0]]

  + [[https://doc.qt.io/qt-5/qpushbutton.html][QPushButton Class | Qt Widgets 5.15.0]]

  + [[https://doc.qt.io/qt-5/qdoublespinbox.html][QDoubleSpinBox Class | Qt Widgets 5.15.0]]


Python 3 Ctypes FFI Documentation: 

  + [[https://docs.python.org/3/library/ctypes.html][Python 3 Ctypes]] (Official Documentation)

  + [[https://www.programiz.com/python-programming/closure][Python Closures]] - Programiz 

  + [[https://pgi-jcns.fz-juelich.de/portal/pages/using-c-from-python.html][Using C from Python: How to create a ctypes wrapper]] - Julich Scientific IT Software.

  + [[https://www.cl.cam.ac.uk/~jdy22/papers/ctypes-foreign-calls-in-your-native-language.pdf][ctypes: foreign calls in your native language]] - Jeremy Yallop - University of Cambridge

  + [[http://www.dalkescientific.com/writings/NBN/ctypes.html][Python 2.5 Ctypes]] - Dalke Scientific

  + [[https://scipy.github.io/old-wiki/pages/Cookbook/Ctypes.html][Scipy Cookbook - Ctypes]] 

  + [[https://stackoverflow.com/questions/874245/python-ctypes-and-function-pointers][Python ctypes and function pointers]] (StackOverflow)

  + [[https://stackoverflow.com/questions/25014191/python-ctypes-function-pointer][Python ctypes function pointer]] (StackOverflow)

  + [[https://dbader.org/blog/python-ctypes-tutorial-part-2][Interfacing Python and C: Advanced "ctypes" Features]]

  + [[https://doc.sagemath.org/html/en/thematic_tutorials/numerical_sage/ctypes.html][Sagemath - Python Ctypes]]

  + [[https://www.cs.unc.edu/~gb/blog/2007/02/11/ctypes-tricks/][Python Ctypes for wrapping OpenCV]]


Julia language: 

  + [[https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/][Calling C and Fortran Code · The Julia Language]]

  + [[https://docs.julialang.org/en/v1/manual/embedding/][Embedding Julia · The Julia Language]]

  + [[https://en.wikipedia.org/wiki/Julia_(programming_language)][Julia (programming language) - Wikipedia]]

  + [[https://www.nature.com/articles/d41586-019-02310-3][Julia: come for the syntax, stay for the speed]] - Nature Magazine

  + [[https://juliabyexample.helpmanual.io/][Julia By Example]]

C# (Csharp) for Dotnet Core:

  + https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet

  + [[https://docs.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual][Microsoft - Install the .NET SDK or the .NET Runtime manually]]

  + [[https://docs.microsoft.com/en-us/dotnet/csharp/][C# Official Documentation]]

  + [[https://docs.microsoft.com/en-us/dotnet/framework/interop/consuming-unmanaged-dll-functions][Consuming Unmanaged DLL Functions]]

  + [[https://docs.microsoft.com/en-us/dotnet/api/system.intptr.zero?view=net-5.0][IntPtr.Zero Field]] 

  + [[https://docs.microsoft.com/en-us/dotnet/framework/interop/exposing-dotnet-components-to-com][Exposing .NET components to COM]]

  + [[https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order][Microsft - Dynamic-Link Library Search Order]]

  + Scott Hanselman - [[https://radu-matei.com/blog/self-contained-dotnet-cli/][Building self-contained, single executable .NET Core 3 CLI tools]]

  + [[https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file][Microsft - Single file deployment and executable]]

  + [[https://www.hanselman.com/blog/exploring-the-new-net-dotnet-command-line-interface-cli][Exploring the new .NET "dotnet" Command Line Interface (CLI)]]


D Language (DLang: 

  + [[https://en.wikipedia.org/wiki/D_(programming_language)][D (programming language) - Wikipedia]]

  + [[https://dlang.org/blog/2017/12/05/interfacing-d-with-c-getting-started/][Interfacing D with C: Getting Started – The D Blog]]

  + [[https://dlang.org/spec/interfaceToC.html][Interfacing to C - D Programming Language]]

  + [[https://forum.dlang.org/thread/o3sb8m$24vp$1@digitalmars.com][Using dlopen/dlsym - D Programming Language Discussion Forum]]

  + [[https://dlang.org/spec/cpp_interface.html][Interfacing to C++ - D Programming Language]]


Nim Programming Language:

  + https://narimiran.github.io/nim-basics/
    
  + [[https://forum.nim-lang.org/t/6399][Nim Forum - Lambda syntax is awkard]]

  + [[https://dev.to/juancarlospaco/python-cython-nim-cheatsheet-hjj][Python/Cython/Nim CheatSheet - DEV Community]]

  + [[https://github.com/nim-lang/Nim/wiki/Nim-for-Python-Programmers][Nim for Python Programmers · nim-lang/Nim Wiki · GitHub]]

  + [[https://github.com/nimterop/nimterop][GitHub - nimterop/nimterop]]
    + Nimterop is a Nim package that aims to make C/C++ interop seamless

  + [[https://github.com/khchen/winim][GitHub - khchen/winim: Nim's Windows API and COM Library]]


Lisp - SBCL (Steel Bank Common Lisp): 

  + http://www.sbcl.org/ - Steel Bank Common Lisp

  + [[https://linux.die.net/man/1/sbcl#:~:text=To%20run%20SBCL%2C%20type%20%22sbcl,wait%20for%20your%20next%20input.&text=Most%20people%20like%20to%20run%20SBCL%20as%20a%20subprocess%20under%20Emacs.][sbcl(1): Steel Bank Common Lisp - Linux man page]]

  + [[https://common-lisp.net/~loliveira/tmp/old/cffi-newtypes/cffi-manual.html][CFFI User Manual]]

  + [[https://z0ltan.wordpress.com/2016/09/16/interop-mini-series-calling-c-and-c-code-from-common-lisp-using-cffi-part-1/][Interop mini-series – Calling C and C++ code Lisp using CFFI (Part 1)]]

  + [[https://gist.github.com/lamberta/212533/c4ac72b6606666ace8d35ada018614cbb1d273d6][use C shared lib with cffi · GitHub]]

  + [[https://gist.github.com/lagagain/df1b97eb08e57c2759c950efc3a2c7dd][practice - Common Lisp hello CFFI (Common Lisp + C) · GitHub]]

  + [[https://www.xach.com/lisp/buildapp/][Buildapp - Create executables with SBCL or CCL]]
    
*** Wrapper Files 

  + GIST with all sources:
    + https://gist.github.com/a59d0f1b17d286dad19842932c931e92

 *Source code for C-wrapper* 

File: _Makefile_

#+BEGIN_SRC makefile
    # Build C wrapper to Qt library
    wrapper:
            cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug
            cmake --build _build --target all 

    # Run python client code
    python:
            env LD_LIBRARY_PATH=$(PWD)/_build ./pywrapper.py

    # Build and run dotnet client code
    dotnet:
            env LD_LIBRARY_PATH=$(PWD)/_build dotnet build myapp.csproj
            env LD_LIBRARY_PATH=$(PWD)/_build ./bin/myapp 

    # Build rust client
    rust:
            rustc rustclient.rs -l qtwrapper -L_build/
#+END_SRC

File: _CMakeLists.txt_

#+BEGIN_SRC cmake 
  cmake_minimum_required(VERSION 3.9)
  project(Qt5_Widgets_Template)
  
  #====== Global Configurations ==================#
  
  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
  set(CMAKE_VERBOSE_MAKEFILE ON)
  
  set(CMAKE_INCLUDE_CURRENT_DIR ON)
  set(CMAKE_AUTOUIC ON)
  set(CMAKE_AUTOMOC ON)
  set(CMAKE_AUTORCC ON)
  
  # Export ALL DLLs symbols on Windows without __declspec(xxxx) annotations.
  set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true)
  
  find_package(Qt5 COMPONENTS Core Widgets Network  REQUIRED)
  
  #=============== Target Configurations ============#
  
  include_directories(.)
  
  # --------------------------------------------------#
            add_library( qtwrapper SHARED qtwrapper.cpp )
  target_link_libraries( qtwrapper PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets )      
  
         add_executable( client1  client1.c )
  target_link_libraries( client1  qtwrapper )      
  
         add_executable( client2 client.cpp)
  target_link_libraries( client2  qtwrapper )      

#+END_SRC

File: _qtwrapper.cpp_

  + C-wrapper or C-interface for QT5 C++ GUI library 

#+BEGIN_SRC cpp 
  #include <QtWidgets>
  #include <QApplication>
  #include <QtUiTools/QtUiTools>
  #include <QSysInfo>
  #include <QtConcurrent/QtConcurrent>

  #include <functional>
  #include <iostream> 
  #include <fstream>
  #include <string>
  #include <map> 

  /** extern "C" =>  Remove C++ name mangling and makes the function 
   ,* compatible with C. It enforces C linkage to the annotated symbol. 
   ,**-----------------------------------------------------------------*/
  #define EXPORT_C extern "C"

  /** Delete any instance of a derived class of QObject
   ,* Note: It can be used for deleting instances QWidget, QApplication or QLayout.
   ,*/
  EXPORT_C 
  void qt_qobject_del(QObject* self)
  {
      delete self;
  }

  EXPORT_C 
  const char* qt_qobject_ClassName(QObject* self)
  {
      return self->metaObject()->className();
  }

  EXPORT_C 
  void qt_qobject_dumpObjectInfo(QObject* self)
  {
      self->dumpObjectInfo();
  }

  EXPORT_C 
  void qt_qobject_dumpObjectTree(QObject* self)
  {
      self->dumpObjectTree(); 
  }

  EXPORT_C 
  void qt_qobject_print(QObject* self)
  {
      qDebug() << " [QOBjec] " << self;
  }

  enum class WidgetType
  {
        Window         = 1
      , Window_main    = 2
      , QPushButton    = 3
      , QLabel         = 4
      , QLineEdit      = 5
      , QDoubleSpinBox = 6
  };

  using CtorFunction = std::function<QWidget* (QWidget* parent)>;
  using CtorDatabase = std::map<int, CtorFunction>;

  template<typename T> void register_ctor(CtorDatabase&  db, int type)
  {
      db[type] = [](QWidget* parent){ return new (std::nothrow) T(parent); };
  }

  // Create an object of a QWidget given class, given its name 
  EXPORT_C 
  QWidget* qt_widget_new(QWidget* parent, int type)
  {
      // 'static' => Initialize static object only once. 
      static const CtorDatabase ctordb = []{
          auto db = CtorDatabase{};
          register_ctor<QWidget>(db,        (int) WidgetType::Window);
          register_ctor<QPushButton>(db,    (int) WidgetType::QPushButton);
          register_ctor<QLabel>(db,         (int) WidgetType::QLabel);
          register_ctor<QLineEdit>(db,      (int) WidgetType::QLineEdit);
          register_ctor<QDoubleSpinBox>(db, (int) WidgetType::QDoubleSpinBox);
        
          db[(int) WidgetType::Window_main] = [](QWidget* parent){
              QWidget* w = new (std::nothrow) QWidget(parent);
              w->resize(500, 400);        
              w->setWindowTitle("MainWindow");
              w->show();
              return w;
          };
          return db;
      }();
   
      if(auto it = ctordb.find(type); it != ctordb.end())
      {  return it->second(parent);  }    
      return nullptr;
  }

  EXPORT_C 
  QWidget* qt_window_main()
  {
      QWidget* w = new (std::nothrow) QWidget{};
      w->resize(500, 400);        
      w->setWindowTitle("MainWindow");
      w->show();
      return w; 
  }

  EXPORT_C 
  QLayout* qt_layout_new(QWidget* parent, int type)
  {
      if(type == 1) return new (std::nothrow) QVBoxLayout(parent);    
      if(type == 2) return new (std::nothrow) QHBoxLayout(parent);    
      if(type == 3) return new (std::nothrow) QFormLayout(parent);
      return nullptr;
  }

  EXPORT_C 
  QObject* qt_QFormLayout_addWidgetAndLabel(QFormLayout* self, int type, const char* label)
  {
      QWidget* wdg = qt_widget_new(nullptr, type);
      if(wdg == nullptr){ return nullptr; }
      self->addRow(label, wdg);
      return wdg;
  }

  EXPORT_C 
  void qt_QFormLayout_addRowItem(QFormLayout* self, QWidget* field)
  {
      self->addRow(field);
  }


  EXPORT_C 
  QLabel* qt_QFormLayout_addLabel1(QFormLayout* self, const char* label_text)
  {
      auto btn = new QLabel(label_text);
      self->addRow(btn);
      return btn;
  }


  EXPORT_C 
  QApplication* qt_app_new(int argc, char** argv)   
  {
      std::cout << " [TRACE] Create QAppliction Object Ok" << std::endl;
      return new QApplication(argc, argv);
  }

  EXPORT_C 
  QApplication* qt_app_new2()   
  {
      std::cout << " [TRACE] Create QAppliction Object Ok" << std::endl;
      static int   argc = 1;
      static const char* argv [] = { "dummy_app" };    
      return new QApplication(argc, (char**) argv);
  }

  EXPORT_C 
  int qt_app_exec(QApplication* self)
  {
      return self->exec();
  }

  // -------- Wrappers for QWidget Class ------------------//

  EXPORT_C 
  void qt_widget_show(QWidget* self)
  {
      self->show();
  }

  template<typename T>
  static bool set_text(QWidget* self, const char* text)
  {
      auto obj = qobject_cast<T>(self);
      // Early return on Error. 
      if(obj == nullptr ){ return false;  }    
      obj->setText(text);
      return true;
  }

  EXPORT_C 
  void qt_widget_setText(QWidget* self, const char* text)
  {        
      if( set_text<QLabel*>(self, text))           return;
      if( set_text<QLineEdit*>(self, text))        return;
      if( set_text<QTextEdit*>(self, text))        return; 
      if( set_text<QMessageBox*>(self, text))      return; 
      if( set_text<QAbstractButton*>(self, text) ) return;    
      // logger().log() << " [TRACE] Called function " << __FUNCTION__ << std::endl;
      self->setWindowTitle(text);  
  }


  // -------- Wrappers for QPushButton Class ------------------//

  // Install event handler (callback) for button clicked event 
  EXPORT_C
  void qt_button_onClicked(  // Pointer to button 
                             QAbstractButton* self
                             // Context of the callback. Note: C does not support closures 
                             // or stateful function pointer. All data needs to be passed 
                             // as arguments
                           , void* ctx
                             // Pointer to callback function pointer. 
                           , void (* callback) (void* ctx) )
  {
      QObject::connect(self, &QPushButton::clicked, [=]{
          callback(ctx);
      });
  }


  // Install event handler for 
  EXPORT_C
  void qt_QLineEdit_onTextChanged(  QLineEdit* self
                                  , void* ctx
                                  , void (* callback) (void* ctx, QLineEdit* self) )
  {
      QObject::connect(self, &QLineEdit::textChanged, [=]{
          callback(ctx, self);
      });
  }


  EXPORT_C 
  void qt_msgbox_info(QWidget* parent, const char* title, const char* text)
  {
      QMessageBox::information(parent, title, text);
  }

  // Note: The string has to be released after with free() function.
  EXPORT_C
  const char* qt_QLineEdit_text(QLineEdit* self)
  {
      return self->text().toLocal8Bit().constData();
  }

  EXPORT_C 
  double qt_QDoubleSpinBox_value(QDoubleSpinBox* self)
  {
      return self->value();
  }

  EXPORT_C 
  void qt_QDoubleSpinBox_setValue(QDoubleSpinBox* self, double value)
  {
      self->setValue(value);
  }

  EXPORT_C 
  void qt_QDoubleSpinBox_onValueChanged( 
       QDoubleSpinBox* self
     , void* ctx
     , void (* callback)(void* ctx) )
  {
      QObject::connect(  self, qOverload<double>(&QDoubleSpinBox::valueChanged)
                       , [=](double x){ callback(ctx);  }
                      );
  }
#+END_SRC

File: _qtwrapper.h_ (header for the C-wrapper or C-interface)

#+BEGIN_SRC cpp
  // Non standard directive, but supported by most compiler to includer the
  // header only once
  #pragma once

  // Type aliases
  typedef void   ANY;
  typedef void   QObject;
  typedef void   QApplication;
  typedef void   QPushButton;
  typedef void   QWidget;
  typedef void   QAbstractButton;
  typedef void   QLineEdit;
  typedef void   QLayout;
  typedef void   QFormLayout;
  typedef void   QLabel;

  void qt_qobject_del(QObject* self);
  void qt_qobject_dumpObjectInfo(QObject* self);
  void qt_qobject_dumpObjectTree(QObject* self);
  void qt_qobject_print(QObject* self);


  QApplication* qt_app_new(int argc, char** argv);
  QApplication* qt_app_new2();

  // Instantiate any QT widget by name
  QWidget* qt_widget_new(QWidget* parent, int type);

  void qt_widget_setText(QWidget* self, const char* text);

  // QApplication
  int  qt_app_exec(QApplication* self);

  // Any QtWidget
  void          qt_QWidget_show(QWidget* self);
  void          qt_QWidget_setToolTip(QWidget* self, const char* tooltip);

  // QPushButton wrappers
  QPushButton*  qt_QPushButton_new(QWidget* parent, const char* label);
  void          qt_QPushButton_onClicked_test(QPushButton* self );

  // Abstract PushbButton
  void          qt_button_onClicked(QAbstractButton* self, void* ctx, void (* callback) (void*) );
  void          qt_button_setText(QAbstractButton* self, const char* text);

  const char*   qt_QLineEdit_text(QLineEdit* self);

  QLayout*     qt_layout_new(QWidget* parent, int type);
  QObject*     qt_QFormLayout_addWidgetAndLabel(QFormLayout* self, int type, const char* label);

  QLabel* qt_QFormLayout_addLabel1(QFormLayout* self, const char* label_text);

  void qt_msgbox_info(QWidget* parent, const char* title, const char* text);

  void qt_QLineEdit_onTextChanged( QLineEdit* self
                                 , void* ctx
                                 , void (* callback) (void* ctx, QLineEdit* self) );
#+END_SRC


  *Considerations:*
   
  1. _(extern "C")_ enforces C linkage, in other words, the function with
     this annotation must have a C compatible signature and use C
     compatible types. 

  2. The C++ operator _new_ throws exception when there is not enough
     memory, it throws std::bad_alloc. As C does not support
     exceptions, it is necessary to use the operator _new (std::nothrow)_ that 
     returns null pointer instead of throwing an exception.

  3. Due to the lack of C support for exceptions, any exception throw
     in the functions annotated with extern "C" should be returned as
     as an additional function parameter. Moreover, C++ exceptions are
     from different compilers may not even be compatible due to the
     lack of standard C++ ABI. 

  4. As C functions does not support closures, they cannot keep state,
     therefore it is necessary to add an extra void pointer parameter
     (void*) in the function pointers arguments and in the functions
     that takes them as parameters such as,
     _qt_button_onClicked()_. This additional (void*) pointer allows the
     calling code to pass and keep state.

  5. Despite that this technique, for creating a C-wrapper (C
     interface), was used for Qt5 Widgets library, this approach can
     be used for any other C++ library such as: Boost-Libraries,
     WxWidgets GUI library, Windows MFC (Microsoft Foundation Classes)
     and so on.

  6. This C-wrapper technique can also be used for creating
     C-libraries in C++, which can be loaded from any programming
     languages (Python, Ruby, Lisp, ...) via FFI (Foreign Function
     Interface) or by linking against the C-wrapper shared library in
     the case of compiled language such as Golang, Rust or
     D-lang.

  7. If Qt had C-binding similar to this one, shipped with Qt on every
     Linux distribution that uses Qt or KDE, it would be possible to
     create Qt applications in any other programming language than
     C++, by loading the C-binding via FFI foreign function interface
     or by linking against the C-binding if the programming language
     is compiled and supports linking against C ABI (Application
     Binary Interface).

  8. Another benefit of a C-wrapper for C++ libraries is that a
     C-wrapper  provides a stable ABI (Application-Binary Interface)
     which allows an application built with different C++ compiler to
     link against the library C-interface without any library
     recompilation and avoiding compiling the library with all
     possible C++ compilers that a client-code can possibly use. 

  9. An alternative method for accessing the C++ libraries from higher
     level languages is by using [[http://www.swig.org/][SWIG]] (SWIG wrapper generator), which
     parses interface files and library header files for generating
     C-code for programming language-specific _native interface API_,
     which allows creating native code libraries in C that are loaded
     at runtime via dlopen() calls, on Unix-like systems, or
     LoadLibrary() calls on Windows. The terminology _native-interface_
     _API_ was borrowed from Java's JNI (Java Native Interface API) as
     unlike FFI (Foreign Function Interfaces), there is a lack of
     proper terminology for describing this feature. For instance, the
     Pythons' native interface API allows creating Python modules (aka
     libraries) entirely in C. The disadvantage of using SWIG is that,
     it requires access to the library source code and compilation of 
     generated binding code for every programming language that
     binding will be generated for while a C-wrapper needs compilation
     only once.  
*** Building the wrapper file. 

Note: this code was compiled and tested on a Linux - Fedora 32, 64
bits machine which contains pre-installed Qt5 libraries.

Fetch the sources:

#+BEGIN_SRC sh
  $ git clone https://gist.github.com/a59d0f1b17d286dad19842932c931e92 qt5-cwrapper
  $ cd qt5-cwrapper

  $ ls
  client1.c  CMakeLists.txt  d_client.d  loader.jl  qtwrapper.cpp  qtwrapper.h
#+END_SRC

Build the project: (Note: Tested on Linux Fedora-32 64 bits x86-64)

#+BEGIN_SRC sh
  $ cmake --config Debug -H. -Bbuild
  $ cmake --build build --target
#+END_SRC

Check binaries:

#+BEGIN_SRC sh
  $ file build/client1
  build/client1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2
  , BuildID[sha1]=122650693cf9845a35afeb76bced113f3d92ed1a, for GNU/Linux 3.2.0, not stripped

  $ file build/libqtwrapper.so
  build/libqtwrapper.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), statically linked
  , BuildID[sha1]=47381d2f687f8cb5f6bf80be737c0baa2b0e2cb1, not stripped
#+END_SRC

Show symbols exported by the C-wrapper shared library:
  + Note: On windows, the equivalent tool is _dumpbin_ and on MacOSX,
    the equivalent tool is _otool_.

#+BEGIN_SRC sh 
   $ nm -D build/libqtwrapper.so 
     ... ... ... ... ... ... ... 
     ... ... ... ... ... ... ... 
   0000000000012d9d T qt_QFormLayout_addRowItem
   0000000000012d0c T qt_QFormLayout_addWidgetAndLabel
   000000000001315a T qt_QLineEdit_onTextChanged
   0000000000013284 T qt_QLineEdit_text
   0000000000012741 T qt_qobject_ClassName
   000000000001271a T qt_qobject_del
   000000000001276a T qt_qobject_dumpObjectInfo
   0000000000012785 T qt_qobject_dumpObjectTree
   00000000000127a0 T qt_qobject_print
                    U qt_version_tag
   0000000000012a47 T qt_widget_new
   0000000000012fbb T qt_widget_setText
   0000000000012fa0 T qt_widget_show
   0000000000012b51 T qt_window_main
                    U strlen
                    U _Unwind_Resume
   00000000000157a8 W _Z12qobject_castIP11QMessageBoxET_P7QObject
   00000000000157cc W _Z12qobject_castIP15QAbstractButtonET_P7QObject
   000000000001573c W _Z12qobject_castIP6QLabelET_P7QObject
   0000000000015760 W _Z12qobject_castIP9QLineEditET_P7QObject
     ... ... ... ... ... ... ... ... 
     ... ... ... ... ... ... ... ... 
#+END_SRC

Show exported symbols demangling (aka decoding) C++ mangled symbols: 

#+BEGIN_SRC sh 
    $ >> nm -D build/libqtwrapper.so | c++filt
    ... ... ... ... ... 
    ... ... ... ... ... 
   000000000001276a T qt_qobject_dumpObjectInfo
   0000000000012785 T qt_qobject_dumpObjectTree
   00000000000127a0 T qt_qobject_print
                    U qt_version_tag
   0000000000012a47 T qt_widget_new
   0000000000012fbb T qt_widget_setText
   0000000000012fa0 T qt_widget_show
   0000000000012b51 T qt_window_main
                    U strlen
                    U _Unwind_Resume
   00000000000157a8 W QMessageBox* qobject_cast<QMessageBox*>(QObject*)
   00000000000157cc W QAbstractButton* qobject_cast<QAbstractButton*>(QObject*)
   000000000001573c W QLabel* qobject_cast<QLabel*>(QObject*)
   0000000000015760 W QLineEdit* qobject_cast<QLineEdit*>(QObject*)
      ... ... ... ... ... 
    ... ... ... ... ... 
#+END_SRC

*** C client code

File: _client1.c_ (C-client code)

#+BEGIN_SRC cpp
  #include <stdio.h>
  #include <stdlib.h>
  #include <assert.h>

  #include "qtwrapper.h"

  typedef struct CallbackContext
  {
      QLineEdit* lin;
      int        counter;
  } CallbackContext;


  // All callback (closure) state should be passed as arguments
  void onclicked_callback(void* context)
  {
      CallbackContext* ctx = (CallbackContext*) context;
      ctx->counter += 1;
      const char* str = qt_QLineEdit_text(ctx->lin);

      printf(" [TRACE] Button clicked => Counter incremented to: %d \n", ctx->counter);
      printf(" [TRACE] QLineEdit Value = %s \n", str);
      // free(str);

      // qt_msgbox_info(NULL, "Message Info", "Button clicked Ok");
  }

  void lineEdit_Callback(void* ctx, QLineEdit* self)
  {
      const char* str = qt_QLineEdit_text(self);
      QLabel*   label = (QLabel*) ctx;
      qt_widget_setText(label, str);
      printf(" [LineEdit Callback] Text changed to %s \n", str);
  }

  void onclick_msgbox_callback(void* context)
  {
      QLineEdit* lin = (QLineEdit*) context;
      const char* str = qt_QLineEdit_text(lin);
      qt_msgbox_info(NULL, "Message Info", str);
      // free(str);
  }

  const int WIDGET_WINDOW           = 1;
  const int WIDGET_WINDOW_MAIN      = 2;
  const int WIDGET_QPUSH_BUTTON     = 3;
  const int WIDGET_QLABEL           = 4;
  const int WIDGET_QLINE_EDIT       = 5;
  const int WIDGET_QDOUBLE_SPINBOX  = 6;

  const int LAYOUT_QFORM_LAYOUT = 3;

  int main(int argc, char** argv)
  {
      // QApplication* qapp = qt_app_new(argc, argv);
      QApplication* qapp = qt_app_new2();

      // -------- Create GUI - Graphical User Interface ------//

      QPushButton* win = qt_widget_new(NULL, WIDGET_WINDOW_MAIN);
      qt_widget_setText(win, "Window Title");
      assert(win != NULL);

      QFormLayout* form = qt_layout_new(win, LAYOUT_QFORM_LAYOUT);
      assert(form != NULL);

      QLineEdit*   lin  = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QLINE_EDIT,   "Input");
      QPushButton* btn1 = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QPUSH_BUTTON, "");
      QPushButton* btn2 = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QPUSH_BUTTON, "");
      QLabel*      disp = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QLABEL,       "Display label");

      qt_widget_setText(btn1, "Button 1");
      qt_widget_setText(btn2 ,"Button 2");
      // qt_widget_setToolTip(btn1, "Click at this button to play!");

      qt_qobject_print(win);

      // -------- Install Event Handlers --------------//
      //

      qt_QLineEdit_onTextChanged(lin, disp, lineEdit_Callback);
      qt_button_onClicked(btn1, lin, &onclick_msgbox_callback);

      CallbackContext ctx;
      ctx.counter = 0;
      ctx.lin = lin;
      qt_button_onClicked(btn2, &ctx, &onclicked_callback);

      // ---- Run QT event loop - blocking main thread  ---//
      qt_app_exec(qapp);

      // ----- Dispose objects ---------------------------//
      qt_qobject_del(win);
      qt_qobject_del(qapp);

      puts("\n [TRACE] Terminate application Ok. ");

      return 0;
  }
#+END_SRC

Run executable: 

#+BEGIN_SRC sh
  $ build/client1
   [TRACE] Create QAppliction Object Ok
   [LineEdit Callback] Text changed to h 
   [LineEdit Callback] Text changed to h 
   [LineEdit Callback] Text changed to h 
   [LineEdit Callback] Text changed to h 
   ...  ...  ...  ...  ...  ... 
   ...  ...  ...  ...  ...  ...  ... 
#+END_SRC

User Interface Screenshot: 

#+CAPTION: C client code user interface
[[file:images/cwrapper-qt5-1.png][file:images/cwrapper-qt5-1.png]] 
*** C++ client code 
    
File: _cpp-client.cpp_

#+BEGIN_SRC cpp
  #include <iostream>
  #include <functional>
  #include <cassert>
  
  // Indicates that symbols from this header file have C linkage (C ABI)
  // rather than C++ ABI.
  extern "C" {
      #include "qtwrapper.h"
  }
  
  const int WIDGET_WINDOW           = 1; 
  const int WIDGET_WINDOW_MAIN      = 2; 
  const int WIDGET_QPUSH_BUTTON     = 3;
  const int WIDGET_QLABEL           = 4;
  const int WIDGET_QLINE_EDIT       = 5; 
  const int WIDGET_QDOUBLE_SPINBOX  = 6;
  
  const int LAYOUT_QFORM_LAYOUT = 3;
  
  using ButtonCallck = std::function<void ()>;
  
  // Implementation with type erasure and dynamic allocations
  // Note: std::functions<> uses dynamic allocaiton underneath 
  void onClicked(QPushButton* btn, ButtonCallck func)
  {
  
      qt_button_onClicked(btn, &func, [](void* ctx){
          auto callback = reinterpret_cast<ButtonCallck*>(ctx);
          callback->operator()();
      });
  }
  
  // Implementation without dynamic allocation 
  // DOES NOT WORK: segmentation fault 
  template<typename Callback>
  void onClicked_tpl(QPushButton* btn, Callback func)
  {
      qt_button_onClicked(btn, &func, [](void* ctx){
          // auto callback = (Callback*) ctx;  
          auto callback = reinterpret_cast<Callback*>(ctx);
          //callback();
          callback->operator()();
      });
  }
  
  int main(int argc, char** argv)
  {
  
      QApplication* qapp = qt_app_new2();
  
      QPushButton* win = qt_widget_new(NULL, WIDGET_WINDOW_MAIN);
      qt_widget_setText(win, "Window Title");
      assert(win != NULL);
  
      QFormLayout* form = qt_layout_new(win, LAYOUT_QFORM_LAYOUT);
      assert(form != NULL);
  
      QPushButton* btn1 = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QPUSH_BUTTON, "");
      qt_widget_setText(btn1, "Button 1");
  
      QLabel*  disp = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QLABEL, "Result");
  
      // qt_widget_setText(btn1, "Button 1");
      int counter = 0;
  
      onClicked(btn1, [&counter, disp]{
          std::cout << " [TRACE] (*) Counter = " << counter++ << "\n";
          auto text = std::string(" Counter set to = ") + std::to_string(counter);
          qt_widget_setText(disp, text.c_str());
  
          if( counter > 10 ){
              qt_msgbox_info(nullptr, "Notification", "Counter greater than 10");
          }
      });
  
      // Blocks current thread until window is closed. 
      qt_app_exec(qapp);
  
      // Dispose QT objects 
      qt_qobject_del(win);
      qt_qobject_del(qapp);
  }
#+END_SRC

Screenshot: 

#+CAPTION: C++ client code user interface  
[[file:images/cwrapper-qt5-cpp-client.png][file:images/cwrapper-qt5-cpp-client.png]]

*** Python3 Client Code 

The following python script, loads the C shared library wrapper for
Qt5 widgets using Python3 ctypes FFI (Foreign Function
Interface). Although, Python3 already has [[https://wiki.python.org/moin/PyQt][PyQt]] and [[https://wiki.qt.io/Qt_for_Python][Pyside2]] library
bindings what make this code seems pointless, the objective of this
code is to be a proof-of-concept about how to load C++ libraries via
FFI (Foreign Function Interface).

 *Files:*

File: _pywrapper.py_ 

#+BEGIN_SRC python 
   #!/usr/bin/env python3
   import ctypes 
   from ctypes import (c_int, c_int, c_char, c_void_p)

   # ===== Aliases for making life easier ===========#
   #-------------------------------------------------#

   c_void  = None                         # void 
   c_pconst_char = ctypes.POINTER(c_char) # const char* 
   c_pnull = c_void_p(0)                  # Null pointer 
   QObject = c_void_p                     # void* - void pointer 
   Qapp    = c_void_p 
   QWidget = c_void_p
   QLayout = c_void_p 

   #  =========== Load shared library ================#
   #--------------------------------------------------#

   #  Arguments of: ctypes.cdll.LoadLibrary(<LIBRARY>)
   #
   #  On Linux, the operating system linker looks for
   # the library at directories listed in $LD_LIBRARY_PATH
   # environment variable or in default set of directories 
   # such as /lib, /usr/lib and so on. 
   #
   #  On Windows, the operating system looks for the library 
   # in C:\\Windows\System32 directory or in directories
   # listed in %PATH% environment variable.
   # 
   # The argument of LoadLibrary() can also be the absolute
   # path to the shared library. 
   #

   lib = ctypes.cdll.LoadLibrary("libqtwrapper.so")

   #======= Set types of C subroutines ===================#
   #------------------------------------------------------#

   # -- Note: There is no keyword const in Python
   # it is assumed that the user or calling code
   # will never modify the following definitions. 
   WIDGET_WINDOW          = 0
   WIDGET_WINDOW_MAIN     = 2
   WIDGET_QPUSH_BUTTON    = 3
   WIDGET_QLABEL          = 4
   WIDGET_QLINE_EDIT      = 5
   WIDGET_QDOUBLE_SPINBOX = 6
   LAYOUT_QFORM_LAYOUT    = 3

   lib.qt_app_new2.argtypes = []
   lib.qt_app_new2.restype  = Qapp 

   lib.qt_app_exec.argtypes = [ Qapp ]
   lib.qt_app_exec.restype  = c_void

   lib.qt_qobject_del.argtypes = [ QObject ]
   lib.qt_qobject_del.restype  = c_void

   lib.qt_window_main.argtypes = []
   lib.qt_window_main.restype  = QWidget

   lib.qt_widget_setText.argtypes = [ QWidget, c_pconst_char  ]
   lib.qt_widget_setText.restype  = c_void 

   lib.qt_layout_new.argytpes = [ QWidget, c_int ]
   lib.qt_layout_new.restype  = QLayout

   lib.qt_msgbox_info.argtypes = [ QWidget, c_pconst_char, c_pconst_char ]
   lib.qt_msgbox_info.restype = c_void

   lib.qt_QFormLayout_addWidgetAndLabel.argtypes = [QWidget, c_int, c_pconst_char ]
   lib.qt_QFormLayout_addWidgetAndLabel.restype  = QWidget

   # Callback type: 
   FnButtonCallback = ctypes.CFUNCTYPE(  c_void    # Return tupe 
                                       , c_void_p  # Type of first parameter (void*)
                                       )

   lib.qt_button_onClicked.argtypes = [ QWidget, c_void_p, FnButtonCallback ]
   lib.qt_button_onClicked.restype  = c_void 

   #===== Create the Qt5 Widgets - GUI - Graphics User ==========#
   #-------------------------------------------------------------#

   qapp = lib.qt_app_new2()

   window = lib.qt_window_main()
   lib.qt_widget_setText(window, "Qt5 Widgets Window from Python3 FFI".encode("UTF-8"))

   form = lib.qt_layout_new(window, LAYOUT_QFORM_LAYOUT)

   label_output = lib.qt_QFormLayout_addWidgetAndLabel(
                   form, WIDGET_QLABEL, b"Result" )

   btnA = lib.qt_QFormLayout_addWidgetAndLabel(
                   form, WIDGET_QPUSH_BUTTON, b"Label A" )

   lib.qt_widget_setText(btnA, "Button A".encode("UTF-8") )

   btnB = lib.qt_QFormLayout_addWidgetAndLabel(
                    form, WIDGET_QPUSH_BUTTON, b"Label B")

   lib.qt_widget_setText(btnB, b"Button B" )

   entry = lib.qt_QFormLayout_addWidgetAndLabel(
                           form, WIDGET_QLINE_EDIT, b"Information:")

   spinbox = lib.qt_QFormLayout_addWidgetAndLabel(
                      form, WIDGET_QDOUBLE_SPINBOX, b"Price:")


   print(" [PYTHON] Qt5 GUI Created Ok. ")

   #===== Install callbacks (event handlers) ===================#
   #------------------------------------------------------------#

   counter = 0

   # Note: Python lacks multi-line lambdas
   def btnA_callback(ctx: c_void_p) -> c_void:
       global counter 
       counter = counter + 1
       text = f" [PYTHON] Button A Clicked => Counter = {counter} "
       text = text.encode("UTF-8")
       print(text)
       lib.qt_widget_setText(label_output, text)
       lib.qt_widget_setText(entry, text)    

   obj_btnA_callback = FnButtonCallback(btnA_callback)
   lib.qt_button_onClicked(btnA, c_pnull, obj_btnA_callback)

   def btnB_callback(ctx: c_void_p) -> c_void:
       global counter
       counter = counter - 1
       text = f" [PYTHON] Button B Clicked => Counter = {counter} "
       text = text.encode("UTF-8")    
       print(text)
       lib.qt_widget_setText(label_output, text)
       lib.qt_widget_setText(entry, text)        
       lib.qt_msgbox_info( window, b"Information:", text)

   obj_btnB_callback = FnButtonCallback(btnB_callback)
   lib.qt_button_onClicked(btnB, c_pnull, obj_btnB_callback)


   #====== Run QT event loop blocking current thread ============#
   #-------------------------------------------------------------#
   print(" [PYTHON] Running QT event loop => Main thread blocked!. ")
   lib.qt_app_exec(qapp)

   #===== Dispose C++ objects loaded via FFI =====================#
   #--------------------------------------------------------------#

   # Note: Only the root object (wihout any parent element) needs
   # to be disposed (aka released from memory).
   lib.qt_qobject_del(window)

   lib.qt_qobject_del(qapp)


   print(" [PYTHON] Application terminated gracefully. Ok. ")

#+END_SRC

 *Running* 

#+BEGIN_SRC sh 
   # Directory where there are the *.so shared libraries files (shared objects)
   $ export LD_LIBRARY_PATH=$PWD/_build 

   $ python3 pywrapper.py
   [TRACE] Create QAppliction Object Ok
   [PYTHON] Qt5 GUI Created Ok. 
   [PYTHON] Running QT event loop => Main thread blocked!. 
  b' [PYTHON] Button A Clicked => Counter = 1 '
  b' [PYTHON] Button A Clicked => Counter = 2 '
  b' [PYTHON] Button A Clicked => Counter = 3 '
  b' [PYTHON] Button A Clicked => Counter = 4 '
  b' [PYTHON] Button A Clicked => Counter = 5 '
  .... ... ...   .... ... ...   .... ... ...   .... ... ... 
  .... ... ...   .... ... ...   .... ... ...   .... ... ... 
  b' [PYTHON] Button B Clicked => Counter = 9 '
  b' [PYTHON] Button B Clicked => Counter = 8 '
  b' [PYTHON] Button A Clicked => Counter = 9 '
  b' [PYTHON] Button A Clicked => Counter = 10 '
  b' [PYTHON] Button A Clicked => Counter = 11 '
   [PYTHON] Application terminated gracefully. Ok. 
#+END_SRC

Or:
 
#+BEGIN_SRC 
  $ env LD_LIBRARY_PATH=$PWD/_build python3 pywrapper.py
#+END_SRC

Or:
 
#+BEGIN_SRC 
  $ chmod +x pywrapper.py 
  $ env LD_LIBRARY_PATH=$PWD/_build ./pywrapper.py
#+END_SRC

 *Screenshot* 

#+CAPTION: Python3 Ctypes FFI wrapper for lib qtwrapper shared library.
[[file:images/cwrapper-qt5-6.png][file:images/cwrapper-qt5-6.png]]
*** Julia Client Code (loader.jl)

File: _loader.jl_ (Julia language client code)

#+BEGIN_SRC julia 
  #!/usr/bin/env julia

  # Refers to file libqexport.so
  #
  # On Linux:
  #  It is assumed that the LD_LIBRARY_PATH environment variable of current
  #  process contains the directory where libqtexport is located.
  #
  # On Windows:
  #  It is made the assumption that the PATH environmenrt variable of current
  #  process contains the directory where libqtexport is located.
  #
  #
  const shlib = "libqtwrapper"

  const QObject = Ptr{Cvoid}
  const QWidget = Ptr{Cvoid}
  const QLayout = Ptr{Cvoid}
  const QApp    = Ptr{Cvoid}
  const QButton = Ptr{Cvoid}
  const QDoubleSpinbox = Ptr{Cvoid}

  const WIDGET_WINDOW           = 1;
  const WIDGET_WINDOW_MAIN      = 2;
  const WIDGET_QPUSH_BUTTON     = 3;
  const WIDGET_QLABEL           = 4;
  const WIDGET_QLINE_EDIT       = 5;
  const WIDGET_QDOUBLE_SPINBOX  = 6;
  const LAYOUT_QFORM_LAYOUT     = 3;


  # Wrapper to function:
  # QWidget* qt_widget_new(QWidget* parent, const char* name)
  function qt_widget_new(type::Int)::QWidget
      return ccall(  (:qt_widget_new, shlib)
                    # Function type signatrue
                   , QWidget, (QWidget, Cint)
                   # Function arguments (parent = null and name = name)
                   , C_NULL, type
                   )
  end

  # Delete any instance of QObject (QWidget, QApplictation, QLayout) and so on
  function qt_qobject_del(obj::QObject)
      ccall(  (:qt_qobject_del, shlib)
            , Cvoid, ( QObject, )
            , obj )
  end

  function qt_qobject_print(self::QObject)
      ccall( (:qt_qobject_print, shlib)
            , Cvoid, (QObject,)
            , self )
  end

  ## => void qt_widget_setText(QWidget* self, const char* text);
  function qt_widget_setText(self::QWidget, text::String)
       ccall(  (:qt_widget_setText, shlib)
              # Function type signatrue
              , Cvoid, (QWidget, Cstring)
              # Function arguments (parent = null and name = name)
              , self, text )
  end

  # extern QLayout* qt_layout_new(QWidget* parent, const char* name);
  function qt_layout_new(type::Int, parent::QWidget)::QLayout
      return ccall(  (:qt_layout_new, shlib)
                    # Function type signatrue
                   , QLayout, (QWidget, Cint)
                   # Function arguments (parent = null and name = name)
                   , parent, type )
  end

  #  QPushButton* qt_QFormLayout_addButton(QFormLayout* self, const char* label)
  function qt_QFormLayout_addWidgetAndLabel(form::QLayout, type::Int, label::String)::QWidget
      return ccall(  (:qt_QFormLayout_addWidgetAndLabel, shlib)
                   # Function type signatrue
                   , QWidget, (QLayout, Cint, Cstring)
                   # Function arguments
                   , form, type, label )
  end

  function qt_qobject_ClassName(self::QObject)::String
      res = ccall( (:qt_qobject_ClassName, shlib)
                   , Cstring, (QObject, )
                   , self )
      return unsafe_string(res)
  end

  function QApplication_new()
      # return ccall( (:qt_app_new2, shlib), Ptr{Cvoid}, ())
      return ccall((:qt_app_new2, shlib), QApp, ())
  end

  function QApplication_exec(self)
      ccall((:qt_app_exec, shlib), Cvoid, ( QApp, ), self )
  end

  function QPushButton_new(label::String)
      # return ccall((:qt_QPushButton_new, shlib), ( Ptr{Cvoid}, Cstring ), C_NULL, label )
      return  btn = ccall( (:qt_QPushButton_new, shlib)
                          , Ptr{Cvoid}, (Ptr{Cvoid}, Cstring)
                          , C_NULL, label
                          )
  end

  function QWidget_show(self)
      return ccall((:qt_QWidget_show, shlib), Cvoid, ( Ptr{Cvoid}, ), self)
  end

  function QPushButton_onClicked_test(self::QButton)
      ccall((:qt_QPushButton_onClicked_test, shlib), Cvoid, ( QButton, ), self)
  end

  function qt_button_onClicked(self::QButton, handler)
      callback = @cfunction $handler Cvoid ( Ptr{Cvoid}, )
      ccall(  (:qt_button_onClicked, shlib)
              # Function return type signature
              , Cvoid
              # Function arguments
              , ( QButton, Ptr{Cvoid}, Ptr{Cvoid} )
              # Arguments passed to function
              , self, C_NULL, callback
              )
  end

  function qt_QDoubleSpinBox_value(self::QDoubleSpinbox)
      return ccall(  (:qt_QDoubleSpinBox_value, shlib)
                  , Cdouble, ( QDoubleSpinbox,)
                  , self)
  end

  # Wrapper:
  #  void qt_msgbox_info(QWidget* parent, const char* title, const char* text);
  function qt_msgbox_info(title::String, text::String)
      ccall(  (:qt_msgbox_info, shlib)
            , Cvoid, (Ptr{Cvoid}, Cstring, Cstring)
            , C_NULL, title, text
      )
  end

  # void qt_QDoubleSpinBox_onValueChanged(
  #               QDoubleSpinBox* self
  #             , void* ctx, void (* callback)(void* ctx))
  function qt_QDoubleSpinBox_onValueChanged(self::QDoubleSpinbox, handler)
      callback = @cfunction $handler Cvoid ( Ptr{Cvoid}, )
      ccall(  (:qt_QDoubleSpinBox_onValueChanged, shlib)
              # Function return type signature
              , Cvoid
              # Function arguments
              , ( QButton, Ptr{Cvoid}, Ptr{Cvoid} )
              # Arguments passed to function
              , self, C_NULL, callback
              )
  end

  function demo_qt_gui_form()
      qapp = QApplication_new()

      window = qt_widget_new( WIDGET_WINDOW_MAIN )
      qt_widget_setText(window, "Sample QT GUI in Julia Language")

      form   = qt_layout_new(LAYOUT_QFORM_LAYOUT, window)

      entry1    = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QDOUBLE_SPINBOX, "Speed in m/s")
      entry2    = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QDOUBLE_SPINBOX, "Acceleration m/s^2")
      btn_run   = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QPUSH_BUTTON,    "")
      btn_clean = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QPUSH_BUTTON,    "")
      label     = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QLABEL,          "")

      qt_widget_setText(btn_run,   "Run calculations");
      qt_widget_setText(btn_clean, "Clean");

      println(" [INFO] class of form object    = ", qt_qobject_ClassName(form))
      println(" [INFO] class of btn_run object = ", qt_qobject_ClassName(btn_run))
      println(" [INFO] class of entry1 object  = ", qt_qobject_ClassName(entry1))
      println(" [INFO] class of entry2 object  = ", qt_qobject_ClassName(entry2))

      qt_widget_setText(btn_run,   "RUN GUI")
      qt_widget_setText(btn_clean, "CLEAN GUI")

      qt_qobject_print(window)

      update_calculations = (ctx) -> begin
          speed = qt_QDoubleSpinBox_value(entry1)
          accel = qt_QDoubleSpinBox_value(entry2)
          z = 4.51 * speed + 9.81 * accel^2 / speed
          out = string(" [JULIA] Result = ", round(z, digits = 4))
          println(" speed = ", speed, " ; accel = ", accel)
          println(out)
          println(" --------------------------------- ")
          qt_widget_setText(label, out)
      end

      qt_QDoubleSpinBox_onValueChanged(entry1, update_calculations)
      qt_QDoubleSpinBox_onValueChanged(entry2, update_calculations)

      n = 1
      qt_button_onClicked(btn_run, (ctx) -> begin
          n = n + 1
          println(" [ JULIA ] I was clicked Ok. n = ", n, " times. ")
          qt_msgbox_info("Notification", "Button was clicked Ok")
      end)

      qt_button_onClicked(btn_clean, (ctx) -> begin
          println(" [TRACE] Button clean clicked Ok")
          qt_widget_setText(label, "Button clean clicked")
      end)

      # --- Block main thread and run QT event loop ---//
      QApplication_exec(qapp)

      #  ----- Dispose objects ----------
      # Only the root widget (window) need to be removed
      qt_qobject_del(window)
      qt_qobject_del(qapp)
  end

  demo_qt_gui_form()
#+END_SRC

Run julia script / attempt 1 -> error: 

#+BEGIN_SRC sh 
   $ julia loader.jl 
   ERROR: LoadError: could not load library "libqtwrapper"
   libqtwrapper.so: cannot open shared object file: No such file or directory
   Stacktrace:
   ... ... .... ... ... 
   ... ... ... ... ... 
#+END_SRC

Run julia script / attempt 2 -> works: 

  + Note: LD_LIBRARY_PATH only works on Linux. The equivalent of this
    variable for windows is the _PATH_ environment variable and for
    MacOSX, the equivalent is _DYLD_LIBRARY_PATH_.
  + The _libqtwrapper.so_ shared library can be installed on Linux, by
    moving the library file to /lib directory.

#+BEGIN_SRC sh 
     $ export LD_LIBRARY_PATH=build:$LD_LIBRARY_PATH

     $ julia loader.jl 
   [TRACE] Create QAppliction Object Ok
   [INFO] class of form object    = QFormLayout
   [INFO] class of btn_run object = QPushButton
   [INFO] class of entry1 object  = QDoubleSpinBox
   [INFO] class of entry2 object  = QDoubleSpinBox
   speed = 1.0 ; accel = 0.0
   [JULIA] Result = 4.51
   --------------------------------- 
   speed = 2.0 ; accel = 0.0
   [JULIA] Result = 9.02
   --------------------------------- 
   speed = 3.0 ; accel = 0.0
   [JULIA] Result = 13.53
   --------------------------------- 
   ... ... ... ... 
#+END_SRC

The previous Julia script could also be run in the following way. The
advantage of this method is that, the variable LD_LIBRARY_PATH is only
set for the command: '$ julia loader.jl'. 

#+BEGIN_SRC sh 
   $ env LD_LIBRARY_PATH=build:$LD_LIBRARY_PATH julia loade.jl 
#+END_SRC 

The library can be consumed without setting the environment variable
LD_LIBRARY_PATH on Linux by moving the library file to /usr/lib,
/usr/local/lib or /lib. 

#+BEGIN_SRC sh 
   # Install shared library to /usr/local/lib
   $ sudo cp -v build/libqtwrapper.so /usr/lib 

   # Update library cache 
   $ sudo ldconfig -v

   # Run the Julia script 
   $ julia loader.jl 
    [TRACE] Create QAppliction Object Ok
   libGL error: MESA-LOADER: failed to open iris (search paths /usr/lib64/dri)
   libGL error: failed to load driver: iris
   libGL error: MESA-LOADER: failed to open iris (search paths /usr/lib64/dri)
   libGL error: failed to load driver: iris
   libGL error: MESA-LOADER: failed to open swrast (search paths /usr/lib64/dri)
   libGL error: failed to load driver: swrast
    [INFO] class of form object    = QFormLayout
    [INFO] class of btn_run object = QPushButton
   ... .... .... 
   ... .... .... 
 #+END_SRC

User Interface Screenshot: 

#+CAPTION: User interface for Julia language client code. 
[[file:images/cwrapper-qt5-2.png][file:images/cwrapper-qt5-2.png]]

*** C# - Dotnet Core Client Code 

This section contains the source code of a C# (dotnet) core client
code that loads the C-wrapper shared library (libqwrap.so) for Qt5
widgets framework through dotnet Pinvoke dotnet feature.


File: _myapp.csproj_ 

#+BEGIN_SRC xml 
  <Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
      <OutputType>Exe</OutputType>
      <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>

  </Project>
#+END_SRC 

File: _Program.cs_  - C# Csharp / core source code (dotnet core)

#+BEGIN_SRC cpp 
  using System;
  using System.Runtime.InteropServices;

  namespace myapp
  {

      class Wrapper 
      {
          public const int WIDGET_WINDOW = 0;
          public const int WIDGET_WINDOW_MAIN = 2;
          public const int WIDGET_QPUSH_BUTTON = 3;
          public const int WIDGET_QLABEL = 4;
          public const int WIDGET_QLINE_EDIT = 5;
          public const int WIDGET_QDOUBLE_SPINBOX = 6;
          public const int LAYOUT_QFORM_LAYOUT = 3;


          [DllImport("qtwrapper")]
          public static extern IntPtr qt_app_new2();

          [DllImport("qtwrapper")]
          public static extern IntPtr qt_app_exec(IntPtr self);


          [DllImport("qtwrapper")]
          public static extern IntPtr qt_qobject_del(IntPtr self);

          [DllImport("qtwrapper")]
          public static extern IntPtr qt_widget_new(IntPtr parent, int type);

          [DllImport("qtwrapper")]
          public static extern IntPtr qt_window_main();

          [DllImport("qtwrapper")]
          public static extern void qt_widget_setText(IntPtr self, string text);

          [DllImport("qtwrapper")]
          public static extern void qt_msgbox_info(IntPtr parent, string title, string text);

          [DllImport("qtwrapper")]
          public static extern IntPtr qt_layout_new(IntPtr parent, int type); 

          [DllImport("qtwrapper")]
          public static extern IntPtr qt_QFormLayout_addWidgetAndLabel(IntPtr parent, int type, string label); 

          // A C# delegate is similar to a C++ function pointer type. 
          public delegate void button_callback(IntPtr ctx);

          [DllImport("qtwrapper")]
          public static extern void qt_button_onClicked(IntPtr self, IntPtr ctx, button_callback callback); 

 
      }

      class Program
      {

          static void Main(string[] args)
          {
                  Console.WriteLine(" [TRACE] Program started Ok. ");

              // Create a QApplication* C++ object 
              // This object must be instantiated before any other Qt Widgets object. 
              IntPtr qapp = Wrapper.qt_app_new2();
              Console.WriteLine(" [TRACE] qapp (IntPtr) value = {0} ", qapp);


              // Create a Qt Window object QWidget* 
              IntPtr qwindow = Wrapper.qt_window_main();
              Wrapper.qt_widget_setText(qwindow, "My QtWidget window in C# dotnet core on Linux!");


              IntPtr form = Wrapper.qt_layout_new(qwindow,  Wrapper.LAYOUT_QFORM_LAYOUT);
              IntPtr btn1 = Wrapper.qt_QFormLayout_addWidgetAndLabel(form , Wrapper.WIDGET_QPUSH_BUTTON , "");
              Wrapper.qt_widget_setText(btn1, "Increment");

              IntPtr btn2 = Wrapper.qt_QFormLayout_addWidgetAndLabel(form, Wrapper.WIDGET_QPUSH_BUTTON, "");
              Wrapper.qt_widget_setText(btn2, "Messagebox");
            
              IntPtr text_entry = Wrapper.qt_QFormLayout_addWidgetAndLabel( form, Wrapper.WIDGET_QLINE_EDIT, "Result"); 

              // ------------ Set event handlers --------------// 
              //-----------------------------------------------// 

              int counter = 0;

              // Set button 1 event handler 
              Wrapper.qt_button_onClicked(btn1, IntPtr.Zero, (IntPtr ctx) => 
              {
                  String s = String.Format("Button clicked {0} times.", counter++);
                  Console.WriteLine(" [TRACE] QtPushButton cliked Ok. => " + s);
                  Wrapper.qt_widget_setText(text_entry, s);
              });

              Wrapper.qt_button_onClicked(btn2, IntPtr.Zero, (IntPtr ctx) => 
              {
                  Wrapper.qt_msgbox_info(IntPtr.Zero , "System Notification" , "Batch workload finished Ok." );
              });

              // Run application and block current thread. 
              Wrapper.qt_app_exec(qapp);

              // ---- Dispose C++ objects -------------------//
              // --------------------------------------------//
              Wrapper.qt_qobject_del(qwindow);
              Wrapper.qt_qobject_del(qapp);
              Console.WriteLine(" [TRACE] Program terminated successfully Ok. ");
          }
      }
  }


#+END_SRC

 *Building* 

The following command sets the LD_LIBRARY_PATH environment variable
for allowing the linker (aka loader) to find the shared library
(libqtwrapper.so on Linux or BSD or qtwrapper.dll on Windows). On
Windows, the operating system will linker will look for the shared
library on the same directory where is the executable, then on
predefined directories such as _C:\\Windows\\System32\_, and finally on
directories listed in the %PATH% enviroment variable.  

#+BEGIN_SRC sh 
  export LD_LIBRARY_PATH=$HOME/_build/
#+END_SRC

Adding the shared library directory path to the %PATH% enviroment
variable has the same effect of setting LD_LIBRARY_PATH on Linux. This
setting can be performed on cmd.exe shell via the as in the following
code block. The shared library can be located without setting the PATH
variable by moving it to the System32 directory or placing it on the
same directory where is the executable.

#+BEGIN_SRC sh 
  set PATH="%cd%\_build\path\to\shared\library\directory";%PATH%
#+END_SRC

Build from command line: 

#+BEGIN_SRC sh 

  $ ~/dotnet/dotnet build myapp.csproj
  Microsoft (R) Build Engine version 16.9.0+57a23d249 for .NET
  Copyright (C) Microsoft Corporation. All rights reserved.

    Determining projects to restore...
    All projects are up-to-date for restore.
    myapp -> /home/unixuser/Documents/temp-projects/qt5-cwrapper/bin/Debug/net5.0/myapp.dll

  Build succeeded.
      0 Warning(s)
      0 Error(s)

  Time Elapsed 00:00:01.06


#+END_SRC

Check the executable: 

#+BEGIN_SRC sh 
   $ tree ./bin
  ./bin
  └── Debug
      └── net5.0
          ├── myapp
          ├── myapp.deps.json
          ├── myapp.dll
          ├── myapp.pdb
          ├── myapp.runtimeconfig.dev.json
          ├── myapp.runtimeconfig.json
          └── ref
              └── myapp.dll

  3 directories, 7 files

   $ ldd bin/Debug/net5.0/myapp
          linux-vdso.so.1 (0x00007fff251d4000)
          libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa56e3e0000)
          libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa56e3da000)
          libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa56e1f9000)
          libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa56e0aa000)
          libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa56e08f000)
          libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa56dea5000)
          /lib64/ld-linux-x86-64.so.2 (0x00007fa56e64b000)
#+END_SRC

Running the application: 

#+BEGIN_SRC sh 
   $ file bin/Debug/net5.0/myapp
  bin/Debug/net5.0/myapp: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, 
  interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1894b9678ab18e8406448464668a8949e4f9d2b5, stripped

   $ ~/Documents/temp-projects/qt5-cwrapper master
   $ bin/Debug/net5.0/myapp
   [TRACE] Program started Ok. 
   [TRACE] Create QAppliction Object Ok
   [TRACE] qapp (IntPtr) value = 94225009981200 
   [TRACE] QtPushButton cliked Ok. => Button clicked 0 times.
   ... ... ... .. 
   ... ... ... ... ... ... ... ..
#+END_SRC

Running the application via dotnet core tool: 

#+BEGIN_SRC sh
   $ ~/dotnet/dotnet run --project=myapp.csproj
#+END_SRC

 *Screenshot* 

[[file:images/cwrapper-qt5-5.png][file:images/cwrapper-qt5-5.png]]
*** D language - D-lang Client Code

Note: This client code load function pointers from the C-wrapper
shared library at runtime via Dlopen() API. The benefit of this
approach over linking directly, at-compile time, is that application
can build on machines without Qt installation or on dockers images
without X11 installed. 

 *Files:* 

File: _d_client.d_ (Dlang / D-language client code)

#+BEGIN_SRC cpp
  import core.stdc.stdio;
  import std.range: empty;
  import str = std.string;

  // Import dlopen
  import DLL = core.sys.posix.dlfcn;

  alias QLayout         = void;
  alias QObject         = void;
  alias QWidget         = void;
  alias QLabel          = void;
  alias QApplication    = void;
  alias QPushButton     = void;
  alias QFormLayout     = void;
  alias QAbstractButton = void;


  const int WIDGET_WINDOW           = 1;
  const int WIDGET_WINDOW_MAIN      = 2;
  const int WIDGET_QPUSH_BUTTON     = 3;
  const int WIDGET_QLABEL           = 4;
  const int WIDGET_QLINE_EDIT       = 5;
  const int WIDGET_QDOUBLE_SPINBOX  = 6;
  const int LAYOUT_QFORM_LAYOUT = 3;

  alias qapp_new_t          = extern(C) QApplication* function ();
  alias qapp_exec_t         = extern(C) int      function(QApplication* self);
  alias qobject_del_t       = extern(C) void     function (QObject*);
  alias qt_widget_new_t     = extern(C) QWidget* function(QWidget* parent, int type);
  alias qt_window_main_t    = extern(C) QWidget* function();
  alias qt_widget_setText_t = extern(C) void     function(QWidget* self, const char* text);
  alias qt_layout_new_t     = extern(C) QLayout* function (QWidget* parent, int type);
  alias qt_msgbox_info_t    = extern(C) void     function(QWidget* parent, const char* title, const char* text);

  // QObject* qt_QFormLayout_addWidgetAndLabel(QFormLayout* self, int type, const char* label)
  alias qt_QFormLayout_addWidgetAndLabel_t =
          extern(C) QPushButton* function (QFormLayout* self, int type, const char* label);

  alias qt_button_onClicked_t = extern(C) void function ( QAbstractButton* self
                                                        , void* ctx
                                                        , void function(void* self) );


  struct callback_state
  {
      int                 counter           = 0;
      QLabel*             label             = null;
      qt_widget_setText_t qt_widget_setText = null;
      qt_msgbox_info_t    qt_msgbox_info    = null;
  };

  extern(C) void button_callback1(void* ctx)
  {
      import std.conv: to;

      auto pstate = cast(callback_state*) ctx;
      pstate.counter = pstate.counter + 1;
      printf(" [TRACE] Button click event happened => state = %d. \n", pstate.counter);
      string text = "Button clicked / counter = ";
      text = text ~ to!string(pstate.counter);

      pstate.qt_widget_setText(pstate.label, str.toStringz(text));

      if(pstate.counter > 20){
          pstate.qt_msgbox_info(null, "QT Event => Button Clicked", str.toStringz(text));
      }
  }

  int main()
  {
      // ------------- Load Symbols from Shared Library -------------------//

      void* dll = DLL.dlopen("./build/libqtwrapper.so", DLL.RTLD_GLOBAL | DLL.RTLD_LAZY);
      if (!dll)
      {
          fprintf(stderr, " [ERROR] dlopen error: %s\n", DLL.dlerror());
          return 1;
      }

      auto  qapp_new   = cast(qapp_new_t) DLL.dlsym(dll, "qt_app_new2");
      auto  qapp_exec   = cast(qapp_exec_t) DLL.dlsym(dll, "qt_app_exec");

      auto qt_qobject_del      = cast(qobject_del_t)         DLL.dlsym(dll, "qt_qobject_del");
      auto qt_widget_new       = cast(qt_widget_new_t)       DLL.dlsym(dll, "qt_widget_new");
      auto qt_layout_new       = cast(qt_widget_new_t)       DLL.dlsym(dll, "qt_layout_new");
      auto qt_window_main      = cast(qt_window_main_t)      DLL.dlsym(dll, "qt_window_main");
      auto qt_widget_setText   = cast(qt_widget_setText_t)   DLL.dlsym(dll, "qt_widget_setText");
      auto qt_button_onClicked = cast(qt_button_onClicked_t) DLL.dlsym(dll, "qt_button_onClicked");
      auto qt_msgbox_info      = cast(qt_msgbox_info_t)      DLL.dlsym(dll, "qt_msgbox_info");

      auto form_add_item = cast(qt_QFormLayout_addWidgetAndLabel_t) DLL.dlsym(dll, "qt_QFormLayout_addWidgetAndLabel");
      assert(form_add_item);

      // ---------- Create QT GUI -----------------------------//
      //

      // Create an instance of class QApplication
      auto qapp   = qapp_new();

      auto window = qt_widget_new(null, WIDGET_WINDOW_MAIN) ;
      assert( window );
      qt_widget_setText(window, "QT Widgets GUI in D Language");

      auto form  = qt_layout_new(window, LAYOUT_QFORM_LAYOUT);
      auto btn   = form_add_item(form, WIDGET_QPUSH_BUTTON, "");
      auto label = form_add_item(form, WIDGET_QLABEL,       "Display");

      qt_widget_setText(btn, "Click ME NOW!!");


      callback_state state;
      state.counter = 10;
      state.label   = label;
      state.qt_msgbox_info = qt_msgbox_info;
      state.qt_widget_setText = qt_widget_setText;

      // Install button event handler
      qt_button_onClicked(btn, &state, &button_callback1);

      // ------ Run QT Event Loop blocking main thread ------//
      qapp_exec(qapp);

      // ------ Dipose QT Objects ---------------------------//
      //
      qt_qobject_del(window);
      qt_qobject_del(qapp);

      return 0;
  }
#+END_SRC


Run file d_client.d as a script: 

#+BEGIN_SRC 
  $ rdmd d_client.d 
   [TRACE] Create QAppliction Object Ok
   [TRACE] Button click event happened => state = 11. 
   [TRACE] Button click event happened => state = 12. 
   [TRACE] Button click event happened => state = 13. 
   [TRACE] Button click event happened => state = 14. 
   [TRACE] Button click event happened => state = 15. 
    ... ... ... ... ... ... ... ...   
#+END_SRC

Build and run executable: 

#+BEGIN_SRC sh 
  $ dmd d_client.d -g 
 
  $ ./d_client 
   [TRACE] Create QAppliction Object Ok
   [TRACE] Button click event happened => state = 11. 
   [TRACE] Button click event happened => state = 12. 
   [TRACE] Button click event happened => state = 13.
   ... ... ...  ... ... ...  ... ... ...  ... ... ... 
   ... ... ...  ... ... ...  ... ... ...  ... ... ... 
#+END_SRC

User Interface Screenshot: 

#+CAPTION: User interface for Dlang (D Language)
[[file:images/cwrapper-qt5-3.png][file:images/cwrapper-qt5-3.png]]

*** Nim language - Nim Client Code
    
File: _nim_app.nim_

#+BEGIN_SRC python 
  # Necessary for defining lambdas in Nim, otherwise compilation errors happen.
  import sugar
  
  # For converting number to string
  import strutils
  
  const qlib = "libqtwrapper.so"
  
  type 
      QApplication   = pointer 
      QObject        = pointer
      QLineEdit      = pointer 
      QWidget        = pointer  
      QPushButton    = pointer
      QFormLayout    = pointer 
      QDoubleSpinBox = pointer
      Context        = pointer
      QLayout        = pointer 
  
  type 
      callback_clicked = proc(ctx: Context): void
      callback_onValueChanged = proc(ctx: Context): void
  
  
  const 
      WIDGET_WINDOW           = 1 
      WIDGET_WINDOW_MAIN      = 2 
      WIDGET_QPUSH_BUTTON     = 3
      WIDGET_QLABEL           = 4
      WIDGET_QLINE_EDIT       = 5 
      WIDGET_QDOUBLE_SPINBOX  = 6
      LAYOUT_QFORM_LAYOUT     = 3
  
  
  proc qt_app_new2(): QApplication {.importc: "qt_app_new2", dynlib: qlib}
  
  proc qt_app_exec2(qapp: QApplication): void {.importc: "qt_app_exec", dynlib: qlib}
  
  proc qt_qobject_del(self: QWidget): void  {.importc: "qt_qobject_del", dynlib: qlib}
  
  # Set text and label
  proc qt_widget_setText(self: QWidget, text: cstring): void 
      {.importc: "qt_widget_setText", dynlib: qlib}
  
  # Factory function for instantiating any QT widget
  proc qt_widget_new(parent: QWidget, ntype: cint): QPushButton
      {.importc: "qt_widget_new", dynlib: qlib}
  
  proc qt_widget_show(self: QWidget): void
      {.importc: "qt_widget_show", dynlib: qlib}
  
  #  void  qt_button_onClicked(QAbstractButton* self, void* ctx, void (* callback) (void*) );
  proc qt_button_onClicked(self: QPushButton, ctx: Context, callback: callback_clicked ): void
      {.importc: "qt_button_onClicked", dynlib: qlib}
  
  proc qt_msgbox_info(parent: QWidget, title: cstring, text: cstring): void
      {.importc: "qt_msgbox_info", dynlib: qlib}
  
  # Retrieves text from QLineEdit widget
  proc qt_QLineEdit_text(self: QLineEdit): cstring
      {.importc: "qt_QLineEdit_text", dynlib: qlib}
  
  proc qt_layout_new(parent: QWidget, ntype: cint): QFormLayout
      {.importc: "qt_layout_new", dynlib: qlib}
  
  proc qt_QFormLayout_addWidgetAndLabel(self: QFormLayout, nype: cint, label: cstring): QObject
      {.importc: "qt_QFormLayout_addWidgetAndLabel", dynlib: qlib}
  
  proc qt_QDoubleSpinBox_value(self: QDoubleSpinBox): cdouble
      {.importc: "qt_QDoubleSpinBox_value", dynlib: qlib}
  
  proc qt_QDoubleSpinBox_setValue(self: QDoubleSpinBox, value: cdouble): void 
      {.importc: "qt_QDoubleSpinBox_setValue", dynlib: qlib}
  
  
  proc qt_QDoubleSpinBox_onValueChanged( self: QDoubleSpinBox
                                        ,ctx: Context
                                        ,callback: callback_onValueChanged 
                                        )
      {.importc: "qt_QDoubleSpinBox_onValueChanged", dynlib: qlib}
  
  
      #-------------------------------------------------#
      #             QT Application                      #
      #-------------------------------------------------#
  
  echo " [TRACE] Staring application OK."
  
  var app = qt_app_new2()
  
  # The window is immediately shown after created.
  var window = qt_widget_new(nil, WIDGET_WINDOW_MAIN)
  
  # UFS - Universal function call => the object is the first function parameter
  ## Same as:  qt_widget_setText(window, "NIM + Qt5-Widgets Application")
  window.qt_widget_setText("NIM + Qt5-Widgets Application")
  # Instantiate QT widgets 
  var form    = qt_layout_new(window, LAYOUT_QFORM_LAYOUT)
  var buttonA = form.qt_QFormLayout_addWidgetAndLabel(WIDGET_QPUSH_BUTTON, "ButtonA's label")
  var buttonB = form.qt_QFormLayout_addWidgetAndLabel(WIDGET_QPUSH_BUTTON, "ButtonB's label")
  var buttonC = form.qt_QFormLayout_addWidgetAndLabel(WIDGET_QPUSH_BUTTON, "ButtonC's label")
  var spinbox = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QDOUBLE_SPINBOX, "Price")
  var entry   = qt_QFormLayout_addWidgetAndLabel(form, WIDGET_QLINE_EDIT, "User entry")
  
  qt_widget_setText(buttonC, "ButtonC")
  # Set button text using (UFS) universal function call
  buttonA.qt_widget_setText("ButtonA")
  buttonB.qt_widget_setText("ButtonB")
  
   #--------- Install event handlers (callbacks) -----------#
  
  var counter = 0 
  
  qt_button_onClicked(buttonA, nil, (ctx: Context) => (block: 
      counter = counter + 1
      echo " [INFO] ButtonA clicked => counter = " & counter.intToStr
      entry.qt_widget_setText "Counter changed to " & counter.intToStr
  ))
  
  buttonB.qt_button_onClicked(nil, (ctx: Context) => (block: 
      counter = counter - 1    
      echo " [INFO] ButtonB clicked => counter = " & counter.intToStr
      entry.qt_widget_setText "Counter changed to " & counter.intToStr
  ))
  
  buttonC.qt_button_onClicked(nil) do (ctx: Context):
      counter = 100
      qt_msgbox_info( nil
                    , "Button A notification"
                    , "[INFO] ButtonC => reset counter to = " & counter.intToStr)
      entry.qt_widget_setText " [BUTTON C] Counter changed to " & counter.intToStr
      echo " [BUTTON C] Counter changed to " & counter.intToStr
  
  spinbox.qt_QDoubleSpinBox_onValueChanged(nil) do (ctx: Context) -> void:
      let value = spinbox.qt_QDoubleSpinBox_value
      # echo " [INFO] Spbinbox Value changed to ", value
      entry.qt_widget_setText "Spinbox value changed to = " &  $value
  
   #---------- Run QT applicaton ---------------------------#
   #
  
  # Display root widget  
  qt_widget_show(window)
  
  # Run QT application and block current thread 
  qt_app_exec2(app)
  
    # ---- Dispose QT objects -------------#
    # 
  qt_qobject_del(window)
  qt_qobject_del(app)
  
  echo " [TRACE] Application terminated. Ok."
#+END_SRC

Copy the shared library to the project's root directory:

#+BEGIN_SRC sh
   $ >> cp -v _build/libqtwrapper.so .
  '_build/libqtwrapper.so' -> './libqtwrapper.so'
#+END_SRC

Compile and run in a single step: 

#+BEGIN_SRC sh
 $ nim c -r nim_app.nim
#+END_SRC

#+CAPTION: Qt5 GUI written in NIM programming language.
[[file:images/cwrapper-qt5-nim.png][file:images/cwrapper-qt5-nim.png]]

*** TODO Rust client code 
 
File: _rustclient.rs_

#+BEGIN_SRC cpp 
    use std::os;
    use std::path::Path;

    use std::os::raw::{c_int, c_char, c_long, c_void, c_uint, c_short};
    use std::ffi::CString;
    use std::mem;

    type c_str  = *const c_char;

    type LPVOID  = *mut c_void;
    type QObject = LPVOID;
    type QWidget = LPVOID;
    type QLabel  = LPVOID;
    type QApplication = LPVOID;
    type QPushButton = LPVOID;

    // void* null pointer
    const null_ptr: LPVOID = std::ptr::null_mut();

    const WIDGET_WINDOW:          c_int = 0;
    const WIDGET_WINDOW_MAIN:     c_int = 2;
    const WIDGET_QPUSH_BUTTON:    c_int = 3;
    const WIDGET_QLABEL:          c_int = 4;
    const WIDGET_QLINE_EDIT:      c_int = 5;
    const WIDGET_QDOUBLE_SPINBOX: c_int = 6;
    const LAYOUT_QFORM_LAYOUT:    c_int = 3;


    // Function pointer type alias 
    // type qt_object_del_t = unsafe extern "C" fn(this: QObject) -> c_void;

    #[no_mangle]
    extern "C" fn btn_callback1(this: QWidget, ctx: LPVOID) -> LPVOID 
    {
        unsafe {
            let mut& counter = ctx as *mut c_int;
            *counter = *counter + 1;
            println!(" [TRACE] Button clicked Ok  => counter = {}", *counter);
        }
        return null_ptr;
    }


    fn main(){
        println!(" Hello world!");

        let qapp: QApplication = app_new(); 

        let button: QWidget = widget_new(null_ptr, WIDGET_QPUSH_BUTTON);
        widget_set_text(button, "Click me now!");

        unsafe{ qt_widget_show(button) };

        let mut counter: c_int = 0;

        unsafe{ qt_button_onClicked(button, counter as *mut _, btn_callback1); }

        // Run QT event loop and block current thread 
        app_exec(qapp);
    }

    // void (* callback) (void* ctx) )
    type button_callback = unsafe extern "C" fn(this: QWidget, ctx: LPVOID) -> LPVOID;


    #[link(name = "qtwrapper")]
    extern "C" {

        fn qt_object_del(this: QObject) -> c_void;

        fn qt_app_new2() -> QApplication;
        fn qt_app_exec(this: QApplication) -> c_void;

        fn qt_widget_new(parent: QWidget, typ: c_int) -> QWidget;

        fn qt_widget_show(this: QWidget) -> c_void;

        fn qt_QPushButton_new(parent: QWidget, label: c_str) -> QPushButton;

        fn qt_msgbox_info(parent: QWidget, title: c_str, text: c_str) -> c_void;

            // void qt_widget_setText(QWidget* self, const char* text)
        fn qt_widget_setText(this: QWidget, text: c_str);

        // void  qt_button_onClicked(QAbstractButton* self, void* ctx, void (* callback) (void*) );

        fn qt_button_onClicked(this: QWidget, ctx: LPVOID, callback: button_callback) -> c_void;
        }

    // -------- Safe wrappers to unsafe operations ------------//

    fn app_new() -> QApplication
    {
        let qapp: QApplication = unsafe { qt_app_new2() };
        return qapp;
    }

    fn app_exec(qapp: QApplication) 
    {
        unsafe{ qt_app_exec(qapp) };
    }

    fn widget_set_text(this: QWidget, text: &str)
    {
        let text = CString::new(text).unwrap(); 
        unsafe {
            qt_widget_setText(this, text.as_ptr() );
        }
    }

    fn widget_new(parent: LPVOID, typ: c_int) -> QWidget
    {
        let wdg: QWidget = unsafe{ qt_widget_new(parent, typ) };
        return wdg;
    }


#+END_SRC

Building: 

#+BEGIN_SRC sh 
 $ rustc rustclient.rs -l qtwrapper -L_build/
#+END_SRC

Running: 

#+BEGIN_SRC sh 
  $ ./rustclient 
#+END_SRC

*** Common Lisp client code 

This common lisp client code, more specifically Stell Bank Common Lisp
(SBCL), loads the shared library _libqtwrapper.so_ by using CFFI
foreign-function interface library.

Procedure for install SBCL on Fedora 32 and dependencies:

#+BEGIN_SRC sh 
  # Fedora as root 
  $ dnf install sbcl 

  # Download and install quicklisp 
  $ >> curl -O -L http://beta.quicklisp.org/quicklisp.lisp 
  $ >> rlwrap sbcl --load quicklisp.lisp 

  # Run sbcl and install quicklisp
  $ >> rlwrap sbcl --load quicklisp.lisp
  ,* (quicklisp-quickstart:install)
  ,* (ql:add-to-init-file)

  # Install CFFI 
  ,* (ql:quickload "cffi")

  # Quit SBCL repl 
  ,* (quit)
#+END_SRC

File: _sbcl_client.lisp_ 

#+BEGIN_SRC lisp 
  ;; ---- Experimental binding to Qt5 GUI Framework ----------;;
  ;;
  (require :cffi)

  (cffi:define-foreign-library qtw (:unix "libqtwrapper.so" ))
  (cffi:use-foreign-library qtw )

  ;; ------- Define bindings ---------------------------..

  (cffi:defcfun "qt_app_new2"   :pointer)
  (cffi:defcfun "qt_widget_new"  :pointer (parent :pointer) (type :int))

  ;; Signature: void qt_widget_show(QWidget* self)
  (cffi:defcfun "qt_widget_show" :void    (self :pointer))

  ;; Signature: int qt_app_exec(QApplication* self)
  (cffi:defcfun "qt_app_exec" :int (self :pointer))

  ;; Signature: void qt_qobject_del(QObject* self)
  (cffi:defcfun "qt_qobject_del" :void (self :pointer))

  ;; Signature: void qt_widget_setText(QWidget* self, const char* text)
  (cffi:defcfun "qt_widget_setText" :void (self :pointer) (text :string))

  ;; Signature: QLayout* qt_layout_new(QWidget* parent, int type)
  (cffi:defcfun "qt_layout_new" :pointer (self :pointer) (type :int))

  ;; Signature: QObject* qt_QFormLayout_addWidgetAndLabel(
  ;;                            QFormLayout* self
  ;;                          , int type, const char* label)
  (cffi:defcfun "qt_QFormLayout_addWidgetAndLabel"
                 :pointer (self :pointer) (type :int))

  ;; Callback 
  ;; ---------------------------------------------------
  ;; Signature:
  ;; -----------------------------------------------------
  ;; void qt_button_onClicked( 
  ;;                QAbstractButton* self
  ;;               , void* ctx
  ;;               , void (* callback) (void* self) )
  (cffi:defcfun "qt_button_onClicked"
                 :void (self :pointer) (ctx :pointer) (callback :pointer))

  ;; Signature: void qt_msgbox_info( QWidget* parent
  ;;                                , const char* title
  ;;                                , const char* text )
  (cffi:defcfun "qt_msgbox_info"
                 :void (parent :pointer) (title :string) (text :string))

  (defvar WIDGET-WINDOW-MAIN 2)
  (defvar WIDGET-BUTTON      3)
  (defvar WIDGET-LINE-EDIT   5)
  (defvar LAYOUT-QFORM       3)

  ;; ----- Define variables --------------------------- ;;
  ;;

  ;; This function must always be called
  ;; before creating any widget.
  (defvar qapp   (qt-app-new2))

  ;; Create main window 
  (defvar window (qt-widget-new (cffi:null-pointer) WIDGET-WINDOW-MAIN))

  (cffi:with-foreign-string (title "My LISP QT Window")
                            (qt-widget-settext window title))

  (defvar form (qt-layout-new window LAYOUT-QFORM))

  ;; Note: Common Lisp is case insensitive 
  (defvar button     (qt-QFormLayout-addWidgetAndLabel form WIDGET-BUTTON))
  (defvar line-edit  (qt-QFormLayout-addWidgetAndLabel form WIDGET-LINE-EDIT))

  ;; Set button label 
  (cffi:with-foreign-string (title "click me")
                            (qt-widget-settext button title))

  (defvar counter 0)

  ;; Define button callback
  ;; void (* callback) (void* self) )
  (cffi:defcallback button-callback
               ;; Return type of callback
               :void               
               ;; List containing callback arguments
               ( (ctx :pointer) )  
               ;; Function main body 
               (progn
                 ;; Increment counter
                 (setf counter (+ counter 1))

                 (cffi:with-foreign-string
                        (text (format nil "counter set to ~a" counter ))
                        (qt-widget-settext button text))

                 (cffi:with-foreign-string
                     (title "User notification")
                     (cffi:with-foreign-string
                        (text "Counter clicked")
                        (qt-msgbox-info window title text)))

                 (print (format t "Counter set to = ~D \n" counter))

                 ))
                ;; --- End of button callback() ----- ;;

  (qt-button-onClicked button (cffi:null-pointer)
                              (cffi:callback button-callback))

  ;; Run QT event loop and blocks current thread. 
  (qt-app-exec qapp)

  ;; Dispose C++ objects calling destructors 
  (qt-qobject-del window)
  (qt-qobject-del qapp)
#+END_SRC

Run Common Lisp Client Code: 

#+BEGIN_SRC sh 
    $ >> export LD_LIBRARY_PATH=$PWD/build

    $ >> sbcl --load sbcl_client.lisp
   This is SBCL 2.0.1-1.fc32, an implementation of ANSI Common Lisp.
   More information about SBCL is available at <http://www.sbcl.org/>.

   SBCL is free software, provided as is, with absolutely no warranty.
   It is mostly in the public domain; some portions are provided under
   BSD-style licenses.  See the CREDITS and COPYING files in the
   distribution for more information.
    [TRACE] Create QAppliction Object Ok
    ... ... ... ... ...  
#+END_SRC

User interface screenshot: 

#+CAPTION: User interface for Common Lisp (SBCL) client code. 
[[file:images/cwrapper-qt5-4.png][file:images/cwrapper-qt5-4.png]]

