aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorMiguel Costa <miguel.costa@qt.io>2023-05-17 17:58:22 +0200
committerMiguel Costa <miguel.costa@qt.io>2023-06-12 11:03:49 +0000
commita1c6a7c1d48120ee477817257fee38f8adbfadb8 (patch)
treee3f456f2f6da80e0028a2208622b42fdf9a5ae62 /tests
parent12f60e442ec2b333fd880382bc0b6c3fbab79388 (diff)
Add test projects
tst_qtdotnet: * Auto tests targeting the full Qt/.NET stack. * Tests are implemented with Qt Test. Test_Qt.DotNet.Adapter: * Auto tests targeting the Qt/.NET Adapter module. * Uses the .NET unit-test SDK. Perf_Qt.DotNet.Adapter: * Performance tests targeting the Qt/.NET Adapter module. * Can be tested with the VS performance profiler. FooLib: * "Dummy" .NET library, used for testing purposes. Change-Id: Iefcf5ce8e2479e4dd7bfcf3298792989b38cce1f Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/FooLib/FooClass.cs87
-rw-r--r--tests/FooLib/FooLib.csproj16
-rw-r--r--tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs10
-rw-r--r--tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj20
-rw-r--r--tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs21
-rw-r--r--tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj25
-rw-r--r--tests/tst_qtdotnet/foo.cpp70
-rw-r--r--tests/tst_qtdotnet/foo.h56
-rw-r--r--tests/tst_qtdotnet/stringbuilder.cpp43
-rw-r--r--tests/tst_qtdotnet/stringbuilder.h34
-rw-r--r--tests/tst_qtdotnet/tst_qtdotnet.cpp508
-rw-r--r--tests/tst_qtdotnet/tst_qtdotnet.vcxproj225
-rw-r--r--tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters52
-rw-r--r--tests/tst_qtdotnet/uri.cpp244
-rw-r--r--tests/tst_qtdotnet/uri.h89
15 files changed, 1500 insertions, 0 deletions
diff --git a/tests/FooLib/FooClass.cs b/tests/FooLib/FooClass.cs
new file mode 100644
index 0000000..5182192
--- /dev/null
+++ b/tests/FooLib/FooClass.cs
@@ -0,0 +1,87 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace FooLib
+{
+ public interface IBarTransformation
+ {
+ string Transform(string bar);
+ }
+
+ public class BarIdentity : IBarTransformation
+ {
+ public string Transform(string bar) => bar;
+ }
+
+ public class Foo : INotifyPropertyChanged
+ {
+ public Foo(IBarTransformation barTransformation)
+ {
+ BarTransformation = barTransformation ?? new BarIdentity();
+ }
+
+ public Foo() : this(null)
+ { }
+
+ private IBarTransformation BarTransformation { get; }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ private string bar;
+ public string Bar
+ {
+ get => bar;
+ set
+ {
+ bar = BarTransformation?.Transform(value) ?? value;
+ NotifyPropertyChanged();
+ }
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ [return: MarshalAs(UnmanagedType.LPWStr)]
+ public delegate string FormatNumberDelegate(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string format, int number);
+
+ public static string FormatNumber(string format, int number)
+ {
+ return string.Format(format, number);
+ }
+
+ public static int EntryPoint(IntPtr arg, int argLength)
+ {
+ return Convert.ToInt32(Marshal.PtrToStringUni(arg, argLength));
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public class Date
+ {
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string Year = "";
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string Month = "";
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string Day = "";
+ }
+
+ [return: MarshalAs(UnmanagedType.LPWStr)]
+ public delegate string FormatDateDelegate(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string format, [In] Date date);
+
+ public static string FormatDate(string format, Date date)
+ {
+ return string.Format(format, date.Year, date.Month, date.Day);
+ }
+ }
+}
diff --git a/tests/FooLib/FooLib.csproj b/tests/FooLib/FooLib.csproj
new file mode 100644
index 0000000..2b8cb94
--- /dev/null
+++ b/tests/FooLib/FooLib.csproj
@@ -0,0 +1,16 @@
+<!--
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+-->
+
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>disable</Nullable>
+ </PropertyGroup>
+
+</Project>
diff --git a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs
new file mode 100644
index 0000000..67e2a88
--- /dev/null
+++ b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.cs
@@ -0,0 +1,10 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using Qt.DotNet;
+
+#if DEBUG || TESTS
+Adapter.Test();
+#endif
diff --git a/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj
new file mode 100644
index 0000000..f49a9b8
--- /dev/null
+++ b/tests/Perf_Qt.DotNet.Adapter/Perf_Qt.DotNet.Adapter.csproj
@@ -0,0 +1,20 @@
+<!--
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+-->
+
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <Configurations>Debug;Release;Tests</Configurations>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" />
+ <ProjectReference Include="..\FooLib\FooLib.csproj" />
+ </ItemGroup>
+</Project>
diff --git a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs
new file mode 100644
index 0000000..dad1c86
--- /dev/null
+++ b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.cs
@@ -0,0 +1,21 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Qt.DotNet.Test
+{
+ [TestClass]
+ public class Test_QDotNetAdapter
+ {
+ [TestMethod]
+ public void BuiltInTest()
+ {
+#if DEBUG || TESTS
+ Assert.IsTrue(Adapter.Test());
+#endif
+ }
+ }
+}
diff --git a/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj
new file mode 100644
index 0000000..37742b4
--- /dev/null
+++ b/tests/Test_Qt.DotNet.Adapter/Test_Qt.DotNet.Adapter.csproj
@@ -0,0 +1,25 @@
+<!--
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+-->
+
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>disable</Nullable>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+ <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
+ <PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
+ <PackageReference Include="coverlet.collector" Version="3.1.2" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj" />
+ <ProjectReference Include="..\FooLib\FooLib.csproj" />
+ </ItemGroup>
+</Project>
diff --git a/tests/tst_qtdotnet/foo.cpp b/tests/tst_qtdotnet/foo.cpp
new file mode 100644
index 0000000..8c8c3d3
--- /dev/null
+++ b/tests/tst_qtdotnet/foo.cpp
@@ -0,0 +1,70 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#include "foo.h"
+
+#include <qdotnetevent.h>
+
+struct FooPrivate final : QDotNetObject::IEventHandler
+{
+ Foo *q;
+ FooPrivate(Foo *q) : q(q) {}
+
+ QDotNetFunction<Foo, IBarTransformation> ctor = nullptr;
+
+ QDotNetFunction<QString> bar;
+ QDotNetFunction<void, QString> setBar;
+
+ void handleEvent(const QString &eventName, QDotNetObject &sender, QDotNetObject &args) override
+ {
+ if (eventName != "PropertyChanged")
+ return;
+
+ if (args.type().fullName() != QDotNetPropertyEvent::FullyQualifiedTypeName)
+ return;
+
+ const auto propertyChangedEvent = args.cast<QDotNetPropertyEvent>();
+ if (propertyChangedEvent.propertyName() == "Bar")
+ emit q->barChanged();
+ }
+};
+
+Q_DOTNET_OBJECT_IMPL(Foo, Q_DOTNET_OBJECT_INIT(d(new FooPrivate(this))));
+
+Foo::Foo() : d(new FooPrivate(this))
+{
+ const auto ctor = constructor<Foo, Null<IBarTransformation>>();
+ *this = ctor(nullptr);
+ subscribeEvent("PropertyChanged", d);
+}
+
+Foo::Foo(const IBarTransformation &transformation) : d(new FooPrivate(this))
+{
+ *this = constructor(d->ctor).invoke(*this, transformation);
+ subscribeEvent("PropertyChanged", d);
+}
+
+Foo::~Foo()
+{
+ delete d;
+}
+
+QString Foo::bar() const
+{
+ return method("get_Bar", d->bar).invoke(*this);
+}
+
+void Foo::setBar(const QString &value)
+{
+ method("set_Bar", d->setBar).invoke(*this, value);
+}
+
+IBarTransformation::IBarTransformation() : QDotNetInterface(FullyQualifiedTypeName)
+{
+ setCallback<QString, QString>("Transform", { QDotNetParameter::String, UnmanagedType::LPWStr },
+ [this](const QString &bar) {
+ return transform(bar);
+ });
+}
diff --git a/tests/tst_qtdotnet/foo.h b/tests/tst_qtdotnet/foo.h
new file mode 100644
index 0000000..1c8f9fc
--- /dev/null
+++ b/tests/tst_qtdotnet/foo.h
@@ -0,0 +1,56 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#pragma once
+
+#include <qdotnetinterface.h>
+#include <qdotnetobject.h>
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <QObject>
+#include <QString>
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+struct FooPrivate;
+
+class IBarTransformation : public QDotNetInterface
+{
+public:
+ static inline const QString &FullyQualifiedTypeName =
+ QStringLiteral("FooLib.IBarTransformation, FooLib");
+
+ virtual QString transform(const QString &) = 0;
+
+protected:
+ IBarTransformation();
+ ~IBarTransformation() override = default;
+};
+
+class Foo final : public QObject, public QDotNetObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString bar READ bar WRITE setBar NOTIFY barChanged)
+
+public:
+ Q_DOTNET_OBJECT(Foo, "FooLib.Foo, FooLib");
+
+ Foo();
+ Foo(const IBarTransformation &transformation);
+ ~Foo() override;
+
+ [[nodiscard]] QString bar() const;
+ void setBar(const QString &value);
+
+signals:
+ void barChanged();
+
+private:
+ FooPrivate *d;
+};
diff --git a/tests/tst_qtdotnet/stringbuilder.cpp b/tests/tst_qtdotnet/stringbuilder.cpp
new file mode 100644
index 0000000..657bdd6
--- /dev/null
+++ b/tests/tst_qtdotnet/stringbuilder.cpp
@@ -0,0 +1,43 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#include <qdotnetsafemethod.h>
+
+#include "stringbuilder.h"
+
+struct StringBuilderPrivate final
+{
+ StringBuilderPrivate() = default;
+ StringBuilderPrivate(StringBuilder *q) : q(q)
+ {}
+ StringBuilder *q = nullptr;
+ QDotNetSafeMethod<StringBuilder, QString> append;
+};
+
+Q_DOTNET_OBJECT_IMPL(StringBuilder, Q_DOTNET_OBJECT_INIT(d(new StringBuilderPrivate(this))));
+
+StringBuilder::StringBuilder() :
+ d(new StringBuilderPrivate(this))
+{
+ const QDotNetFunction<StringBuilder> ctor = constructor<StringBuilder>();
+ *this = ctor();
+}
+
+StringBuilder::StringBuilder(qint32 capacity, qint32 maxCapacity) :
+ d(new StringBuilderPrivate(this))
+{
+ const auto ctor = constructor<StringBuilder, qint32, qint32>();
+ *this = ctor(capacity, maxCapacity);
+}
+
+StringBuilder::~StringBuilder()
+{
+ delete d;
+}
+
+StringBuilder StringBuilder::append(const QString &str)
+{
+ return method("Append", d->append).invoke(*this, str);
+}
diff --git a/tests/tst_qtdotnet/stringbuilder.h b/tests/tst_qtdotnet/stringbuilder.h
new file mode 100644
index 0000000..a475fe2
--- /dev/null
+++ b/tests/tst_qtdotnet/stringbuilder.h
@@ -0,0 +1,34 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#pragma once
+
+#include <qdotnetobject.h>
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <QString>
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+struct StringBuilderPrivate;
+
+class StringBuilder final : public QDotNetObject
+{
+public:
+ Q_DOTNET_OBJECT(StringBuilder, "System.Text.StringBuilder");
+
+ StringBuilder();
+ StringBuilder(qint32 capacity, qint32 maxCapacity);
+ ~StringBuilder() override;
+
+ StringBuilder append(const QString &str);
+
+private:
+ StringBuilderPrivate *d;
+};
diff --git a/tests/tst_qtdotnet/tst_qtdotnet.cpp b/tests/tst_qtdotnet/tst_qtdotnet.cpp
new file mode 100644
index 0000000..92dbe85
--- /dev/null
+++ b/tests/tst_qtdotnet/tst_qtdotnet.cpp
@@ -0,0 +1,508 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#include "foo.h"
+#include "stringbuilder.h"
+#include "uri.h"
+
+#include <qdotnetadapter.h>
+#include <qdotnetarray.h>
+#include <qdotnetcallback.h>
+#include <qdotnethost.h>
+#include <qdotnetmarshal.h>
+#include <qdotnetobject.h>
+#include <qdotnetsafemethod.h>
+#include <qdotnettype.h>
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wconversion"
+#endif
+#include <QChar>
+#include <QDebug>
+#include <QDir>
+#include <QElapsedTimer>
+#include <QList>
+#include <QMap>
+#include <QObject>
+#include <QSignalSpy>
+#include <QString>
+
+#include <QtTest>
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+class tst_qtdotnet : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_qtdotnet() = default;
+
+private slots:
+ void loadHost();
+ void runtimeProperties();
+ void resolveFunction();
+ void callFunction();
+ void callFunctionWithCustomMarshaling();
+ void callDefaultEntryPoint();
+ void callWithComplexArg();
+ void adapterInit();
+ void callStaticMethod();
+ void handleException();
+ void createObject();
+ void callInstanceMethod();
+ void useWrapperClass();
+ void emitSignalFromEvent();
+ void propertyBinding();
+ void implementInterface();
+ void arrayOfInts();
+ void arrayOfStrings();
+ void arrayOfObjects();
+ void unloadHost();
+};
+
+QDotNetHost dotNetHost;
+
+void tst_qtdotnet::loadHost()
+{
+ QVERIFY(!dotNetHost.isLoaded());
+ QVERIFY(dotNetHost.load());
+ QVERIFY(dotNetHost.isLoaded());
+}
+
+void tst_qtdotnet::runtimeProperties()
+{
+ QVERIFY(dotNetHost.isLoaded());
+ QMap<QString, QString> runtimeProperties = dotNetHost.runtimeProperties();
+ QVERIFY(!runtimeProperties.isEmpty());
+ for (auto prop = runtimeProperties.constBegin(); prop != runtimeProperties.constEnd(); ++prop) {
+ qInfo() << prop.key() << "=" << QString("%1%2")
+ .arg(prop.value().left(100)).arg(prop.value().length() > 100 ? "..." : "");
+ }
+}
+
+QDotNetFunction<QString, QString, int> formatNumber;
+
+void tst_qtdotnet::resolveFunction()
+{
+ QVERIFY(dotNetHost.isLoaded());
+ QVERIFY(!formatNumber.isValid());
+ QVERIFY(dotNetHost.resolveFunction(formatNumber,
+ QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"),
+ Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib"));
+
+ QVERIFY(formatNumber.isValid());
+}
+
+void tst_qtdotnet::callFunction()
+{
+ QVERIFY(dotNetHost.isLoaded());
+ QVERIFY(formatNumber.isValid());
+
+ const QString formattedText = formatNumber("[{0}]", 42);
+
+ QCOMPARE(formattedText, "[42]");
+}
+
+struct DoubleAsInt {};
+
+template<>
+struct QDotNetOutbound<DoubleAsInt>
+{
+ using SourceType = double;
+ using OutboundType = int;
+ static OutboundType convert(SourceType arg)
+ {
+ return qRound(arg);
+ }
+};
+
+struct QUpperCaseString
+{};
+
+template<>
+struct QDotNetNull<QUpperCaseString>
+{
+ static QString value() { return {}; }
+ static bool isNull(const QString& s) { return s.isNull() || s.isEmpty(); }
+};
+
+template<>
+struct QDotNetInbound<QUpperCaseString>
+{
+ using InboundType = QChar*;
+ using TargetType = QString;
+ static TargetType convert(InboundType inboundValue)
+ {
+ return QString(inboundValue).toUpper();
+ }
+};
+
+void tst_qtdotnet::callFunctionWithCustomMarshaling()
+{
+ QVERIFY(dotNetHost.isLoaded());
+
+ QDotNetFunction<QUpperCaseString, QString, DoubleAsInt> formatDouble;
+ QVERIFY(dotNetHost.resolveFunction(formatDouble,
+ QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"),
+ Foo::FullyQualifiedTypeName, "FormatNumber", "FooLib.Foo+FormatNumberDelegate, FooLib"));
+
+ QVERIFY(formatDouble.isValid());
+
+ const QString formattedText = formatDouble("result = [{0}]", 41.5);
+
+ QCOMPARE(formattedText, "RESULT = [42]");
+}
+
+void tst_qtdotnet::callDefaultEntryPoint()
+{
+ QVERIFY(dotNetHost.isLoaded());
+
+ QDotNetFunction<quint32, void*, qint32> entryPoint;
+ QVERIFY(dotNetHost.resolveFunction(entryPoint,
+ QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"),
+ Foo::FullyQualifiedTypeName, "EntryPoint"));
+
+ QVERIFY(entryPoint.isValid());
+
+ QString fortyTwo("42");
+ const qint32 returnValue = entryPoint(fortyTwo.data(), static_cast<qint32>(fortyTwo.length()));
+
+ QCOMPARE(returnValue, 42);
+}
+
+struct Date
+{
+ QString year;
+ QString month;
+ QString day;
+};
+
+struct DateOutbound
+{
+ const QChar* year;
+ const QChar* month;
+ const QChar* day;
+};
+
+template<>
+struct QDotNetOutbound<Date>
+{
+ using SourceType = const Date&;
+ using OutboundType = const DateOutbound;
+ static DateOutbound convert(SourceType arg)
+ {
+ return { arg.year.data(), arg.month.data(), arg.day.data() };
+ }
+};
+
+void tst_qtdotnet::callWithComplexArg()
+{
+ QVERIFY(dotNetHost.isLoaded());
+ QDotNetFunction<QString, QString, Date> formatDate;
+ QVERIFY(dotNetHost.resolveFunction(formatDate,
+ QDir(QCoreApplication::applicationDirPath()).filePath("FooLib.dll"),
+ Foo::FullyQualifiedTypeName, "FormatDate", "FooLib.Foo+FormatDateDelegate, FooLib"));
+
+ QVERIFY(formatDate.isValid());
+
+ const Date xmas{ "2022", "12", "25" };
+ const QString formattedText = formatDate("Today is {0}-{1}-{2}", xmas);
+
+ QCOMPARE(formattedText, "Today is 2022-12-25");
+}
+
+void tst_qtdotnet::adapterInit()
+{
+ QVERIFY(!QDotNetAdapter::instance().isValid());
+ QDotNetAdapter::instance().init(
+ QDir(QCoreApplication::applicationDirPath()).filePath("Qt.DotNet.Adapter.dll"),
+ "Qt.DotNet.Adapter", "Qt.DotNet.Adapter", &dotNetHost);
+ QVERIFY(QDotNetAdapter::instance().isValid());
+}
+
+void tst_qtdotnet::callStaticMethod()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ const QDotNetType environment = QDotNetType::find("System.Environment");
+ const auto getEnvironmentVariable
+ = environment.staticMethod<QString, QString>("GetEnvironmentVariable");
+ const QString path = getEnvironmentVariable("PATH");
+ QVERIFY(path.length() > 0);
+ const QString samePath = QtDotNet::call<QString, QString>(
+ "System.Environment", "GetEnvironmentVariable", "PATH");
+ QVERIFY(path == samePath);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::createObject()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder");
+ QDotNetObject stringBuilder = newStringBuilder();
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 1);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::callInstanceMethod()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ const auto newStringBuilder = QDotNetObject::constructor("System.Text.StringBuilder");
+ const auto stringBuilder = newStringBuilder();
+ const auto append = stringBuilder.method<QDotNetObject, QString>("Append");
+ std::ignore = append("Hello");
+ std::ignore = append(" World!");
+ const QString helloWorld = stringBuilder.toString();
+ QVERIFY(helloWorld == "Hello World!");
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::useWrapperClass()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ StringBuilder sb;
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 1);
+ QVERIFY(sb.isValid());
+ sb.append("Hello").append(" ");
+ StringBuilder sbCpy(sb);
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 2);
+ QVERIFY(sbCpy.isValid());
+ sbCpy.append("World");
+ sb = StringBuilder(std::move(sbCpy));
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 1);
+ sb.append("!");
+ QCOMPARE(sb.toString(), "Hello World!");
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ const Uri uri(QStringLiteral(
+ "https://user:password@www.contoso.com:80/Home/Index.htm?q1=v1&q2=v2#FragmentName"));
+ QVERIFY(uri.segments().length() == 3);
+ QVERIFY(uri.segments()[0]->compare("/") == 0);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::handleException()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ StringBuilder stringBuilder(5, 5);
+ QString helloWorld;
+ try {
+ stringBuilder.append("Hello");
+ QVERIFY(stringBuilder.toString() == "Hello");
+ stringBuilder.append(" World!");
+ helloWorld = stringBuilder.toString();
+ }
+ catch (QDotNetException&) {
+ helloWorld = "<ERROR>";
+ }
+ QVERIFY(helloWorld == "<ERROR>");
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+class Ping final : public QObject, public QDotNetObject, public QDotNetObject::IEventHandler
+{
+ Q_OBJECT
+
+public:
+ Q_DOTNET_OBJECT_INLINE(Ping, "System.Net.NetworkInformation.Ping, System", );
+
+ Ping()
+ : QDotNetObject(QDotNetSafeMethod(constructor<Ping>()).invoke(nullptr))
+ {
+ subscribeEvent("PingCompleted", this);
+ }
+ ~Ping() override = default;
+
+ void sendAsync(const QString& hostNameOrAddress)
+ {
+ method("SendAsync", safeSendAsync).invoke(*this, hostNameOrAddress, nullptr);
+ }
+
+ void sendAsyncCancel()
+ {
+ method("SendAsyncCancel", safeSendAsyncCancel).invoke(*this);
+ }
+
+signals:
+ void pingCompleted(const QString& address, qint64 roundtripMsecs);
+ void pingError();
+
+private:
+ void handleEvent(const QString& evName, QDotNetObject& evSrc, QDotNetObject& evArgs) override
+ {
+ if (evName != "PingCompleted")
+ return;
+ if (evArgs.type().fullName() != "System.Net.NetworkInformation.PingCompletedEventArgs")
+ return;
+ const auto getReply = evArgs.method<QDotNetObject>("get_Reply");
+ const auto reply = getReply();
+ if (reply.isValid()) {
+ const auto replyAddress = reply.method<QDotNetObject>("get_Address");
+ const auto replyRoundtrip = reply.method<qint64>("get_RoundtripTime");
+ emit pingCompleted(replyAddress().toString(), replyRoundtrip());
+ }
+ else {
+ emit pingError();
+ }
+ }
+ QDotNetSafeMethod<void, QString, QtDotNet::Null> safeSendAsync;
+ QDotNetSafeMethod<void> safeSendAsyncCancel;
+};
+
+void tst_qtdotnet::emitSignalFromEvent()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ Ping ping;
+ bool waiting = true;
+ int signalCount = 0;
+ connect(&ping, &Ping::pingCompleted,
+ [&waiting, &signalCount](const QString& address, qint64 roundtripMsecs) {
+ qInfo() << "Reply from" << address << "in" << roundtripMsecs << "msecs";
+ signalCount++;
+ waiting = false;
+ });
+ connect(&ping, &Ping::pingError,
+ [&waiting, &signalCount] {
+ qInfo() << "Ping error";
+ signalCount++;
+ waiting = false;
+ });
+ qInfo() << "Pinging www.qt.io:";
+ QElapsedTimer waitTime;
+ for (int i = 0; i < 4; ++i) {
+ waitTime.restart();
+ waiting = true;
+ ping.sendAsync("www.qt.io");
+ while (waiting) {
+ QCoreApplication::processEvents();
+ if (waitTime.elapsed() > 3000) {
+ ping.sendAsyncCancel();
+ waiting = false;
+ qInfo() << "Ping timeout";
+ }
+ }
+ }
+ QVERIFY(signalCount == 4);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::propertyBinding()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ Foo foo;
+ const QSignalSpy spy(&foo, &Foo::barChanged);
+ for (int i = 0; i < 1000; ++i)
+ foo.setBar(QString("hello x %1").arg(i + 1));
+ QVERIFY(foo.bar() == "hello x 1000");
+ QVERIFY(spy.count() == 1000);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+struct ToUpper : IBarTransformation
+{
+ QString transform(const QString& bar) override
+ {
+ return bar.toUpper();
+ }
+};
+
+void tst_qtdotnet::implementInterface()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ const ToUpper transfToUpper;
+ Foo foo(transfToUpper);
+ const QSignalSpy spy(&foo, &Foo::barChanged);
+ for (int i = 0; i < 1000; ++i)
+ foo.setBar(QString("hello x %1").arg(i + 1));
+ QVERIFY(foo.bar() == "HELLO X 1000");
+ QVERIFY(spy.count() == 1000);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::arrayOfInts()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ QDotNetArray<qint32> a(11);
+ a[0] = 0;
+ a[1] = 1;
+ for (int i = 2; i < a.length(); ++i)
+ a[i] = a[i - 1] + a[i - 2];
+ QVERIFY(a[10] == 55);
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::arrayOfStrings()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ QDotNetArray<QString> a(8);
+ a[0] = "Lorem";
+ a[1] = "ipsum";
+ a[2] = "dolor";
+ a[3] = "sit";
+ a[4] = "amet,";
+ a[5] = "consectetur";
+ a[6] = "adipiscing";
+ a[7] = "elit.";
+ const auto stringType = QDotNetType::find("System.String");
+ const auto join = stringType.staticMethod<QString, QString, QDotNetArray<QString>>("Join");
+ const auto loremIpsum = join(" ", a);
+ QVERIFY(loremIpsum == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::arrayOfObjects()
+{
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+ {
+ QDotNetArray<StringBuilder> a(8);
+ for (int i = 0; i < a.length(); ++i)
+ a[i] = StringBuilder();
+ a[0]->append("Lorem");
+ a[1]->append(a[0]->toString()).append(" ipsum");
+ a[2]->append(a[1]->toString()).append(" dolor");
+ a[3]->append(a[2]->toString()).append(" sit");
+ a[4]->append(a[3]->toString()).append(" amet,");
+ a[5]->append(a[4]->toString()).append(" consectetur");
+ a[6]->append(a[5]->toString()).append(" adipiscing");
+ a[7]->append(a[6]->toString()).append(" elit.");
+ QVERIFY(a[7]->toString() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
+ }
+ QVERIFY(QDotNetAdapter::instance().stats().refCount == 0);
+}
+
+void tst_qtdotnet::unloadHost()
+{
+ QVERIFY(dotNetHost.isLoaded());
+
+ dotNetHost.unload();
+
+ QVERIFY(!dotNetHost.isLoaded());
+}
+
+QTEST_MAIN(tst_qtdotnet)
+#include "tst_qtdotnet.moc"
diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj
new file mode 100644
index 0000000..1d0efa1
--- /dev/null
+++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+-->
+<Project DefaultTargets="Build" ToolsVersion="17.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{4E317E0F-0565-4B8A-84F3-56ADF18C65AD}</ProjectGuid>
+ <Keyword>QtVS_v304</Keyword>
+ <RootNamespace>tst_qtdotnet</RootNamespace>
+ <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='15.0'">10.0.17763.0</WindowsTargetPlatformVersion>
+ <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='16.0'">10.0</WindowsTargetPlatformVersion>
+ <WindowsTargetPlatformVersion Condition="'$(VisualStudioVersion)'=='17.0'">10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup>
+ <PlatformToolset Condition="'$(VisualStudioVersion)'=='15.0'">v141</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)'=='16.0'">v142</PlatformToolset>
+ <PlatformToolset Condition="'$(VisualStudioVersion)'=='17.0'">v143</PlatformToolset>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
+ <Import Project="$(QtMsBuild)\qt_defaults.props" />
+ </ImportGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
+ <QtInstall>$(DefaultQtVersion)</QtInstall>
+ <QtModules>core;testlib</QtModules>
+ <QtBuildConfig>debug</QtBuildConfig>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="QtSettings">
+ <QtInstall>$(DefaultQtVersion)</QtInstall>
+ <QtModules>core;testlib</QtModules>
+ <QtBuildConfig>debug</QtBuildConfig>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
+ <QtInstall>$(DefaultQtVersion)</QtInstall>
+ <QtModules>core;testlib</QtModules>
+ <QtBuildConfig>release</QtBuildConfig>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="QtSettings">
+ <QtInstall>$(DefaultQtVersion)</QtInstall>
+ <QtModules>core;testlib</QtModules>
+ <QtBuildConfig>release</QtBuildConfig>
+ </PropertyGroup>
+ <Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
+ <Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
+ </Target>
+ <ImportGroup Label="ExtensionSettings" />
+ <ImportGroup Label="Shared" />
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(QtMsBuild)\Qt.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(QtMsBuild)\Qt.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(QtMsBuild)\Qt.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(QtMsBuild)\Qt.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir>
+ <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir>
+ <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir>
+ <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>bin\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>obj\$(VisualStudioVersion)\$(Platform)\$(Configuration)\</IntDir>
+ <IncludePath>$(SolutionDir)include;$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Link>
+ <Profile>true</Profile>
+ </Link>
+ <ClCompile />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Link>
+ <Profile>true</Profile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
+ <ClCompile>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ClCompile>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <Optimization>Disabled</Optimization>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
+ <ClCompile>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <Optimization>MaxSpeed</Optimization>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ClCompile>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <Optimization>MaxSpeed</Optimization>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <QtMoc Include="tst_qtdotnet.cpp">
+ <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource>
+ <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">input</DynamicSource>
+ <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName>
+ <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(Filename).moc</QtMocFileName>
+ <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>
+ <DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">input</DynamicSource>
+ <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName>
+ <QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">%(Filename).moc</QtMocFileName>
+ </QtMoc>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="foo.cpp" />
+ <ClCompile Include="uri.cpp" />
+ <ClCompile Include="stringbuilder.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <QtMoc Include="foo.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="uri.h" />
+ <ClInclude Include="stringbuilder.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\src\Qt.DotNet.Adapter\Qt.DotNet.Adapter.csproj">
+ <Project>{3863807c-2f87-4e27-a9c9-8675645a8da5}</Project>
+ </ProjectReference>
+ <ProjectReference Include="..\FooLib\FooLib.csproj">
+ <Project>{45d3ddf3-135b-46ca-b3ee-3537fcfffbeb}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
+ <Import Project="$(QtMsBuild)\qt.targets" />
+ </ImportGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters
new file mode 100644
index 0000000..4c9a2f3
--- /dev/null
+++ b/tests/tst_qtdotnet/tst_qtdotnet.vcxproj.filters
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ <Filter Include="Form Files">
+ <UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
+ <Extensions>ui</Extensions>
+ </Filter>
+ <Filter Include="Translation Files">
+ <UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier>
+ <Extensions>ts</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <QtMoc Include="tst_qtdotnet.cpp">
+ <Filter>Source Files</Filter>
+ </QtMoc>
+ <QtMoc Include="foo.h">
+ <Filter>Header Files</Filter>
+ </QtMoc>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="foo.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="stringbuilder.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="uri.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="stringbuilder.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="uri.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/tests/tst_qtdotnet/uri.cpp b/tests/tst_qtdotnet/uri.cpp
new file mode 100644
index 0000000..92cbee6
--- /dev/null
+++ b/tests/tst_qtdotnet/uri.cpp
@@ -0,0 +1,244 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#include "uri.h"
+
+using namespace QDotNetTypes::System;
+
+template<>
+struct QDotNetOutbound<UriHostNameType>
+{
+ using SourceType = UriHostNameType;
+ using OutboundType = qint32;
+ static inline const QDotNetParameter Parameter = QDotNetParameter(
+ QDotNetTypeOf<UriHostNameType>::TypeName,
+ QDotNetTypeOf<UriHostNameType>::MarshalAs);
+ static OutboundType convert(SourceType srvValue)
+ {
+ return static_cast<qint32>(srvValue);
+ }
+};
+
+template<>
+struct QDotNetInbound<UriHostNameType>
+{
+ using InboundType = qint32;
+ using TargetType = UriHostNameType;
+ static inline const QDotNetParameter Parameter = QDotNetParameter(
+ QDotNetTypeOf<UriHostNameType>::TypeName,
+ QDotNetTypeOf<UriHostNameType>::MarshalAs);
+ static TargetType convert(InboundType inboundValue)
+ {
+ return static_cast<UriHostNameType>(inboundValue);
+ }
+};
+
+template<>
+struct QDotNetNull<UriHostNameType>
+{
+ static UriHostNameType value() { return UriHostNameType::Unknown; }
+ static bool isNull(const UriHostNameType &) { return false; }
+};
+
+template<>
+struct QDotNetOutbound<UriKind>
+{
+ using SourceType = UriKind;
+ using OutboundType = qint32;
+ static inline const QDotNetParameter Parameter =
+ QDotNetParameter(QDotNetTypeOf<UriKind>::TypeName, QDotNetTypeOf<UriKind>::MarshalAs);
+ static OutboundType convert(SourceType srvValue)
+ {
+ return static_cast<qint32>(srvValue);
+ }
+};
+
+template<>
+struct QDotNetInbound<UriKind>
+{
+ using InboundType = qint32;
+ using TargetType = UriKind;
+ static inline const QDotNetParameter Parameter =
+ QDotNetParameter(QDotNetTypeOf<UriKind>::TypeName, QDotNetTypeOf<UriKind>::MarshalAs);
+ static TargetType convert(InboundType inboundValue)
+ {
+ return static_cast<UriKind>(inboundValue);
+ }
+};
+
+template<>
+struct QDotNetNull<UriKind>
+{
+ static UriKind value() { return UriKind::RelativeOrAbsolute; }
+ static bool isNull(const UriKind &) { return false; }
+};
+
+struct UriPrivate
+{
+ QDotNetSafeMethod<QString> absolutePath;
+ QDotNetSafeMethod<QString> absoluteUri;
+ QDotNetSafeMethod<QString> authority;
+ QDotNetSafeMethod<QString> dnsSafeHost;
+ QDotNetSafeMethod<QString> fragment;
+ QDotNetSafeMethod<QString> host;
+ QDotNetSafeMethod<UriHostNameType> hostNameType;
+ QDotNetSafeMethod<QString> idnHost;
+ QDotNetSafeMethod<bool> isAbsoluteUri;
+ QDotNetSafeMethod<bool> isDefaultPort;
+ QDotNetSafeMethod<bool> isFile;
+ QDotNetSafeMethod<bool> isLoopback;
+ QDotNetSafeMethod<bool> isUnc;
+ QDotNetSafeMethod<QString> localPath;
+ QDotNetSafeMethod<QString> originalString;
+ QDotNetSafeMethod<QString> pathAndQuery;
+ QDotNetSafeMethod<qint32> port;
+ QDotNetSafeMethod<QString> query;
+ QDotNetSafeMethod<QString> scheme;
+ QDotNetSafeMethod<QDotNetArray<QString>> segments;
+ QDotNetSafeMethod<bool> userEscaped;
+ QDotNetSafeMethod<QString> userInfo;
+};
+
+Q_DOTNET_OBJECT_IMPL(Uri,
+ Q_DOTNET_OBJECT_INIT(d(new UriPrivate)));
+
+Uri::Uri() : d(new UriPrivate)
+{}
+
+Uri::Uri(const QString &uriString, UriKind uriKind)
+ : d(new UriPrivate)
+{
+ const auto ctor = constructor<Uri, QString, UriKind>();
+ *this = ctor(uriString, uriKind);
+}
+
+Uri::Uri(const Uri &baseUri, const QString &relativeUri)
+ : d(new UriPrivate)
+{
+ const auto ctor = constructor<Uri, Uri, QString>();
+ *this = ctor(&baseUri, relativeUri);
+}
+
+Uri::Uri(const Uri &baseUri, const Uri &relativeUri)
+ : d(new UriPrivate)
+{
+ const auto ctor = constructor<Uri, Uri, Uri>();
+ *this = ctor(&baseUri, &relativeUri);
+}
+
+Uri::~Uri()
+{
+ delete d;
+}
+
+QString Uri::absolutePath() const
+{
+ return method("get_AbsolutePath", d->absolutePath).invoke(*this);
+}
+
+QString Uri::absoluteUri() const
+{
+ return method("get_AbsoluteUri", d->absoluteUri).invoke(*this);
+}
+
+QString Uri::authority() const
+{
+ return method("get_Authority", d->authority).invoke(*this);
+}
+
+QString Uri::dnsSafeHost() const
+{
+ return method("get_DnsSafeHost", d->dnsSafeHost).invoke(*this);
+}
+
+QString Uri::fragment() const
+{
+ return method("get_Fragment", d->fragment).invoke(*this);
+}
+
+QString Uri::host() const
+{
+ return method("get_Host", d->host).invoke(*this);
+}
+
+UriHostNameType Uri::hostNameType() const
+{
+ return method("get_HostNameType", d->hostNameType).invoke(*this);
+}
+
+QString Uri::idnHost() const
+{
+ return method("get_IdnHost", d->idnHost).invoke(*this);
+}
+
+bool Uri::isAbsoluteUri() const
+{
+ return method("get_IsAbsoluteUri", d->isAbsoluteUri).invoke(*this);
+}
+
+bool Uri::isDefaultPort() const
+{
+ return method("get_IsDefaultPort", d->isDefaultPort).invoke(*this);
+}
+
+bool Uri::isFile() const
+{
+ return method("get_IsFile", d->isFile).invoke(*this);
+}
+
+bool Uri::isLoopback() const
+{
+ return method("get_IsLoopback", d->isLoopback).invoke(*this);
+}
+
+bool Uri::isUnc() const
+{
+ return method("get_IsUnc", d->isUnc).invoke(*this);
+}
+
+QString Uri::localPath() const
+{
+ return method("get_LocalPath", d->localPath).invoke(*this);
+}
+
+QString Uri::originalString() const
+{
+ return method("get_OriginalString", d->originalString).invoke(*this);
+}
+
+QString Uri::pathAndQuery() const
+{
+ return method("get_PathAndQuery", d->pathAndQuery).invoke(*this);
+}
+
+qint32 Uri::port() const
+{
+ return method("get_Port", d->port).invoke(*this);
+}
+
+QString Uri::query() const
+{
+ return method("get_Query", d->query).invoke(*this);
+}
+
+QString Uri::scheme() const
+{
+ return method("get_Scheme", d->scheme).invoke(*this);
+}
+
+QDotNetArray<QString> Uri::segments() const
+{
+ return method("get_Segments", d->segments).invoke(*this);
+}
+
+bool Uri::userEscaped() const
+{
+ return method("get_UserEscaped", d->userEscaped).invoke(*this);
+}
+
+QString Uri::userInfo() const
+{
+ return method("get_UserInfo", d->userInfo).invoke(*this);
+}
diff --git a/tests/tst_qtdotnet/uri.h b/tests/tst_qtdotnet/uri.h
new file mode 100644
index 0000000..8ddd89c
--- /dev/null
+++ b/tests/tst_qtdotnet/uri.h
@@ -0,0 +1,89 @@
+/***************************************************************************************************
+ Copyright (C) 2023 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+#pragma once
+
+#include <qdotnetarray.h>
+#include <qdotnetmarshal.h>
+#include <qdotnetobject.h>
+
+struct UriPrivate;
+
+namespace QDotNetTypes
+{
+ namespace System
+ {
+ enum class UriHostNameType
+ {
+ Unknown = 0,
+ Basic = 1,
+ Dns = 2,
+ IPv4 = 3,
+ IPv6 = 4
+ };
+
+ enum class UriKind
+ {
+ RelativeOrAbsolute = 0,
+ Absolute = 1,
+ Relative = 2
+ };
+ }
+}
+
+template<>
+struct QDotNetTypeOf<QDotNetTypes::System::UriHostNameType>
+{
+ static inline const QString TypeName = QString("System.UriHostNameType, System");
+ static inline UnmanagedType MarshalAs = UnmanagedType::I4;
+};
+
+template<>
+struct QDotNetTypeOf<QDotNetTypes::System::UriKind>
+{
+ static inline const QString TypeName = QString("System.UriKind, System");
+ static inline UnmanagedType MarshalAs = UnmanagedType::I4;
+};
+
+class Uri : public QDotNetObject
+{
+public:
+ Q_DOTNET_OBJECT(Uri, "System.Uri, System");
+ Uri();
+ Uri(const char *uriString) : Uri(QString(uriString)) {}
+ Uri(
+ const QString &uriString,
+ QDotNetTypes::System::UriKind uriKind = QDotNetTypes::System::UriKind::Absolute);
+ Uri(const Uri &baseUri, const QString &relativeUri);
+ Uri(const Uri &baseUri, const Uri &relativeUri);
+ ~Uri() override;
+
+ QString absolutePath() const;
+ QString absoluteUri() const;
+ QString authority() const;
+ QString dnsSafeHost() const;
+ QString fragment() const;
+ QString host() const;
+ QDotNetTypes::System::UriHostNameType hostNameType() const;
+ QString idnHost() const;
+ bool isAbsoluteUri() const;
+ bool isDefaultPort() const;
+ bool isFile() const;
+ bool isLoopback() const;
+ bool isUnc() const;
+ QString localPath() const;
+ QString originalString() const;
+ QString pathAndQuery() const;
+ qint32 port() const;
+ QString query() const;
+ QString scheme() const;
+ QDotNetArray<QString> segments() const;
+ bool userEscaped() const;
+ QString userInfo() const;
+
+
+private:
+ UriPrivate *d;
+};