Skip to content

Commit

Permalink
cxx-qt-lib: add support for QByteArray
Browse files Browse the repository at this point in the history
Related to KDAB#291
  • Loading branch information
ahayzen-kdab committed Dec 19, 2022
1 parent 6892f82 commit 78ecc4e
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added

- Support for container types: `QSet<T>`, `QHash<K, V>`
- Support for further types: `QModelIndex`
- Support for further types: `QByteArray`, `QModelIndex`

### Fixed

Expand Down
27 changes: 27 additions & 0 deletions crates/cxx-qt-lib-headers/include/qbytearray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// clang-format off
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include <QByteArray>

#include "rust/cxx.h"

template<>
struct rust::IsRelocatable<QByteArray> : std::true_type
{
};

namespace rust {
namespace cxxqtlib1 {

QByteArray
qbytearrayFromRustString(rust::Str string);
rust::String
qbytearrayToRustString(const QByteArray& string);

}
}
1 change: 1 addition & 0 deletions crates/cxx-qt-lib-headers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static HEADERS: [(&str, &str); 20] = [
(include_str!("../include/common.h"), "common.h"),
(include_str!("../include/convert.h"), "convert.h"),
(include_str!("../include/cxxqt_thread.h"), "cxxqt_thread.h"),
(include_str!("../include/qbytearray.h"), "qbytearray.h"),
(include_str!("../include/qcolor.h"), "qcolor.h"),
(include_str!("../include/qdate.h"), "qdate.h"),
(include_str!("../include/qdatetime.h"), "qdatetime.h"),
Expand Down
2 changes: 2 additions & 0 deletions crates/cxx-qt-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn main() {
);

let rust_bridges = [
"qbytearray",
"qcolor",
"qdate",
"qdatetime",
Expand Down Expand Up @@ -68,6 +69,7 @@ fn main() {
);

let cpp_files = [
"qbytearray",
"qcolor",
"qdate",
"qdatetime",
Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

mod qbytearray;
pub use qbytearray::QByteArray;

mod qcolor;
pub use qcolor::QColor;

Expand Down
54 changes: 54 additions & 0 deletions crates/cxx-qt-lib/src/types/qbytearray.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// clang-format off
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#include "cxx-qt-lib/qbytearray.h"

#include "assertion_utils.h"

// The layout has changed between Qt 5 and Qt 6
//
// Qt5 QByteArray has one pointer as a member
// https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/text/qbytearray.h?h=v5.15.6-lts-lgpl#n470
//
// Qt6 QByteArray has one member, which contains two pointers and a size_t
// https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/text/qbytearray.h?h=v6.2.4#n91
// DataPointer is then a QByteArrayData, which is a QArrayDataPointer<char>
// https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/tools/qarraydatapointer.h?h=v6.2.4#n390
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
assert_alignment_and_size(QByteArray,
alignof(std::size_t),
sizeof(std::size_t[3]));
#else
assert_alignment_and_size(QByteArray,
alignof(std::size_t),
sizeof(std::size_t));
#endif

static_assert(!std::is_trivially_copy_assignable<QByteArray>::value);
static_assert(!std::is_trivially_copy_constructible<QByteArray>::value);

static_assert(!std::is_trivially_destructible<QByteArray>::value);

static_assert(QTypeInfo<QByteArray>::isRelocatable);

namespace rust {
namespace cxxqtlib1 {

QByteArray
qbytearrayFromRustString(rust::Str string)
{
// Note that rust::Str here is borrowed
return QByteArray(string.data(), string.size());
}

rust::String
qbytearrayToRustString(const QByteArray& byteArray)
{
return rust::String(byteArray.constData(), byteArray.size());
}

}
}
111 changes: 111 additions & 0 deletions crates/cxx-qt-lib/src/types/qbytearray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use cxx::{type_id, ExternType};
use std::mem::MaybeUninit;

#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx-qt-lib/qbytearray.h");

type QByteArray = super::QByteArray;
}

#[namespace = "rust::cxxqtlib1"]
unsafe extern "C++" {
include!("cxx-qt-lib/common.h");

#[doc(hidden)]
#[rust_name = "qbytearray_drop"]
fn drop(string: &mut QByteArray);

#[doc(hidden)]
#[rust_name = "qbytearray_default"]
fn construct() -> QByteArray;
#[doc(hidden)]
#[rust_name = "qbytearray_from_rust_string"]
fn qbytearrayFromRustString(string: &str) -> QByteArray;
#[doc(hidden)]
#[rust_name = "qbytearray_clone"]
fn construct(string: &QByteArray) -> QByteArray;

#[doc(hidden)]
#[rust_name = "qbytearray_to_rust_string"]
fn qbytearrayToRustString(string: &QByteArray) -> String;
}
}

/// The QByteArray class provides an array of bytes.
pub struct QByteArray {
/// The layout has changed between Qt 5 and Qt 6
///
/// Qt5 QByteArray has one pointer as a member
/// Qt6 QByteArray has one member, which contains two pointers and a size_t
#[cfg(qt_version_major = "5")]
_space: MaybeUninit<usize>,
#[cfg(qt_version_major = "6")]
_space: MaybeUninit<[usize; 3]>,
}

impl Clone for QByteArray {
/// Constructs a copy of other.
///
/// This operation takes constant time, because QByteArray is implicitly shared.
/// This makes returning a QByteArray from a function very fast.
/// If a shared instance is modified, it will be copied (copy-on-write), and that takes linear time.
fn clone(&self) -> Self {
ffi::qbytearray_clone(self)
}
}

impl Default for QByteArray {
/// Constructs an empty byte array.
fn default() -> Self {
ffi::qbytearray_default()
}
}

impl std::fmt::Display for QByteArray {
/// Convert the QByteArray to a Rust string
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", <&QByteArray as Into<String>>::into(self))
}
}

