From 7ec45402985ce5409ee6e1af526e42ff9aaa35ee Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Thu, 15 Dec 2022 17:07:14 +0000 Subject: [PATCH] cxx-qt-lib: add support for QByteArray Related to #291 --- CHANGELOG.md | 4 + .../cxx-qt-lib-headers/include/qbytearray.h | 27 +++++ crates/cxx-qt-lib-headers/src/lib.rs | 3 +- crates/cxx-qt-lib/build.rs | 2 + crates/cxx-qt-lib/src/types/mod.rs | 3 + crates/cxx-qt-lib/src/types/qbytearray.cpp | 54 +++++++++ crates/cxx-qt-lib/src/types/qbytearray.rs | 111 ++++++++++++++++++ tests/qt_types_standalone/CMakeLists.txt | 1 + tests/qt_types_standalone/cpp/main.cpp | 2 + tests/qt_types_standalone/cpp/qbytearray.h | 55 +++++++++ tests/qt_types_standalone/rust/build.rs | 1 + tests/qt_types_standalone/rust/src/lib.rs | 1 + .../rust/src/qbytearray.rs | 59 ++++++++++ 13 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 crates/cxx-qt-lib-headers/include/qbytearray.h create mode 100644 crates/cxx-qt-lib/src/types/qbytearray.cpp create mode 100644 crates/cxx-qt-lib/src/types/qbytearray.rs create mode 100644 tests/qt_types_standalone/cpp/qbytearray.h create mode 100644 tests/qt_types_standalone/rust/src/qbytearray.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6198515c0..cd47aecf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/KDAB/cxx-qt/compare/v0.4.1...HEAD) +### Added + +- Support for further types: `QByteArray` + ## [0.4.1](https://github.com/KDAB/cxx-qt/compare/v0.4.0...v0.4.1) - 2022-11-18 ### Added diff --git a/crates/cxx-qt-lib-headers/include/qbytearray.h b/crates/cxx-qt-lib-headers/include/qbytearray.h new file mode 100644 index 000000000..d962c0406 --- /dev/null +++ b/crates/cxx-qt-lib-headers/include/qbytearray.h @@ -0,0 +1,27 @@ +// clang-format off +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include + +#include "rust/cxx.h" + +template<> +struct rust::IsRelocatable : std::true_type +{ +}; + +namespace rust { +namespace cxxqtlib1 { + +QByteArray +qbytearrayFromRustString(rust::Str string); +rust::String +qbytearrayToRustString(const QByteArray& string); + +} +} diff --git a/crates/cxx-qt-lib-headers/src/lib.rs b/crates/cxx-qt-lib-headers/src/lib.rs index 76237b3a1..227256d80 100644 --- a/crates/cxx-qt-lib-headers/src/lib.rs +++ b/crates/cxx-qt-lib-headers/src/lib.rs @@ -10,7 +10,8 @@ use std::{fs::File, io::Write, path::Path}; -static HEADERS: [(&str, &str); 18] = [ +static HEADERS: [(&str, &str); 19] = [ + (include_str!("../include/qbytearray.h"), "qbytearray.h"), (include_str!("../include/common.h"), "common.h"), (include_str!("../include/convert.h"), "convert.h"), (include_str!("../include/cxxqt_thread.h"), "cxxqt_thread.h"), diff --git a/crates/cxx-qt-lib/build.rs b/crates/cxx-qt-lib/build.rs index de3a89cf0..27c3fd825 100644 --- a/crates/cxx-qt-lib/build.rs +++ b/crates/cxx-qt-lib/build.rs @@ -19,6 +19,7 @@ fn main() { ); let rust_bridges = [ + "qbytearray", "qcolor", "qdate", "qdatetime", @@ -65,6 +66,7 @@ fn main() { ); let cpp_files = [ + "qbytearray", "qcolor", "qdate", "qdatetime", diff --git a/crates/cxx-qt-lib/src/types/mod.rs b/crates/cxx-qt-lib/src/types/mod.rs index 2884ece42..3b81c0879 100644 --- a/crates/cxx-qt-lib/src/types/mod.rs +++ b/crates/cxx-qt-lib/src/types/mod.rs @@ -3,6 +3,9 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod qbytearray; +pub use qbytearray::QByteArray; + mod qcolor; pub use qcolor::QColor; diff --git a/crates/cxx-qt-lib/src/types/qbytearray.cpp b/crates/cxx-qt-lib/src/types/qbytearray.cpp new file mode 100644 index 000000000..4c0dc8c86 --- /dev/null +++ b/crates/cxx-qt-lib/src/types/qbytearray.cpp @@ -0,0 +1,54 @@ +// clang-format off +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// 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 +// 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::value); +static_assert(!std::is_trivially_copy_constructible::value); + +static_assert(!std::is_trivially_destructible::value); + +static_assert(QTypeInfo::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()); +} + +} +} diff --git a/crates/cxx-qt-lib/src/types/qbytearray.rs b/crates/cxx-qt-lib/src/types/qbytearray.rs new file mode 100644 index 000000000..b7396c168 --- /dev/null +++ b/crates/cxx-qt-lib/src/types/qbytearray.rs @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// +// 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, + #[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>::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; +} diff --git a/tests/qt_types_standalone/CMakeLists.txt b/tests/qt_types_standalone/CMakeLists.txt index 5451d0b23..ef55aa1ec 100644 --- a/tests/qt_types_standalone/CMakeLists.txt +++ b/tests/qt_types_standalone/CMakeLists.txt @@ -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 diff --git a/tests/qt_types_standalone/cpp/main.cpp b/tests/qt_types_standalone/cpp/main.cpp index 0618202b2..902b5e47b 100644 --- a/tests/qt_types_standalone/cpp/main.cpp +++ b/tests/qt_types_standalone/cpp/main.cpp @@ -8,6 +8,7 @@ #include #include "cxxqtconvert.h" +#include "qbytearray.h" #include "qcolor.h" #include "qdate.h" #include "qdatetime.h" @@ -37,6 +38,7 @@ main(int argc, char* argv[]) }; runTest(QScopedPointer(new CxxQtConvertTest)); + runTest(QScopedPointer(new QByteArrayTest)); runTest(QScopedPointer(new QColorTest)); runTest(QScopedPointer(new QDateTest)); runTest(QScopedPointer(new QDateTimeTest)); diff --git a/tests/qt_types_standalone/cpp/qbytearray.h b/tests/qt_types_standalone/cpp/qbytearray.h new file mode 100644 index 000000000..51117113b --- /dev/null +++ b/tests/qt_types_standalone/cpp/qbytearray.h @@ -0,0 +1,55 @@ +// clang-format off +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// clang-format on +// SPDX-FileContributor: Andrew Hayzen +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +#pragma once + +#include +#include + +#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("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")); + } +}; diff --git a/tests/qt_types_standalone/rust/build.rs b/tests/qt_types_standalone/rust/build.rs index 78d3d1f8a..414462ea9 100644 --- a/tests/qt_types_standalone/rust/build.rs +++ b/tests/qt_types_standalone/rust/build.rs @@ -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") diff --git a/tests/qt_types_standalone/rust/src/lib.rs b/tests/qt_types_standalone/rust/src/lib.rs index a91d0854f..5609e9d62 100644 --- a/tests/qt_types_standalone/rust/src/lib.rs +++ b/tests/qt_types_standalone/rust/src/lib.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod qbytearray; mod qcolor; mod qdate; mod qdatetime; diff --git a/tests/qt_types_standalone/rust/src/qbytearray.rs b/tests/qt_types_standalone/rust/src/qbytearray.rs new file mode 100644 index 000000000..e72ea88b5 --- /dev/null +++ b/tests/qt_types_standalone/rust/src/qbytearray.rs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Andrew Hayzen +// SPDX-FileContributor: Gerhard de Clercq +// +// 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() +}