Skip to content

Commit 2a8af74

Browse files
DOC-4997 add Qt integration example page
1 parent 30847dd commit 2a8af74

File tree

1 file changed

+337
-0
lines changed

1 file changed

+337
-0
lines changed
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
---
2+
categories:
3+
- docs
4+
- develop
5+
- stack
6+
- oss
7+
- rs
8+
- rc
9+
- oss
10+
- kubernetes
11+
- clients
12+
description: Use `hiredis` in conjunction with the Qt app framework.
13+
linkTitle: Qt integration
14+
title: Integrate hiredis with a Qt app
15+
weight: 50
16+
---
17+
18+
[Qt](https://www.qt.io/) is a popular cross-platform C++ framework that
19+
you can use to build command line and GUI apps. This guide explains how
20+
to use `hiredis` to connect to a Redis server from a Qt app.
21+
22+
## Install Qt
23+
24+
You should first download and install the
25+
[Qt development environment](https://www.qt.io/download-dev) for your
26+
development platform, if you have not already done so. The example
27+
below briefly explains how to use Qt Creator
28+
to manage your project, but see the [Qt Creator](https://doc.qt.io/qtcreator/)
29+
docs for an extensive set of examples and tutorials.
30+
31+
## Create a simple app
32+
33+
We will use a simple console app to demonstrate how to connect
34+
to Redis from Qt. Create the app project in Qt Creator using the
35+
**File > New Project** command. The generated source code is a
36+
single C++ file, called `main.cpp`, that uses a
37+
[`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html)
38+
object to handle the main event loop. Although it will compile and run,
39+
it doesn't do anything useful at this stage.
40+
41+
## Add `hiredis` files
42+
43+
Build `hiredis` if you have not already done so (see
44+
[Build and install]({{< relref "/develop/clients/hiredis#build-and-install" >}})
45+
for more information). Copy the following files from the `hiredis` folder into
46+
the Header files section of your Qt project:
47+
48+
- `hiredis.h`
49+
- `alloc.h`
50+
- `read.h`
51+
- `sds.h`
52+
- `adapters/qt.h`
53+
54+
In `qt.h`, edit the line near the top of the file that reads
55+
56+
```c
57+
#include "../async.h"
58+
```
59+
60+
to
61+
62+
```c
63+
#include "async.h"
64+
```
65+
66+
You must do this because `qt.h` is in the same enclosing folder as `async.h` for
67+
this project.
68+
69+
You should also make the `libhiredis` library available to the project. For example,
70+
if you have used the default option of [`cmake`](https://cmake.org/) for the project
71+
build and you have installed the `.dylib` or `.so` file for `hiredis` in `/usr/local/lib`,
72+
you should add the following lines to the `CMakeLists.txt` file:
73+
74+
```
75+
add_library(hiredis SHARED IMPORTED)
76+
set_property(TARGET hiredis PROPERTY
77+
IMPORTED_LOCATION "/usr/local/lib/libhiredis.dylib")
78+
```
79+
80+
You should also modify the `target_link_libraries` directive to include
81+
`hiredis`:
82+
83+
```
84+
target_link_libraries(ConsoleTest Qt${QT_VERSION_MAJOR}::Core hiredis)
85+
```
86+
87+
## Add code to access Redis
88+
89+
You can add a class using the **Add new** context menu
90+
on the project folder in Qt Creator. The sections below give
91+
examples of the code you should add to this class to
92+
connect to Redis. The code is separated into header and
93+
implementation files.
94+
95+
### Header file
96+
97+
The header file for a class called `RedisExample` is shown below.
98+
An explanation follows the code.
99+
100+
```c++
101+
// redisexample.h
102+
103+
#ifndef REDISEXAMPLE_H
104+
#define REDISEXAMPLE_H
105+
106+
#include <QObject>
107+
108+
#include "hiredis.h"
109+
#include "async.h"
110+
#include "qt.h"
111+
112+
113+
class RedisExample : public QObject
114+
{
115+
Q_OBJECT
116+
117+
public:
118+
// Constructor
119+
RedisExample(const char *keyForRedis, const char *valueForRedis, QObject *parent = 0)
120+
:QObject(parent), m_key(keyForRedis), m_value(valueForRedis) {}
121+
122+
public slots:
123+
// Slot method to hold the code that connects to Redis and issues
124+
// commands.
125+
void run();
126+
127+
signals:
128+
// Signal to indicate that our code has finished executing.
129+
void finished();
130+
131+
public:
132+
// Method to close the Redis connection and signal that we've
133+
// finished.
134+
void finish();
135+
136+
private:
137+
const char *m_key; // Key for Redis string.
138+
const char *m_value; // Value for Redis string.
139+
redisAsyncContext *m_ctx; // Redis connection context.
140+
RedisQtAdapter m_adapter; // Adapter to let `hiredis` work with Qt.
141+
};
142+
143+
#endif // REDISEXAMPLE_H
144+
```
145+
146+
[`QObject`](https://doc.qt.io/qt-6/qobject.html) is a key Qt class that
147+
implements the [Object model](https://doc.qt.io/qt-6/object.html) for
148+
communication between objects. When you create your class in Qt Creator,
149+
you can specify that you want it to be a subclass of `QObject` (this will
150+
add the appropriate header files and include the `Q_OBJECT` macro in the
151+
class declaration).
152+
153+
The `QObject` communication model uses some instance methods as *signals*
154+
to report events and others as *slots* to act as callbacks that process the
155+
events (see [Signals and slots](https://doc.qt.io/qt-6/signalsandslots.html)
156+
for an introduction). The Qt [meta-object compiler](https://doc.qt.io/qt-6/moc.html)
157+
recognizes the non-standard C++ access specifiers `signals:` and `slots:` in the
158+
class declaration and adds extra code for them during compilation to enable
159+
the communication mechanism.
160+
161+
In our class, there is a `run()` slot that will implement the code to access Redis.
162+
The code eventually emits a `finished()` signal when it is complete to indicate that
163+
the app should exit.
164+
165+
Our simple example code just sets and gets a Redis
166+
[string]({{< relref "/develop/data-types/strings" >}}) key. The class contains
167+
private attributes for the key and value (following the Qt `m_xxx` naming convention
168+
for class members). These are set by the constructor along with a call to the
169+
`QObject` constructor. The other attributes represent the connection context for
170+
Redis (which should be asynchronous for a Qt app) and an adapter object that
171+
`hiredis` uses to integrate with Qt.
172+
173+
### Implementation file
174+
175+
The file that implements the methods declared in the header is shown
176+
below. A full explanation follows the code.
177+
178+
```c++
179+
// redisexample.cpp
180+
181+
#include <iostream>
182+
183+
#include "redisexample.h"
184+
185+
186+
void RedisExample::finish() {
187+
// Disconnect gracefully.
188+
redisAsyncDisconnect(m_ctx);
189+
190+
// Emit the `finished()` signal to indicate that the
191+
// execution is complete.
192+
emit finished();
193+
}
194+
195+
196+
// Callback used by our `GET` command in the `run()` method.
197+
void getCallback(redisAsyncContext *, void * r, void * privdata) {
198+
199+
// Cast data pointers to their appropriate types.
200+
redisReply *reply = static_cast<redisReply *>(r);
201+
RedisExample *ex = static_cast<RedisExample *>(privdata);
202+
203+
if (reply == nullptr || ex == nullptr) {
204+
return;
205+
}
206+
207+
std::cout << "Value: " << reply->str << std::endl;
208+
209+
// Close the Redis connection and quit the app.
210+
ex->finish();
211+
}
212+
213+
214+
void RedisExample::run() {
215+
// Open the connection to Redis.
216+
m_ctx = redisAsyncConnect("localhost", 6379);
217+
218+
if (m_ctx->err) {
219+
std::cout << "Error: " << m_ctx->errstr << std::endl;
220+
finish();
221+
}
222+
223+
// Configure the connection to work with Qt.
224+
m_adapter.setContext(m_ctx);
225+
226+
// Issue some simple commands. For the `GET` command, pass a
227+
// callback function and a pointer to this object instance
228+
// so that we can access the object's members from the callback.
229+
redisAsyncCommand(m_ctx, NULL, NULL, "SET %s %s", m_key, m_value);
230+
redisAsyncCommand(m_ctx, getCallback, this, "GET %s", m_key);
231+
}
232+
```
233+
234+
The code that accesses Redis is in the `run()` method (recall that this
235+
implements a Qt slot that will be called in response to a signal). The
236+
code connects to Redis and stores the connection context pointer in the
237+
`m_ctx` attribute of the class instance. The call to `m_adapter.setContext()`
238+
initializes the Qt support for the context.
239+
240+
The code then issues two Redis commands to [`SET`]({{< relref "/commands/set" >}})
241+
the string key and value that were supplied using the class's constructor. We are
242+
not interested in the response returned by this command, but we are interested in the
243+
response from the [`GET`]({{< relref "/commands/get" >}}) command that follows it.
244+
Because the commands are asynchronous, we need to set a callback to handle
245+
the `GET` response when it arrives. In the `redisAsyncCommand()` call, we pass
246+
a pointer to our `getCallback()` function and also pass a pointer to the
247+
`RedisExample` instance. This is a custom data field that will simply
248+
be passed on to the callback when it executes.
249+
250+
The code in the `getCallback()` function starts by casting the reply pointer
251+
parameter to [`redisReply`]({{< relref "/develop/clients/hiredis/handle-replies" >}})
252+
and the custom data pointer to `RedisExample`. Here, the example just prints
253+
the reply string to the console, but you can process it in any way you like.
254+
You can add methods to your class and call them within the callback using the
255+
custom data pointer passed during the `redisAsyncCommand()` call. Here, we
256+
simply use the pointer to call the `finish()` method.
257+
258+
The `finish()` method calls
259+
`redisAsyncDisconnect()` to close the connection and then uses the
260+
Qt signalling mechanism to emit the `finished()` signal. You may need to
261+
process several commands with a particular connection context, but you should
262+
close it from a callback when you have finished using it.
263+
264+
### Main program
265+
266+
To access the `RedisExample` class, you should use code like the
267+
following in the `main()` function defined in `main.cpp`:
268+
269+
```c++
270+
#include <QCoreApplication>
271+
#include <QTimer>
272+
273+
#include "redisexample.h"
274+
275+
276+
int main(int argc, char *argv[])
277+
{
278+
QCoreApplication app(argc, argv);
279+
280+
// Instance of our object.
281+
RedisExample r("url", "https://redis.io/");
282+
283+
// Call the `run()` slot on our `RedisExample` instance to
284+
// run our Redis commands.
285+
QTimer::singleShot(0, &r, SLOT(run()));
286+
287+
// Set up a communication connection between our `finished()`
288+
// signal and the application's `quit()` slot.
289+
QObject::connect(&r, SIGNAL(finished()), &app, SLOT(quit()));
290+
291+
// Start the app's main event loop.
292+
return app.exec();
293+
}
294+
```
295+
296+
This creates the [`QCoreApplication`](https://doc.qt.io/qt-6/qcoreapplication.html)
297+
instance that manages the main event loop for a console app. It
298+
then creates the instance of `RedisExample` with the key ("url") and
299+
value ("https://redis.io/") for our Redis string.
300+
301+
The two lines below set up the `QObject` communication mechanism
302+
for the app. The call to `QTimer::singleShot()` activates the `run()`
303+
slot method on our `RedisExample` instance. The `QObject::connect()`
304+
call creates a communication link between the `finished()` signal of
305+
out `RedisExample` instance and the `quit()` slot of our
306+
`QCoreApplication` instance. This quits the application event loop and
307+
exits the app when the `finished()` signal is emitted by the
308+
`RedisExample` object. This happens when the `finish()` method is called
309+
at the end of the `GET` command callback.
310+
311+
## Run the code
312+
313+
When you have added the code, you can run it from the **Build** menu of
314+
Qt Creator or from the toolbar at the left hand side of the window.
315+
Assuming the connection to Redis succeeds, it will print the message
316+
`Value: https://redis.io/` and quit. You can use the
317+
[`KEYS`]({{< relref "/commands/keys" >}}) command from
318+
[`redis-cli`]({{< relref "/develop/tools/cli" >}}) or
319+
[Redis Insight]({{< relref "/develop/tools/insight" >}}) to check
320+
that the "url" string key was added to the Redis database.
321+
322+
## Key information
323+
324+
There are many ways you could use Redis with a Qt app, but our example
325+
demonstrates some techniques that are broadly useful:
326+
327+
- Use the `QObject` communication mechanism to simplify your code.
328+
- Use the `hiredis` asynchronous API. Add a `RedisQtAdapter` instance
329+
to your code and ensure you call its `setContext()` method to
330+
initialize it before issuing Redis commands.
331+
- Place all code and data you need to interact with Redis
332+
(including the connection context) in a single
333+
class or ensure it is available from a class via pointers and
334+
Qt signals. Pass a pointer to an instance of your class in the
335+
custom data parameter when you issue a Redis command with
336+
`redisAsyncCommand()` and use this to process the reply or
337+
issue more commands from the callback.

0 commit comments

Comments
 (0)