Rust Bindings: Add extension-based UNO integration with FFI architecture
Implements LibreOffice extension providing Rust language binding for UNO API using opaque pointer FFI architecture with type-safe handles. Extension Features: - User-controlled execution via "Rust UNO → Example" menu - Optional installation/removal through LibreOffice extension system - Complete Writer document manipulation example Technical Implementation: - Generated Rust wrappers for all UNO types via rustmaker - C++ extern "C" bridges with proper UNO integration - Type-safe opaque handles (XTextDocumentHandle, etc.) - Extension architecture following LibreOffice standards - Menu integration via Addons.xcu and ProtocolHandler.xcu Architecture: - Rust RAII Wrappers ↔ C++ extern "C" Bridges ↔ LibreOffice UNO - Extension packaging with rust_uno-example.oxt - Integrated build system with CustomTarget and Library makefiles Build Commands: ./configure --enable-rust-uno Extension Structure: - rust_uno/example/ - Extension source files - workdir/Extension/rust_uno-example.oxt - Packaged extension - Automatic installation during LibreOffice build Runtime Validation: Successfully demonstrates complete UNO integration: - Desktop service creation and XComponentLoader interface - Writer document loading via loadComponentFromURL - Text manipulation through XTextDocument and XSimpleText - Proper interface querying and method invocation GSoC 2025: Rust UNO Language Binding Change-Id: I5cecb8ebd05aab396f444488c0ee2a9d483a9f62 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/186425 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <stephan.bergmann@collabora.com>
This commit is contained in:
committed by
Stephan Bergmann
parent
d9f05dc007
commit
3d75577640
@@ -779,6 +779,7 @@ $(eval $(call gb_Helper_register_libraries,EXTENSIONLIBS, \
|
||||
active_native \
|
||||
passive_native \
|
||||
crashextension \
|
||||
rust_uno-example \
|
||||
))
|
||||
|
||||
ifneq ($(ENABLE_JAVA),)
|
||||
|
||||
@@ -126,6 +126,7 @@ $(eval $(call gb_Module_add_moduledirs,libreoffice,\
|
||||
reportbuilder \
|
||||
$(call gb_Helper_optional,DBCONNECTIVITY,reportdesign) \
|
||||
ridljar \
|
||||
rust_uno \
|
||||
sal \
|
||||
salhelper \
|
||||
sax \
|
||||
|
||||
@@ -19,6 +19,7 @@ $(eval $(call gb_Executable_add_defs,soffice_bin,\
|
||||
))
|
||||
|
||||
$(eval $(call gb_Executable_use_libraries,soffice_bin,\
|
||||
$(if $(ENABLE_RUST_UNO),rust_uno-cpp) \
|
||||
sal \
|
||||
sofficeapp \
|
||||
))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
generated/
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- Mode: toml; tab-width: 4; indent-tabs-mode: nil; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
[package]
|
||||
name = "rust_uno"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Rust FFI binding for LibreOffice UNO API"
|
||||
|
||||
[lib]
|
||||
name = "rust_uno"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_CustomTarget_CustomTarget,rust_uno/cargo))
|
||||
|
||||
.PHONY: $(call gb_CustomTarget_get_target,rust_uno/cargo)
|
||||
$(call gb_CustomTarget_get_target,rust_uno/cargo): \
|
||||
$(call gb_Library_get_target,rust_uno-cpp) \
|
||||
$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp
|
||||
cd $(SRCDIR)/rust_uno && cargo build $(if $(verbose),--verbose,) --release
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_CustomTarget_CustomTarget,rust_uno/rustmaker))
|
||||
|
||||
$(call gb_CustomTarget_get_target,rust_uno/rustmaker): \
|
||||
$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp
|
||||
|
||||
$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp: \
|
||||
$(call gb_Executable_get_target,rustmaker) \
|
||||
$(call gb_Executable_get_runtime_dependencies,rustmaker) \
|
||||
$(call gb_UnoApi_get_target,offapi) \
|
||||
$(call gb_UnoApi_get_target,udkapi) \
|
||||
$(gb_CustomTarget_workdir)/rust_uno/rustmaker/.dir
|
||||
rm -fr $(SRCDIR)/rust_uno/src/generated
|
||||
mkdir $(SRCDIR)/rust_uno/src/generated
|
||||
rm -fr $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp
|
||||
mkdir $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp
|
||||
$(call gb_Helper_abbreviate_dirs, \
|
||||
$(call gb_Helper_execute,rustmaker $(if $(verbose),--verbose,) -Ocpp $(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp \
|
||||
$(if $(verbose),--verbose,) -Orust $(SRCDIR)/rust_uno/src/generated \
|
||||
$(call gb_UnoApi_get_target,offapi) $(call gb_UnoApi_get_target,udkapi)))
|
||||
touch $@
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,26 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_Extension_Extension,rust_uno-example,rust_uno/example,nodeliver))
|
||||
|
||||
$(eval $(call gb_Extension_add_file,rust_uno-example,platform.components,$(call gb_Rdb_get_target,rust_uno-example)))
|
||||
|
||||
$(eval $(call gb_Extension_add_files,rust_uno-example,, \
|
||||
$(SRCDIR)/rust_uno/example/Addons.xcu \
|
||||
$(SRCDIR)/rust_uno/example/ProtocolHandler.xcu \
|
||||
$(SRCDIR)/rust_uno/target/release/librust_uno.so \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Extension_add_libraries,rust_uno-example, \
|
||||
rust_uno-example \
|
||||
))
|
||||
|
||||
$(SRCDIR)/rust_uno/target/release/librust_uno.so: $(call gb_CustomTarget_get_target,rust_uno/cargo)
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_Library_Library,rust_uno-cpp))
|
||||
|
||||
$(eval $(call gb_Library_use_libraries,rust_uno-cpp, \
|
||||
cppu \
|
||||
cppuhelper \
|
||||
sal \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_use_sdk_api,rust_uno-cpp))
|
||||
|
||||
# Add the uno_bootstrap.cxx file for bootstrap functionality
|
||||
$(eval $(call gb_Library_add_exception_objects,rust_uno-cpp, \
|
||||
rust_uno/uno_bootstrap \
|
||||
))
|
||||
|
||||
# Combined generated files instead of thousands of individual files
|
||||
rust_uno_generated_cxx = \
|
||||
rust_uno_bindings
|
||||
|
||||
define rust_uno_add_generated_cxx
|
||||
$(gb_CustomTarget_workdir)/rust_uno/rustmaker/cpp/$(1).cxx: \
|
||||
$(call gb_CustomTarget_get_target,rust_uno/rustmaker)
|
||||
|
||||
$(eval $(call gb_Library_add_generated_exception_objects,rust_uno-cpp, \
|
||||
CustomTarget/rust_uno/rustmaker/cpp/$(1) \
|
||||
))
|
||||
|
||||
endef
|
||||
|
||||
$(foreach gencxx,$(rust_uno_generated_cxx),$(eval $(call rust_uno_add_generated_cxx,$(gencxx))))
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_Library_Library,rust_uno-example))
|
||||
|
||||
$(eval $(call gb_Library_add_exception_objects,rust_uno-example, \
|
||||
rust_uno/example/example \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_set_componentfile,rust_uno-example,rust_uno/example/example,rust_uno-example))
|
||||
|
||||
$(eval $(call gb_Library_set_external_code,rust_uno-example))
|
||||
|
||||
$(eval $(call gb_Library_use_externals,rust_uno-example, \
|
||||
boost_headers \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_use_libraries,rust_uno-example, \
|
||||
cppu \
|
||||
cppuhelper \
|
||||
sal \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_use_sdk_api,rust_uno-example))
|
||||
|
||||
$(call gb_Library_get_target,rust_uno-example): $(call gb_CustomTarget_get_target,rust_uno/cargo)
|
||||
$(eval $(call gb_Library_add_libs,rust_uno-example,\
|
||||
$(SRCDIR)/rust_uno/target/release/librust_uno.so \
|
||||
))
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
|
||||
include $(module_directory)/../solenv/gbuild/partial_build.mk
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*-
|
||||
#
|
||||
# This file is part of the LibreOffice project.
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
|
||||
$(eval $(call gb_Module_Module,rust_uno))
|
||||
|
||||
ifeq ($(ENABLE_RUST_UNO),TRUE)
|
||||
$(eval $(call gb_Module_add_targets,rust_uno, \
|
||||
CustomTarget_cargo \
|
||||
CustomTarget_rustmaker \
|
||||
Extension_rust_uno-example \
|
||||
Library_rust_uno-cpp \
|
||||
Library_rust_uno-example \
|
||||
))
|
||||
endif
|
||||
|
||||
# vim: set noet sw=4 ts=4:
|
||||
@@ -0,0 +1,39 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Build script for rust_uno crate
|
||||
//!
|
||||
//! Links the generated C++ bridge library (librust_uno-cpp.so) to the Rust cdylib
|
||||
|
||||
fn main() {
|
||||
// Tell cargo to link against the rust_uno-cpp library
|
||||
// This library contains the generated C++ bridge functions
|
||||
println!("cargo:rustc-link-lib=rust_uno-cpplo");
|
||||
|
||||
// Add the LibreOffice instdir/program directory to the library search path
|
||||
// This is where librust_uno-cpplo.so is located
|
||||
if let Ok(instdir) = std::env::var("INSTDIR") {
|
||||
println!("cargo:rustc-link-search=native={}/program", instdir);
|
||||
}
|
||||
|
||||
// Also try the workdir path where the library might be during build
|
||||
if let Ok(workdir) = std::env::var("WORKDIR") {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}/LinkTarget/Library",
|
||||
workdir
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback: try relative paths from the rust_uno directory
|
||||
println!("cargo:rustc-link-search=native=../instdir/program");
|
||||
println!("cargo:rustc-link-search=native=../workdir/LinkTarget/Library");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
-->
|
||||
|
||||
<o:items xmlns:o="http://openoffice.org/2001/registry">
|
||||
<item o:path="/org.openoffice.Office.Addons">
|
||||
<node o:name="AddonUI">
|
||||
<node o:name="OfficeMenuBar">
|
||||
<node o:name="org.libreoffice.rust_uno.example"
|
||||
o:op="replace">
|
||||
<prop o:name="Title" xml:lang="en-US">
|
||||
<value>Rust UNO</value>
|
||||
</prop>
|
||||
<node o:name="Submenu">
|
||||
<node o:name="1" o:op="replace">
|
||||
<prop o:name="URL">
|
||||
<value>vnd.org.libreoffice.rust_uno.example:</value>
|
||||
</prop>
|
||||
<prop o:name="Title" xml:lang="en-US">
|
||||
<value>Example</value>
|
||||
</prop>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</item>
|
||||
</o:items>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
-->
|
||||
|
||||
<m:manifest xmlns:m="http://openoffice.org/2001/manifest">
|
||||
<m:file-entry m:media-type="application/vnd.sun.star.configuration-data"
|
||||
m:full-path="Addons.xcu"/>
|
||||
<m:file-entry m:media-type="application/vnd.sun.star.configuration-data"
|
||||
m:full-path="ProtocolHandler.xcu"/>
|
||||
<m:file-entry
|
||||
m:media-type="application/vnd.sun.star.uno-components;platform=@PLATFORM@"
|
||||
m:full-path="platform.components"/>
|
||||
</m:manifest>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
-->
|
||||
|
||||
<o:component-data xmlns:o="http://openoffice.org/2001/registry"
|
||||
o:package="org.openoffice.Office" o:name="ProtocolHandler">
|
||||
<node o:name="HandlerSet">
|
||||
<node o:name="org.libreoffice.rust_uno.example" o:op="replace">
|
||||
<prop o:name="Protocols">
|
||||
<value>vnd.org.libreoffice.rust_uno.example:*</value>
|
||||
</prop>
|
||||
</node>
|
||||
</node>
|
||||
</o:component-data>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
-->
|
||||
|
||||
<d:description xmlns:d="http://openoffice.org/extensions/description/2006">
|
||||
<d:identifier value="org.libreoffice/rust_uno/example"/>
|
||||
<d:version value="1"/>
|
||||
<d:dependencies>
|
||||
<d:OpenOffice.org-minimal-version d:name="OpenOffice.org 3.4" value="3.4"/>
|
||||
</d:dependencies>
|
||||
</d:description>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
-->
|
||||
|
||||
<component loader="com.sun.star.loader.SharedLibrary"
|
||||
xmlns="http://openoffice.org/2010/uno-components">
|
||||
<implementation name="org.libreoffice.comp.rust_uno.example">
|
||||
<service name="org.libreoffice.rust_uno.example"/>
|
||||
</implementation>
|
||||
<implementation name="org.libreoffice.comp.rust_uno.example_singleton">
|
||||
<singleton name="org.libreoffice.rust_uno.example_singleton"/>
|
||||
</implementation>
|
||||
</component>
|
||||
@@ -0,0 +1,243 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* This file incorporates work covered by the following license notice:
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed
|
||||
* with this work for additional information regarding copyright
|
||||
* ownership. The ASF licenses this file to you under the Apache
|
||||
* License, Version 2.0 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
||||
*/
|
||||
|
||||
#include <sal/config.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <com/sun/star/beans/PropertyValue.hpp>
|
||||
#include <com/sun/star/frame/DispatchDescriptor.hpp>
|
||||
#include <com/sun/star/frame/Desktop.hpp>
|
||||
#include <com/sun/star/frame/XDispatch.hpp>
|
||||
#include <com/sun/star/frame/XDispatchProvider.hpp>
|
||||
#include <com/sun/star/frame/XStatusListener.hpp>
|
||||
#include <com/sun/star/lang/XServiceInfo.hpp>
|
||||
#include <com/sun/star/uno/DeploymentException.hpp>
|
||||
#include <com/sun/star/uno/Exception.hpp>
|
||||
#include <com/sun/star/uno/Reference.hxx>
|
||||
#include <com/sun/star/uno/RuntimeException.hpp>
|
||||
#include <com/sun/star/uno/Sequence.hxx>
|
||||
#include <com/sun/star/uno/XComponentContext.hpp>
|
||||
#include <com/sun/star/uno/XInterface.hpp>
|
||||
#include <com/sun/star/util/URL.hpp>
|
||||
#include <cppuhelper/factory.hxx>
|
||||
#include <cppuhelper/implbase2.hxx>
|
||||
#include <cppuhelper/implementationentry.hxx>
|
||||
#include <cppuhelper/supportsservice.hxx>
|
||||
#include <cppuhelper/weak.hxx>
|
||||
#include <rtl/ustring.hxx>
|
||||
#include <sal/log.hxx>
|
||||
#include <sal/types.h>
|
||||
#include <uno/lbnames.h>
|
||||
|
||||
#include "rust_uno_hook.hxx"
|
||||
|
||||
namespace
|
||||
{
|
||||
class Provider
|
||||
: public cppu::WeakImplHelper2<css::lang::XServiceInfo, css::frame::XDispatchProvider>
|
||||
{
|
||||
public:
|
||||
Provider(const Provider&) = delete;
|
||||
const Provider& operator=(const Provider&) = delete;
|
||||
|
||||
static css::uno::Reference<css::uno::XInterface>
|
||||
SAL_CALL static_create(css::uno::Reference<css::uno::XComponentContext> const& xContext)
|
||||
{
|
||||
return static_cast<cppu::OWeakObject*>(new Provider(xContext));
|
||||
}
|
||||
|
||||
static rtl::OUString SAL_CALL static_getImplementationName();
|
||||
|
||||
static css::uno::Sequence<rtl::OUString> SAL_CALL static_getSupportedServiceNames();
|
||||
|
||||
private:
|
||||
explicit Provider(css::uno::Reference<css::uno::XComponentContext> const& context)
|
||||
: context_(context)
|
||||
{
|
||||
assert(context.is());
|
||||
}
|
||||
|
||||
virtual ~Provider() {}
|
||||
|
||||
virtual rtl::OUString SAL_CALL getImplementationName() override
|
||||
{
|
||||
return static_getImplementationName();
|
||||
}
|
||||
|
||||
virtual sal_Bool SAL_CALL supportsService(rtl::OUString const& ServiceName) override
|
||||
{
|
||||
return cppu::supportsService(this, ServiceName);
|
||||
}
|
||||
|
||||
virtual css::uno::Sequence<rtl::OUString> SAL_CALL getSupportedServiceNames() override
|
||||
{
|
||||
return static_getSupportedServiceNames();
|
||||
}
|
||||
|
||||
virtual css::uno::Reference<css::frame::XDispatch>
|
||||
SAL_CALL queryDispatch(css::util::URL const&, rtl::OUString const&, sal_Int32) override;
|
||||
|
||||
virtual css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> SAL_CALL
|
||||
queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests) override;
|
||||
|
||||
css::uno::Reference<css::uno::XComponentContext> context_;
|
||||
};
|
||||
|
||||
rtl::OUString Provider::static_getImplementationName()
|
||||
{
|
||||
return rtl::OUString("org.libreoffice.comp.rust_uno.example");
|
||||
}
|
||||
|
||||
css::uno::Sequence<rtl::OUString> Provider::static_getSupportedServiceNames()
|
||||
{
|
||||
rtl::OUString name("org.libreoffice.rust_uno.example");
|
||||
return css::uno::Sequence<rtl::OUString>(&name, 1);
|
||||
}
|
||||
|
||||
css::uno::Reference<css::frame::XDispatch> Provider::queryDispatch(css::util::URL const&,
|
||||
rtl::OUString const&, sal_Int32)
|
||||
{
|
||||
css::uno::Reference<css::frame::XDispatch> dispatch;
|
||||
if (!(context_->getValueByName("/singletons/org.libreoffice.rust_uno.example_singleton")
|
||||
>>= dispatch)
|
||||
|| !dispatch.is())
|
||||
{
|
||||
throw css::uno::DeploymentException("component context fails to supply singleton "
|
||||
"org.libreoffice.rust_uno.example_singleton of type "
|
||||
"com.sun.star.frame.XDispatch",
|
||||
context_);
|
||||
}
|
||||
return dispatch;
|
||||
}
|
||||
|
||||
css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>>
|
||||
Provider::queryDispatches(css::uno::Sequence<css::frame::DispatchDescriptor> const& Requests)
|
||||
{
|
||||
css::uno::Sequence<css::uno::Reference<css::frame::XDispatch>> s(Requests.getLength());
|
||||
for (sal_Int32 i = 0; i < s.getLength(); ++i)
|
||||
{
|
||||
s[i]
|
||||
= queryDispatch(Requests[i].FeatureURL, Requests[i].FrameName, Requests[i].SearchFlags);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
class Dispatch : public cppu::WeakImplHelper2<css::lang::XServiceInfo, css::frame::XDispatch>
|
||||
{
|
||||
public:
|
||||
Dispatch(const Dispatch&) = delete;
|
||||
const Dispatch& operator=(const Dispatch&) = delete;
|
||||
|
||||
static css::uno::Reference<css::uno::XInterface>
|
||||
SAL_CALL static_create(css::uno::Reference<css::uno::XComponentContext> const& xContext)
|
||||
{
|
||||
return static_cast<cppu::OWeakObject*>(new Dispatch(xContext));
|
||||
}
|
||||
|
||||
static rtl::OUString SAL_CALL static_getImplementationName();
|
||||
|
||||
static css::uno::Sequence<rtl::OUString> SAL_CALL static_getSupportedServiceNames()
|
||||
{
|
||||
return css::uno::Sequence<rtl::OUString>();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Dispatch(css::uno::Reference<css::uno::XComponentContext> const& context)
|
||||
: context_(context)
|
||||
{
|
||||
assert(context.is());
|
||||
}
|
||||
|
||||
virtual ~Dispatch() {}
|
||||
|
||||
virtual rtl::OUString SAL_CALL getImplementationName() override
|
||||
{
|
||||
return static_getImplementationName();
|
||||
}
|
||||
|
||||
virtual sal_Bool SAL_CALL supportsService(rtl::OUString const& ServiceName) override
|
||||
{
|
||||
return cppu::supportsService(this, ServiceName);
|
||||
}
|
||||
|
||||
virtual css::uno::Sequence<rtl::OUString> SAL_CALL getSupportedServiceNames() override
|
||||
{
|
||||
return static_getSupportedServiceNames();
|
||||
}
|
||||
|
||||
virtual void SAL_CALL dispatch(css::util::URL const&,
|
||||
css::uno::Sequence<css::beans::PropertyValue> const&) override;
|
||||
|
||||
virtual void SAL_CALL addStatusListener(css::uno::Reference<css::frame::XStatusListener> const&,
|
||||
css::util::URL const&) override
|
||||
{
|
||||
}
|
||||
|
||||
virtual void SAL_CALL removeStatusListener(
|
||||
css::uno::Reference<css::frame::XStatusListener> const&, css::util::URL const&) override
|
||||
{
|
||||
}
|
||||
|
||||
css::uno::Reference<css::uno::XComponentContext> context_;
|
||||
};
|
||||
|
||||
rtl::OUString Dispatch::static_getImplementationName()
|
||||
{
|
||||
return rtl::OUString("org.libreoffice.comp.rust_uno.example_singleton");
|
||||
}
|
||||
|
||||
void Dispatch::dispatch(css::util::URL const&, css::uno::Sequence<css::beans::PropertyValue> const&)
|
||||
{
|
||||
// === RUST UNO BINDING TEST ===
|
||||
// Call our Rust UNO binding to test it works within LibreOffice
|
||||
SAL_INFO("desktop.app", "Testing Rust UNO binding...");
|
||||
try
|
||||
{
|
||||
run_rust_uno_test();
|
||||
SAL_INFO("desktop.app", "Rust UNO binding test completed successfully!");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SAL_WARN("desktop.app", "Rust UNO binding test failed!");
|
||||
}
|
||||
}
|
||||
|
||||
cppu::ImplementationEntry const services[] = {
|
||||
{ &Provider::static_create, &Provider::static_getImplementationName,
|
||||
&Provider::static_getSupportedServiceNames, &cppu::createSingleComponentFactory, nullptr, 0 },
|
||||
{ &Dispatch::static_create, &Dispatch::static_getImplementationName,
|
||||
&Dispatch::static_getSupportedServiceNames, &cppu::createSingleComponentFactory, nullptr, 0 },
|
||||
{ nullptr, nullptr, nullptr, nullptr, nullptr, 0 }
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" SAL_DLLPUBLIC_EXPORT void*
|
||||
component_getFactory(char const* pImplName, void* pServiceManager, void* pRegistryKey)
|
||||
{
|
||||
return cppu::component_getFactoryHelper(pImplName, pServiceManager, pRegistryKey, services);
|
||||
}
|
||||
|
||||
extern "C" SAL_DLLPUBLIC_EXPORT void
|
||||
component_getImplementationEnvironment(char const** ppEnvTypeName, uno_Environment**)
|
||||
{
|
||||
*ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
||||
@@ -0,0 +1,17 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
/// Function from the Rust UNO binding library that we'll call from LibreOffice
|
||||
void run_rust_uno_test();
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,257 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Any Types and Functions
|
||||
//!
|
||||
//! This module provides the high-level safe Rust wrapper around LibreOffice's uno_Any.
|
||||
//! It includes automatic memory management and safe conversions to/from Rust values.
|
||||
//!
|
||||
//! ## Key Components
|
||||
//! - `Any` - Safe Rust wrapper around LibreOffice's uno_Any
|
||||
//! - Value conversion functions with automatic type handling
|
||||
//! - Memory management with automatic cleanup
|
||||
//!
|
||||
//! ## Features
|
||||
//! - Safe construction from various Rust types
|
||||
//! - Automatic memory management via RAII
|
||||
//! - Safe value extraction with type checking
|
||||
//! - Clone, equality, and display trait implementations
|
||||
//! - Direct FFI interop with raw pointer access methods
|
||||
|
||||
use crate::ffi::uno_any::*;
|
||||
// Interface operations now use generated XInterface wrapper methods
|
||||
// TODO: Replace with generated XInterface::from_ptr(ptr).acquire() pattern
|
||||
|
||||
// === Safe UNO Any Wrapper ===
|
||||
|
||||
/// UNO Any wrapper - a safe Rust wrapper around LibreOffice's uno_Any
|
||||
///
|
||||
/// Any provides a safe, memory-managed interface to LibreOffice's native
|
||||
/// any type. It automatically handles reference counting and memory cleanup.
|
||||
pub struct Any {
|
||||
inner: uno_Any,
|
||||
}
|
||||
|
||||
impl Any {
|
||||
/// Create a new empty UNO Any (void type)
|
||||
///
|
||||
/// This creates an empty Any that contains no value (void type).
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let mut any = Any {
|
||||
inner: uno_Any {
|
||||
pType: std::ptr::null_mut(),
|
||||
pData: std::ptr::null_mut(),
|
||||
pReserved: std::ptr::null_mut(),
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize as void using uno_any_construct
|
||||
uno_any_construct(
|
||||
&mut any.inner,
|
||||
std::ptr::null_mut(), // pSource (null for void)
|
||||
std::ptr::null_mut(), // pTypeDescr (null for void)
|
||||
None,
|
||||
);
|
||||
any
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Any from a boolean value
|
||||
///
|
||||
/// This creates an Any containing a boolean value.
|
||||
pub fn from_bool(value: bool) -> Self {
|
||||
unsafe {
|
||||
let mut any = Any {
|
||||
inner: uno_Any {
|
||||
pType: std::ptr::null_mut(),
|
||||
pData: std::ptr::null_mut(),
|
||||
pReserved: std::ptr::null_mut(),
|
||||
},
|
||||
};
|
||||
|
||||
let bool_val = if value { 1u8 } else { 0u8 };
|
||||
|
||||
// Initialize with boolean data using uno_type_any_construct
|
||||
uno_type_any_construct(
|
||||
&mut any.inner,
|
||||
&bool_val as *const u8 as *mut std::ffi::c_void,
|
||||
get_boolean_type(),
|
||||
None,
|
||||
);
|
||||
any
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Any from a 32-bit integer
|
||||
///
|
||||
/// This creates an Any containing a 32-bit signed integer.
|
||||
pub fn from_i32(value: i32) -> Self {
|
||||
unsafe {
|
||||
let mut any = Any {
|
||||
inner: uno_Any {
|
||||
pType: std::ptr::null_mut(),
|
||||
pData: std::ptr::null_mut(),
|
||||
pReserved: std::ptr::null_mut(),
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize with integer data using uno_type_any_construct
|
||||
uno_type_any_construct(
|
||||
&mut any.inner,
|
||||
&value as *const i32 as *mut std::ffi::c_void,
|
||||
get_long_type(),
|
||||
None,
|
||||
);
|
||||
|
||||
any
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Any from raw uno_Any (takes ownership)
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - The uno_Any is valid and properly initialized
|
||||
/// - The uno_Any is not used elsewhere after this call
|
||||
/// - Proper reference counting is maintained
|
||||
pub unsafe fn from_raw(any: uno_Any) -> Self {
|
||||
Any { inner: any }
|
||||
}
|
||||
|
||||
/// Convert to raw uno_Any (releases ownership)
|
||||
///
|
||||
/// The caller becomes responsible for calling uno_any_destruct
|
||||
pub fn into_raw(self) -> uno_Any {
|
||||
unsafe {
|
||||
let inner = std::ptr::read(&self.inner);
|
||||
std::mem::forget(self); // Don't run destructor
|
||||
inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_ptr(&self) -> *const uno_Any {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Get the mutable raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_mut_ptr(&mut self) -> *mut uno_Any {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
/// Check if the Any contains a value (not void)
|
||||
pub fn has_value(&self) -> bool {
|
||||
!self.inner.pType.is_null()
|
||||
}
|
||||
|
||||
/// Clear the Any (set to void)
|
||||
pub fn clear(&mut self) {
|
||||
unsafe {
|
||||
uno_any_clear(&mut self.inner, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Any {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone implementation: Create a copy of another Any
|
||||
///
|
||||
/// This creates a new Any that contains the same value but is independently
|
||||
/// managed (proper deep copy with reference counting).
|
||||
impl Clone for Any {
|
||||
fn clone(&self) -> Self {
|
||||
let mut new_any = Any::new();
|
||||
|
||||
if self.has_value() {
|
||||
unsafe {
|
||||
// Clone by assigning from the existing any's data using type reference
|
||||
uno_type_any_assign(
|
||||
&mut new_any.inner,
|
||||
self.inner.pData,
|
||||
self.inner.pType,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
new_any
|
||||
}
|
||||
}
|
||||
|
||||
/// Comparison with other Any
|
||||
impl PartialEq for Any {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Handle cases where one or both are void
|
||||
match (self.has_value(), other.has_value()) {
|
||||
(false, false) => true, // Both void = equal
|
||||
(false, true) | (true, false) => false, // One void, one not = not equal
|
||||
(true, true) => {
|
||||
// Both have values - basic pointer comparison for now
|
||||
self.inner.pType == other.inner.pType && self.inner.pData == other.inner.pData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Any {}
|
||||
|
||||
/// From trait implementation for bool
|
||||
impl From<bool> for Any {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::from_bool(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for i32
|
||||
impl From<i32> for Any {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::from_i32(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Display trait for printing
|
||||
impl std::fmt::Display for Any {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if !self.has_value() {
|
||||
write!(f, "Any(void)")
|
||||
} else {
|
||||
write!(f, "Any(<value>)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug trait for debugging
|
||||
impl std::fmt::Debug for Any {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Any")
|
||||
.field("has_value", &self.has_value())
|
||||
.field("pType", &self.inner.pType)
|
||||
.field("pData", &self.inner.pData)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor: Automatic cleanup when Any goes out of scope
|
||||
impl Drop for Any {
|
||||
fn drop(&mut self) {
|
||||
if self.has_value() {
|
||||
unsafe {
|
||||
uno_any_destruct(&mut self.inner, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,37 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Core Module
|
||||
//!
|
||||
//! This module contains the high-level safe Rust wrappers for LibreOffice UNO types.
|
||||
//! These provide memory-safe, idiomatic Rust interfaces to the underlying C APIs.
|
||||
|
||||
pub mod any;
|
||||
pub mod oustring;
|
||||
pub mod sequence;
|
||||
pub mod r#type;
|
||||
pub mod uno_wrapper;
|
||||
|
||||
// Re-export the main types for convenience
|
||||
pub use crate::ffi::type_ffi::typelib_TypeClass;
|
||||
pub use any::Any;
|
||||
pub use oustring::OUString;
|
||||
pub use sequence::Sequence;
|
||||
pub use r#type::Type;
|
||||
// UnoInterface replaced with generated XInterface from rustmaker
|
||||
pub use uno_wrapper::{UnoError, UnoResult, defaultBootstrap_InitialComponentContext};
|
||||
|
||||
// Include unit tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod string_tests;
|
||||
mod type_tests;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,256 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO String Types and Functions
|
||||
//!
|
||||
//! This module provides the high-level safe Rust wrapper around LibreOffice's rtl_uString.
|
||||
//! It includes automatic memory management and safe conversions to/from Rust strings.
|
||||
//!
|
||||
//! ## Key Components
|
||||
//! - `OUString` - Safe Rust wrapper around LibreOffice's rtl_uString
|
||||
//! - String conversion functions with automatic encoding handling
|
||||
//! - Memory management with automatic reference counting
|
||||
//!
|
||||
//! ## Features
|
||||
//! - UTF-8, UTF-16, and ASCII string creation methods
|
||||
//! - Automatic memory management via RAII
|
||||
//! - Safe conversion to/from Rust strings
|
||||
//! - Clone, equality, and display trait implementations
|
||||
//! - Direct FFI interop with raw pointer access methods
|
||||
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::ffi::rtl_string::*;
|
||||
use crate::ffi::sal_types::*;
|
||||
|
||||
// === Safe UNO String Wrapper ===
|
||||
|
||||
/// UNO String wrapper - a safe Rust wrapper around LibreOffice's rtl_uString
|
||||
///
|
||||
/// OUString provides a safe, memory-managed interface to LibreOffice's native
|
||||
/// string type. It automatically handles reference counting and memory cleanup,
|
||||
/// and provides conversions to/from standard Rust string types.
|
||||
pub struct OUString {
|
||||
inner: NonNull<rtl_uString>,
|
||||
}
|
||||
|
||||
impl OUString {
|
||||
/// Create a new empty UNO string
|
||||
///
|
||||
/// This creates an empty OUString with zero length.
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let mut ptr: *mut rtl_uString = std::ptr::null_mut();
|
||||
rtl_uString_new(&mut ptr);
|
||||
|
||||
// Convert to NonNull, panic if allocation failed
|
||||
let inner =
|
||||
NonNull::new(ptr).expect("RTL string allocation failed - system out of memory");
|
||||
|
||||
OUString { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a UNO string from UTF-8 text
|
||||
///
|
||||
/// This uses LibreOffice's direct UTF-8 to UString conversion function,
|
||||
/// which is more efficient than converting through UTF-16. Handles all
|
||||
/// valid UTF-8 including Unicode characters and emojis.
|
||||
pub fn from_utf8(text: &str) -> Self {
|
||||
unsafe {
|
||||
let mut ptr: *mut rtl_uString = std::ptr::null_mut();
|
||||
rtl_string2UString(
|
||||
&mut ptr,
|
||||
text.as_ptr() as *const std::os::raw::c_char,
|
||||
text.len() as sal_Int32,
|
||||
RTL_TEXTENCODING_UTF8,
|
||||
OSTRING_TO_OUSTRING_CVTFLAGS,
|
||||
);
|
||||
|
||||
let inner = NonNull::new(ptr).expect("RTL UTF-8 string creation failed");
|
||||
|
||||
OUString { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a UNO string from ASCII text
|
||||
///
|
||||
/// This method uses LibreOffice's native ASCII conversion function for
|
||||
/// actual ASCII text (7-bit characters only). More efficient than UTF-8
|
||||
/// conversion when you know the text is pure ASCII.
|
||||
pub fn from_ascii(text: &str) -> Self {
|
||||
unsafe {
|
||||
let mut ptr: *mut rtl_uString = std::ptr::null_mut();
|
||||
// Ensure the string is null-terminated for the C function
|
||||
let c_string = std::ffi::CString::new(text).expect("CString creation failed");
|
||||
rtl_uString_newFromAscii(&mut ptr, c_string.as_ptr());
|
||||
|
||||
let inner = NonNull::new(ptr).expect("RTL ASCII string creation failed");
|
||||
|
||||
OUString { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create OUString from UTF-16 data
|
||||
///
|
||||
/// Creates a UNO string directly from UTF-16 data. This is the most efficient
|
||||
/// method when you already have UTF-16 data, since LibreOffice uses UTF-16
|
||||
/// internally (sal_Unicode is UTF-16 code units).
|
||||
pub fn from_utf16(data: &[sal_Unicode]) -> Self {
|
||||
unsafe {
|
||||
let mut ptr: *mut rtl_uString = std::ptr::null_mut();
|
||||
rtl_uString_newFromStr_WithLength(&mut ptr, data.as_ptr(), data.len() as sal_Int32);
|
||||
|
||||
let inner = NonNull::new(ptr).expect("RTL UTF-16 string creation failed");
|
||||
|
||||
OUString { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create OUString from raw rtl_uString pointer (takes ownership)
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - The pointer is valid and points to a properly initialized rtl_uString
|
||||
/// - The pointer is not used elsewhere after this call
|
||||
/// - The rtl_uString has the correct reference count
|
||||
pub unsafe fn from_raw(str: *mut rtl_uString) -> Self {
|
||||
let inner = NonNull::new(str).expect("Cannot create OUString from null pointer");
|
||||
|
||||
unsafe {
|
||||
rtl_uString_acquire(inner.as_ptr());
|
||||
}
|
||||
OUString { inner }
|
||||
}
|
||||
|
||||
/// Convert to raw rtl_uString pointer (releases ownership)
|
||||
///
|
||||
/// The caller becomes responsible for calling rtl_uString_release
|
||||
pub fn into_raw(self) -> *mut rtl_uString {
|
||||
let ptr = self.inner.as_ptr();
|
||||
std::mem::forget(self); // Don't run destructor
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Get the raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_ptr(&self) -> *const rtl_uString {
|
||||
self.inner.as_ptr() as *const _
|
||||
}
|
||||
|
||||
/// Get the mutable raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_mut_ptr(&mut self) -> *mut rtl_uString {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
/// Get the length of the string
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { rtl_uString_getLength(self.inner.as_ptr()) as usize }
|
||||
}
|
||||
|
||||
/// Check if the string is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OUString {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone implementation: Create a copy of another OUString
|
||||
///
|
||||
/// This creates a new OUString that shares the same content but is independently
|
||||
/// managed (proper deep copy with reference counting).
|
||||
impl Clone for OUString {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
rtl_uString_acquire(self.inner.as_ptr());
|
||||
OUString { inner: self.inner }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for &str (UTF-8)
|
||||
///
|
||||
/// Convenient conversion from string slices to OUString using UTF-8 encoding.
|
||||
impl<T: AsRef<str>> From<T> for OUString {
|
||||
fn from(text: T) -> Self {
|
||||
Self::from_utf8(text.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Comparison with other OUString
|
||||
impl PartialEq for OUString {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
// Compare lengths first (fast check)
|
||||
let self_len = rtl_uString_getLength(self.inner.as_ptr());
|
||||
let other_len = rtl_uString_getLength(other.inner.as_ptr());
|
||||
|
||||
if self_len != other_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If lengths are equal, compare UTF-16 data directly
|
||||
if self_len == 0 {
|
||||
return true; // Both empty strings
|
||||
}
|
||||
|
||||
let self_ptr = rtl_uString_getStr(self.inner.as_ptr());
|
||||
let other_ptr = rtl_uString_getStr(other.inner.as_ptr());
|
||||
|
||||
// Compare UTF-16 data byte by byte
|
||||
let self_slice = std::slice::from_raw_parts(self_ptr, self_len as usize);
|
||||
let other_slice = std::slice::from_raw_parts(other_ptr, other_len as usize);
|
||||
|
||||
self_slice == other_slice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OUString {}
|
||||
|
||||
/// Display trait for printing
|
||||
impl std::fmt::Display for OUString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = rtl_uString_getStr(self.inner.as_ptr());
|
||||
let len = rtl_uString_getLength(self.inner.as_ptr()) as usize;
|
||||
|
||||
// Convert from UTF-16 to UTF-8
|
||||
let utf16_slice = std::slice::from_raw_parts(ptr, len);
|
||||
let string = String::from_utf16_lossy(utf16_slice);
|
||||
write!(f, "{string}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug trait for debugging - shows type and content for UNO string identification
|
||||
impl std::fmt::Debug for OUString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("OUString").field(&self.to_string()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor: Automatic cleanup when OUString goes out of scope
|
||||
impl Drop for OUString {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
rtl_uString_release(self.inner.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,245 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Sequence Types and Functions
|
||||
//!
|
||||
//! This module provides the high-level safe Rust wrapper around LibreOffice's uno_Sequence.
|
||||
//! It includes automatic memory management and safe conversions to/from Rust values.
|
||||
//!
|
||||
//! ## Key Components
|
||||
//! - `Sequence` - Safe Rust wrapper around LibreOffice's uno_Sequence
|
||||
//! - Value conversion functions with automatic type handling
|
||||
//! - Memory management with automatic cleanup
|
||||
//!
|
||||
//! ## Features
|
||||
//! - Safe construction from various Rust types
|
||||
//! - Automatic memory management via RAII
|
||||
//! - Safe value extraction with type checking
|
||||
//! - Clone, equality, and display trait implementations
|
||||
//! - Direct FFI interop with raw pointer access methods
|
||||
|
||||
use crate::core::r#type::*;
|
||||
use crate::ffi::type_ffi::*;
|
||||
// Interface operations now use generated XInterface wrapper methods
|
||||
// TODO: Replace with generated XInterface::from_ptr(ptr).acquire() pattern
|
||||
use crate::ffi::uno_sequence::*;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
// === Safe UNO Sequence Wrapper ===
|
||||
|
||||
/// UNO Sequence wrapper - a safe Rust wrapper around LibreOffice's uno_Sequence
|
||||
///
|
||||
/// Sequence provides a safe, memory-managed interface to LibreOffice's native
|
||||
/// Sequence type. It automatically handles reference counting and memory cleanup.
|
||||
pub struct Sequence {
|
||||
inner: NonNull<uno_Sequence>,
|
||||
}
|
||||
|
||||
impl Sequence {
|
||||
/// Create a new empty Sequence
|
||||
///
|
||||
/// Creates an empty sequence of Any elements, similar to the C++ default constructor.
|
||||
/// The sequence will be empty (0 elements) and uses the Any type as the element type.
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(0)
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: i32) -> Self {
|
||||
if capacity < 0 {
|
||||
panic!("Sequence capacity cannot be negative: {}", capacity);
|
||||
}
|
||||
unsafe {
|
||||
let mut ptr: *mut uno_Sequence = std::ptr::null_mut();
|
||||
|
||||
// Create an Any type for the sequence elements
|
||||
let element_type = Type::new();
|
||||
|
||||
// Get the type description from the type reference
|
||||
let mut type_desc = std::ptr::null_mut();
|
||||
typelib_typedescriptionreference_getDescription(
|
||||
&mut type_desc,
|
||||
element_type.get_typelib_type(),
|
||||
);
|
||||
|
||||
// Construct empty sequence
|
||||
let success = uno_type_sequence_construct(
|
||||
&mut ptr,
|
||||
type_desc,
|
||||
std::ptr::null(),
|
||||
capacity,
|
||||
None, // TODO: Replace with generated XInterface acquire wrapper
|
||||
);
|
||||
|
||||
// Check for construction success
|
||||
if success == 0 {
|
||||
panic!("Failed to construct UNO sequence");
|
||||
}
|
||||
|
||||
Sequence {
|
||||
inner: NonNull::new(ptr)
|
||||
.expect("uno_type_sequence_construct returned null pointer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of the sequence
|
||||
///
|
||||
/// Returns the number of elements in this sequence.
|
||||
/// Equivalent to C++ `getLength()` method.
|
||||
pub fn get_length(&self) -> i32 {
|
||||
unsafe { self.inner.as_ref().nElements }
|
||||
}
|
||||
|
||||
/// Check if the sequence has elements
|
||||
///
|
||||
/// Returns true if the sequence contains at least one element.
|
||||
/// Equivalent to C++ `hasElements()` method.
|
||||
pub fn has_elements(&self) -> bool {
|
||||
self.get_length() > 0
|
||||
}
|
||||
|
||||
/// Get the length as usize for Rust iterator compatibility
|
||||
///
|
||||
/// Returns the length as usize, which is commonly used in Rust.
|
||||
pub fn len(&self) -> usize {
|
||||
self.get_length() as usize
|
||||
}
|
||||
|
||||
/// Check if the sequence is empty
|
||||
///
|
||||
/// Returns true if the sequence has no elements.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.get_length() == 0
|
||||
}
|
||||
|
||||
/// Get raw pointer to the underlying uno_Sequence
|
||||
///
|
||||
/// This provides direct access to the underlying UNO sequence for FFI interop.
|
||||
/// The returned pointer remains valid as long as this Sequence instance exists.
|
||||
pub fn as_raw(&self) -> *mut uno_Sequence {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
/// Get the raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_ptr(&self) -> *const uno_Sequence {
|
||||
self.inner.as_ptr() as *const _
|
||||
}
|
||||
|
||||
/// Get the mutable raw pointer for FFI calls (retains ownership)
|
||||
pub fn as_mut_ptr(&mut self) -> *mut uno_Sequence {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sequence {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone implementation: Create an independent copy of another Sequence
|
||||
///
|
||||
/// This creates a new Sequence that references the same sequence data but is
|
||||
/// independently managed. The clone operation increments the reference count
|
||||
/// of the underlying sequence, ensuring proper memory management.
|
||||
impl Clone for Sequence {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
let ptr = self.inner.as_ptr();
|
||||
// Increment reference count
|
||||
(*ptr).nRefCount += 1;
|
||||
|
||||
Sequence {
|
||||
inner: NonNull::new(ptr).expect("Cloning sequence with invalid pointer"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality comparison with other Sequence instances
|
||||
///
|
||||
/// Two Sequence instances are considered equal if they have the same length
|
||||
/// and contain the same elements in the same order. This uses pointer comparison
|
||||
/// for efficiency when both sequences reference the same underlying data.
|
||||
impl PartialEq for Sequence {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
unsafe {
|
||||
let self_ptr = self.inner.as_ptr();
|
||||
let other_ptr = other.inner.as_ptr();
|
||||
|
||||
// Fast path: same pointer
|
||||
if self_ptr == other_ptr {
|
||||
return true;
|
||||
}
|
||||
|
||||
let self_seq = self.inner.as_ref();
|
||||
let other_seq = other.inner.as_ref();
|
||||
|
||||
// Different lengths means not equal
|
||||
if self_seq.nElements != other_seq.nElements {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For now, do basic comparison - in a full implementation,
|
||||
// this would need to compare actual element values
|
||||
self_seq.nElements == other_seq.nElements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Sequence {}
|
||||
|
||||
/// Debug trait for debugging - shows sequence length and type for UNO sequence identification
|
||||
impl std::fmt::Debug for Sequence {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("Sequence")
|
||||
.field(&format!("length={}", self.get_length()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Display trait for printing sequences
|
||||
///
|
||||
/// Provides a user-friendly string representation of the sequence.
|
||||
impl std::fmt::Display for Sequence {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_empty() {
|
||||
write!(f, "Sequence[]")
|
||||
} else {
|
||||
write!(f, "Sequence[{}]", self.get_length())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor: Automatic cleanup when Sequence goes out of scope
|
||||
impl Drop for Sequence {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let ptr = self.inner.as_ptr();
|
||||
let seq = &mut *ptr;
|
||||
seq.nRefCount -= 1;
|
||||
|
||||
// If reference count reaches zero, destroy the sequence
|
||||
if seq.nRefCount == 0 {
|
||||
// Get the element type for proper cleanup
|
||||
let element_type = Type::new();
|
||||
let mut type_desc = std::ptr::null_mut();
|
||||
typelib_typedescriptionreference_getDescription(
|
||||
&mut type_desc,
|
||||
element_type.get_typelib_type(),
|
||||
);
|
||||
|
||||
// Call UNO sequence destructor
|
||||
uno_type_sequence_destroy(ptr, type_desc, None); // TODO: Replace with generated XInterface release wrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,387 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Unit tests for OUString (Rust-side functionality)
|
||||
//!
|
||||
//! These tests verify OUString's Rust implementation including:
|
||||
//! - Creation methods (new, from_utf8, from_ascii, from_utf16)
|
||||
//! - Trait implementations (Clone, PartialEq, From, Display, Debug, Default)
|
||||
//! - Instance methods (len, is_empty, as_ptr, etc.)
|
||||
//! - Memory management and safety (RAII, reference counting)
|
||||
//! - FFI interop (from_raw, into_raw)
|
||||
//!
|
||||
//! These tests do NOT require LibreOffice UNO components and test
|
||||
//! only the Rust-side implementation and FFI safety.
|
||||
|
||||
use crate::core::OUString;
|
||||
|
||||
// === Creation Method Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_oustring_new() {
|
||||
let s = OUString::new();
|
||||
assert_eq!(s.len(), 0);
|
||||
assert!(s.is_empty());
|
||||
assert_eq!(s.to_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_default() {
|
||||
let s = OUString::default();
|
||||
assert_eq!(s.len(), 0);
|
||||
assert!(s.is_empty());
|
||||
assert_eq!(s.to_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_from_utf8() {
|
||||
let s = OUString::from_utf8("Hello, 世界!");
|
||||
assert!(!s.is_empty());
|
||||
assert_eq!(s.to_string(), "Hello, 世界!");
|
||||
|
||||
// Test empty string
|
||||
let empty = OUString::from_utf8("");
|
||||
assert!(empty.is_empty());
|
||||
assert_eq!(empty.len(), 0);
|
||||
|
||||
// Test with Unicode characters
|
||||
let unicode = OUString::from_utf8("Ñiño 🦀 café");
|
||||
assert_eq!(unicode.to_string(), "Ñiño 🦀 café");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_from_ascii() {
|
||||
let s = OUString::from_ascii("Hello, World!");
|
||||
assert_eq!(s.to_string(), "Hello, World!");
|
||||
assert!(!s.is_empty());
|
||||
|
||||
// Test empty ASCII string
|
||||
let empty = OUString::from_ascii("");
|
||||
assert!(empty.is_empty());
|
||||
assert_eq!(empty.len(), 0);
|
||||
}
|
||||
|
||||
// === Trait Implementation Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_oustring_clone() {
|
||||
let original = OUString::from_utf8("Test string for cloning");
|
||||
let cloned = original.clone();
|
||||
|
||||
// They should be equal
|
||||
assert_eq!(original, cloned);
|
||||
assert_eq!(original.to_string(), cloned.to_string());
|
||||
assert_eq!(original.len(), cloned.len());
|
||||
|
||||
// Test cloning empty string
|
||||
let empty = OUString::new();
|
||||
let empty_clone = empty.clone();
|
||||
assert_eq!(empty, empty_clone);
|
||||
assert!(empty_clone.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_partial_eq() {
|
||||
let s1 = OUString::from_utf8("Hello");
|
||||
let s2 = OUString::from_utf8("Hello");
|
||||
let s3 = OUString::from_utf8("World");
|
||||
let empty1 = OUString::new();
|
||||
let empty2 = OUString::new();
|
||||
|
||||
// Equal strings
|
||||
assert_eq!(s1, s2);
|
||||
assert_eq!(empty1, empty2);
|
||||
|
||||
// Different strings
|
||||
assert_ne!(s1, s3);
|
||||
assert_ne!(s1, empty1);
|
||||
assert_ne!(empty1, s1);
|
||||
|
||||
// Test with Unicode
|
||||
let unicode1 = OUString::from_utf8("café");
|
||||
let unicode2 = OUString::from_utf8("café");
|
||||
let unicode3 = OUString::from_utf8("cafe"); // without accent
|
||||
|
||||
assert_eq!(unicode1, unicode2);
|
||||
assert_ne!(unicode1, unicode3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_from_str() {
|
||||
let s = OUString::from("Hello from &str");
|
||||
assert_eq!(s.to_string(), "Hello from &str");
|
||||
|
||||
// Test with Unicode
|
||||
let unicode: OUString = "Héllo wørld 🌍".into();
|
||||
assert_eq!(unicode.to_string(), "Héllo wørld 🌍");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_from_string() {
|
||||
let rust_string = String::from("Hello from String");
|
||||
let s = OUString::from(rust_string);
|
||||
assert_eq!(s.to_string(), "Hello from String");
|
||||
|
||||
// Test with owned Unicode String
|
||||
let unicode_string = "Ñiño 🦀".to_string();
|
||||
let unicode_oustring = OUString::from(unicode_string);
|
||||
assert_eq!(unicode_oustring.to_string(), "Ñiño 🦀");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_display() {
|
||||
let s = OUString::from_utf8("Display test");
|
||||
assert_eq!(format!("{s}"), "Display test");
|
||||
|
||||
// Test empty string display
|
||||
let empty = OUString::new();
|
||||
assert_eq!(format!("{empty}"), "");
|
||||
|
||||
// Test Unicode display
|
||||
let unicode = OUString::from_utf8("Display 测试 🦀");
|
||||
assert_eq!(format!("{unicode}"), "Display 测试 🦀");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_debug() {
|
||||
let s = OUString::from_utf8("Debug test");
|
||||
let debug_output = format!("{s:?}");
|
||||
assert!(debug_output.contains("OUString"));
|
||||
assert!(debug_output.contains("Debug test"));
|
||||
|
||||
// Test empty string debug
|
||||
let empty = OUString::new();
|
||||
let empty_debug = format!("{empty:?}");
|
||||
assert!(empty_debug.contains("OUString"));
|
||||
}
|
||||
|
||||
// === Method Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_oustring_len() {
|
||||
let empty = OUString::new();
|
||||
assert_eq!(empty.len(), 0);
|
||||
|
||||
let ascii = OUString::from_utf8("Hello");
|
||||
assert_eq!(ascii.len(), 5);
|
||||
|
||||
// Note: len() returns UTF-16 code units, not Unicode codepoints
|
||||
let unicode = OUString::from_utf8("café"); // 'é' is one UTF-16 unit
|
||||
assert_eq!(unicode.len(), 4);
|
||||
|
||||
// Emoji takes 2 UTF-16 code units (surrogate pair)
|
||||
let emoji = OUString::from_utf8("🦀");
|
||||
assert_eq!(emoji.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_is_empty() {
|
||||
let empty = OUString::new();
|
||||
assert!(empty.is_empty());
|
||||
|
||||
let non_empty = OUString::from_utf8("Not empty");
|
||||
assert!(!non_empty.is_empty());
|
||||
|
||||
let empty_from_str = OUString::from_utf8("");
|
||||
assert!(empty_from_str.is_empty());
|
||||
}
|
||||
|
||||
// === Special Cases and Edge Cases ===
|
||||
|
||||
#[test]
|
||||
fn test_oustring_special_characters() {
|
||||
// Test various special Unicode characters
|
||||
let special_chars = vec![
|
||||
"\0", // Null character
|
||||
"\n", // Newline
|
||||
"\r\n", // Windows line ending
|
||||
"\t", // Tab
|
||||
"\"", // Quote
|
||||
"\\", // Backslash
|
||||
"©", // Copyright symbol
|
||||
"€", // Euro symbol
|
||||
"™", // Trademark
|
||||
"🦀", // Rust crab emoji
|
||||
"👨💻", // Man technologist (complex emoji)
|
||||
];
|
||||
|
||||
for special in special_chars {
|
||||
let s = OUString::from_utf8(special);
|
||||
assert_eq!(s.to_string(), special);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_long_strings() {
|
||||
// Test with long strings to ensure proper memory management
|
||||
let long_text = "A".repeat(10000);
|
||||
let s = OUString::from_utf8(&long_text);
|
||||
assert_eq!(s.len(), 10000);
|
||||
assert_eq!(s.to_string(), long_text);
|
||||
|
||||
// Test cloning long strings
|
||||
let cloned = s.clone();
|
||||
assert_eq!(cloned, s);
|
||||
assert_eq!(cloned.len(), 10000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_multilingual() {
|
||||
// Test various languages and scripts
|
||||
let multilingual = "English, 中文, العربية, Русский, ελληνικά, עברית";
|
||||
let s = OUString::from_utf8(multilingual);
|
||||
assert_eq!(s.to_string(), multilingual);
|
||||
assert!(!s.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_consistency_across_constructors() {
|
||||
let text = "Test consistency";
|
||||
|
||||
let from_utf8 = OUString::from_utf8(text);
|
||||
let from_str: OUString = text.into();
|
||||
let from_string = OUString::from(text.to_string());
|
||||
|
||||
// All should produce equivalent strings
|
||||
assert_eq!(from_utf8, from_str);
|
||||
assert_eq!(from_utf8, from_string);
|
||||
assert_eq!(from_str, from_string);
|
||||
|
||||
// All should have same content
|
||||
assert_eq!(from_utf8.to_string(), text);
|
||||
assert_eq!(from_str.to_string(), text);
|
||||
assert_eq!(from_string.to_string(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_memory_safety() {
|
||||
// Test that dropping strings doesn't cause issues
|
||||
{
|
||||
let s1 = OUString::from_utf8("Temporary string 1");
|
||||
let s2 = s1.clone();
|
||||
let s3 = OUString::from_utf8("Temporary string 2");
|
||||
|
||||
assert_eq!(s1, s2);
|
||||
assert_ne!(s1, s3);
|
||||
|
||||
// Strings will be dropped here - this should not cause issues
|
||||
}
|
||||
|
||||
// Create new strings after the previous ones were dropped
|
||||
let s4 = OUString::from_utf8("After drop test");
|
||||
assert_eq!(s4.to_string(), "After drop test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_memory_release() {
|
||||
// Simple memory release test - create and drop many strings
|
||||
// If memory isn't released properly, this would cause memory leaks
|
||||
|
||||
// Create many strings in a loop and let them drop
|
||||
for i in 0..1000 {
|
||||
let s = OUString::from_utf8(&format!("Test string number {i}"));
|
||||
assert_eq!(s.to_string(), format!("Test string number {i}"));
|
||||
// String automatically drops here
|
||||
}
|
||||
|
||||
// Create and explicitly drop strings
|
||||
for _ in 0..1000 {
|
||||
let s1 = OUString::from_utf8("Temporary string");
|
||||
let s2 = s1.clone(); // This should increase ref count
|
||||
let s3 = s2.clone(); // This should increase ref count more
|
||||
|
||||
// All should be equal
|
||||
assert_eq!(s1, s2);
|
||||
assert_eq!(s2, s3);
|
||||
|
||||
// All will be dropped here - ref counts should decrease properly
|
||||
}
|
||||
|
||||
// If we reach here without crashes or memory issues, memory management is working
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_reference_counting_behavior() {
|
||||
// Test that cloning works properly with reference counting
|
||||
let original = OUString::from_utf8("Reference counted string");
|
||||
|
||||
// Create multiple references
|
||||
let clone1 = original.clone();
|
||||
let clone2 = original.clone();
|
||||
let clone3 = clone1.clone();
|
||||
|
||||
// All should be equal
|
||||
assert_eq!(original, clone1);
|
||||
assert_eq!(original, clone2);
|
||||
assert_eq!(original, clone3);
|
||||
assert_eq!(clone1, clone2);
|
||||
assert_eq!(clone1, clone3);
|
||||
assert_eq!(clone2, clone3);
|
||||
|
||||
// All have same content
|
||||
assert_eq!(original.to_string(), "Reference counted string");
|
||||
assert_eq!(clone1.to_string(), "Reference counted string");
|
||||
assert_eq!(clone2.to_string(), "Reference counted string");
|
||||
assert_eq!(clone3.to_string(), "Reference counted string");
|
||||
|
||||
// When this function ends, all clones should be properly released
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_memory_from_raw_into_raw() {
|
||||
// Test raw pointer ownership transfer (unique functionality)
|
||||
for i in 0..100 {
|
||||
let original = OUString::from_utf8(&format!("Raw pointer test {i}"));
|
||||
let original_content = original.to_string();
|
||||
|
||||
// Transfer ownership to raw pointer
|
||||
let raw_ptr = original.into_raw();
|
||||
|
||||
// Create new OUString from raw pointer (takes ownership back)
|
||||
let restored = unsafe { OUString::from_raw(raw_ptr) };
|
||||
|
||||
assert_eq!(restored.to_string(), original_content);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oustring_memory_mixed_operations() {
|
||||
// Test complex combinations of operations (not covered by existing tests)
|
||||
for i in 0..50 {
|
||||
// Create initial string
|
||||
let s1 = OUString::from_utf8(&format!("Mixed test {i}"));
|
||||
|
||||
// Convert through different types
|
||||
let s2 = OUString::from(s1.to_string());
|
||||
let s3: OUString = s1.to_string().as_str().into();
|
||||
|
||||
// Clone operations
|
||||
let c1 = s1.clone();
|
||||
let c2 = s2.clone();
|
||||
let c3 = s3.clone();
|
||||
|
||||
// Verify equality
|
||||
assert_eq!(s1, s2);
|
||||
assert_eq!(s2, s3);
|
||||
assert_eq!(c1, c2);
|
||||
assert_eq!(c2, c3);
|
||||
|
||||
// Raw pointer operations
|
||||
let raw1 = c1.into_raw();
|
||||
let raw2 = c2.into_raw();
|
||||
|
||||
let restored1 = unsafe { OUString::from_raw(raw1) };
|
||||
let restored2 = unsafe { OUString::from_raw(raw2) };
|
||||
|
||||
assert_eq!(restored1, restored2);
|
||||
assert_eq!(restored1.to_string(), format!("Mixed test {i}"));
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,520 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Unit tests for Type (Rust-side functionality)
|
||||
//!
|
||||
//! These tests verify Type's Rust implementation including:
|
||||
//! - Creation methods (new, new_with_name, from_typelib_ref, from_typelib_ref_no_acquire)
|
||||
//! - Trait implementations (Clone, PartialEq, From, Default)
|
||||
//! - Instance methods (get_type_class, get_type_name, equals, is_assignable_from)
|
||||
//! - Memory management and safety (RAII, reference counting)
|
||||
//! - FFI interop (get_typelib_type)
|
||||
//! - Ergonomic API (generic string parameters)
|
||||
//!
|
||||
//! These tests do NOT require LibreOffice UNO components and test
|
||||
//! only the Rust-side implementation and FFI safety.
|
||||
|
||||
use crate::core::{OUString, Type, typelib_TypeClass};
|
||||
|
||||
// === Creation Method Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_type_new() {
|
||||
let void_type = Type::new();
|
||||
assert_eq!(
|
||||
void_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_VOID
|
||||
);
|
||||
assert_eq!(void_type.get_type_name().to_string(), "void");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_default() {
|
||||
let default_type = Type::default();
|
||||
assert_eq!(
|
||||
default_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_VOID
|
||||
);
|
||||
assert_eq!(default_type.get_type_name().to_string(), "void");
|
||||
|
||||
// Test that default equals new()
|
||||
let new_type = Type::new();
|
||||
assert!(default_type.equals(&new_type));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_new_with_name_str() {
|
||||
let void_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_VOID, "void");
|
||||
assert_eq!(
|
||||
void_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_VOID
|
||||
);
|
||||
assert_eq!(void_type.get_type_name().to_string(), "void");
|
||||
|
||||
let int_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
assert_eq!(
|
||||
int_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_LONG
|
||||
);
|
||||
assert_eq!(int_type.get_type_name().to_string(), "sal_Int32");
|
||||
|
||||
let interface_type = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
);
|
||||
assert_eq!(
|
||||
interface_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
interface_type.get_type_name().to_string(),
|
||||
"com.sun.star.lang.XComponent"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_new_with_name_string() {
|
||||
let type_name = "com.sun.star.text.XText".to_string();
|
||||
let text_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name);
|
||||
assert_eq!(
|
||||
text_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
text_type.get_type_name().to_string(),
|
||||
"com.sun.star.text.XText"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_new_with_name_oustring() {
|
||||
let oustring_name = OUString::from("com.sun.star.beans.XPropertySet");
|
||||
let prop_type = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
oustring_name,
|
||||
);
|
||||
assert_eq!(
|
||||
prop_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
prop_type.get_type_name().to_string(),
|
||||
"com.sun.star.beans.XPropertySet"
|
||||
);
|
||||
}
|
||||
|
||||
// === From Trait Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_type_from_typelib_typeclass() {
|
||||
let string_type = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
assert_eq!(
|
||||
string_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_STRING
|
||||
);
|
||||
|
||||
let boolean_type = Type::from(typelib_TypeClass::typelib_TypeClass_BOOLEAN);
|
||||
assert_eq!(
|
||||
boolean_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_BOOLEAN
|
||||
);
|
||||
|
||||
let double_type = Type::from(typelib_TypeClass::typelib_TypeClass_DOUBLE);
|
||||
assert_eq!(
|
||||
double_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_DOUBLE
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_from_tuple_with_str() {
|
||||
let primitive_types = vec![
|
||||
("void", typelib_TypeClass::typelib_TypeClass_VOID),
|
||||
("boolean", typelib_TypeClass::typelib_TypeClass_BOOLEAN),
|
||||
("byte", typelib_TypeClass::typelib_TypeClass_BYTE),
|
||||
("short", typelib_TypeClass::typelib_TypeClass_SHORT),
|
||||
(
|
||||
"unsigned short",
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_SHORT,
|
||||
),
|
||||
("long", typelib_TypeClass::typelib_TypeClass_LONG),
|
||||
(
|
||||
"unsigned long",
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_LONG,
|
||||
),
|
||||
("hyper", typelib_TypeClass::typelib_TypeClass_HYPER),
|
||||
(
|
||||
"unsigned hyper",
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_HYPER,
|
||||
),
|
||||
("float", typelib_TypeClass::typelib_TypeClass_FLOAT),
|
||||
("double", typelib_TypeClass::typelib_TypeClass_DOUBLE),
|
||||
("char", typelib_TypeClass::typelib_TypeClass_CHAR),
|
||||
("string", typelib_TypeClass::typelib_TypeClass_STRING),
|
||||
("type", typelib_TypeClass::typelib_TypeClass_TYPE),
|
||||
("any", typelib_TypeClass::typelib_TypeClass_ANY),
|
||||
];
|
||||
|
||||
for (type_name, expected_class) in primitive_types {
|
||||
let type_obj = Type::from((expected_class, type_name));
|
||||
assert_eq!(
|
||||
type_obj.get_type_class(),
|
||||
expected_class,
|
||||
"Failed for type: {}",
|
||||
type_name
|
||||
);
|
||||
assert_eq!(type_obj.get_type_name().to_string(), type_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_from_tuple_with_string() {
|
||||
let type_name = "com.sun.star.awt.XControl".to_string();
|
||||
let control_type = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name));
|
||||
assert_eq!(
|
||||
control_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
control_type.get_type_name().to_string(),
|
||||
"com.sun.star.awt.XControl"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_from_tuple_with_oustring() {
|
||||
let type_name = OUString::from("com.sun.star.beans.XPropertySet");
|
||||
let prop_type = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, type_name));
|
||||
assert_eq!(
|
||||
prop_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
prop_type.get_type_name().to_string(),
|
||||
"com.sun.star.beans.XPropertySet"
|
||||
);
|
||||
}
|
||||
|
||||
// === Trait Implementation Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_type_clone() {
|
||||
let original = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
);
|
||||
let cloned = original.clone();
|
||||
|
||||
// They should be equal
|
||||
assert!(original.equals(&cloned));
|
||||
assert_eq!(original.get_type_class(), cloned.get_type_class());
|
||||
assert_eq!(
|
||||
original.get_type_name().to_string(),
|
||||
cloned.get_type_name().to_string()
|
||||
);
|
||||
|
||||
// Test cloning void type
|
||||
let void_original = Type::new();
|
||||
let void_clone = void_original.clone();
|
||||
assert!(void_original.equals(&void_clone));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_partial_eq() {
|
||||
let type1 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
let type2 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
let type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16");
|
||||
let void1 = Type::new();
|
||||
let void2 = Type::new();
|
||||
|
||||
// Equal types
|
||||
assert_eq!(type1, type2);
|
||||
assert_eq!(void1, void2);
|
||||
|
||||
// Different types
|
||||
assert_ne!(type1, type3);
|
||||
assert_ne!(type1, void1);
|
||||
|
||||
// Test with interface types
|
||||
let interface1 = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
let interface2 = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
let interface3 = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.uno.XInterface",
|
||||
));
|
||||
|
||||
assert_eq!(interface1, interface2);
|
||||
assert_ne!(interface1, interface3);
|
||||
}
|
||||
|
||||
// === Method Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_type_get_type_class() {
|
||||
let void_type = Type::new();
|
||||
assert_eq!(
|
||||
void_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_VOID
|
||||
);
|
||||
|
||||
let string_type = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
assert_eq!(
|
||||
string_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_STRING
|
||||
);
|
||||
|
||||
let interface_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
assert_eq!(
|
||||
interface_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_get_type_name() {
|
||||
let void_type = Type::new();
|
||||
assert_eq!(void_type.get_type_name().to_string(), "void");
|
||||
|
||||
let int_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
assert_eq!(int_type.get_type_name().to_string(), "sal_Int32");
|
||||
|
||||
let interface_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.text.XTextDocument",
|
||||
));
|
||||
assert_eq!(
|
||||
interface_type.get_type_name().to_string(),
|
||||
"com.sun.star.text.XTextDocument"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_equals() {
|
||||
let type1 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
let type2 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
let type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16");
|
||||
|
||||
// Test equality
|
||||
assert!(type1.equals(&type2));
|
||||
assert!(type2.equals(&type1)); // Symmetry
|
||||
|
||||
// Test inequality
|
||||
assert!(!type1.equals(&type3));
|
||||
assert!(!type3.equals(&type1)); // Symmetry
|
||||
|
||||
// Test self-equality
|
||||
assert!(type1.equals(&type1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_is_assignable_from() {
|
||||
// Create different types for testing assignment compatibility
|
||||
let long_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
let short_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_SHORT, "sal_Int16");
|
||||
let byte_type = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_BYTE, "sal_Int8");
|
||||
|
||||
// Test self-assignment (should always be true)
|
||||
assert!(long_type.is_assignable_from(&long_type));
|
||||
assert!(short_type.is_assignable_from(&short_type));
|
||||
assert!(byte_type.is_assignable_from(&byte_type));
|
||||
|
||||
// These tests depend on UNO's specific assignment rules
|
||||
// Note: The actual behavior depends on LibreOffice's typelib implementation
|
||||
// We're just testing that the method calls work correctly
|
||||
let _long_from_short = long_type.is_assignable_from(&short_type);
|
||||
let _long_from_byte = long_type.is_assignable_from(&byte_type);
|
||||
let _short_from_byte = short_type.is_assignable_from(&byte_type);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_get_typelib_type() {
|
||||
let type_obj = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_STRING, "string");
|
||||
let raw_ptr = type_obj.get_typelib_type();
|
||||
|
||||
// Should return a non-null pointer
|
||||
assert!(!raw_ptr.is_null());
|
||||
|
||||
// Test with void type
|
||||
let void_type = Type::new();
|
||||
let void_ptr = void_type.get_typelib_type();
|
||||
assert!(!void_ptr.is_null());
|
||||
}
|
||||
|
||||
// === Special Cases and Edge Cases ===
|
||||
|
||||
#[test]
|
||||
fn test_type_primitive_type_classes() {
|
||||
// Test creating types for primitive type classes that have static type references
|
||||
let primitive_type_classes = vec![
|
||||
typelib_TypeClass::typelib_TypeClass_VOID,
|
||||
typelib_TypeClass::typelib_TypeClass_CHAR,
|
||||
typelib_TypeClass::typelib_TypeClass_BOOLEAN,
|
||||
typelib_TypeClass::typelib_TypeClass_BYTE,
|
||||
typelib_TypeClass::typelib_TypeClass_SHORT,
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_SHORT,
|
||||
typelib_TypeClass::typelib_TypeClass_LONG,
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_LONG,
|
||||
typelib_TypeClass::typelib_TypeClass_HYPER,
|
||||
typelib_TypeClass::typelib_TypeClass_UNSIGNED_HYPER,
|
||||
typelib_TypeClass::typelib_TypeClass_FLOAT,
|
||||
typelib_TypeClass::typelib_TypeClass_DOUBLE,
|
||||
typelib_TypeClass::typelib_TypeClass_STRING,
|
||||
typelib_TypeClass::typelib_TypeClass_TYPE,
|
||||
typelib_TypeClass::typelib_TypeClass_ANY,
|
||||
];
|
||||
|
||||
for type_class in primitive_type_classes {
|
||||
let type_obj = Type::from(type_class);
|
||||
assert_eq!(type_obj.get_type_class(), type_class);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_complex_interface_names() {
|
||||
let complex_names = vec![
|
||||
"com.sun.star.lang.XComponent",
|
||||
"com.sun.star.uno.XInterface",
|
||||
"com.sun.star.text.XTextDocument",
|
||||
"com.sun.star.beans.XPropertySet",
|
||||
"com.sun.star.awt.XControl",
|
||||
"com.sun.star.frame.XController",
|
||||
];
|
||||
|
||||
for name in complex_names {
|
||||
let type_obj = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name));
|
||||
assert_eq!(
|
||||
type_obj.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(type_obj.get_type_name().to_string(), name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_consistency_across_creation_methods() {
|
||||
// Test that different creation methods produce equivalent results for same type
|
||||
|
||||
// String type via different methods
|
||||
let string_from_class = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
let string_from_tuple = Type::from((typelib_TypeClass::typelib_TypeClass_STRING, "string"));
|
||||
let string_from_new_with_name =
|
||||
Type::new_with_name(typelib_TypeClass::typelib_TypeClass_STRING, "string");
|
||||
|
||||
assert!(string_from_class.equals(&string_from_tuple));
|
||||
assert!(string_from_tuple.equals(&string_from_new_with_name));
|
||||
assert!(string_from_class.equals(&string_from_new_with_name));
|
||||
|
||||
// Interface type via different methods
|
||||
let interface_name = "com.sun.star.lang.XComponent";
|
||||
let interface_from_tuple = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
interface_name,
|
||||
));
|
||||
let interface_from_new_with_name = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
interface_name,
|
||||
);
|
||||
|
||||
assert!(interface_from_tuple.equals(&interface_from_new_with_name));
|
||||
}
|
||||
|
||||
// === Memory Management Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_type_memory_safety() {
|
||||
// Test that Types can be created and dropped without issues
|
||||
|
||||
{
|
||||
let _type1 = Type::new();
|
||||
let _type2 = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
let _type3 = Type::new_with_name(typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32");
|
||||
} // Types should be properly cleaned up here
|
||||
|
||||
// Create more types after the previous ones were dropped
|
||||
let type4 = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
let type5 = type4.clone();
|
||||
|
||||
assert!(type4.equals(&type5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_multiple_references() {
|
||||
// Test that multiple references to the same logical type work correctly
|
||||
let name = "com.sun.star.text.XTextDocument";
|
||||
|
||||
let type1 = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name));
|
||||
let type2 = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, name));
|
||||
let type3 = type1.clone();
|
||||
|
||||
// All should be equal
|
||||
assert!(type1.equals(&type2));
|
||||
assert!(type2.equals(&type3));
|
||||
assert!(type1.equals(&type3));
|
||||
|
||||
// All should have same properties
|
||||
assert_eq!(type1.get_type_class(), type2.get_type_class());
|
||||
assert_eq!(type2.get_type_class(), type3.get_type_class());
|
||||
|
||||
assert_eq!(
|
||||
type1.get_type_name().to_string(),
|
||||
type2.get_type_name().to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
type2.get_type_name().to_string(),
|
||||
type3.get_type_name().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_mixed_operations() {
|
||||
// Test mixing different operations
|
||||
let original = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
);
|
||||
|
||||
// Clone and compare
|
||||
let cloned = original.clone();
|
||||
assert!(original.equals(&cloned));
|
||||
|
||||
// Create equivalent via From trait
|
||||
let from_tuple = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
assert!(original.equals(&from_tuple));
|
||||
|
||||
// Test assignment compatibility with itself
|
||||
assert!(original.is_assignable_from(&original));
|
||||
assert!(cloned.is_assignable_from(&from_tuple));
|
||||
|
||||
// Check type information
|
||||
assert_eq!(
|
||||
original.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE
|
||||
);
|
||||
assert_eq!(
|
||||
original.get_type_name().to_string(),
|
||||
"com.sun.star.lang.XComponent"
|
||||
);
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,328 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Type System and Type References
|
||||
//!
|
||||
//! This module provides the high-level safe Rust wrapper around LibreOffice's type system.
|
||||
//! It includes safe type creation, automatic memory management, and type compatibility testing.
|
||||
//!
|
||||
//! ## Key Components
|
||||
//! - `Type` - Safe Rust wrapper around LibreOffice's typelib_TypeDescriptionReference
|
||||
//!
|
||||
//! ## Features
|
||||
//! - Safe type creation with automatic memory management via RAII
|
||||
//! - Type equality and assignment compatibility testing
|
||||
//! - Type class and name retrieval
|
||||
//! - Clone, equality, and default trait implementations
|
||||
//! - Direct FFI interop with raw pointer access methods
|
||||
//! - Support for all UNO type classes (void, primitive types, interfaces, etc.)
|
||||
//! - Safe From trait implementations for creating types from type classes and explicit type/name tuples
|
||||
|
||||
use crate::core::oustring::*;
|
||||
use crate::ffi::type_ffi::*;
|
||||
use std::ptr;
|
||||
|
||||
// === Safe Type Wrapper ===
|
||||
|
||||
/// Safe Rust wrapper for UNO Type
|
||||
///
|
||||
/// This struct provides a safe, ergonomic interface to UNO's type system.
|
||||
/// It handles memory management automatically and provides idiomatic Rust
|
||||
/// methods for type operations and comparisons.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use rust_uno::{Type, typelib_TypeClass};
|
||||
///
|
||||
/// // Create a void type
|
||||
/// let void_type = Type::new();
|
||||
///
|
||||
/// // Create a primitive type
|
||||
/// let int_type = Type::from(typelib_TypeClass::typelib_TypeClass_LONG);
|
||||
///
|
||||
/// // Create an interface type
|
||||
/// let interface_type = Type::from((
|
||||
/// typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
/// "com.sun.star.lang.XComponent"
|
||||
/// ));
|
||||
/// ```
|
||||
#[allow(non_snake_case)] // _pType follows FFI naming conventions
|
||||
pub struct Type {
|
||||
/// C typelib reference pointer
|
||||
_pType: *mut typelib_TypeDescriptionReference,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// Create a new Type set to void
|
||||
///
|
||||
/// Creates the default UNO type (void). This is equivalent to the default
|
||||
/// constructor in C++ com::sun::star::uno::Type.
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
let void_type_ref =
|
||||
typelib_static_type_getByTypeClass(typelib_TypeClass::typelib_TypeClass_VOID);
|
||||
typelib_typedescriptionreference_acquire(*void_type_ref);
|
||||
Type {
|
||||
_pType: *void_type_ref,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Type from type class and name
|
||||
///
|
||||
/// Creates a new Type from the specified type class and a string that will be
|
||||
/// converted to OUString. Accepts any type that can be converted to OUString
|
||||
/// (such as &str, String, etc.) through the Into trait. This is the most common
|
||||
/// way to create custom types with specific names.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `type_class` - The UNO type class (VOID, STRING, INTERFACE, etc.)
|
||||
/// * `type_name` - The fully qualified type name (convertible to OUString)
|
||||
pub fn new_with_name<T: Into<OUString>>(type_class: typelib_TypeClass, type_name: T) -> Self {
|
||||
let mut oustring_name = type_name.into();
|
||||
let mut type_ref: *mut typelib_TypeDescriptionReference = ptr::null_mut();
|
||||
|
||||
unsafe {
|
||||
typelib_typedescriptionreference_new(
|
||||
&mut type_ref,
|
||||
type_class,
|
||||
oustring_name.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
|
||||
Type { _pType: type_ref }
|
||||
}
|
||||
|
||||
/// Create Type from existing typelib reference (acquires reference)
|
||||
///
|
||||
/// Takes ownership of an existing type reference by incrementing its reference count.
|
||||
/// This is useful when interfacing with C code that provides type references.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that `type_ref` points to a valid typelib_TypeDescriptionReference.
|
||||
pub unsafe fn from_typelib_ref(type_ref: *mut typelib_TypeDescriptionReference) -> Self {
|
||||
unsafe {
|
||||
typelib_typedescriptionreference_acquire(type_ref);
|
||||
}
|
||||
Type { _pType: type_ref }
|
||||
}
|
||||
|
||||
/// Create Type from existing typelib reference (no acquire)
|
||||
///
|
||||
/// Takes ownership of an existing type reference without incrementing reference count.
|
||||
/// Use this when transferring ownership from C code that already has a reference.
|
||||
/// This avoids unnecessary reference count manipulation when you know the ownership
|
||||
/// is being transferred.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - `type_ref` points to a valid typelib_TypeDescriptionReference
|
||||
/// - The reference count is properly managed (caller transfers ownership)
|
||||
pub unsafe fn from_typelib_ref_no_acquire(
|
||||
type_ref: *mut typelib_TypeDescriptionReference,
|
||||
) -> Self {
|
||||
Type { _pType: type_ref }
|
||||
}
|
||||
|
||||
/// Get the type class of this type
|
||||
///
|
||||
/// Returns the UNO type class (VOID, STRING, INTERFACE, etc.) that categorizes
|
||||
/// this type within the UNO type system. This is used for type checking and
|
||||
/// dispatch in UNO method calls.
|
||||
pub fn get_type_class(&self) -> typelib_TypeClass {
|
||||
unsafe { (*self._pType).eTypeClass }
|
||||
}
|
||||
|
||||
/// Get the fully qualified name of this type
|
||||
///
|
||||
/// Returns an OUString containing the type name (e.g., "com.sun.star.lang.XComponent").
|
||||
/// For primitive types, returns the type name (e.g., "long", "string"). The returned
|
||||
/// string is a copy, so it can be safely used without worrying about the lifetime
|
||||
/// of the original Type instance.
|
||||
pub fn get_type_name(&self) -> OUString {
|
||||
unsafe {
|
||||
let name_ptr = (*self._pType).pTypeName;
|
||||
if name_ptr.is_null() {
|
||||
return OUString::new();
|
||||
}
|
||||
// Use OUString::from_raw which handles the acquisition internally
|
||||
OUString::from_raw(name_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the raw typelib reference pointer for FFI calls
|
||||
///
|
||||
/// Returns the underlying pointer for FFI interoperability. The returned
|
||||
/// pointer remains owned by this Type instance and should not be freed
|
||||
/// by the caller. Useful when calling LibreOffice C APIs directly.
|
||||
pub fn get_typelib_type(&self) -> *mut typelib_TypeDescriptionReference {
|
||||
self._pType
|
||||
}
|
||||
|
||||
/// Convert Type into raw pointer (releases Rust ownership)
|
||||
///
|
||||
/// Transfers ownership of the underlying type reference to the caller.
|
||||
/// The caller becomes responsible for calling typelib_typedescriptionreference_release
|
||||
/// when done with the pointer. The Type instance is consumed and cannot be used
|
||||
/// after this call.
|
||||
pub fn into_raw(self) -> *mut typelib_TypeDescriptionReference {
|
||||
let ptr = self._pType;
|
||||
std::mem::forget(self); // Don't run destructor since we're transferring ownership
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Test if values of this type can be assigned from values of the given type
|
||||
///
|
||||
/// This includes widening conversions (e.g., long assignable from short)
|
||||
/// as long as there is no data loss. The test follows UNO assignment rules
|
||||
/// and is used in method dispatch to determine if argument types are compatible.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `other` - The source type to test assignment compatibility from
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if values of `other` type can be assigned to this type, `false` otherwise
|
||||
pub fn is_assignable_from(&self, other: &Type) -> bool {
|
||||
unsafe { typelib_typedescriptionreference_isAssignableFrom(self._pType, other._pType) != 0 }
|
||||
}
|
||||
|
||||
/// Test if this type is equal to another type
|
||||
///
|
||||
/// Two types are equal if they have the same type class and fully qualified name.
|
||||
/// This is used for type matching in UNO method dispatch and interface queries.
|
||||
/// The comparison is efficient and uses LibreOffice's native type comparison.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `other` - The type to compare with
|
||||
///
|
||||
/// # Returns
|
||||
/// `true` if the types are equal, `false` otherwise
|
||||
pub fn equals(&self, other: &Type) -> bool {
|
||||
unsafe { typelib_typedescriptionreference_equals(self._pType, other._pType) != 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// Default implementation: Creates a void type
|
||||
///
|
||||
/// When a Type is created without explicit initialization, it defaults to
|
||||
/// the void type, which is the UNO equivalent of "no type" or "empty type".
|
||||
impl Default for Type {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone implementation: Create an independent copy of another Type
|
||||
///
|
||||
/// This creates a new Type that references the same type description but is
|
||||
/// independently managed. The clone operation increments the reference count
|
||||
/// of the underlying type description, ensuring proper memory management.
|
||||
impl Clone for Type {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
typelib_typedescriptionreference_acquire(self._pType);
|
||||
}
|
||||
Type {
|
||||
_pType: self._pType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality comparison with other Type instances
|
||||
///
|
||||
/// Two Type instances are considered equal if they refer to the same type
|
||||
/// (same type class and name). This uses LibreOffice's native type comparison
|
||||
/// which is efficient and handles all type categories correctly.
|
||||
impl PartialEq for Type {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.equals(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Type {}
|
||||
|
||||
/// Debug trait for debugging - shows type class and name for UNO type identification
|
||||
///
|
||||
/// Displays the Type in a debug-friendly format showing both the type class
|
||||
/// and the type name, which is useful for debugging UNO type-related issues.
|
||||
impl std::fmt::Debug for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let type_class = self.get_type_class();
|
||||
let type_name = self.get_type_name().to_string();
|
||||
f.debug_struct("Type")
|
||||
.field("type_class", &type_class)
|
||||
.field("type_name", &type_name)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Destructor: Automatic cleanup when Type goes out of scope
|
||||
///
|
||||
/// Automatically releases the reference to the underlying type description
|
||||
/// when the Type instance is dropped. This ensures proper memory management
|
||||
/// without manual intervention.
|
||||
impl Drop for Type {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
typelib_typedescriptionreference_release(self._pType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for typelib_TypeClass (primitive types)
|
||||
///
|
||||
/// Creates a Type from a primitive type class. This uses LibreOffice's static
|
||||
/// type system to get pre-defined type references for standard UNO types like
|
||||
/// void, boolean, string, etc. This is the most efficient way to create
|
||||
/// primitive types since they're statically allocated.
|
||||
impl From<typelib_TypeClass> for Type {
|
||||
fn from(type_class: typelib_TypeClass) -> Self {
|
||||
unsafe {
|
||||
let type_ref = typelib_static_type_getByTypeClass(type_class);
|
||||
typelib_typedescriptionreference_acquire(*type_ref);
|
||||
Type { _pType: *type_ref }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for (typelib_TypeClass, &str) tuples
|
||||
///
|
||||
/// Convenient conversion from type class and string slice to Type. The string
|
||||
/// is automatically converted to OUString using UTF-8 encoding. This is useful
|
||||
/// for creating complex types with explicit type names.
|
||||
impl From<(typelib_TypeClass, &str)> for Type {
|
||||
fn from((type_class, type_name): (typelib_TypeClass, &str)) -> Self {
|
||||
Self::new_with_name(type_class, type_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for (typelib_TypeClass, String) tuples
|
||||
///
|
||||
/// Convenient conversion from type class and owned String to Type. The string
|
||||
/// is automatically converted to OUString using UTF-8 encoding. This allows
|
||||
/// using dynamically generated type names.
|
||||
impl From<(typelib_TypeClass, String)> for Type {
|
||||
fn from((type_class, type_name): (typelib_TypeClass, String)) -> Self {
|
||||
Self::new_with_name(type_class, type_name)
|
||||
}
|
||||
}
|
||||
|
||||
/// From trait implementation for (typelib_TypeClass, OUString) tuples
|
||||
///
|
||||
/// Direct conversion from type class and OUString to Type. This is the most
|
||||
/// efficient conversion when you already have an OUString, as it avoids
|
||||
/// additional string conversions.
|
||||
impl From<(typelib_TypeClass, OUString)> for Type {
|
||||
fn from((type_class, type_name): (typelib_TypeClass, OUString)) -> Self {
|
||||
Self::new_with_name(type_class, type_name)
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,59 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Safe Rust wrappers for UNO interfaces
|
||||
//!
|
||||
//! This module provides memory-safe, RAII-based wrappers around raw UNO interface pointers.
|
||||
//! For specific UNO interface method calls, use the auto-generated FFI functions
|
||||
//! in the `generated` module directly.
|
||||
|
||||
use crate::ffi::uno_bridge;
|
||||
use crate::generated::com::sun::star::uno::XComponentContext::XComponentContext;
|
||||
use std::ffi::NulError;
|
||||
|
||||
/// Error types for UNO operations
|
||||
#[derive(Debug)]
|
||||
pub enum UnoError {
|
||||
/// Failed to create C string (contains null byte)
|
||||
InvalidString(NulError),
|
||||
/// UNO operation failed
|
||||
OperationFailed,
|
||||
/// Interface not supported
|
||||
InterfaceNotSupported,
|
||||
/// Null pointer encountered
|
||||
NullPointer,
|
||||
/// Bridge initialization failed
|
||||
BridgeInitializationFailed,
|
||||
}
|
||||
|
||||
impl From<NulError> for UnoError {
|
||||
fn from(err: NulError) -> Self {
|
||||
UnoError::InvalidString(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type UnoResult<T> = Result<T, UnoError>;
|
||||
|
||||
/// Initialize the UNO bridge by directly calling LibreOffice bootstrap
|
||||
/// Returns the initial component context
|
||||
#[allow(non_snake_case)]
|
||||
pub fn defaultBootstrap_InitialComponentContext() -> UnoResult<XComponentContext> {
|
||||
unsafe {
|
||||
let context_ptr = uno_bridge::defaultBootstrap_InitialComponentContext();
|
||||
if context_ptr.is_null() {
|
||||
Err(UnoError::BridgeInitializationFailed)
|
||||
} else {
|
||||
// The bootstrap function returns Reference<XComponentContext>* directly
|
||||
// Use from_ptr which now handles Reference<T>* correctly
|
||||
XComponentContext::from_ptr(context_ptr).ok_or(UnoError::BridgeInitializationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,158 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Simple UNO Any Examples
|
||||
//!
|
||||
//! This example shows the basic usage of Any for UNO operations.
|
||||
//!
|
||||
//! ## Any Creation Methods:
|
||||
//! - `Any::new()` - Creates an empty Any (void type)
|
||||
//! - `Any::from_bool(value)` - Creates an Any containing a boolean
|
||||
//! - `Any::from_i32(value)` - Creates an Any containing a 32-bit integer
|
||||
//! - `Any::from(value)` - Uses From trait for supported types
|
||||
|
||||
use crate::core::Any;
|
||||
|
||||
/// Run the UNO Any example
|
||||
///
|
||||
/// This function demonstrates the usage of Any for UNO operations,
|
||||
/// including various creation methods, operations, and traits.
|
||||
pub fn run_example() {
|
||||
println!("=== UNO Any Examples ===\n");
|
||||
|
||||
// Create Any values using different methods
|
||||
let empty = Any::new();
|
||||
let bool_any = Any::from_bool(true);
|
||||
let int_any = Any::from_i32(42);
|
||||
let false_any = Any::from_bool(false);
|
||||
let zero_any = Any::from_i32(0);
|
||||
|
||||
println!("Empty Any: {} (has value: {})", empty, empty.has_value());
|
||||
println!(
|
||||
"Boolean Any (true): {} (has value: {})",
|
||||
bool_any,
|
||||
bool_any.has_value()
|
||||
);
|
||||
println!(
|
||||
"Integer Any (42): {} (has value: {})",
|
||||
int_any,
|
||||
int_any.has_value()
|
||||
);
|
||||
println!(
|
||||
"Boolean Any (false): {} (has value: {})",
|
||||
false_any,
|
||||
false_any.has_value()
|
||||
);
|
||||
println!(
|
||||
"Integer Any (0): {} (has value: {})",
|
||||
zero_any,
|
||||
zero_any.has_value()
|
||||
);
|
||||
|
||||
// Test From trait implementations
|
||||
println!("\n--- From Trait Examples ---");
|
||||
let from_bool: Any = true.into();
|
||||
let from_int: Any = 123i32.into();
|
||||
|
||||
println!(
|
||||
"From bool (true): {} (has value: {})",
|
||||
from_bool,
|
||||
from_bool.has_value()
|
||||
);
|
||||
println!(
|
||||
"From i32 (123): {} (has value: {})",
|
||||
from_int,
|
||||
from_int.has_value()
|
||||
);
|
||||
|
||||
// Any operations and traits
|
||||
println!("\n--- Any Operations ---");
|
||||
let cloned = bool_any.clone();
|
||||
println!("Cloned == Original: {}", cloned == bool_any);
|
||||
|
||||
// Trait examples
|
||||
println!("\n--- Trait Examples ---");
|
||||
|
||||
// Default trait
|
||||
let default_any = Any::default();
|
||||
println!(
|
||||
"Default Any: '{}' (has value: {})",
|
||||
default_any,
|
||||
default_any.has_value()
|
||||
);
|
||||
|
||||
// Debug trait
|
||||
println!("Debug output: {bool_any:?}");
|
||||
|
||||
// PartialEq trait
|
||||
let test1 = Any::from_bool(true);
|
||||
let test2 = Any::from_bool(true);
|
||||
let test3 = Any::from_bool(false);
|
||||
let test4 = Any::new(); // void
|
||||
let test5 = Any::new(); // void
|
||||
|
||||
println!("true == true: {}", test1 == test2);
|
||||
println!("true != false: {}", test1 != test3);
|
||||
println!("void == void: {}", test4 == test5);
|
||||
println!("true != void: {}", test1 != test4);
|
||||
|
||||
// Test clear operation
|
||||
println!("\n--- Clear Operation ---");
|
||||
let mut mutable_any = Any::from_i32(999);
|
||||
println!(
|
||||
"Before clear: {} (has value: {})",
|
||||
mutable_any,
|
||||
mutable_any.has_value()
|
||||
);
|
||||
mutable_any.clear();
|
||||
println!(
|
||||
"After clear: {} (has value: {})",
|
||||
mutable_any,
|
||||
mutable_any.has_value()
|
||||
);
|
||||
|
||||
// Raw conversion examples
|
||||
println!("\n--- Raw Conversion Examples ---");
|
||||
let original = Any::from_bool(true);
|
||||
println!(
|
||||
"Original: {} (has value: {})",
|
||||
original,
|
||||
original.has_value()
|
||||
);
|
||||
|
||||
// Convert to raw and back (unsafe operations)
|
||||
let raw_any = original.into_raw();
|
||||
let restored = unsafe { Any::from_raw(raw_any) };
|
||||
println!(
|
||||
"Restored: {} (has value: {})",
|
||||
restored,
|
||||
restored.has_value()
|
||||
);
|
||||
|
||||
// Test various integer values
|
||||
println!("\n--- Integer Value Tests ---");
|
||||
let numbers = vec![-1, 0, 1, 42, 2024, i32::MAX, i32::MIN];
|
||||
for num in numbers {
|
||||
let any = Any::from_i32(num);
|
||||
println!("Any({num}): {} (has value: {})", any, any.has_value());
|
||||
}
|
||||
|
||||
// Test memory safety with multiple clones
|
||||
println!("\n--- Memory Safety Test ---");
|
||||
let original = Any::from_i32(12345);
|
||||
let clones: Vec<Any> = (0..5).map(|_| original.clone()).collect();
|
||||
println!("Created {} clones of Any(12345)", clones.len());
|
||||
for (i, clone) in clones.iter().enumerate() {
|
||||
println!("Clone {}: {} (has value: {})", i, clone, clone.has_value());
|
||||
}
|
||||
|
||||
println!("\n✓ Any examples completed!");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,120 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Basic usage example of the Rust UNO Language Binding
|
||||
//!
|
||||
//! This example demonstrates how to use the Rust UNO bindings to interact
|
||||
//! with LibreOffice UNO components, including the OUString wrapper for safe
|
||||
//! string handling and cross-platform SAL types.
|
||||
|
||||
// Import from the current crate since examples are now part of the library
|
||||
use crate::core::OUString;
|
||||
|
||||
/// Example function demonstrating OUString creation and operations
|
||||
pub fn oustring_basic_example() {
|
||||
println!("=== LibreOffice UNO Rust Binding - OUString Demonstration ===");
|
||||
|
||||
// Basic string creation
|
||||
let empty = OUString::new();
|
||||
let hello = OUString::from("Hello UNO!");
|
||||
let unicode = OUString::from_utf8("Unicode: café 中文 🚀");
|
||||
|
||||
println!("Empty string: '{}' (len: {})", empty, empty.len());
|
||||
println!("Hello string: '{}' (len: {})", hello, hello.len());
|
||||
println!("Unicode string: '{}' (len: {})", unicode, unicode.len());
|
||||
|
||||
// String operations
|
||||
let cloned = hello.clone();
|
||||
println!("Cloned == Original: {}", cloned == hello);
|
||||
|
||||
// Display string conversion
|
||||
println!("Display conversion: '{unicode}'");
|
||||
|
||||
println!("✓ OUString operations completed successfully!");
|
||||
println!("✓ Successfully demonstrated Rust UNO String Handling");
|
||||
}
|
||||
|
||||
/// Example function demonstrating advanced OUString operations
|
||||
///
|
||||
/// Shows various ways to create and manipulate OUString instances,
|
||||
/// including UTF-8, ASCII, and Unicode handling.
|
||||
pub fn advanced_string_example() {
|
||||
println!("=== Advanced UNO String Types (OUString) Examples ===");
|
||||
|
||||
// Basic string creation using different methods
|
||||
let empty_string = OUString::new();
|
||||
let hello_string = OUString::from("Hello, LibreOffice!");
|
||||
let unicode_string = OUString::from_utf8("Unicode: αβγ, 中文, 🚀");
|
||||
|
||||
println!("Empty: '{empty_string}' (length: {})", empty_string.len());
|
||||
println!("Hello: '{hello_string}' (length: {})", hello_string.len());
|
||||
println!(
|
||||
"Unicode: '{unicode_string}' (length: {})",
|
||||
unicode_string.len()
|
||||
);
|
||||
|
||||
// String operations
|
||||
let cloned = hello_string.clone();
|
||||
println!("Cloned == Original: {}", cloned == hello_string);
|
||||
println!("Convert to String: '{unicode_string}'");
|
||||
|
||||
// More string examples
|
||||
let ascii_only = OUString::from_ascii("ASCII Only!");
|
||||
println!(
|
||||
"ASCII string: '{ascii_only}' (length: {})",
|
||||
ascii_only.len()
|
||||
);
|
||||
|
||||
// Comparison operations
|
||||
let same_hello = OUString::from("Hello, LibreOffice!");
|
||||
println!("String equality: {}", hello_string == same_hello);
|
||||
|
||||
println!("✓ Advanced string operations completed successfully!");
|
||||
}
|
||||
|
||||
/// Run the basic UNO example
|
||||
///
|
||||
/// This function demonstrates the complete basic usage of the Rust UNO bindings,
|
||||
/// focusing on OUString operations and core library functionality.
|
||||
pub fn run_example() {
|
||||
println!("=== Rust UNO Language Binding - Basic Usage Example ===\n");
|
||||
|
||||
// Run the basic OUString demonstration
|
||||
oustring_basic_example();
|
||||
|
||||
println!("\n=== Advanced String Examples ===");
|
||||
|
||||
// Run advanced string examples
|
||||
advanced_string_example();
|
||||
|
||||
println!("\n=== Data Type Examples ===");
|
||||
|
||||
// Demonstrate various data type operations
|
||||
println!("UNO Type System Examples:");
|
||||
|
||||
// Basic type demonstrations (without requiring UNO runtime)
|
||||
println!(" Rust types compatible with UNO:");
|
||||
println!(" i8 (sal_Int8/byte): {}", i8::MIN);
|
||||
println!(" i16 (sal_Int16/short): {}", i16::MIN);
|
||||
println!(" i32 (sal_Int32/long): {}", i32::MIN);
|
||||
println!(" i64 (sal_Int64/hyper): {}", i64::MIN);
|
||||
println!(" u16 (sal_uInt16/unsigned short): {}", u16::MAX);
|
||||
println!(" u32 (sal_uInt32/unsigned long): {}", u32::MAX);
|
||||
println!(" u64 (sal_uInt64/unsigned hyper): {}", u64::MAX);
|
||||
println!(" f32 (float): {}", std::f32::consts::PI);
|
||||
println!(" f64 (double): {}", std::f64::consts::E);
|
||||
println!(" bool (sal_Bool): true");
|
||||
println!(" u16 (sal_Unicode/char): {}", 'Ö' as u16);
|
||||
|
||||
println!("\n✓ All UNO basic operations completed successfully!");
|
||||
println!("✓ Rust UNO Language Binding core functionality demonstrated!");
|
||||
println!("✓ OUString operations are fully functional!");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,124 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
use crate::core::uno_wrapper::{UnoError, defaultBootstrap_InitialComponentContext};
|
||||
use crate::core::OUString;
|
||||
use crate::generated::rustmaker::com::sun::star::frame::Desktop::Desktop;
|
||||
use crate::generated::rustmaker::com::sun::star::frame::XComponentLoader::XComponentLoader;
|
||||
use crate::generated::rustmaker::com::sun::star::text::XTextDocument::XTextDocument;
|
||||
use crate::generated::rustmaker::com::sun::star::text::XSimpleText::XSimpleText;
|
||||
use std::ffi::c_void;
|
||||
|
||||
/// LibreOffice Writer automation demo using generated Rust UNO bindings.
|
||||
/// Creates a Writer document and inserts text using auto-generated interface wrappers.
|
||||
pub fn run() {
|
||||
println!("=== Rust UNO Bridge Test ===");
|
||||
println!("-> Running 'load_writer' example...");
|
||||
|
||||
match run_internal() {
|
||||
Ok(()) => {
|
||||
println!("=== Rust UNO Bridge Test Done ===");
|
||||
println!("<- 'load_writer' example completed successfully.");
|
||||
},
|
||||
Err(e) => {
|
||||
println!("=== Rust UNO Bridge Test Done ===");
|
||||
eprintln!(" ERROR: 'load_writer' example failed: {e:?}");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn run_internal() -> Result<(), UnoError> {
|
||||
// Initialize UNO bridge
|
||||
println!(" Initializing UNO bridge...");
|
||||
let context = defaultBootstrap_InitialComponentContext()?;
|
||||
println!(" ✓ UNO bridge initialized successfully!");
|
||||
|
||||
// Create Desktop service
|
||||
println!(" Creating Desktop service using generated wrapper...");
|
||||
let desktop = Desktop::create(context.as_ptr()).ok_or(UnoError::OperationFailed)?;
|
||||
println!(" ✓ Desktop service created: {:p}", desktop.as_ptr());
|
||||
|
||||
// Get XComponentLoader interface
|
||||
let component_loader = XComponentLoader::from_ptr(desktop.as_ptr()).ok_or(UnoError::InterfaceNotSupported)?;
|
||||
println!(" ✓ XComponentLoader interface acquired: {:p}", component_loader.as_ptr());
|
||||
|
||||
// Load Writer document
|
||||
println!(" Loading Writer document via XComponentLoader::loadComponentFromURL...");
|
||||
let url = OUString::from("private:factory/swriter");
|
||||
let target = OUString::from("_blank");
|
||||
let search_flags: i32 = 0;
|
||||
let empty_args = std::ptr::null_mut();
|
||||
|
||||
let document_interface_ptr = component_loader.loadComponentFromURL(
|
||||
url.as_ptr() as *mut c_void,
|
||||
target.as_ptr() as *mut c_void,
|
||||
&search_flags as *const i32 as *mut c_void,
|
||||
empty_args,
|
||||
).ok_or(UnoError::OperationFailed)?;
|
||||
|
||||
if document_interface_ptr.as_ptr().is_null() {
|
||||
return Err(UnoError::OperationFailed);
|
||||
}
|
||||
|
||||
println!(" ✓ Writer document loaded successfully!");
|
||||
println!(" Document interface pointer: {:p}", document_interface_ptr.as_ptr());
|
||||
|
||||
// Cast to XTextDocument
|
||||
println!(" Casting document to XTextDocument...");
|
||||
let text_document = XTextDocument::from_ptr(document_interface_ptr.as_ptr()).ok_or(UnoError::InterfaceNotSupported)?;
|
||||
println!(" ✓ XTextDocument interface acquired: {:p}", text_document.as_ptr());
|
||||
|
||||
// Get text object
|
||||
println!(" Getting text object via XTextDocument::getText...");
|
||||
let text = text_document.getText().ok_or(UnoError::OperationFailed)?;
|
||||
println!(" ✓ Text object acquired: {:p}", text.as_ptr());
|
||||
|
||||
// Cast to XSimpleText
|
||||
println!(" Casting XText to XSimpleText...");
|
||||
let simple_text = XSimpleText::from_ptr(text.as_ptr()).ok_or(UnoError::InterfaceNotSupported)?;
|
||||
println!(" ✓ XSimpleText interface acquired: {:p}", simple_text.as_ptr());
|
||||
|
||||
// Create text cursor
|
||||
println!(" Creating text cursor via XSimpleText::createTextCursor...");
|
||||
let cursor = simple_text.createTextCursor().ok_or(UnoError::OperationFailed)?;
|
||||
println!(" ✓ Text cursor created: {:p}", cursor.as_ptr());
|
||||
|
||||
// Insert text
|
||||
println!(" Inserting text via XSimpleText::insertString...");
|
||||
let hello_text = OUString::from(
|
||||
"Hello from Generated Rust UNO Bindings!\n\n\
|
||||
This text was inserted using auto-generated service and interface wrappers:\n\n\
|
||||
- Desktop::create() - Generated service wrapper\n\
|
||||
- XComponentLoader::loadComponentFromURL() - Generated interface method\n\
|
||||
- XTextDocument::getText() - Generated interface method\n\
|
||||
- XSimpleText::createTextCursor() - Generated interface method\n\
|
||||
- XSimpleText::insertString() - Generated interface method\n\n\
|
||||
All types generated automatically from UNO IDL with type-safe opaque pointer architecture!\n\n\
|
||||
FFI Validation Features Demonstrated:\n\
|
||||
- Automatic null pointer checking in all generated methods\n\
|
||||
- Reference validity validation (is() checking) \n\
|
||||
- Memory cleanup on validation failure (delete + return nullptr)\n\
|
||||
- Detailed debug logging for troubleshooting\n\
|
||||
- Type-safe opaque pointer architecture prevents crashes\n\
|
||||
- Follows uno_bootstrap.cxx validation pattern exactly"
|
||||
);
|
||||
let absorb: i32 = 0;
|
||||
|
||||
simple_text.insertString(
|
||||
cursor.as_ptr() as *mut c_void,
|
||||
hello_text.as_ptr() as *mut c_void,
|
||||
&absorb as *const i32 as *mut c_void,
|
||||
);
|
||||
|
||||
println!(" ✓ Text inserted successfully using generated interfaces!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,41 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Example usage of the Rust UNO bridge
|
||||
|
||||
pub mod any_example;
|
||||
pub mod basic_example;
|
||||
pub mod load_writer;
|
||||
pub mod string_example;
|
||||
pub mod type_example;
|
||||
// pub mod autogen_test; // Temporarily disabled
|
||||
|
||||
/// Run all examples
|
||||
pub fn run_all() {
|
||||
println!("=== Running all UNO examples ===");
|
||||
|
||||
println!("\n--- Load Writer example ---");
|
||||
load_writer::run();
|
||||
|
||||
println!("\n--- Basic example ---");
|
||||
basic_example::run_example();
|
||||
|
||||
println!("\n--- String example ---");
|
||||
string_example::run_example();
|
||||
|
||||
println!("\n--- Any example ---");
|
||||
any_example::run_example();
|
||||
|
||||
println!("\n--- Type example ---");
|
||||
type_example::run_example();
|
||||
|
||||
println!("\n=== All examples completed ===");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,131 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Simple UNO String Examples
|
||||
//!
|
||||
//! This example shows the basic usage of OUString for UNO operations.
|
||||
//!
|
||||
//! ## String Creation Methods:
|
||||
//! - `OUString::from(str)` - Uses UTF-8 via From trait (most common)
|
||||
//! - `OUString::from_utf8(str)` - Explicit UTF-8 conversion (recommended)
|
||||
//! - `OUString::from_ascii(str)` - For pure ASCII text only
|
||||
//! - `OUString::from_utf16(data)` - When you already have UTF-16 data
|
||||
|
||||
use crate::core::oustring::OUString;
|
||||
use crate::ffi::rtl_string::rtl_uString;
|
||||
use crate::ffi::sal_types::sal_Unicode;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
/// Test what happens with allocation failure
|
||||
/// NOTE: This is just for demonstration - real failure is hard to trigger
|
||||
pub fn test_allocation_failure_behavior() {
|
||||
println!("=== Testing Allocation Failure Behavior ===");
|
||||
|
||||
// Simulate what happens when NonNull::new gets a null pointer
|
||||
let null_ptr: *mut rtl_uString = std::ptr::null_mut();
|
||||
|
||||
println!("Simulating allocation failure...");
|
||||
|
||||
// This will demonstrate the behavior:
|
||||
match NonNull::new(null_ptr) {
|
||||
Some(_) => println!("Got valid pointer"),
|
||||
None => {
|
||||
println!("Got null pointer - this would cause panic in OUString::new()");
|
||||
println!("In real code: .expect(\"RTL string allocation failed\") would panic here");
|
||||
println!(
|
||||
"Program would terminate with: 'RTL string allocation failed - system out of memory'"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
println!("If you see this message, allocation succeeded");
|
||||
}
|
||||
|
||||
/// Run the UNO string example
|
||||
///
|
||||
/// This function demonstrates the usage of OUString for UNO operations,
|
||||
/// including various creation methods, operations, and traits.
|
||||
pub fn run_example() {
|
||||
println!("=== UNO String Examples ===");
|
||||
|
||||
// Create strings using different methods
|
||||
let empty = OUString::new();
|
||||
let hello = OUString::from("Hello UNO!"); // Uses from_utf8 via From trait (most common)
|
||||
let unicode = OUString::from_utf8("Unicode: café 中文 🚀"); // Explicit UTF-8 (recommended)
|
||||
let ascii_only = OUString::from_ascii("ASCII Only!"); // For pure ASCII text only
|
||||
|
||||
// UTF-16 example (for when you already have UTF-16 data)
|
||||
let utf16_data: Vec<sal_Unicode> = "UTF-16 example"
|
||||
.encode_utf16()
|
||||
.map(|c| c as sal_Unicode)
|
||||
.collect();
|
||||
let utf16_string = OUString::from_utf16(&utf16_data);
|
||||
|
||||
println!("Empty string: '{}' (len: {})", empty, empty.len());
|
||||
println!("Hello string: '{}' (len: {})", hello, hello.len());
|
||||
println!("UTF-8 string: '{}' (len: {})", unicode, unicode.len());
|
||||
println!("ASCII string: '{}' (len: {})", ascii_only, ascii_only.len());
|
||||
println!(
|
||||
"UTF-16 string: '{}' (len: {})",
|
||||
utf16_string,
|
||||
utf16_string.len()
|
||||
);
|
||||
|
||||
// String operations and traits
|
||||
let cloned = hello.clone();
|
||||
println!("Cloned == Original: {}", cloned == hello);
|
||||
|
||||
// Convert to Rust String (inherited from Display trait)
|
||||
let rust_str = unicode.to_string();
|
||||
println!("As Rust String: '{rust_str}'");
|
||||
|
||||
// Trait examples
|
||||
println!("\n--- Trait Examples ---");
|
||||
|
||||
// Default trait
|
||||
let default_str = OUString::default();
|
||||
println!(
|
||||
"Default string: '{}' (empty: {})",
|
||||
default_str,
|
||||
default_str.is_empty()
|
||||
);
|
||||
|
||||
// Debug trait
|
||||
println!("Debug output: {hello:?}");
|
||||
|
||||
// PartialEq trait
|
||||
let test1 = OUString::from("test");
|
||||
let test2 = OUString::from("test");
|
||||
let test3 = OUString::from("different");
|
||||
println!("'{}' == '{}': {}", test1, test2, test1 == test2);
|
||||
println!("'{}' != '{}': {}", test1, test2, test1 != test2);
|
||||
println!("'{}' == '{}': {}", test1, test3, test1 == test3);
|
||||
println!("'{}' != '{}': {}", test1, test3, test1 != test3);
|
||||
|
||||
// Additional string examples
|
||||
println!("\n--- Additional String Operations ---");
|
||||
|
||||
let special_chars = OUString::from_utf8("Special: αβγ, ñañá, العربية");
|
||||
println!("Special characters: '{special_chars}'");
|
||||
|
||||
let mixed = OUString::from("Mixed content: 123 + αβγ = 🎉");
|
||||
println!("Mixed content: '{mixed}' (len: {})", mixed.len());
|
||||
|
||||
// Test empty string operations
|
||||
println!("Empty string is empty: {}", empty.is_empty());
|
||||
println!("Hello string is empty: {}", hello.is_empty());
|
||||
|
||||
println!("\n--- Testing Allocation Failure Behavior ---");
|
||||
test_allocation_failure_behavior();
|
||||
|
||||
println!("\n✓ OUString examples completed!");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,324 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Type System Examples
|
||||
//!
|
||||
//! This example demonstrates the usage of the UNO Type system in Rust.
|
||||
//! The Type class represents IDL meta types, holding type names and type classes.
|
||||
//!
|
||||
//! ## Type Creation Methods:
|
||||
//! - `Type::new()` - Creates void type (default)
|
||||
//! - `Type::from(typelib_TypeClass)` - Creates type from type class only
|
||||
//! - `Type::from((typelib_TypeClass, name))` - Creates type with class and name
|
||||
//! - `Type::new_with_name(class, name)` - Explicit creation with name
|
||||
//!
|
||||
//! ## Type Operations:
|
||||
//! - `get_type_class()` - Get the UNO type class
|
||||
//! - `get_type_name()` - Get the fully qualified type name
|
||||
//! - `equals()` - Test type equality
|
||||
//! - `is_assignable_from()` - Test assignment compatibility
|
||||
|
||||
use crate::core::{OUString, Type};
|
||||
use crate::ffi::type_ffi::typelib_TypeClass;
|
||||
|
||||
/// Run the UNO type system example
|
||||
///
|
||||
/// This function demonstrates the usage of the UNO Type system in Rust,
|
||||
/// including type creation, operations, traits, and UNO interface integration.
|
||||
pub fn run_example() {
|
||||
println!("=== UNO Type System Examples ===\n");
|
||||
|
||||
// === Basic Type Creation ===
|
||||
println!("--- Basic Type Creation ---");
|
||||
|
||||
// Default void type
|
||||
let void_type = Type::new();
|
||||
let default_type = Type::default();
|
||||
println!("Void type: {void_type:?}");
|
||||
println!("Default type: {default_type:?}");
|
||||
println!("Void == Default: {}", void_type.equals(&default_type));
|
||||
|
||||
// === Primitive Types from Type Classes ===
|
||||
println!("\n--- Primitive Types from Type Classes ---");
|
||||
|
||||
let boolean_type = Type::from(typelib_TypeClass::typelib_TypeClass_BOOLEAN);
|
||||
let byte_type = Type::from(typelib_TypeClass::typelib_TypeClass_BYTE);
|
||||
let short_type = Type::from(typelib_TypeClass::typelib_TypeClass_SHORT);
|
||||
let long_type = Type::from(typelib_TypeClass::typelib_TypeClass_LONG);
|
||||
let float_type = Type::from(typelib_TypeClass::typelib_TypeClass_FLOAT);
|
||||
let double_type = Type::from(typelib_TypeClass::typelib_TypeClass_DOUBLE);
|
||||
let char_type = Type::from(typelib_TypeClass::typelib_TypeClass_CHAR);
|
||||
let string_type = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
let type_type = Type::from(typelib_TypeClass::typelib_TypeClass_TYPE);
|
||||
let any_type = Type::from(typelib_TypeClass::typelib_TypeClass_ANY);
|
||||
|
||||
println!(
|
||||
"Boolean: {} (class: {:?})",
|
||||
boolean_type.get_type_name(),
|
||||
boolean_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Byte: {} (class: {:?})",
|
||||
byte_type.get_type_name(),
|
||||
byte_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Short: {} (class: {:?})",
|
||||
short_type.get_type_name(),
|
||||
short_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Long: {} (class: {:?})",
|
||||
long_type.get_type_name(),
|
||||
long_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Float: {} (class: {:?})",
|
||||
float_type.get_type_name(),
|
||||
float_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Double: {} (class: {:?})",
|
||||
double_type.get_type_name(),
|
||||
double_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Char: {} (class: {:?})",
|
||||
char_type.get_type_name(),
|
||||
char_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"String: {} (class: {:?})",
|
||||
string_type.get_type_name(),
|
||||
string_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Type: {} (class: {:?})",
|
||||
type_type.get_type_name(),
|
||||
type_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Any: {} (class: {:?})",
|
||||
any_type.get_type_name(),
|
||||
any_type.get_type_class()
|
||||
);
|
||||
|
||||
// === Complex Types with Names ===
|
||||
println!("\n--- Complex Types with Names ---");
|
||||
|
||||
// Interface types using tuple syntax
|
||||
let component_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XComponent",
|
||||
));
|
||||
|
||||
let property_set_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.beans.XPropertySet",
|
||||
));
|
||||
|
||||
// Service types
|
||||
let text_service = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_SERVICE,
|
||||
"com.sun.star.text.Text",
|
||||
);
|
||||
|
||||
// Using String and OUString for type names
|
||||
let string_name = "com.sun.star.awt.XControl".to_string();
|
||||
let control_type = Type::from((typelib_TypeClass::typelib_TypeClass_INTERFACE, string_name));
|
||||
|
||||
let oustring_name = OUString::from("com.sun.star.uno.XInterface");
|
||||
let interface_type = Type::new_with_name(
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
oustring_name,
|
||||
);
|
||||
|
||||
println!(
|
||||
"Component: {} (class: {:?})",
|
||||
component_type.get_type_name(),
|
||||
component_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"PropertySet: {} (class: {:?})",
|
||||
property_set_type.get_type_name(),
|
||||
property_set_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Text Service: {} (class: {:?})",
|
||||
text_service.get_type_name(),
|
||||
text_service.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Control: {} (class: {:?})",
|
||||
control_type.get_type_name(),
|
||||
control_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"XInterface: {} (class: {:?})",
|
||||
interface_type.get_type_name(),
|
||||
interface_type.get_type_class()
|
||||
);
|
||||
|
||||
// === Type Properties and Operations ===
|
||||
println!("\n--- Type Properties and Operations ---");
|
||||
|
||||
println!("Type class of long: {:?}", long_type.get_type_class());
|
||||
println!(
|
||||
"Type name of component: '{}'",
|
||||
component_type.get_type_name()
|
||||
);
|
||||
|
||||
// Type equality testing
|
||||
let long_type2 = Type::from(typelib_TypeClass::typelib_TypeClass_LONG);
|
||||
let short_type2 = Type::from(typelib_TypeClass::typelib_TypeClass_SHORT);
|
||||
|
||||
println!("long == long: {}", long_type.equals(&long_type2));
|
||||
println!("long == short: {}", long_type.equals(&short_type2));
|
||||
|
||||
// Assignment compatibility testing
|
||||
println!(
|
||||
"long assignable from short: {}",
|
||||
long_type.is_assignable_from(&short_type)
|
||||
);
|
||||
println!(
|
||||
"short assignable from long: {}",
|
||||
short_type.is_assignable_from(&long_type)
|
||||
);
|
||||
println!(
|
||||
"any assignable from long: {}",
|
||||
any_type.is_assignable_from(&long_type)
|
||||
);
|
||||
println!(
|
||||
"long assignable from any: {}",
|
||||
long_type.is_assignable_from(&any_type)
|
||||
);
|
||||
|
||||
// === Trait Examples ===
|
||||
println!("\n--- Trait Examples ---");
|
||||
|
||||
// Clone trait
|
||||
let original = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.lang.XServiceInfo",
|
||||
));
|
||||
let cloned = original.clone();
|
||||
println!("Original: {}", original.get_type_name());
|
||||
println!("Cloned: {}", cloned.get_type_name());
|
||||
println!("Clone equals original: {}", original.equals(&cloned));
|
||||
|
||||
// PartialEq trait (using == operator)
|
||||
let type1 = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
let type2 = Type::from(typelib_TypeClass::typelib_TypeClass_STRING);
|
||||
let type3 = Type::from(typelib_TypeClass::typelib_TypeClass_LONG);
|
||||
println!("string == string: {}", type1 == type2);
|
||||
println!("string != long: {}", type1 != type3);
|
||||
|
||||
// Debug trait
|
||||
println!("Debug output: {long_type:?}");
|
||||
|
||||
// === Additional Type Examples ===
|
||||
println!("\n--- Additional Type Examples ---");
|
||||
|
||||
// Create types for common UNO interfaces
|
||||
let service_manager_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_SERVICE,
|
||||
"com.sun.star.lang.XMultiServiceFactory",
|
||||
));
|
||||
|
||||
let document_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.document.XDocumentPropertiesSupplier",
|
||||
));
|
||||
|
||||
println!(
|
||||
"Service Manager: {} (class: {:?})",
|
||||
service_manager_type.get_type_name(),
|
||||
service_manager_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"Document: {} (class: {:?})",
|
||||
document_type.get_type_name(),
|
||||
document_type.get_type_class()
|
||||
);
|
||||
|
||||
// Create expected sal_Int32 type for comparison
|
||||
let sal_int32_type = Type::from((typelib_TypeClass::typelib_TypeClass_LONG, "sal_Int32"));
|
||||
println!(
|
||||
"sal_Int32 type: {} (class: {:?})",
|
||||
sal_int32_type.get_type_name(),
|
||||
sal_int32_type.get_type_class()
|
||||
);
|
||||
|
||||
// === Advanced Type Operations ===
|
||||
println!("\n--- Advanced Type Operations ---");
|
||||
|
||||
// Demonstrate type compatibility chains
|
||||
let unsigned_short_type = Type::from(typelib_TypeClass::typelib_TypeClass_UNSIGNED_SHORT);
|
||||
let unsigned_long_type = Type::from(typelib_TypeClass::typelib_TypeClass_UNSIGNED_LONG);
|
||||
let hyper_type = Type::from(typelib_TypeClass::typelib_TypeClass_HYPER);
|
||||
|
||||
println!("Type compatibility chain:");
|
||||
println!(
|
||||
" unsigned_long assignable from unsigned_short: {}",
|
||||
unsigned_long_type.is_assignable_from(&unsigned_short_type)
|
||||
);
|
||||
println!(
|
||||
" hyper assignable from unsigned_short: {}",
|
||||
hyper_type.is_assignable_from(&unsigned_short_type)
|
||||
);
|
||||
println!(
|
||||
" hyper assignable from long: {}",
|
||||
hyper_type.is_assignable_from(&long_type)
|
||||
);
|
||||
|
||||
// Interface hierarchy compatibility
|
||||
let xinterface_type = Type::from((
|
||||
typelib_TypeClass::typelib_TypeClass_INTERFACE,
|
||||
"com.sun.star.uno.XInterface",
|
||||
));
|
||||
println!(
|
||||
"XInterface assignable from XComponent: {}",
|
||||
xinterface_type.is_assignable_from(&component_type)
|
||||
);
|
||||
println!(
|
||||
"XInterface type: {} (class: {:?})",
|
||||
xinterface_type.get_type_name(),
|
||||
xinterface_type.get_type_class()
|
||||
);
|
||||
println!(
|
||||
"XComponent type: {} (class: {:?})",
|
||||
component_type.get_type_name(),
|
||||
component_type.get_type_class()
|
||||
);
|
||||
|
||||
// === Memory Management Demo ===
|
||||
println!("\n--- Memory Management Demo ---");
|
||||
|
||||
// Create multiple references to the same type
|
||||
let type_a = Type::from(typelib_TypeClass::typelib_TypeClass_DOUBLE);
|
||||
let type_b = type_a.clone();
|
||||
let type_c = Type::from(typelib_TypeClass::typelib_TypeClass_DOUBLE);
|
||||
|
||||
println!("type_a: {}", type_a.get_type_name());
|
||||
println!("type_b (cloned): {}", type_b.get_type_name());
|
||||
println!("type_c (new): {}", type_c.get_type_name());
|
||||
println!(
|
||||
"All three equal: {}",
|
||||
type_a.equals(&type_b) && type_b.equals(&type_c)
|
||||
);
|
||||
|
||||
// Types are automatically cleaned up when they go out of scope
|
||||
// due to RAII (Drop trait implementation)
|
||||
|
||||
println!("\n✓ UNO Type system examples completed!");
|
||||
println!("✓ All Type creation methods, operations, and traits demonstrated!");
|
||||
println!("✓ Type system functionality working correctly!");
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,27 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! FFI modules for interacting with C/C++ code.
|
||||
|
||||
// This module will contain the raw bindings generated by bindgen.
|
||||
pub mod uno_any;
|
||||
pub mod uno_bridge;
|
||||
pub mod uno_sequence;
|
||||
|
||||
pub mod rtl_string;
|
||||
pub mod sal_types;
|
||||
pub mod type_ffi;
|
||||
|
||||
// Include unit tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod integration_tests;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,87 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! RTL String FFI Bindings
|
||||
//!
|
||||
//! This module contains the raw FFI bindings for LibreOffice's rtl_uString
|
||||
//! functions and types. These are low-level C API bindings that should
|
||||
//! typically be used through the high-level OUString wrapper.
|
||||
|
||||
// Allow non-standard naming for FFI types that must match C API exactly
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::ffi::sal_types::*;
|
||||
|
||||
// === RTL String Types and FFI Declarations ===
|
||||
|
||||
/// Interlocked count type for reference counting and atomic operations
|
||||
pub type oslInterlockedCount = sal_Int32;
|
||||
|
||||
/// Text encoding constants
|
||||
pub type rtl_TextEncoding = sal_Int32;
|
||||
pub const RTL_TEXTENCODING_UTF8: rtl_TextEncoding = 76;
|
||||
|
||||
/// Conversion flags for string conversion
|
||||
pub const OSTRING_TO_OUSTRING_CVTFLAGS: sal_uInt32 = 0x00000003;
|
||||
|
||||
/// Internal structure for LibreOffice UNO strings
|
||||
#[repr(C)]
|
||||
pub struct rtl_uString {
|
||||
/// reference count of string
|
||||
pub refCount: oslInterlockedCount,
|
||||
/// length of the string in UTF-16 code units
|
||||
pub length: sal_Int32,
|
||||
/// UTF-16 buffer array
|
||||
pub buffer: [sal_Unicode; 1usize],
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
/// Create a new empty string
|
||||
pub fn rtl_uString_new(newStr: *mut *mut rtl_uString);
|
||||
|
||||
/// Create string from ASCII
|
||||
pub fn rtl_uString_newFromAscii(
|
||||
newStr: *mut *mut rtl_uString,
|
||||
value: *const std::os::raw::c_char,
|
||||
);
|
||||
|
||||
/// Create string from existing string
|
||||
pub fn rtl_uString_newFromString(newStr: *mut *mut rtl_uString, value: *const rtl_uString);
|
||||
|
||||
/// Create string from UTF-16 data with length
|
||||
pub fn rtl_uString_newFromStr_WithLength(
|
||||
newStr: *mut *mut rtl_uString,
|
||||
value: *const sal_Unicode,
|
||||
len: sal_Int32,
|
||||
);
|
||||
|
||||
/// Create UNO string from byte string with encoding
|
||||
pub fn rtl_string2UString(
|
||||
newStr: *mut *mut rtl_uString,
|
||||
str: *const std::os::raw::c_char,
|
||||
len: sal_Int32,
|
||||
encoding: rtl_TextEncoding,
|
||||
convertFlags: sal_uInt32,
|
||||
);
|
||||
|
||||
/// Release string reference
|
||||
pub fn rtl_uString_release(str: *mut rtl_uString);
|
||||
|
||||
/// Acquire string reference
|
||||
pub fn rtl_uString_acquire(str: *mut rtl_uString);
|
||||
|
||||
/// Get string length
|
||||
pub fn rtl_uString_getLength(str: *const rtl_uString) -> sal_Int32;
|
||||
|
||||
/// Get string data pointer
|
||||
pub fn rtl_uString_getStr(str: *const rtl_uString) -> *const sal_Unicode;
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,50 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Basic Types - Cross-Platform Implementation
|
||||
//!
|
||||
//! This module contains the fundamental SAL (System Abstraction Layer) types
|
||||
//! that form the foundation of UNO's type system. These types use Rust native
|
||||
//! types to ensure consistent behavior across all platforms.
|
||||
|
||||
// Allow non-standard naming for FFI types that must match C API exactly
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// === Fixed-size integer types ===
|
||||
// These types have guaranteed sizes across all platforms
|
||||
|
||||
pub type sal_Bool = u8; // Always 8-bit boolean
|
||||
pub type sal_Int8 = i8; // Always 8-bit signed
|
||||
pub type sal_uInt8 = u8; // Always 8-bit unsigned
|
||||
pub type sal_Int16 = i16; // Always 16-bit signed
|
||||
pub type sal_uInt16 = u16; // Always 16-bit unsigned
|
||||
pub type sal_Int32 = i32; // Always 32-bit signed
|
||||
pub type sal_uInt32 = u32; // Always 32-bit unsigned
|
||||
pub type sal_Int64 = i64; // Always 64-bit signed
|
||||
pub type sal_uInt64 = u64; // Always 64-bit unsigned
|
||||
|
||||
// === Platform-dependent types ===
|
||||
// These types adapt to the target platform's pointer width
|
||||
|
||||
pub type sal_Size = usize; // Platform pointer width (32/64-bit)
|
||||
pub type sal_sSize = isize; // Signed platform pointer width
|
||||
pub type sal_PtrDiff = isize; // Pointer difference type
|
||||
pub type sal_IntPtr = isize; // Integer type that can hold a pointer
|
||||
pub type sal_uIntPtr = usize; // Unsigned integer type that can hold a pointer
|
||||
|
||||
// === Special types ===
|
||||
|
||||
pub type sal_Unicode = u16; // UTF-16 code unit (always 16-bit)
|
||||
pub type sal_Handle = *mut std::ffi::c_void; // Generic opaque handle
|
||||
|
||||
pub const SAL_FALSE: sal_Bool = 0;
|
||||
pub const SAL_TRUE: sal_Bool = 1;
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,828 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! Integration tests for Rust UNO Language Binding
|
||||
//!
|
||||
//! These tests verify that the UNO binding works correctly with the
|
||||
//! actual LibreOffice UNO components through FFI/C interop.
|
||||
//!
|
||||
//! **Note**: Rust-side unit tests (traits, constructors, etc.) are located
|
||||
//! in separate test files like `string_tests.rs`.
|
||||
//!
|
||||
//! # Test Coverage
|
||||
//!
|
||||
//! ## Core FFI Functionality Tests
|
||||
//! - `test_xtest_interface_creation()` - Basic interface instantiation
|
||||
//! - `test_boolean_operations()` - Boolean data type FFI handling (both true/false)
|
||||
//! - `test_integer_operations()` - Signed integer types FFI with wrong values
|
||||
//! - `test_unsigned_integer_operations()` - Unsigned integer types FFI with wrong values
|
||||
//! - `test_64bit_operations()` - 64-bit integer types FFI with wrong values
|
||||
//! - `test_floating_point_operations()` - Float and double types FFI with wrong values
|
||||
//! - `test_char_operations()` - Unicode character FFI handling with wrong values
|
||||
//! - `test_string_operations()` - OUString FFI handling with UNO components
|
||||
//! - `test_type_operations()` - UNO Type FFI interop with C++ components
|
||||
//! - `test_rust_type_creation()` - Rust Type wrapper construction and basic operations
|
||||
//! - `test_type_assignment_compatibility()` - Type assignment compatibility testing
|
||||
//! - `test_type_cloning_and_memory()` - Type cloning and memory management validation
|
||||
//!
|
||||
//! ## Comprehensive UNO Component Validation Tests
|
||||
//! - `test_wrong_values_comprehensive()` - Extensive wrong value testing for all data types
|
||||
//! - `test_edge_cases_and_boundary_values()` - Boundary conditions and special values
|
||||
//! - `test_consistency_across_multiple_calls()` - Stability and consistency verification
|
||||
//! - `test_all_operations_comprehensive()` - Cross-functional integration testing
|
||||
//!
|
||||
//! ## Test Strategy
|
||||
//! Each FFI function is tested with:
|
||||
//! - Correct expected values (should return true)
|
||||
//! - Multiple wrong values (should return false)
|
||||
//! - Boundary values and edge cases
|
||||
//! - Consistency across multiple calls
|
||||
//!
|
||||
//! This ensures robust validation of the UNO binding's FFI data type handling
|
||||
//! and error detection capabilities with actual UNO components.
|
||||
|
||||
use crate::ffi::rtl_string::rtl_uString;
|
||||
use crate::ffi::sal_types::*;
|
||||
use crate::ffi::type_ffi::typelib_TypeDescriptionReference;
|
||||
use crate::core::{OUString, Type, typelib_TypeClass};
|
||||
|
||||
// === FFI Declarations for Test Functions ===
|
||||
#[allow(unused)]
|
||||
unsafe extern "C" {
|
||||
/// Print a greeting message from the UNO test component
|
||||
pub fn xtest_hello();
|
||||
|
||||
/// Get a boolean value from UNO test component (always returns true)
|
||||
pub fn xtest_getBoolean() -> sal_Bool;
|
||||
|
||||
/// Test if a boolean value matches the expected UNO boolean
|
||||
pub fn xtest_isBoolean(value: sal_Bool) -> sal_Bool;
|
||||
|
||||
/// Get a byte value from UNO test component (always returns -12)
|
||||
pub fn xtest_getByte() -> sal_Int8;
|
||||
|
||||
/// Test if a byte value matches the expected UNO byte (-12)
|
||||
pub fn xtest_isByte(value: sal_Int8) -> sal_Bool;
|
||||
|
||||
/// Get a short value from UNO test component (always returns -1234)
|
||||
pub fn xtest_getShort() -> sal_Int16;
|
||||
|
||||
/// Test if a short value matches the expected UNO short (-1234)
|
||||
pub fn xtest_isShort(value: sal_Int16) -> sal_Bool;
|
||||
|
||||
/// Get a long value from UNO test component (always returns -123456)
|
||||
pub fn xtest_getLong() -> sal_Int32;
|
||||
|
||||
/// Test if a long value matches the expected UNO long (-123456)
|
||||
pub fn xtest_isLong(value: sal_Int32) -> sal_Bool;
|
||||
|
||||
/// Get an unsigned short value from UNO test component (always returns 54321)
|
||||
pub fn xtest_getUnsignedShort() -> sal_uInt16;
|
||||
|
||||
/// Test if an unsigned short value matches the expected UNO unsigned short (54321)
|
||||
pub fn xtest_isUnsignedShort(value: sal_uInt16) -> sal_Bool;
|
||||
|
||||
/// Get an unsigned long value from UNO test component (always returns 3456789012)
|
||||
pub fn xtest_getUnsignedLong() -> sal_uInt32;
|
||||
|
||||
/// Test if an unsigned long value matches the expected UNO unsigned long (3456789012)
|
||||
pub fn xtest_isUnsignedLong(value: sal_uInt32) -> sal_Bool;
|
||||
|
||||
/// Get a hyper (64-bit) value from UNO test component (always returns -123456789)
|
||||
pub fn xtest_getHyper() -> sal_Int64;
|
||||
|
||||
/// Test if a hyper value matches the expected UNO hyper (-123456789)
|
||||
pub fn xtest_isHyper(value: sal_Int64) -> sal_Bool;
|
||||
|
||||
/// Get an unsigned hyper (64-bit) value from UNO test component (always returns 9876543210)
|
||||
pub fn xtest_getUnsignedHyper() -> sal_uInt64;
|
||||
|
||||
/// Test if an unsigned hyper value matches the expected UNO unsigned hyper (9876543210)
|
||||
pub fn xtest_isUnsignedHyper(value: sal_uInt64) -> sal_Bool;
|
||||
|
||||
/// Get a float value from UNO test component (always returns -10.25)
|
||||
pub fn xtest_getFloat() -> f32;
|
||||
|
||||
/// Test if a float value matches the expected UNO float (-10.25)
|
||||
pub fn xtest_isFloat(value: f32) -> sal_Bool;
|
||||
|
||||
/// Get a double value from UNO test component (always returns 100.5)
|
||||
pub fn xtest_getDouble() -> f64;
|
||||
|
||||
/// Test if a double value matches the expected UNO double (100.5)
|
||||
pub fn xtest_isDouble(value: f64) -> sal_Bool;
|
||||
|
||||
/// Test if a character value matches the expected UNO character ('Ö')
|
||||
pub fn xtest_isChar(value: sal_Unicode) -> sal_Bool;
|
||||
|
||||
/// Get a character value from UNO test component (always returns 'Ö')
|
||||
pub fn xtest_getChar() -> sal_Unicode;
|
||||
|
||||
/// Get a string value from UNO test component - returns pointer to rtl_uString
|
||||
pub fn xtest_getString() -> *mut rtl_uString;
|
||||
|
||||
/// Test if a string value matches the expected string ("hä") - takes pointer to rtl_uString
|
||||
pub fn xtest_isString(value: *mut rtl_uString) -> sal_Bool;
|
||||
|
||||
/// Get a Type value from UNO test component - returns pointer to typelib_TypeDescriptionReference
|
||||
pub fn xtest_getType() -> *mut typelib_TypeDescriptionReference;
|
||||
|
||||
/// Test if a Type value matches the expected Type (cppu::UnoType<sal_Int32>) - takes pointer to typelib_TypeDescriptionReference
|
||||
pub fn xtest_isType(value: *mut typelib_TypeDescriptionReference) -> sal_Bool;
|
||||
}
|
||||
|
||||
// === Test Interface Wrapper ===
|
||||
|
||||
/// Rust wrapper for the XTest UNO interface (TEST ONLY)
|
||||
///
|
||||
/// This interface provides methods for testing basic UNO data types
|
||||
/// and serves as a demonstration of the Rust UNO binding functionality.
|
||||
///
|
||||
/// **Note**: This is a test interface, not part of the core UNO API.
|
||||
pub struct XTest;
|
||||
|
||||
#[allow(unused)]
|
||||
impl XTest {
|
||||
/// Create a new XTest interface instance
|
||||
pub fn new() -> Self {
|
||||
XTest
|
||||
}
|
||||
|
||||
/// Print a greeting message from the UNO component
|
||||
pub fn hello(&self) {
|
||||
unsafe {
|
||||
xtest_hello();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a boolean value from UNO
|
||||
pub fn get_boolean(&self) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_getBoolean();
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if a boolean value matches the expected UNO boolean
|
||||
pub fn is_boolean(&self, value: bool) -> bool {
|
||||
unsafe {
|
||||
let sal_value = if value { 1 } else { 0 };
|
||||
let result = xtest_isBoolean(sal_value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a byte value from UNO
|
||||
pub fn get_byte(&self) -> i8 {
|
||||
unsafe { xtest_getByte() }
|
||||
}
|
||||
|
||||
/// Test if a byte value matches the expected UNO byte
|
||||
pub fn is_byte(&self, value: i8) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isByte(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a short value from UNO
|
||||
pub fn get_short(&self) -> i16 {
|
||||
unsafe { xtest_getShort() }
|
||||
}
|
||||
|
||||
/// Test if a short value matches the expected UNO short
|
||||
pub fn is_short(&self, value: i16) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isShort(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a long value from UNO
|
||||
pub fn get_long(&self) -> i32 {
|
||||
unsafe { xtest_getLong() }
|
||||
}
|
||||
|
||||
/// Test if a long value matches the expected UNO long
|
||||
pub fn is_long(&self, value: i32) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isLong(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an unsigned short value from UNO
|
||||
pub fn get_unsigned_short(&self) -> u16 {
|
||||
unsafe { xtest_getUnsignedShort() }
|
||||
}
|
||||
|
||||
/// Test if an unsigned short value matches the expected UNO unsigned short
|
||||
pub fn is_unsigned_short(&self, value: u16) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isUnsignedShort(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an unsigned long value from UNO
|
||||
pub fn get_unsigned_long(&self) -> u32 {
|
||||
unsafe { xtest_getUnsignedLong() }
|
||||
}
|
||||
|
||||
/// Test if an unsigned long value matches the expected UNO unsigned long
|
||||
pub fn is_unsigned_long(&self, value: u32) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isUnsignedLong(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a hyper (64-bit) value from UNO
|
||||
pub fn get_hyper(&self) -> i64 {
|
||||
unsafe { xtest_getHyper() }
|
||||
}
|
||||
|
||||
/// Test if a hyper value matches the expected UNO hyper
|
||||
pub fn is_hyper(&self, value: i64) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isHyper(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an unsigned hyper (64-bit) value from UNO
|
||||
pub fn get_unsigned_hyper(&self) -> u64 {
|
||||
unsafe { xtest_getUnsignedHyper() }
|
||||
}
|
||||
|
||||
/// Test if an unsigned hyper value matches the expected UNO unsigned hyper
|
||||
pub fn is_unsigned_hyper(&self, value: u64) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isUnsignedHyper(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a float value from UNO
|
||||
pub fn get_float(&self) -> f32 {
|
||||
unsafe { xtest_getFloat() }
|
||||
}
|
||||
|
||||
/// Test if a float value matches the expected UNO float
|
||||
pub fn is_float(&self, value: f32) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isFloat(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a double value from UNO
|
||||
pub fn get_double(&self) -> f64 {
|
||||
unsafe { xtest_getDouble() }
|
||||
}
|
||||
|
||||
/// Test if a double value matches the expected UNO double
|
||||
pub fn is_double(&self, value: f64) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isDouble(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
/// Get a char value from UNO
|
||||
pub fn get_char(&self) -> sal_Unicode {
|
||||
unsafe { xtest_getChar() }
|
||||
}
|
||||
|
||||
/// Test if a char value matches the expected UNO char
|
||||
pub fn is_char(&self, value: sal_Unicode) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isChar(value);
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a string value from UNO
|
||||
pub fn get_string(&self) -> OUString {
|
||||
unsafe {
|
||||
let result = xtest_getString();
|
||||
OUString::from_raw(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if a string value matches the expected UNO string
|
||||
pub fn is_string(&self, value: OUString) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isString(value.into_raw());
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a Type value from UNO (returns cppu::UnoType<sal_Int32>)
|
||||
pub fn get_type(&self) -> Type {
|
||||
unsafe { Type::from_typelib_ref_no_acquire(xtest_getType()) }
|
||||
}
|
||||
|
||||
/// Test if a Type value matches the expected UNO Type
|
||||
///
|
||||
/// Note: This test uses raw pointers for C++ Type interop.
|
||||
/// For Rust Type instances, use direct comparison methods.
|
||||
pub fn is_type(&self, cpp_type: Type) -> bool {
|
||||
unsafe {
|
||||
let result = xtest_isType(cpp_type.into_raw());
|
||||
result != 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for XTest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// === Integration Tests ===
|
||||
|
||||
#[test]
|
||||
fn test_xtest_interface_creation() {
|
||||
let xtest = XTest::new();
|
||||
// Test that we can create the interface without panicking
|
||||
xtest.hello(); // This should print a message
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test getBoolean
|
||||
let result = xtest.get_boolean();
|
||||
assert!(result, "getBoolean should return true");
|
||||
|
||||
// Test isBoolean with true
|
||||
let is_true = xtest.is_boolean(true);
|
||||
assert!(is_true, "isBoolean(true) should return true");
|
||||
|
||||
// Test isBoolean with false
|
||||
let is_false = xtest.is_boolean(false);
|
||||
assert!(!is_false, "isBoolean(false) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test byte operations
|
||||
let byte_val = xtest.get_byte();
|
||||
assert_eq!(byte_val, -12, "getByte should return -12");
|
||||
|
||||
let is_byte = xtest.is_byte(-12);
|
||||
assert!(is_byte, "isByte(-12) should return true");
|
||||
|
||||
let is_wrong_byte = xtest.is_byte(0);
|
||||
assert!(!is_wrong_byte, "isByte(0) should return false");
|
||||
|
||||
// Test short operations
|
||||
let short_val = xtest.get_short();
|
||||
assert_eq!(short_val, -1234, "getShort should return -1234");
|
||||
|
||||
let is_short = xtest.is_short(-1234);
|
||||
assert!(is_short, "isShort(-1234) should return true");
|
||||
|
||||
let is_wrong_short = xtest.is_short(0);
|
||||
assert!(!is_wrong_short, "isShort(0) should return false");
|
||||
|
||||
// Test long operations
|
||||
let long_val = xtest.get_long();
|
||||
assert_eq!(long_val, -123456, "getLong should return -123456");
|
||||
|
||||
let is_long = xtest.is_long(-123456);
|
||||
assert!(is_long, "isLong(-123456) should return true");
|
||||
|
||||
let is_wrong_long = xtest.is_long(0);
|
||||
assert!(!is_wrong_long, "isLong(0) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsigned_integer_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test unsigned short operations
|
||||
let ushort_val = xtest.get_unsigned_short();
|
||||
assert_eq!(ushort_val, 54321, "getUnsignedShort should return 54321");
|
||||
|
||||
let is_ushort = xtest.is_unsigned_short(54321);
|
||||
assert!(is_ushort, "isUnsignedShort(54321) should return true");
|
||||
|
||||
let is_wrong_ushort = xtest.is_unsigned_short(0);
|
||||
assert!(!is_wrong_ushort, "isUnsignedShort(0) should return false");
|
||||
|
||||
// Test unsigned long operations
|
||||
let ulong_val = xtest.get_unsigned_long();
|
||||
assert_eq!(
|
||||
ulong_val, 3456789012,
|
||||
"getUnsignedLong should return 3456789012"
|
||||
);
|
||||
|
||||
let is_ulong = xtest.is_unsigned_long(3456789012);
|
||||
assert!(is_ulong, "isUnsignedLong(3456789012) should return true");
|
||||
|
||||
let is_wrong_ulong = xtest.is_unsigned_long(0);
|
||||
assert!(!is_wrong_ulong, "isUnsignedLong(0) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_64bit_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test hyper operations
|
||||
let hyper_val = xtest.get_hyper();
|
||||
assert_eq!(hyper_val, -123456789, "getHyper should return -123456789");
|
||||
|
||||
let is_hyper = xtest.is_hyper(-123456789);
|
||||
assert!(is_hyper, "isHyper(-123456789) should return true");
|
||||
|
||||
let is_wrong_hyper = xtest.is_hyper(0);
|
||||
assert!(!is_wrong_hyper, "isHyper(0) should return false");
|
||||
|
||||
// Test unsigned hyper operations
|
||||
let uhyper_val = xtest.get_unsigned_hyper();
|
||||
assert_eq!(
|
||||
uhyper_val, 9876543210,
|
||||
"getUnsignedHyper should return 9876543210"
|
||||
);
|
||||
|
||||
let is_uhyper = xtest.is_unsigned_hyper(9876543210);
|
||||
assert!(is_uhyper, "isUnsignedHyper(9876543210) should return true");
|
||||
|
||||
let is_wrong_uhyper = xtest.is_unsigned_hyper(0);
|
||||
assert!(!is_wrong_uhyper, "isUnsignedHyper(0) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_floating_point_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test float operations
|
||||
let float_val = xtest.get_float();
|
||||
assert_eq!(float_val, -10.25, "getFloat should return -10.25");
|
||||
|
||||
let is_float = xtest.is_float(-10.25);
|
||||
assert!(is_float, "isFloat(-10.25) should return true");
|
||||
|
||||
let is_wrong_float = xtest.is_float(0.0);
|
||||
assert!(!is_wrong_float, "isFloat(0.0) should return false");
|
||||
|
||||
// Test double operations
|
||||
let double_val = xtest.get_double();
|
||||
assert_eq!(double_val, 100.5, "getDouble should return 100.5");
|
||||
|
||||
let is_double = xtest.is_double(100.5);
|
||||
assert!(is_double, "isDouble(100.5) should return true");
|
||||
|
||||
let is_wrong_double = xtest.is_double(0.0);
|
||||
assert!(!is_wrong_double, "isDouble(0.0) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_char_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test char operations - 'Ö' is Unicode codepoint U+00D6 (214)
|
||||
let char_val = xtest.get_char();
|
||||
assert_eq!(
|
||||
char_val, 214u16,
|
||||
"getChar should return 214 (Unicode for 'Ö')"
|
||||
);
|
||||
|
||||
let is_char = xtest.is_char(214u16);
|
||||
assert!(is_char, "isChar(214) should return true");
|
||||
|
||||
let is_wrong_char = xtest.is_char(65u16); // 'A'
|
||||
assert!(!is_wrong_char, "isChar(65) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test string operations - expected value is "hä"
|
||||
let string_val = xtest.get_string();
|
||||
assert_eq!(string_val.to_string(), "hä", "getString should return 'hä'");
|
||||
|
||||
// Test string validation with correct value
|
||||
let test_string = OUString::from_utf8("hä");
|
||||
let is_string = xtest.is_string(test_string);
|
||||
assert!(is_string, "isString('hä') should return true");
|
||||
|
||||
// Test string validation with wrong value
|
||||
let wrong_string = OUString::from_utf8("wrong");
|
||||
let is_wrong_string = xtest.is_string(wrong_string);
|
||||
assert!(!is_wrong_string, "isString('wrong') should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_operations() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test getting a Type value from C++ component
|
||||
let cpp_type = xtest.get_type();
|
||||
// Test that we got a valid Type (non-void type in this case)
|
||||
assert_ne!(
|
||||
cpp_type.get_type_class(),
|
||||
typelib_TypeClass::typelib_TypeClass_VOID,
|
||||
"C++ Type should not be void type"
|
||||
);
|
||||
println!("Got C++ Type: {:?}", cpp_type);
|
||||
|
||||
// Test that the returned Type matches the expected Type (cppu::UnoType<sal_Int32>)
|
||||
let is_correct = xtest.is_type(cpp_type);
|
||||
assert!(is_correct, "C++ Type should match expected sal_Int32 type");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_values_comprehensive() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test multiple wrong values for each function to ensure robust validation
|
||||
|
||||
// Test multiple wrong byte values
|
||||
let is_wrong_byte1 = xtest.is_byte(100);
|
||||
assert!(!is_wrong_byte1, "isByte(100) should return false");
|
||||
|
||||
let is_wrong_byte2 = xtest.is_byte(-100);
|
||||
assert!(!is_wrong_byte2, "isByte(-100) should return false");
|
||||
|
||||
// Test multiple wrong short values
|
||||
let is_wrong_short1 = xtest.is_short(1000);
|
||||
assert!(!is_wrong_short1, "isShort(1000) should return false");
|
||||
|
||||
let is_wrong_short2 = xtest.is_short(-5000);
|
||||
assert!(!is_wrong_short2, "isShort(-5000) should return false");
|
||||
|
||||
// Test multiple wrong long values
|
||||
let is_wrong_long1 = xtest.is_long(999999);
|
||||
assert!(!is_wrong_long1, "isLong(999999) should return false");
|
||||
|
||||
let is_wrong_long2 = xtest.is_long(-999999);
|
||||
assert!(!is_wrong_long2, "isLong(-999999) should return false");
|
||||
|
||||
// Test multiple wrong unsigned short values
|
||||
let is_wrong_ushort1 = xtest.is_unsigned_short(12345);
|
||||
assert!(
|
||||
!is_wrong_ushort1,
|
||||
"isUnsignedShort(12345) should return false"
|
||||
);
|
||||
|
||||
let is_wrong_ushort2 = xtest.is_unsigned_short(65535);
|
||||
assert!(
|
||||
!is_wrong_ushort2,
|
||||
"isUnsignedShort(65535) should return false"
|
||||
);
|
||||
|
||||
// Test multiple wrong unsigned long values
|
||||
let is_wrong_ulong1 = xtest.is_unsigned_long(1234567890);
|
||||
assert!(
|
||||
!is_wrong_ulong1,
|
||||
"isUnsignedLong(1234567890) should return false"
|
||||
);
|
||||
|
||||
let is_wrong_ulong2 = xtest.is_unsigned_long(4294967295);
|
||||
assert!(
|
||||
!is_wrong_ulong2,
|
||||
"isUnsignedLong(4294967295) should return false"
|
||||
);
|
||||
|
||||
// Test multiple wrong hyper values
|
||||
let is_wrong_hyper1 = xtest.is_hyper(987654321);
|
||||
assert!(!is_wrong_hyper1, "isHyper(987654321) should return false");
|
||||
|
||||
let is_wrong_hyper2 = xtest.is_hyper(-987654321);
|
||||
assert!(!is_wrong_hyper2, "isHyper(-987654321) should return false");
|
||||
|
||||
// Test multiple wrong unsigned hyper values
|
||||
let is_wrong_uhyper1 = xtest.is_unsigned_hyper(1234567890);
|
||||
assert!(
|
||||
!is_wrong_uhyper1,
|
||||
"isUnsignedHyper(1234567890) should return false"
|
||||
);
|
||||
|
||||
let is_wrong_uhyper2 = xtest.is_unsigned_hyper(18446744073709551615);
|
||||
assert!(
|
||||
!is_wrong_uhyper2,
|
||||
"isUnsignedHyper(18446744073709551615) should return false"
|
||||
);
|
||||
|
||||
// Test multiple wrong float values
|
||||
let is_wrong_float1 = xtest.is_float(std::f32::consts::PI);
|
||||
assert!(!is_wrong_float1, "isFloat(3.14159) should return false");
|
||||
|
||||
let is_wrong_float2 = xtest.is_float(-99.99);
|
||||
assert!(!is_wrong_float2, "isFloat(-99.99) should return false");
|
||||
|
||||
// Test multiple wrong double values
|
||||
let is_wrong_double1 = xtest.is_double(std::f64::consts::E);
|
||||
assert!(!is_wrong_double1, "isDouble(2.71828) should return false");
|
||||
|
||||
let is_wrong_double2 = xtest.is_double(-200.75);
|
||||
assert!(!is_wrong_double2, "isDouble(-200.75) should return false");
|
||||
|
||||
// Test multiple wrong char values
|
||||
let is_wrong_char1 = xtest.is_char(72u16); // 'H'
|
||||
assert!(!is_wrong_char1, "isChar(72) should return false");
|
||||
|
||||
let is_wrong_char2 = xtest.is_char(8364u16); // Euro symbol '€'
|
||||
assert!(!is_wrong_char2, "isChar(8364) should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_operations_comprehensive() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// This test verifies that all methods work together
|
||||
// and the component maintains state correctly
|
||||
|
||||
// Test multiple calls to the same method
|
||||
let bool1 = xtest.get_boolean();
|
||||
let bool2 = xtest.get_boolean();
|
||||
assert_eq!(
|
||||
bool1, bool2,
|
||||
"Multiple calls to getBoolean should return same value"
|
||||
);
|
||||
|
||||
// Test that different data types work independently
|
||||
let _byte_val = xtest.get_byte();
|
||||
let _short_val = xtest.get_short();
|
||||
let _long_val = xtest.get_long();
|
||||
let _float_val = xtest.get_float();
|
||||
let _double_val = xtest.get_double();
|
||||
|
||||
// Verify boolean still works after other operations
|
||||
let bool3 = xtest.get_boolean();
|
||||
assert_eq!(bool1, bool3, "Boolean operations should be consistent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_cases_and_boundary_values() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test edge cases for each data type to ensure robust handling
|
||||
|
||||
// Test maximum and minimum values for signed integers
|
||||
let is_max_byte = xtest.is_byte(i8::MAX);
|
||||
assert!(!is_max_byte, "isByte(i8::MAX) should return false");
|
||||
|
||||
let is_min_byte = xtest.is_byte(i8::MIN);
|
||||
assert!(!is_min_byte, "isByte(i8::MIN) should return false");
|
||||
|
||||
let is_max_short = xtest.is_short(i16::MAX);
|
||||
assert!(!is_max_short, "isShort(i16::MAX) should return false");
|
||||
|
||||
let is_min_short = xtest.is_short(i16::MIN);
|
||||
assert!(!is_min_short, "isShort(i16::MIN) should return false");
|
||||
|
||||
let is_max_long = xtest.is_long(i32::MAX);
|
||||
assert!(!is_max_long, "isLong(i32::MAX) should return false");
|
||||
|
||||
let is_min_long = xtest.is_long(i32::MIN);
|
||||
assert!(!is_min_long, "isLong(i32::MIN) should return false");
|
||||
|
||||
// Test maximum values for unsigned integers
|
||||
let is_max_ushort = xtest.is_unsigned_short(u16::MAX);
|
||||
assert!(
|
||||
!is_max_ushort,
|
||||
"isUnsignedShort(u16::MAX) should return false"
|
||||
);
|
||||
|
||||
let is_max_ulong = xtest.is_unsigned_long(u32::MAX);
|
||||
assert!(
|
||||
!is_max_ulong,
|
||||
"isUnsignedLong(u32::MAX) should return false"
|
||||
);
|
||||
|
||||
// Test edge cases for 64-bit integers
|
||||
let is_max_hyper = xtest.is_hyper(i64::MAX);
|
||||
assert!(!is_max_hyper, "isHyper(i64::MAX) should return false");
|
||||
|
||||
let is_min_hyper = xtest.is_hyper(i64::MIN);
|
||||
assert!(!is_min_hyper, "isHyper(i64::MIN) should return false");
|
||||
|
||||
let is_max_uhyper = xtest.is_unsigned_hyper(u64::MAX);
|
||||
assert!(
|
||||
!is_max_uhyper,
|
||||
"isUnsignedHyper(u64::MAX) should return false"
|
||||
);
|
||||
|
||||
// Test floating point edge cases
|
||||
let is_infinity = xtest.is_float(f32::INFINITY);
|
||||
assert!(!is_infinity, "isFloat(INFINITY) should return false");
|
||||
|
||||
let is_neg_infinity = xtest.is_float(f32::NEG_INFINITY);
|
||||
assert!(
|
||||
!is_neg_infinity,
|
||||
"isFloat(NEG_INFINITY) should return false"
|
||||
);
|
||||
|
||||
let is_double_infinity = xtest.is_double(f64::INFINITY);
|
||||
assert!(
|
||||
!is_double_infinity,
|
||||
"isDouble(INFINITY) should return false"
|
||||
);
|
||||
|
||||
let is_double_neg_infinity = xtest.is_double(f64::NEG_INFINITY);
|
||||
assert!(
|
||||
!is_double_neg_infinity,
|
||||
"isDouble(NEG_INFINITY) should return false"
|
||||
);
|
||||
|
||||
// Test special Unicode characters
|
||||
let is_null_char = xtest.is_char(0u16); // Null character
|
||||
assert!(!is_null_char, "isChar(0) should return false");
|
||||
|
||||
let is_high_unicode = xtest.is_char(0xFFFF); // Maximum Unicode BMP
|
||||
assert!(!is_high_unicode, "isChar(0xFFFF) should return false");
|
||||
|
||||
// Test empty and special strings
|
||||
let empty_string = OUString::from_utf8("");
|
||||
let is_empty_string = xtest.is_string(empty_string);
|
||||
assert!(!is_empty_string, "isString(\"\") should return false");
|
||||
|
||||
let unicode_string = OUString::from_utf8("🦀"); // Rust crab emoji
|
||||
let is_unicode_string = xtest.is_string(unicode_string);
|
||||
assert!(!is_unicode_string, "isString(\"🦀\") should return false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_consistency_across_multiple_calls() {
|
||||
let xtest = XTest::new();
|
||||
|
||||
// Test that all getter functions return consistent values across multiple calls
|
||||
|
||||
// Test consistency for all data types
|
||||
for _ in 0..5 {
|
||||
// Boolean consistency
|
||||
let bool_val = xtest.get_boolean();
|
||||
assert!(bool_val, "getBoolean should always return true");
|
||||
|
||||
// Integer consistency
|
||||
let byte_val = xtest.get_byte();
|
||||
assert_eq!(byte_val, -12, "getByte should always return -12");
|
||||
|
||||
let short_val = xtest.get_short();
|
||||
assert_eq!(short_val, -1234, "getShort should always return -1234");
|
||||
|
||||
let long_val = xtest.get_long();
|
||||
assert_eq!(long_val, -123456, "getLong should always return -123456");
|
||||
|
||||
// Unsigned integer consistency
|
||||
let ushort_val = xtest.get_unsigned_short();
|
||||
assert_eq!(
|
||||
ushort_val, 54321,
|
||||
"getUnsignedShort should always return 54321"
|
||||
);
|
||||
|
||||
let ulong_val = xtest.get_unsigned_long();
|
||||
assert_eq!(
|
||||
ulong_val, 3456789012,
|
||||
"getUnsignedLong should always return 3456789012"
|
||||
);
|
||||
|
||||
// 64-bit consistency
|
||||
let hyper_val = xtest.get_hyper();
|
||||
assert_eq!(
|
||||
hyper_val, -123456789,
|
||||
"getHyper should always return -123456789"
|
||||
);
|
||||
|
||||
let uhyper_val = xtest.get_unsigned_hyper();
|
||||
assert_eq!(
|
||||
uhyper_val, 9876543210,
|
||||
"getUnsignedHyper should always return 9876543210"
|
||||
);
|
||||
|
||||
// Floating point consistency
|
||||
let float_val = xtest.get_float();
|
||||
assert_eq!(float_val, -10.25, "getFloat should always return -10.25");
|
||||
|
||||
let double_val = xtest.get_double();
|
||||
assert_eq!(double_val, 100.5, "getDouble should always return 100.5");
|
||||
|
||||
// Character consistency
|
||||
let char_val = xtest.get_char();
|
||||
assert_eq!(char_val, 214u16, "getChar should always return 214");
|
||||
|
||||
// String consistency
|
||||
let string_val = xtest.get_string();
|
||||
assert_eq!(
|
||||
string_val.to_string(),
|
||||
"hä",
|
||||
"getString should always return 'hä'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,173 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! UNO Type System FFI
|
||||
//!
|
||||
//! This module contains FFI bindings for the UNO type system, including the
|
||||
//! typelib_TypeClass enum and related structures for type introspection.
|
||||
|
||||
// Allow non-standard naming for FFI types that must match C API exactly
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::ffi::rtl_string::rtl_uString;
|
||||
use crate::ffi::sal_types::*;
|
||||
|
||||
// === UNO Type System FFI Declarations ===
|
||||
|
||||
/// UNO type class enumeration - binary compatible with the IDL enum com.sun.star.uno.TypeClass
|
||||
///
|
||||
/// This enumeration defines all possible type categories in the UNO type system.
|
||||
/// Each type has a specific class that determines how it's handled in marshalling,
|
||||
/// method dispatch, and memory management. The values must match exactly with
|
||||
/// the corresponding C/C++ enum to ensure binary compatibility.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum typelib_TypeClass {
|
||||
typelib_TypeClass_VOID = 0,
|
||||
typelib_TypeClass_CHAR = 1,
|
||||
typelib_TypeClass_BOOLEAN = 2,
|
||||
typelib_TypeClass_BYTE = 3,
|
||||
typelib_TypeClass_SHORT = 4,
|
||||
typelib_TypeClass_UNSIGNED_SHORT = 5,
|
||||
typelib_TypeClass_LONG = 6,
|
||||
typelib_TypeClass_UNSIGNED_LONG = 7,
|
||||
typelib_TypeClass_HYPER = 8,
|
||||
typelib_TypeClass_UNSIGNED_HYPER = 9,
|
||||
typelib_TypeClass_FLOAT = 10,
|
||||
typelib_TypeClass_DOUBLE = 11,
|
||||
typelib_TypeClass_STRING = 12,
|
||||
typelib_TypeClass_TYPE = 13,
|
||||
typelib_TypeClass_ANY = 14,
|
||||
typelib_TypeClass_ENUM = 15,
|
||||
typelib_TypeClass_TYPEDEF = 16,
|
||||
typelib_TypeClass_STRUCT = 17,
|
||||
|
||||
#[deprecated = "UNOIDL does not have a union concept"]
|
||||
typelib_TypeClass_UNION = 18,
|
||||
typelib_TypeClass_EXCEPTION = 19,
|
||||
typelib_TypeClass_SEQUENCE = 20,
|
||||
|
||||
#[deprecated = "UNOIDL does not have an array concept"]
|
||||
typelib_TypeClass_ARRAY = 21,
|
||||
typelib_TypeClass_INTERFACE = 22,
|
||||
typelib_TypeClass_SERVICE = 23,
|
||||
typelib_TypeClass_MODULE = 24,
|
||||
typelib_TypeClass_INTERFACE_METHOD = 25,
|
||||
typelib_TypeClass_INTERFACE_ATTRIBUTE = 26,
|
||||
typelib_TypeClass_UNKNOWN = 27,
|
||||
typelib_TypeClass_PROPERTY = 28,
|
||||
typelib_TypeClass_CONSTANT = 29,
|
||||
typelib_TypeClass_CONSTANTS = 30,
|
||||
typelib_TypeClass_SINGLETON = 31,
|
||||
typelib_TypeClass_MAKE_FIXED_SIZE = 0x7fffffff,
|
||||
}
|
||||
|
||||
/// Weak reference to a type description - the core UNO type reference structure
|
||||
///
|
||||
/// This structure holds a weak reference to a type description in the UNO type system.
|
||||
/// It provides efficient type identification without requiring the full type description
|
||||
/// to be loaded. Memory layout is designed for binary compatibility with LibreOffice's
|
||||
/// C implementation.
|
||||
#[repr(C)]
|
||||
pub struct typelib_TypeDescriptionReference {
|
||||
/// Reference count of type - managed automatically by acquire/release functions
|
||||
pub nRefCount: sal_Int32,
|
||||
/// Static reference count for types that persist until program termination
|
||||
pub nStaticRefCount: sal_Int32,
|
||||
/// Type class category (VOID, STRING, INTERFACE, etc.)
|
||||
pub eTypeClass: typelib_TypeClass,
|
||||
/// Fully qualified name of the type (e.g., "com.sun.star.lang.XComponent")
|
||||
pub pTypeName: *mut rtl_uString,
|
||||
/// Pointer to full type description (may be null if not loaded)
|
||||
pub pType: *mut typelib_TypeDescription,
|
||||
/// Internal unique identifier for runtime optimization
|
||||
pub pUniqueIdentifier: *mut std::os::raw::c_void,
|
||||
/// Reserved for future use
|
||||
pub pReserved: *mut std::os::raw::c_void,
|
||||
}
|
||||
|
||||
/// Full type description with complete metadata
|
||||
///
|
||||
/// Contains comprehensive information about a UNO type including size, alignment,
|
||||
/// and member details. This is loaded on demand when detailed type information
|
||||
/// is needed for marshalling, method dispatch, or introspection.
|
||||
#[repr(C)]
|
||||
pub struct typelib_TypeDescription {
|
||||
/// Reference count - managed automatically by acquire/release functions
|
||||
pub nRefCount: sal_Int32,
|
||||
/// Static reference count for types that persist until program termination
|
||||
pub nStaticRefCount: sal_Int32,
|
||||
/// Type class category (VOID, STRING, INTERFACE, etc.)
|
||||
pub eTypeClass: typelib_TypeClass,
|
||||
/// Fully qualified name of the type
|
||||
pub pTypeName: *mut rtl_uString,
|
||||
/// Self-reference pointer for type identification
|
||||
pub pSelf: *mut typelib_TypeDescription,
|
||||
/// Internal unique identifier for runtime optimization
|
||||
pub pUniqueIdentifier: *mut std::os::raw::c_void,
|
||||
/// Reserved for future use
|
||||
pub pReserved: *mut std::os::raw::c_void,
|
||||
/// Flag indicating whether the description is complete and ready to use
|
||||
pub bComplete: sal_Bool,
|
||||
/// Size of the type in bytes
|
||||
pub nSize: sal_Int32,
|
||||
/// Memory alignment requirements for the type
|
||||
pub nAlignment: sal_Int32,
|
||||
/// Back-reference to the weak type reference
|
||||
pub pWeakRef: *mut typelib_TypeDescriptionReference,
|
||||
/// Flag indicating if type can be unloaded and reloaded on demand
|
||||
pub bOnDemand: sal_Bool,
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
/// Increment reference count of type description reference
|
||||
pub fn typelib_typedescriptionreference_acquire(pRef: *mut typelib_TypeDescriptionReference);
|
||||
|
||||
/// Decrement reference count of type description reference
|
||||
pub fn typelib_typedescriptionreference_release(pRef: *mut typelib_TypeDescriptionReference);
|
||||
|
||||
/// Create new type description reference from type class and name
|
||||
pub fn typelib_typedescriptionreference_new(
|
||||
ppTDR: *mut *mut typelib_TypeDescriptionReference,
|
||||
eTypeClass: typelib_TypeClass,
|
||||
pTypeName: *mut rtl_uString,
|
||||
);
|
||||
|
||||
/// Test equality of two type description references
|
||||
pub fn typelib_typedescriptionreference_equals(
|
||||
p1: *const typelib_TypeDescriptionReference,
|
||||
p2: *const typelib_TypeDescriptionReference,
|
||||
) -> sal_Bool;
|
||||
|
||||
/// Test type assignment compatibility
|
||||
pub fn typelib_typedescriptionreference_isAssignableFrom(
|
||||
pAssignable: *mut typelib_TypeDescriptionReference,
|
||||
pFrom: *mut typelib_TypeDescriptionReference,
|
||||
) -> sal_Bool;
|
||||
|
||||
/// Get full type description from type reference
|
||||
pub fn typelib_typedescriptionreference_getDescription(
|
||||
ppRet: *mut *mut typelib_TypeDescription,
|
||||
pRef: *mut typelib_TypeDescriptionReference,
|
||||
);
|
||||
|
||||
/// Get static type reference for primitive type classes
|
||||
pub fn typelib_static_type_getByTypeClass(
|
||||
eTypeClass: typelib_TypeClass,
|
||||
) -> *mut *mut typelib_TypeDescriptionReference;
|
||||
|
||||
/// Assign type reference (with proper reference counting)
|
||||
pub fn typelib_typedescriptionreference_assign(
|
||||
ppDest: *mut *mut typelib_TypeDescriptionReference,
|
||||
pSource: *mut typelib_TypeDescriptionReference,
|
||||
);
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,108 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! FFI bindings for UNO Any type operations.
|
||||
//!
|
||||
//! This module contains direct bindings to LibreOffice's uno_Any
|
||||
//! C functions for creating, manipulating, and destroying Any values.
|
||||
//!
|
||||
//! These are low-level FFI bindings. For safe Rust wrapper, use
|
||||
//! the `Any` type from the `core` module instead.
|
||||
|
||||
// Allow non-standard naming for FFI types that must match C API exactly
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// Note: sal_types are included via typelib types
|
||||
use crate::ffi::type_ffi::{
|
||||
typelib_TypeClass, typelib_TypeDescription, typelib_TypeDescriptionReference,
|
||||
};
|
||||
|
||||
/// UNO Any type - represents a value of any UNO type
|
||||
/// This matches the C structure _uno_Any from include/uno/any2.h
|
||||
#[repr(C)]
|
||||
pub struct uno_Any {
|
||||
/// type of value
|
||||
pub pType: *mut typelib_TypeDescriptionReference,
|
||||
/// pointer to value; this may point to pReserved
|
||||
pub pData: *mut std::ffi::c_void,
|
||||
/// reserved space for storing value
|
||||
pub pReserved: *mut std::ffi::c_void,
|
||||
}
|
||||
|
||||
/// Function types for acquire/release callbacks
|
||||
pub type uno_AcquireFunc = unsafe extern "C" fn(*mut std::ffi::c_void);
|
||||
pub type uno_ReleaseFunc = unsafe extern "C" fn(*mut std::ffi::c_void);
|
||||
|
||||
unsafe extern "C" {
|
||||
/// Assign an any with a given value
|
||||
pub fn uno_any_assign(
|
||||
pDest: *mut uno_Any,
|
||||
pSource: *mut std::ffi::c_void,
|
||||
pTypeDescr: *mut typelib_TypeDescription,
|
||||
acquire: Option<uno_AcquireFunc>,
|
||||
release: Option<uno_ReleaseFunc>,
|
||||
);
|
||||
|
||||
/// Assign an any with a given value (using type reference)
|
||||
pub fn uno_type_any_assign(
|
||||
pDest: *mut uno_Any,
|
||||
pSource: *mut std::ffi::c_void,
|
||||
pType: *mut typelib_TypeDescriptionReference,
|
||||
acquire: Option<uno_AcquireFunc>,
|
||||
release: Option<uno_ReleaseFunc>,
|
||||
);
|
||||
|
||||
/// Constructs an any with a given value
|
||||
pub fn uno_any_construct(
|
||||
pDest: *mut uno_Any,
|
||||
pSource: *mut std::ffi::c_void,
|
||||
pTypeDescr: *mut typelib_TypeDescription,
|
||||
acquire: Option<uno_AcquireFunc>,
|
||||
);
|
||||
|
||||
/// Constructs an any with a given value (using type reference)
|
||||
pub fn uno_type_any_construct(
|
||||
pDest: *mut uno_Any,
|
||||
pSource: *mut std::ffi::c_void,
|
||||
pType: *mut typelib_TypeDescriptionReference,
|
||||
acquire: Option<uno_AcquireFunc>,
|
||||
);
|
||||
|
||||
/// Destructs an any
|
||||
pub fn uno_any_destruct(pValue: *mut uno_Any, release: Option<uno_ReleaseFunc>);
|
||||
|
||||
/// Sets value to void (clears the any)
|
||||
pub fn uno_any_clear(pValue: *mut uno_Any, release: Option<uno_ReleaseFunc>);
|
||||
|
||||
// Get static type descriptors by type class (returns pointer to pointer)
|
||||
pub fn typelib_static_type_getByTypeClass(
|
||||
eTypeClass: typelib_TypeClass,
|
||||
) -> *mut *mut typelib_TypeDescriptionReference;
|
||||
|
||||
}
|
||||
|
||||
/// Get type descriptor for boolean values
|
||||
pub fn get_boolean_type() -> *mut typelib_TypeDescriptionReference {
|
||||
unsafe {
|
||||
let ptr_ptr =
|
||||
typelib_static_type_getByTypeClass(typelib_TypeClass::typelib_TypeClass_BOOLEAN);
|
||||
*ptr_ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// Get type descriptor for 32-bit integer values
|
||||
pub fn get_long_type() -> *mut typelib_TypeDescriptionReference {
|
||||
unsafe {
|
||||
let ptr_ptr = typelib_static_type_getByTypeClass(typelib_TypeClass::typelib_TypeClass_LONG);
|
||||
*ptr_ptr
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,37 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! FFI bindings for UNO bridge functionality.
|
||||
//!
|
||||
//! This module contains direct bindings to C++ functions
|
||||
//! in the UNO bridge for core functionality like initialization
|
||||
//! and interface management.
|
||||
//!
|
||||
//! For specific UNO interface method calls, use the auto-generated
|
||||
//! FFI functions in the `generated` module instead.
|
||||
|
||||
// c_char import removed - no longer needed
|
||||
|
||||
// Basic UNO types
|
||||
pub use crate::ffi::sal_types::*;
|
||||
|
||||
/// Opaque pointer to a UNO XInterface
|
||||
pub type XInterface = std::ffi::c_void;
|
||||
|
||||
/// Function types for acquire/release callbacks
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type uno_AcquireFunc = unsafe extern "C" fn(*mut std::ffi::c_void);
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type uno_ReleaseFunc = unsafe extern "C" fn(*mut std::ffi::c_void);
|
||||
|
||||
unsafe extern "C" {
|
||||
/// Direct LibreOffice bootstrap function from cppuhelper
|
||||
/// Returns Reference<XComponentContext> as opaque pointer
|
||||
pub fn defaultBootstrap_InitialComponentContext() -> *mut XInterface;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/* -*- Mode: rust; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
//! FFI bindings for UNO Any type operations.
|
||||
//!
|
||||
//! This module contains direct bindings to LibreOffice's uno_Sequence
|
||||
//! C functions for creating, manipulating, and destroying Sequence values.
|
||||
//!
|
||||
//! These are low-level FFI bindings. For safe Rust wrapper, use
|
||||
//! the `Sequence` type from the `core` module instead.
|
||||
|
||||
// Allow non-standard naming for FFI types that must match C API exactly
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::ffi::sal_types::sal_Bool;
|
||||
use crate::ffi::type_ffi::*;
|
||||
use crate::ffi::uno_bridge::{uno_AcquireFunc, uno_ReleaseFunc};
|
||||
|
||||
/// UNO Sequence type - a dynamically sized array type.
|
||||
/// This matches the C structure _uno_Sequence from include/uno/sequence.h
|
||||
#[repr(C)]
|
||||
pub struct uno_Sequence {
|
||||
/// Reference count of sequence
|
||||
pub nRefCount: i32,
|
||||
/// element count
|
||||
pub nElements: i32,
|
||||
/// elements array
|
||||
pub elements: [u8; 0],
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn uno_type_sequence_construct(
|
||||
ppSequence: *mut *mut uno_Sequence,
|
||||
pType: *const typelib_TypeDescription,
|
||||
pElements: *const c_void,
|
||||
nElements: i32,
|
||||
acquire: Option<uno_AcquireFunc>,
|
||||
) -> sal_Bool;
|
||||
|
||||
pub fn uno_type_sequence_destroy(
|
||||
pSequence: *mut uno_Sequence,
|
||||
pType: *const typelib_TypeDescription,
|
||||
release: Option<uno_ReleaseFunc>,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//! Generated Rust bindings for LibreOffice UNO types
|
||||
//!
|
||||
//! This crate contains automatically generated Rust bindings
|
||||
//! for LibreOffice UNO (Universal Network Objects) types.
|
||||
|
||||
// Core UNO functionality
|
||||
pub mod core;
|
||||
pub mod examples;
|
||||
pub mod ffi;
|
||||
|
||||
// Auto-generated FFI bindings
|
||||
pub mod generated;
|
||||
|
||||
/// Entry point function called by LibreOffice to test Rust UNO bindings
|
||||
/// This function is called from desktop/source/app/app.cxx during LibreOffice startup
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn run_rust_uno_test() {
|
||||
println!("=== Rust UNO Bridge Test with Auto-Generated FFI ===");
|
||||
|
||||
// Run the load_writer example to demonstrate service-style FFI functions
|
||||
examples::load_writer::run();
|
||||
|
||||
println!("=== Rust UNO Bridge Test Done ===");
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <cppuhelper/bootstrap.hxx>
|
||||
#include <com/sun/star/uno/XComponentContext.hpp>
|
||||
#include <com/sun/star/uno/Reference.hxx>
|
||||
#include <com/sun/star/lang/XMultiComponentFactory.hpp>
|
||||
#include <com/sun/star/frame/XComponentLoader.hpp>
|
||||
#include <com/sun/star/frame/XDesktop.hpp>
|
||||
#include <sal/log.hxx>
|
||||
#include <sal/types.h>
|
||||
#include <rtl/ustring.hxx>
|
||||
|
||||
using namespace com::sun::star;
|
||||
using namespace com::sun::star::uno;
|
||||
using namespace com::sun::star::lang;
|
||||
using namespace com::sun::star::frame;
|
||||
|
||||
extern "C" {
|
||||
|
||||
/**
|
||||
* C wrapper for cppu::defaultBootstrap_InitialComponentContext()
|
||||
*
|
||||
* This function provides a C interface to the LibreOffice UNO bootstrap
|
||||
* functionality, allowing Rust code to initialize the UNO component context.
|
||||
*
|
||||
* @return XInterface* - Opaque pointer to XComponentContext (as XInterface)
|
||||
* Returns nullptr on failure.
|
||||
*/
|
||||
SAL_DLLPUBLIC_EXPORT void* defaultBootstrap_InitialComponentContext()
|
||||
{
|
||||
SAL_INFO("rust_uno", "Initializing UNO component context for Rust binding");
|
||||
|
||||
try
|
||||
{
|
||||
// Call the actual LibreOffice bootstrap function
|
||||
SAL_INFO("rust_uno", "Calling cppu::defaultBootstrap_InitialComponentContext");
|
||||
Reference<XComponentContext> xContext = cppu::defaultBootstrap_InitialComponentContext();
|
||||
|
||||
if (!xContext.is())
|
||||
{
|
||||
SAL_WARN("rust_uno", "LibreOffice bootstrap failed - component context is invalid");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SAL_INFO("rust_uno", "Component context created successfully");
|
||||
|
||||
auto res = new Reference<XComponentContext>(xContext);
|
||||
|
||||
if (!res->is())
|
||||
{
|
||||
SAL_WARN("rust_uno", "Reference wrapper creation failed - context became invalid");
|
||||
delete res;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SAL_INFO("rust_uno", "Reference wrapper created successfully - returning context to Rust");
|
||||
// Return as opaque XComponentContext pointer for Rust
|
||||
// Note: This creates a new Reference that transfers ownership to caller
|
||||
return res;
|
||||
}
|
||||
catch (const Exception& e)
|
||||
{
|
||||
SAL_WARN("rust_uno",
|
||||
"UNO exception during component context initialization: " << e.Message);
|
||||
return nullptr;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SAL_WARN("rust_uno",
|
||||
"Standard exception during component context initialization: " << e.what());
|
||||
return nullptr;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SAL_WARN("rust_uno", "Unknown exception during component context initialization");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user