|
| 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