impl Drop for QByteArray {
/// Destroys the byte array.
fn drop(&mut self) {
ffi::qbytearray_drop(self)
}
}

impl From<&str> for QByteArray {
/// Constructs a QByteArray from a Rust string
fn from(str: &str) -> Self {
ffi::qbytearray_from_rust_string(str)
}
}

impl From<&String> for QByteArray {
/// Constructs a QByteArray from a Rust string
fn from(str: &String) -> Self {
ffi::qbytearray_from_rust_string(str)
}
}

impl From<&QByteArray> for String {
/// Convert the QByteArray to a Rust string
fn from(qbytearray: &QByteArray) -> Self {
ffi::qbytearray_to_rust_string(qbytearray)
}
}

// Safety:
//
// Static checks on the C++ side to ensure the size is the same.
unsafe impl ExternType for QByteArray {
type Id = type_id!("QByteArray");
type Kind = cxx::kind::Trivial;
}
1 change: 1 addition & 0 deletions tests/qt_types_standalone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ target_link_libraries(${CRATE} INTERFACE
add_executable(${APP_NAME}
cpp/cxxqtconvert.h
cpp/main.cpp
cpp/qbytearray.h
cpp/qcolor.h
cpp/qdate.h
cpp/qdatetime.h
Expand Down
2 changes: 2 additions & 0 deletions tests/qt_types_standalone/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <QtTest/QTest>

#include "cxxqtconvert.h"
#include "qbytearray.h"
#include "qcolor.h"
#include "qdate.h"
#include "qdatetime.h"
Expand Down Expand Up @@ -38,6 +39,7 @@ main(int argc, char* argv[])
};

runTest(QScopedPointer<QObject>(new CxxQtConvertTest));
runTest(QScopedPointer<QObject>(new QByteArrayTest));
runTest(QScopedPointer<QObject>(new QColorTest));
runTest(QScopedPointer<QObject>(new QDateTest));
runTest(QScopedPointer<QObject>(new QDateTimeTest));
Expand Down
55 changes: 55 additions & 0 deletions tests/qt_types_standalone/cpp/qbytearray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// clang-format off
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// clang-format on
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
#pragma once

#include <QtCore/QByteArray>
#include <QtTest/QTest>

#include "cxx-qt-gen/qbytearray_cxx.cxx.h"

class QByteArrayTest : public QObject
{
Q_OBJECT

private Q_SLOTS:
void construct()
{
QFETCH(bool, slice);
const auto s = construct_qbytearray(slice);
QCOMPARE(s, QByteArrayLiteral("String constructed by Rust"));
}

void construct_data()
{
QTest::addColumn<bool>("slice");

QTest::newRow("From &str") << true;
QTest::newRow("From String") << false;
}

void read()
{
const auto s = QByteArrayLiteral("String constructed by C++");
QVERIFY(read_qbytearray(s));
}

void clone()
{
auto s = QByteArrayLiteral("String constructed by C++");
const auto c = clone_qbytearray(s);
QCOMPARE(c, QByteArrayLiteral("String constructed by C++"));
}

void modify_rust() { QVERIFY(can_handle_qbytearray_change()); }

void modify_updates_cpp()
{
auto s = QByteArrayLiteral("String constructed by C++");
modify_qbytearray(s);
QCOMPARE(s, QByteArrayLiteral("Updated string value"));
}
};
1 change: 1 addition & 0 deletions tests/qt_types_standalone/rust/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use cxx_qt_build::CxxQtBuilder;

fn main() {
CxxQtBuilder::new()
.file("src/qbytearray.rs")
.file("src/qcolor.rs")
.file("src/qdate.rs")
.file("src/qdatetime.rs")
Expand Down
1 change: 1 addition & 0 deletions tests/qt_types_standalone/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

mod qbytearray;
mod qcolor;
mod qdate;
mod qdatetime;
Expand Down
59 changes: 59 additions & 0 deletions tests/qt_types_standalone/rust/src/qbytearray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use cxx_qt_lib::QByteArray;

#[cxx::bridge]
mod qbytearray_cxx {
unsafe extern "C++" {
include!("cxx-qt-lib/qbytearray.h");

type QByteArray = cxx_qt_lib::QByteArray;
}

extern "Rust" {
fn construct_qbytearray(slice: bool) -> QByteArray;
fn read_qbytearray(s: &QByteArray) -> bool;
fn modify_qbytearray(s: Pin<&mut QByteArray>);
fn can_handle_qbytearray_change() -> bool;
fn clone_qbytearray(s: &QByteArray) -> QByteArray;
}
}

fn construct_qbytearray(slice: bool) -> QByteArray {
if slice {
QByteArray::from("String constructed by Rust")
} else {
let rs_string = "String constructed by Rust".to_owned();
QByteArray::from(&rs_string)
}
}

fn read_qbytearray(s: &cxx_qt_lib::QByteArray) -> bool {
let rs = s.to_string();
rs == "String constructed by C++"
}

fn modify_qbytearray(mut s: std::pin::Pin<&mut cxx_qt_lib::QByteArray>) {
*s = QByteArray::from("Updated string value");
}

fn can_handle_qbytearray_change() -> bool {
let long_s = "Very very long string that is hopefully long enough to allocate and get Valgrind's attention :)";
let long_s_ptr = QByteArray::from(long_s);

let short_s = "Short string";
let mut short_s_ptr = QByteArray::from(short_s);
assert!(short_s_ptr.to_string() == short_s);

short_s_ptr = long_s_ptr;

short_s_ptr.to_string() == long_s
}

fn clone_qbytearray(s: &QByteArray) -> QByteArray {
s.clone()
}

0 comments on commit 78ecc4e

Please sign in to comment.