Integrate hiredis with a Qt app
Use hiredis
in conjunction with the Qt app framework.
Qt is a popular cross-platform C++ framework that
you can use to build command line and GUI apps. This guide explains how
to use hiredis
to connect to a Redis server from a Qt app.
Install Qt
You should first download and install the Qt development environment for your development platform, if you have not already done so. The example below briefly explains how to use Qt Creator to manage your project, but see the Qt Creator docs for an extensive set of examples and tutorials.
Create a simple app
We will use a simple console app to demonstrate how to connect
to Redis from Qt. Create the app project in Qt Creator using the
File > New Project command. The generated source code is a
single C++ file, called main.cpp
, that uses a
QCoreApplication
object to handle the main event loop. Although it will compile and run,
it doesn't do anything useful at this stage.
Add hiredis
files
Build hiredis
if you have not already done so (see
Build and install
for more information).
You should also make the libhiredis
library available to the project. For example,
if you have used the default option of cmake
as the project
build tool and you have installed the .dylib
or .so
file for hiredis
in /usr/local/lib
,
you should add the following lines to the CMakeLists.txt
file:
add_library(hiredis SHARED IMPORTED)
set_property(TARGET hiredis PROPERTY
IMPORTED_LOCATION "/usr/local/lib/libhiredis.dylib")
You should also modify the target_link_libraries
directive to include
hiredis
:
target_link_libraries(ConsoleTest Qt${QT_VERSION_MAJOR}::Core hiredis)
Add code to access Redis
You can add a class using the Add new context menu on the project folder in Qt Creator. The sections below give examples of the code you should add to this class to connect to Redis. The code is separated into header and implementation files.
Header file
The header file for a class called RedisExample
is shown below.
An explanation follows the code.
// redisexample.h
#ifndef REDISEXAMPLE_H
#define REDISEXAMPLE_H
#include <QObject>
#include <hiredis/hiredis.h>
#include <hiredis/async.h>
#include <hiredis/adapters/qt.h>
class RedisExample : public QObject
{
Q_OBJECT
public:
// Constructor
RedisExample(const char *keyForRedis, const char *valueForRedis, QObject *parent = 0)
:QObject(parent), m_key(keyForRedis), m_value(valueForRedis) {}
public slots:
// Slot method to hold the code that connects to Redis and issues
// commands.
void run();
signals:
// Signal to indicate that our code has finished executing.
void finished();
public:
// Method to close the Redis connection and signal that we've
// finished.
void finish();
private:
const char *m_key; // Key for Redis string.
const char *m_value; // Value for Redis string.
redisAsyncContext *m_ctx; // Redis connection context.
RedisQtAdapter m_adapter; // Adapter to let `hiredis` work with Qt.
};
#endif // REDISEXAMPLE_H
QObject
is a key Qt class that
implements the Object model for
communication between objects. When you create your class in Qt Creator,
you can specify that you want it to be a subclass of QObject
(this will
add the appropriate header files and include the Q_OBJECT
macro in the
class declaration).
The QObject
communication model uses some instance methods as signals
to report events and others as slots to act as callbacks that process the
events (see Signals and slots
for an introduction). The Qt meta-object compiler
recognizes the non-standard C++ access specifiers signals:
and slots:
in the
class declaration and adds extra code for them during compilation to enable
the communication mechanism.
In our class, there is a run()
slot that will implement the code to access Redis.
The code eventually emits a finished()
signal when it is complete to indicate that
the app should exit.
Our simple example code just sets and gets a Redis
string key. The class contains
private attributes for the key and value (following the Qt m_xxx
naming convention
for class members). These are set by the constructor along with a call to the
QObject
constructor. The other attributes represent the connection context for
Redis (which should generally be
asynchronous
for a Qt app) and an adapter object that hiredis
uses to integrate with Qt.
Implementation file
The file that implements the methods declared in the header is shown below. A full explanation follows the code.
// redisexample.cpp
#include <iostream>
#include "redisexample.h"
void RedisExample::finish() {
// Disconnect gracefully.
redisAsyncDisconnect(m_ctx);
// Emit the `finished()` signal to indicate that the
// execution is complete.
emit finished();
}
// Callback used by our `GET` command in the `run()` method.
void getCallback(redisAsyncContext *, void * r, void * privdata) {
// Cast data pointers to their appropriate types.
redisReply *reply = static_cast<redisReply *>(r);
RedisExample *ex = static_cast<RedisExample *>(privdata);
if (reply == nullptr || ex == nullptr) {
return;
}
std::cout << "Value: " << reply->str << std::endl;
// Close the Redis connection and quit the app.
ex->finish();
}
void RedisExample::run() {
// Open the connection to Redis.
m_ctx = redisAsyncConnect("localhost", 6379);
if (m_ctx->err) {
std::cout << "Error: " << m_ctx->errstr << std::endl;
finish();
}
// Configure the connection to work with Qt.
m_adapter.setContext(m_ctx);
// Issue some simple commands. For the `GET` command, pass a
// callback function and a pointer to this object instance
// so that we can access the object's members from the callback.
redisAsyncCommand(m_ctx, NULL, NULL, "SET %s %s", m_key, m_value);
redisAsyncCommand(m_ctx, getCallback, this, "GET %s", m_key);
}
The code that accesses Redis is in the run()
method (recall that this
implements a Qt slot that will be called in response to a signal). The
code connects to Redis and stores the connection context pointer in the
m_ctx
attribute of the class instance. The call to m_adapter.setContext()
initializes the Qt support for the context. Note that we need an
asynchronous connection for Qt. See
Asynchronous connection
for more information.
The code then issues two Redis commands to SET
the string key and value that were supplied using the class's constructor. We are
not interested in the response returned by this command, but we are interested in the
response from the GET
command that follows it.
Because the commands are asynchronous, we need to set a callback to handle
the GET
response when it arrives. In the redisAsyncCommand()
call, we pass
a pointer to our getCallback()
function and also pass a pointer to the
RedisExample
instance. This is a custom data field that will simply
be passed on to the callback when it executes (see
Construct asynchronous commands
for more information).
The code in the getCallback()
function starts by casting the reply pointer
parameter to redisReply
and the custom data pointer to RedisExample
. Here, the example just prints
the reply string to the console, but you can process it in any way you like.
You can add methods to your class and call them within the callback using the
custom data pointer passed during the redisAsyncCommand()
call. Here, we
simply use the pointer to call the finish()
method.
The finish()
method calls
redisAsyncDisconnect()
to close the connection and then uses the
Qt signalling mechanism to emit the finished()
signal. You may need to
process several commands with a particular connection context, but you should
close it from a callback when you have finished using it.
Main program
To access the RedisExample
class, you should use code like the
following in the main()
function defined in main.cpp
:
#include <QCoreApplication>
#include <QTimer>
#include "redisexample.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Instance of our object.
RedisExample r("url", "https://redis.io/");
// Call the `run()` slot on our `RedisExample` instance to
// run our Redis commands.
QTimer::singleShot(0, &r, SLOT(run()));
// Set up a communication connection between our `finished()`
// signal and the application's `quit()` slot.
QObject::connect(&r, SIGNAL(finished()), &app, SLOT(quit()));
// Start the app's main event loop.
return app.exec();
}
This creates the QCoreApplication
instance that manages the main event loop for a console app. It
then creates the instance of RedisExample
with the key ("url") and
value ("https://redis.io/") for our Redis string.
The two lines below set up the QObject
communication mechanism
for the app. The call to
QTimer::singleShot()
activates the run()
slot method on our RedisExample
instance. The
QObject::connect()
call creates a communication link between the finished()
signal of
out RedisExample
instance and the quit()
slot of our
QCoreApplication
instance. This quits the application event loop and
exits the app when the finished()
signal is emitted by the
RedisExample
object. This happens when the finish()
method is called
at the end of the GET
command callback.
Run the code
When you have added the code, you can run it from the Build menu of
Qt Creator or from the toolbar at the left hand side of the window.
Assuming the connection to Redis succeeds, it will print the message
Value: https://redis.io/
and quit. You can use the
KEYS
command from
redis-cli
or
Redis Insight to check
that the "url" string key was added to the Redis database.
Key information
There are many ways you could use Redis with a Qt app, but our example demonstrates some techniques that are broadly useful:
- Use the
QObject
communication mechanism to simplify your code. - Use the
hiredis
asynchronous API. Add aRedisQtAdapter
instance to your code and ensure you call itssetContext()
method to initialize it before issuing Redis commands. - Place all code and data you need to interact with Redis
(including the connection context) in a single
class or ensure it is available from a class via pointers and
Qt signals. Pass a pointer to an instance of your class in the
custom data parameter when you issue a Redis command with
redisAsyncCommand()
and use this to process the reply or issue more commands from the callback.