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.
This section presents how to call a statically linked C++ cod from C.
- Complete code: 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.
#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
File: cppcode.cpp
- Implementation of classes and C interface functions.
Implementation of class RunStat member functions:
... ... ... ...
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++;
}
... ... ... ... ... ... ... ...
Implementation of the C-interface functions for class std::string.
//--------- 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;
}
Implementation of the C-interface functions for class RunStat.
//---------- 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();
}
File: main.c
- C client code that uses C-interface functions export by the C++ file cppcode.
#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;
}
File: CMakeLists.txt
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)
Manual compilation:
# 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
Program output:
./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
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:
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) <<-----<<<-/
| |
+---------------------+
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:
- 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
- TileDB
- Embeddable database that can store time-series, geospatial data, arrays, matrices, sparse matrices and tensors (multi dimensional arrays).
- 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 Python binding uses ctypes for loading the C-89 thin API symbols from the botan shared library.
- 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:
Documentation of features used in this experiment
QT5 Documentation for widgets classes used in the sample code:
- QObject Class | Qt Core 5.15.0
- QWidget Class | Qt Widgets 5.15.0
- QApplication Class | Qt Widgets 5.15.0
- QPushButton Class | Qt Widgets 5.15.0
- QDoubleSpinBox Class | Qt Widgets 5.15.0
Python 3 Ctypes FFI Documentation:
- Python 3 Ctypes (Official Documentation)
- Python Closures - Programiz
- Using C from Python: How to create a ctypes wrapper - Julich Scientific IT Software.
- ctypes: foreign calls in your native language - Jeremy Yallop - University of Cambridge
- Python 2.5 Ctypes - Dalke Scientific
- Scipy Cookbook - Ctypes
- Python ctypes and function pointers (StackOverflow)
- Python ctypes function pointer (StackOverflow)
- Interfacing Python and C: Advanced “ctypes” Features
- Sagemath - Python Ctypes
- Python Ctypes for wrapping OpenCV
Julia language:
- Calling C and Fortran Code · The Julia Language
- Embedding Julia · The Julia Language
- Julia (programming language) - Wikipedia
- Julia: come for the syntax, stay for the speed - Nature Magazine
- Julia By Example
C# (Csharp) for Dotnet Core:
- https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet
- Microsoft - Install the .NET SDK or the .NET Runtime manually
- C# Official Documentation
- Consuming Unmanaged DLL Functions
- IntPtr.Zero Field
- Exposing .NET components to COM
- Microsft - Dynamic-Link Library Search Order
- Scott Hanselman - Building self-contained, single executable .NET Core 3 CLI tools
- Microsft - Single file deployment and executable
- Exploring the new .NET “dotnet” Command Line Interface (CLI)
D Language (DLang:
- D (programming language) - Wikipedia
- Interfacing D with C: Getting Started – The D Blog
- Interfacing to C - D Programming Language
- Using dlopen/dlsym - D Programming Language Discussion Forum
- Interfacing to C++ - D Programming Language
Nim Programming Language:
- https://narimiran.github.io/nim-basics/
- Nim Forum - Lambda syntax is awkard
- Python/Cython/Nim CheatSheet - DEV Community
- Nim for Python Programmers · nim-lang/Nim Wiki · GitHub
- GitHub - nimterop/nimterop
- Nimterop is a Nim package that aims to make C/C++ interop seamless
- GitHub - khchen/winim: Nim’s Windows API and COM Library
Lisp - SBCL (Steel Bank Common Lisp):
- http://www.sbcl.org/ - Steel Bank Common Lisp
- sbcl(1): Steel Bank Common Lisp - Linux man page
- CFFI User Manual
- Interop mini-series – Calling C and C++ code Lisp using CFFI (Part 1)
- use C shared lib with cffi · GitHub
- practice - Common Lisp hello CFFI (Common Lisp + C) · GitHub
- Buildapp - Create executables with SBCL or CCL
- GIST with all sources:
Source code for C-wrapper
File: 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/
File: CMakeLists.txt
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 )
File: qtwrapper.cpp
- C-wrapper or C-interface for QT5 C++ GUI library
#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); }
);
}
File: qtwrapper.h (header for the C-wrapper or C-interface)
// 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) );
Considerations:
- (extern “C”) enforces C linkage, in other words, the function with this annotation must have a C compatible signature and use C compatible types.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- An alternative method for accessing the C++ libraries from higher level languages is by using 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.
Note: this code was compiled and tested on a Linux - Fedora 32, 64 bits machine which contains pre-installed Qt5 libraries.
Fetch the sources:
$ 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
Build the project: (Note: Tested on Linux Fedora-32 64 bits x86-64)
$ cmake --config Debug -H. -Bbuild
$ cmake --build build --target
Check binaries:
$ 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
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.
$ 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
... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ...
Show exported symbols demangling (aka decoding) C++ mangled symbols:
$ >> 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*)
... ... ... ... ...
... ... ... ... ...
File: client1.c (C-client code)
#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;
}
Run executable:
$ 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
... ... ... ... ... ...
... ... ... ... ... ... ...
User Interface Screenshot:
File: cpp-client.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);
}
Screenshot:
The following python script, loads the C shared library wrapper for Qt5 widgets using Python3 ctypes FFI (Foreign Function Interface). Although, Python3 already has PyQt and 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
#!/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. ")
Running
# 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.
Or:
$ env LD_LIBRARY_PATH=$PWD/_build python3 pywrapper.py
Or:
$ chmod +x pywrapper.py $ env LD_LIBRARY_PATH=$PWD/_build ./pywrapper.py
Screenshot
File: loader.jl (Julia language client code)
#!/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()
Run julia script / attempt 1 -> error:
$ julia loader.jl
ERROR: LoadError: could not load library "libqtwrapper"
libqtwrapper.so: cannot open shared object file: No such file or directory
Stacktrace:
... ... .... ... ...
... ... ... ... ...
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.
$ 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
---------------------------------
... ... ... ...
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’.
$ env LD_LIBRARY_PATH=build:$LD_LIBRARY_PATH julia loade.jl
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.
# 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
... .... ....
... .... ....
User Interface Screenshot:
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
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
File: Program.cs - C# Csharp / core source code (dotnet core)
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. ");
}
}
}
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.
export LD_LIBRARY_PATH=$HOME/_build/
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.
set PATH="%cd%\_build\path\to\shared\library\directory";%PATH%
Build from command line:
$ ~/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
Check the executable:
$ 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)
Running the application:
$ 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.
... ... ... ..
... ... ... ... ... ... ... ..
Running the application via dotnet core tool:
$ ~/dotnet/dotnet run --project=myapp.csproj
Screenshot
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)
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;
}
Run file d_client.d as a script:
$ 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. ... ... ... ... ... ... ... ...
Build and run executable:
$ 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.
... ... ... ... ... ... ... ... ... ... ... ...
... ... ... ... ... ... ... ... ... ... ... ...
User Interface Screenshot:
File: nim_app.nim
# 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."
Copy the shared library to the project’s root directory:
$ >> cp -v _build/libqtwrapper.so .
'_build/libqtwrapper.so' -> './libqtwrapper.so'
Compile and run in a single step:
$ nim c -r nim_app.nim
File: rustclient.rs
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;
}
Building:
$ rustc rustclient.rs -l qtwrapper -L_build/
Running:
$ ./rustclient
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:
# 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)
File: sbcl_client.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)
Run Common Lisp Client Code:
$ >> 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
... ... ... ... ...
User interface screenshot: