From 03107ddfe86c797dc59cac9b984d845cf8837a8e Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Saini Date: Fri, 7 Oct 2022 14:03:40 +0530 Subject: [PATCH 1/4] ci: enable share_plus integration tests for windows --- .github/workflows/share_plus.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/share_plus.yaml b/.github/workflows/share_plus.yaml index 44b432641a..90e843ff17 100644 --- a/.github/workflows/share_plus.yaml +++ b/.github/workflows/share_plus.yaml @@ -165,7 +165,6 @@ jobs: run: ./.github/workflows/scripts/build-examples.sh windows ./lib/main.dart windows_integration_test: - if: false # TODO: enable this if it is possible to register a mailto protocol handler on github hosted runners runs-on: windows-latest timeout-minutes: 30 steps: From 8ac8a22b32b00089dbdeec50e6ef5235551a0637 Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Saini Date: Fri, 7 Oct 2022 14:08:05 +0530 Subject: [PATCH 2/4] fix: windows support in share_plus example --- .../share_plus/example/lib/main.dart | 35 +++++++++++++------ .../example/lib/utils/file_picker_web.dart | 4 +++ .../example/lib/utils/file_picker_win.dart | 9 +++++ .../share_plus/example/pubspec.yaml | 1 + 4 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 packages/share_plus/share_plus/example/lib/utils/file_picker_web.dart create mode 100644 packages/share_plus/share_plus/example/lib/utils/file_picker_win.dart diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index 7799d26dd0..8fbef80b38 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -4,11 +4,14 @@ // ignore_for_file: public_member_api_docs +import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:image_picker/image_picker.dart'; import 'image_previews.dart'; +import 'utils/file_picker_win.dart' + if (dart.library.html) 'utils/file_picker_web.dart'; void main() { runApp(const DemoApp()); @@ -67,15 +70,27 @@ class DemoAppState extends State { leading: const Icon(Icons.add), title: const Text('Add image'), onTap: () async { - final imagePicker = ImagePicker(); - final pickedFile = await imagePicker.pickImage( - source: ImageSource.gallery, - ); - if (pickedFile != null) { - setState(() { - imagePaths.add(pickedFile.path); - imageNames.add(pickedFile.name); - }); + // Using `package:image_picker` to get image from gallery. + if (Platform.isWindows) { + // Using `package:filepicker_windows` on Windows, since `package:image_picker` is not supported. + final path = await pickFile(); + if (path != null) { + setState(() { + imagePaths.add(path); + imageNames.add(path.split('\\').last); + }); + } + } else { + final imagePicker = ImagePicker(); + final pickedFile = await imagePicker.pickImage( + source: ImageSource.gallery, + ); + if (pickedFile != null) { + setState(() { + imagePaths.add(pickedFile.path); + imageNames.add(pickedFile.name); + }); + } } }, ), diff --git a/packages/share_plus/share_plus/example/lib/utils/file_picker_web.dart b/packages/share_plus/share_plus/example/lib/utils/file_picker_web.dart new file mode 100644 index 0000000000..77c0c51a56 --- /dev/null +++ b/packages/share_plus/share_plus/example/lib/utils/file_picker_web.dart @@ -0,0 +1,4 @@ +/// Dummy implementation for non `dart.library.io` platforms. +Future pickFile() async { + throw UnimplementedError(); +} diff --git a/packages/share_plus/share_plus/example/lib/utils/file_picker_win.dart b/packages/share_plus/share_plus/example/lib/utils/file_picker_win.dart new file mode 100644 index 0000000000..f4f8e2b20e --- /dev/null +++ b/packages/share_plus/share_plus/example/lib/utils/file_picker_win.dart @@ -0,0 +1,9 @@ +import 'package:filepicker_windows/filepicker_windows.dart'; + +/// Picks a file from the file system on Windows & returns it's path as [String]. +Future pickFile() async { + final picker = OpenFilePicker() + ..filterSpecification = {'Images': '*.jpg;*.jpeg;*.png;*.gif'}; + final result = picker.getFile(); + return result?.path; +} diff --git a/packages/share_plus/share_plus/example/pubspec.yaml b/packages/share_plus/share_plus/example/pubspec.yaml index 20d08a0935..a5b2f55a4b 100644 --- a/packages/share_plus/share_plus/example/pubspec.yaml +++ b/packages/share_plus/share_plus/example/pubspec.yaml @@ -7,6 +7,7 @@ dependencies: share_plus: path: ../ image_picker: ^0.8.4 + filepicker_windows: ^2.0.2 dependency_overrides: share_plus_linux: From 98d31e62a61a4461cc2d1a406ef8f9e21bad52f2 Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Saini Date: Fri, 7 Oct 2022 14:42:44 +0530 Subject: [PATCH 3/4] test: update share_plus_windows tests --- .../test/share_plus_windows_test.dart | 78 +++++++++++++------ 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/packages/share_plus/share_plus_windows/test/share_plus_windows_test.dart b/packages/share_plus/share_plus_windows/test/share_plus_windows_test.dart index 3618120d1f..fffa7c93e4 100644 --- a/packages/share_plus/share_plus_windows/test/share_plus_windows_test.dart +++ b/packages/share_plus/share_plus_windows/test/share_plus_windows_test.dart @@ -1,40 +1,72 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; import 'package:share_plus_windows/share_plus_windows.dart'; +import 'package:share_plus_windows/src/version_helper.dart'; +import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; +import 'package:share_plus_platform_interface/method_channel/method_channel_share.dart'; + import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:url_launcher_platform_interface/link.dart'; void main() { - test('registered instance', () { - ShareWindows.registerWith(); - expect(SharePlatform.instance, isA()); - }); - test('url encoding is correct for &', () async { - final mock = MockUrlLauncherPlatform(); - UrlLauncherPlatform.instance = mock; + test( + 'registered instance', + () { + ShareWindows.registerWith(); + expect(SharePlatform.instance, isA()); + }, + skip: VersionHelper.instance.isWindows10RS5OrGreater, + ); + + test( + 'registered instance', + () { + ShareWindows.registerWith(); + expect(SharePlatform.instance, isA()); + }, + skip: !VersionHelper.instance.isWindows10RS5OrGreater, + ); + + // These tests are only valid on Windows versions lower than 10.0.17763.0. + + test( + 'url encoding is correct for &', + () async { + final mock = MockUrlLauncherPlatform(); + UrlLauncherPlatform.instance = mock; - await ShareWindows().share('foo&bar', subject: 'bar&foo'); + await ShareWindows().share('foo&bar', subject: 'bar&foo'); - expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar'); - }); + expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar'); + }, + skip: VersionHelper.instance.isWindows10RS5OrGreater, + ); // see https://github.com/dart-lang/sdk/issues/43838#issuecomment-823551891 - test('url encoding is correct for spaces', () async { - final mock = MockUrlLauncherPlatform(); - UrlLauncherPlatform.instance = mock; + test( + 'url encoding is correct for spaces', + () async { + final mock = MockUrlLauncherPlatform(); + UrlLauncherPlatform.instance = mock; - await ShareWindows().share('foo bar', subject: 'bar foo'); + await ShareWindows().share('foo bar', subject: 'bar foo'); - expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar'); - }); + expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar'); + }, + skip: VersionHelper.instance.isWindows10RS5OrGreater, + ); - test('throws when url_launcher can\'t launch uri', () async { - final mock = MockUrlLauncherPlatform(); - mock.canLaunchMockValue = false; - UrlLauncherPlatform.instance = mock; + test( + 'throws when url_launcher can\'t launch uri', + () async { + final mock = MockUrlLauncherPlatform(); + mock.canLaunchMockValue = false; + UrlLauncherPlatform.instance = mock; - expect(() async => await ShareWindows().share('foo bar'), throwsException); - }); + expect( + () async => await ShareWindows().share('foo bar'), throwsException); + }, + skip: VersionHelper.instance.isWindows10RS5OrGreater, + ); } class MockUrlLauncherPlatform extends UrlLauncherPlatform { From 4517fcf9fb23ffe6334a419db18e3c31479bb5ff Mon Sep 17 00:00:00 2001 From: Hitesh Kumar Saini Date: Fri, 7 Oct 2022 14:54:16 +0530 Subject: [PATCH 4/4] feat(share_plus)!: native share dialog for windows * Platform specific implementation for using DataTransferManager on Windows * DataTransferManager is only used on (& available for) Windows 10 builds 17763 or higher. * On older Windows versions, existing old implementation is used i.e. launching mailto: URIs with subject & body query parameters. * Getting the share result is not supported on Windows. Thus, shareWithResult & shareFilesWithResult always return ShareResultStatus.unavailable. * This platform specific implementation beings supports for all existing methods available in package:share_plus to Windows, namely: share, shareFiles, shareWithResult, shareFilesWithResult, shareXFiles. --- .../share_plus_windows/.clang-format | 12 + .../lib/share_plus_windows.dart | 11 +- .../lib/src/version_helper.dart | 43 ++ .../share_plus_windows/pubspec.yaml | 6 +- .../share_plus_windows/windows/.gitignore | 17 + .../share_plus_windows/windows/CMakeLists.txt | 53 ++ .../share_plus_windows_plugin_c_api.h | 23 + .../windows/share_plus_windows_plugin.cpp | 270 +++++++++ .../windows/share_plus_windows_plugin.h | 83 +++ .../share_plus_windows_plugin_c_api.cpp | 12 + .../share_plus_windows/windows/vector.h | 572 ++++++++++++++++++ 11 files changed, 1096 insertions(+), 6 deletions(-) create mode 100644 packages/share_plus/share_plus_windows/.clang-format create mode 100644 packages/share_plus/share_plus_windows/lib/src/version_helper.dart create mode 100644 packages/share_plus/share_plus_windows/windows/.gitignore create mode 100644 packages/share_plus/share_plus_windows/windows/CMakeLists.txt create mode 100644 packages/share_plus/share_plus_windows/windows/include/share_plus_windows/share_plus_windows_plugin_c_api.h create mode 100644 packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.cpp create mode 100644 packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.h create mode 100644 packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin_c_api.cpp create mode 100644 packages/share_plus/share_plus_windows/windows/vector.h diff --git a/packages/share_plus/share_plus_windows/.clang-format b/packages/share_plus/share_plus_windows/.clang-format new file mode 100644 index 0000000000..3c73f32a33 --- /dev/null +++ b/packages/share_plus/share_plus_windows/.clang-format @@ -0,0 +1,12 @@ +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium +# This defaults to 'Auto'. Explicitly set it for a while, so that +# 'vector >' in existing files gets formatted to +# 'vector>'. ('Auto' means that clang-format will only use +# 'int>>' if the file already contains at least one such instance.) +Standard: Cpp11 +SortIncludes: true +--- +Language: ObjC +ColumnLimit: 100 diff --git a/packages/share_plus/share_plus_windows/lib/share_plus_windows.dart b/packages/share_plus/share_plus_windows/lib/share_plus_windows.dart index 2bd3974e1d..550fefd55c 100644 --- a/packages/share_plus/share_plus_windows/lib/share_plus_windows.dart +++ b/packages/share_plus/share_plus_windows/lib/share_plus_windows.dart @@ -3,14 +3,19 @@ library share_plus_windows; import 'dart:ui'; +import 'package:share_plus_windows/src/version_helper.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; -/// The Windows implementation of SharePlatform. +/// The fallback Windows implementation of [SharePlatform], for older Windows versions. +/// class ShareWindows extends SharePlatform { - /// Register this dart class as the platform implementation for linux + /// If the modern Share UI i.e. `DataTransferManager` is not available, then use this Dart class instead of platform specific implementation. + /// static void registerWith() { - SharePlatform.instance = ShareWindows(); + if (!VersionHelper.instance.isWindows10RS5OrGreater) { + SharePlatform.instance = ShareWindows(); + } } /// Share text. diff --git a/packages/share_plus/share_plus_windows/lib/src/version_helper.dart b/packages/share_plus/share_plus_windows/lib/src/version_helper.dart new file mode 100644 index 0000000000..53def4e490 --- /dev/null +++ b/packages/share_plus/share_plus_windows/lib/src/version_helper.dart @@ -0,0 +1,43 @@ +import 'dart:io'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +class VersionHelper { + static VersionHelper instance = VersionHelper._(); + + /// Whether the current OS is Windows 10 Redstone 5 or later. + /// This is used to determine whether the modern Share UI i.e. `DataTransferManager` is available or not. + /// + /// References: https://en.wikipedia.org/wiki/Windows_10_version_history + /// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa + /// + bool isWindows10RS5OrGreater = false; + + static const int _kWindows10RS5BuildNumber = 17763; + + VersionHelper._() { + if (Platform.isWindows) { + final pointer = calloc(); + pointer.ref + ..dwOSVersionInfoSize = sizeOf() + ..dwBuildNumber = 0 + ..dwMajorVersion = 0 + ..dwMinorVersion = 0 + ..dwPlatformId = 0 + ..szCSDVersion = '' + ..wServicePackMajor = 0 + ..wServicePackMinor = 0 + ..wSuiteMask = 0 + ..wProductType = 0 + ..wReserved = 0; + final rtlGetVersion = DynamicLibrary.open('ntdll.dll').lookupFunction< + Void Function(Pointer), + void Function(Pointer)>('RtlGetVersion'); + rtlGetVersion(pointer); + isWindows10RS5OrGreater = + pointer.ref.dwBuildNumber >= _kWindows10RS5BuildNumber; + calloc.free(pointer); + } + } +} diff --git a/packages/share_plus/share_plus_windows/pubspec.yaml b/packages/share_plus/share_plus_windows/pubspec.yaml index a4cae432e3..600a947c89 100644 --- a/packages/share_plus/share_plus_windows/pubspec.yaml +++ b/packages/share_plus/share_plus_windows/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: sdk: flutter meta: ^1.7.0 url_launcher: ^6.1.2 + ffi: ^2.0.1 + win32: ^2.3.8 dev_dependencies: flutter_test: @@ -23,8 +25,6 @@ dev_dependencies: flutter: plugin: - implements: share_plus platforms: windows: - dartPluginClass: ShareWindows - pluginClass: none + pluginClass: SharePlusWindowsPluginCApi diff --git a/packages/share_plus/share_plus_windows/windows/.gitignore b/packages/share_plus/share_plus_windows/windows/.gitignore new file mode 100644 index 0000000000..b3eb2be169 --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/share_plus/share_plus_windows/windows/CMakeLists.txt b/packages/share_plus/share_plus_windows/windows/CMakeLists.txt new file mode 100644 index 0000000000..d218cc0e2d --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/CMakeLists.txt @@ -0,0 +1,53 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "share_plus_windows") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "share_plus_windows_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "share_plus_windows_plugin.cpp" + "share_plus_windows_plugin.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/share_plus_windows/share_plus_windows_plugin_c_api.h" + "share_plus_windows_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(share_plus_windows_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/share_plus/share_plus_windows/windows/include/share_plus_windows/share_plus_windows_plugin_c_api.h b/packages/share_plus/share_plus_windows/windows/include/share_plus_windows/share_plus_windows_plugin_c_api.h new file mode 100644 index 0000000000..a5d43111eb --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/include/share_plus_windows/share_plus_windows_plugin_c_api.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void SharePlusWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_ diff --git a/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.cpp b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.cpp new file mode 100644 index 0000000000..08e4d37603 --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.cpp @@ -0,0 +1,270 @@ +#include "share_plus_windows_plugin.h" + +#include +#include +#include + +#include "vector.h" + +namespace share_plus_windows { + +void SharePlusWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows* registrar) { + auto channel = + std::make_unique>( + registrar->messenger(), kSharePlusChannelName, + &flutter::StandardMethodCodec::GetInstance()); + auto plugin = std::make_unique(registrar); + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto& call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +SharePlusWindowsPlugin::SharePlusWindowsPlugin( + flutter::PluginRegistrarWindows* registrar) + : registrar_(registrar) {} + +SharePlusWindowsPlugin::~SharePlusWindowsPlugin() { + if (data_transfer_manager_ != nullptr) { + data_transfer_manager_->remove_DataRequested(data_transfer_manager_token_); + data_transfer_manager_.Reset(); + } + if (data_transfer_manager_interop_ != nullptr) { + data_transfer_manager_interop_.Reset(); + } +} + +HWND SharePlusWindowsPlugin::GetWindow() { + return ::GetAncestor(registrar_->GetView()->GetNativeWindow(), GA_ROOT); +} + +WRL::ComPtr +SharePlusWindowsPlugin::GetDataTransferManager() { + using Microsoft::WRL::Wrappers::HStringReference; + ::RoGetActivationFactory( + HStringReference( + RuntimeClass_Windows_ApplicationModel_DataTransfer_DataTransferManager) + .Get(), + IID_PPV_ARGS(&data_transfer_manager_interop_)); + data_transfer_manager_interop_->GetForWindow( + GetWindow(), IID_PPV_ARGS(&data_transfer_manager_)); + return data_transfer_manager_; +} + +HRESULT SharePlusWindowsPlugin::GetStorageFileFromPath( + wchar_t* path, + WindowsStorage::IStorageFile** file) { + using Microsoft::WRL::Wrappers::HStringReference; + WRL::ComPtr factory = nullptr; + HRESULT hr = S_OK; + *file = nullptr; + if (!factory) { + hr = WindowsFoundation::GetActivationFactory( + HStringReference(RuntimeClass_Windows_Storage_StorageFile).Get(), + &factory); + } + if (SUCCEEDED(hr)) { + WRL::ComPtr< + WindowsFoundation::IAsyncOperation> + async_operation; + hr = factory->GetFileFromPathAsync(HStringReference(path).Get(), + &async_operation); + if (SUCCEEDED(hr)) { + WRL::ComPtr info; + hr = async_operation.As(&info); + if (SUCCEEDED(hr)) { + AsyncStatus status; + while (SUCCEEDED(hr = info->get_Status(&status)) && + status == AsyncStatus::Started) + SleepEx(0, TRUE); + if (FAILED(hr) || status != AsyncStatus::Completed) { + info->get_ErrorCode(&hr); + } else { + async_operation->GetResults(file); + } + } + } + } + return hr; +} + +void SharePlusWindowsPlugin::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) { + if (method_call.method_name().compare(kShare) == 0 || + method_call.method_name().compare(kShareWithResult) == 0) { + auto is_result_requested = + method_call.method_name().compare(kShareWithResult) == 0; + auto data_transfer_manager = GetDataTransferManager(); + auto args = std::get(*method_call.arguments()); + if (auto text_value = + std::get_if(&args[flutter::EncodableValue("text")])) { + share_text_ = *text_value; + } + if (auto subject_value = std::get_if( + &args[flutter::EncodableValue("subject")])) { + share_subject_ = *subject_value; + } + auto callback = WRL::Callback>( + [&](auto&&, DataTransfer::IDataRequestedEventArgs* e) { + using Microsoft::WRL::Wrappers::HStringReference; + WRL::ComPtr request; + e->get_Request(&request); + WRL::ComPtr data; + request->get_Data(&data); + WRL::ComPtr properties; + data->get_Properties(&properties); + // The title is mandatory for Windows. + // Using |share_text_| as title. + auto text = Utf16FromUtf8(share_text_); + properties->put_Title(HStringReference(text.c_str()).Get()); + // If |share_subject_| is available, then set it as text since + // |share_text_| is already set as title. + if (share_subject_ && !share_subject_.value_or("").empty()) { + auto subject = Utf16FromUtf8(share_subject_.value_or("")); + properties->put_Description( + HStringReference(subject.c_str()).Get()); + data->SetText(HStringReference(subject.c_str()).Get()); + } + // If |share_subject_| is not available, then use |share_text_| as + // text aswell. + else { + data->SetText(HStringReference(text.c_str()).Get()); + } + return S_OK; + }); + data_transfer_manager->add_DataRequested(callback.Get(), + &data_transfer_manager_token_); + if (data_transfer_manager_interop_ != nullptr) { + data_transfer_manager_interop_->ShowShareUIForWindow(GetWindow()); + } + if (is_result_requested) { + result->Success(flutter::EncodableValue(kShareResultUnavailable)); + } else { + result->Success(flutter::EncodableValue()); + } + } else if (method_call.method_name().compare(kShareFiles) == 0 || + method_call.method_name().compare(kShareFilesWithResult) == 0) { + auto is_result_requested = + method_call.method_name().compare(kShareFilesWithResult) == 0; + auto data_transfer_manager = GetDataTransferManager(); + auto args = std::get(*method_call.arguments()); + if (auto text_value = + std::get_if(&args[flutter::EncodableValue("text")])) { + share_text_ = *text_value; + } + if (auto subject_value = std::get_if( + &args[flutter::EncodableValue("subject")])) { + share_subject_ = *subject_value; + } + if (auto paths = std::get_if( + &args[flutter::EncodableValue("paths")])) { + paths_.clear(); + for (auto& path : *paths) { + paths_.emplace_back(std::get(path)); + } + } + if (auto mime_types = std::get_if( + &args[flutter::EncodableValue("mimeTypes")])) { + mime_types_.clear(); + for (auto& mime_type : *mime_types) { + mime_types_.emplace_back(std::get(mime_type)); + } + } + auto callback = WRL::Callback>( + [&](auto&&, DataTransfer::IDataRequestedEventArgs* e) { + using Microsoft::WRL::Wrappers::HStringReference; + WRL::ComPtr request; + e->get_Request(&request); + WRL::ComPtr data; + request->get_Data(&data); + WRL::ComPtr properties; + data->get_Properties(&properties); + // The title is mandatory for Windows. + // Using |share_text_| as title if available. + if (!share_text_.empty()) { + auto text = Utf16FromUtf8(share_text_); + properties->put_Title(HStringReference(text.c_str()).Get()); + } + // Or use the file count string as title if there are multiple + // files & use the file name if a single file is shared. + // Same behavior may be seen in File Explorer. + else { + if (paths_.size() > 1) { + auto title = std::to_wstring(paths_.size()) + L" files"; + properties->put_Title(HStringReference(title.c_str()).Get()); + } else if (paths_.size() == 1) { + auto title = Utf16FromUtf8(paths_.front()); + properties->put_Title(HStringReference(title.c_str()).Get()); + } + } + // If |share_subject_| is available, then set it as text since + // |share_text_| is already set as title. + if (share_subject_ && !share_subject_.value_or("").empty()) { + auto subject = Utf16FromUtf8(share_subject_.value_or("")); + properties->put_Description( + HStringReference(subject.c_str()).Get()); + data->SetText(HStringReference(subject.c_str()).Get()); + } + // If |share_subject_| is not available, then use |share_text_| as + // text aswell. + else if (!share_text_.empty()) { + auto text = Utf16FromUtf8(share_text_); + data->SetText(HStringReference(text.c_str()).Get()); + } + // Add files to the data. + Vector storage_items; + for (const std::string& path : paths_) { + auto str = Utf16FromUtf8(path); + wchar_t* ptr = const_cast(str.c_str()); + WindowsStorage::IStorageFile* file = nullptr; + if (SUCCEEDED(GetStorageFileFromPath(ptr, &file)) && + file != nullptr) { + storage_items.Append( + reinterpret_cast(file)); + } + } + data->SetStorageItemsReadOnly(&storage_items); + return S_OK; + }); + data_transfer_manager->add_DataRequested(callback.Get(), + &data_transfer_manager_token_); + if (data_transfer_manager_interop_ != nullptr) { + data_transfer_manager_interop_->ShowShareUIForWindow(GetWindow()); + } + if (is_result_requested) { + result->Success(flutter::EncodableValue(kShareResultUnavailable)); + } else { + result->Success(flutter::EncodableValue()); + } + } else { + result->NotImplemented(); + } +} + +// Converts string encoded in UTF-8 to wstring. +// Returns an empty |std::wstring| on failure. +// Present as static helper method. +std::wstring SharePlusWindowsPlugin::Utf16FromUtf8(std::string string) { + int size_needed = + MultiByteToWideChar(CP_UTF8, 0, string.c_str(), -1, NULL, 0); + if (size_needed == 0) { + return std::wstring(); + } + std::wstring result(size_needed, 0); + int converted_length = MultiByteToWideChar(CP_UTF8, 0, string.c_str(), -1, + &result[0], size_needed); + if (converted_length == 0) { + return std::wstring(); + } + return result; +} + +} // namespace share_plus_windows diff --git a/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.h b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.h new file mode 100644 index 0000000000..b96f9b3b0c --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin.h @@ -0,0 +1,83 @@ +#ifndef FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_H_ +#define FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_H_ + +#include +// Must be present after Windows.h. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#pragma comment(lib, "runtimeobject.lib") + +namespace WRL = Microsoft::WRL; +namespace WindowsFoundation = ABI::Windows::Foundation; +namespace WindowsStorage = ABI::Windows::Storage; +namespace DataTransfer = ABI::Windows::ApplicationModel::DataTransfer; + +namespace share_plus_windows { + +class SharePlusWindowsPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); + + SharePlusWindowsPlugin(flutter::PluginRegistrarWindows* registrar); + + virtual ~SharePlusWindowsPlugin(); + + SharePlusWindowsPlugin(const SharePlusWindowsPlugin&) = delete; + SharePlusWindowsPlugin& operator=(const SharePlusWindowsPlugin&) = delete; + + private: + static constexpr auto kSharePlusChannelName = + "dev.fluttercommunity.plus/share"; + + static constexpr auto kShareResultUnavailable = + "dev.fluttercommunity.plus/share/unavailable"; + + static constexpr auto kShare = "share"; + static constexpr auto kShareFiles = "shareFiles"; + static constexpr auto kShareWithResult = "shareWithResult"; + static constexpr auto kShareFilesWithResult = "shareFilesWithResult"; + + HWND GetWindow(); + + WRL::ComPtr GetDataTransferManager(); + + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + + static HRESULT GetStorageFileFromPath(wchar_t* path, + WindowsStorage::IStorageFile** file); + + static std::wstring SharePlusWindowsPlugin::Utf16FromUtf8(std::string string); + + flutter::PluginRegistrarWindows* registrar_ = nullptr; + WRL::ComPtr data_transfer_manager_interop_ = + nullptr; + WRL::ComPtr data_transfer_manager_ = + nullptr; + EventRegistrationToken data_transfer_manager_token_; + + // Present here to keep |std::string| in memory until data request callback + // from |IDataTransferManager| takes place. + // Subsequent calls on the platform channel will overwrite the existing value. + std::string share_text_ = ""; + std::optional share_subject_ = std::nullopt; + std::vector paths_ = {}; + std::vector mime_types_ = {}; +}; + +} // namespace share_plus_windows + +#endif // FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_H_ diff --git a/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin_c_api.cpp b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin_c_api.cpp new file mode 100644 index 0000000000..8237a1b8fb --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/share_plus_windows_plugin_c_api.cpp @@ -0,0 +1,12 @@ +#include "include/share_plus_windows/share_plus_windows_plugin_c_api.h" + +#include + +#include "share_plus_windows_plugin.h" + +void SharePlusWindowsPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + share_plus_windows::SharePlusWindowsPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/packages/share_plus/share_plus_windows/windows/vector.h b/packages/share_plus/share_plus_windows/windows/vector.h new file mode 100644 index 0000000000..c33319b5f5 --- /dev/null +++ b/packages/share_plus/share_plus_windows/windows/vector.h @@ -0,0 +1,572 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "winstring.h" + +#include +#include +#include +#include + +using namespace Microsoft::WRL::Wrappers; + +// Helpers for error handling. General strategy is that errors are always +// reported as exceptions (so we don't need to special case any STL usage). + +// Methods that need to return HRESULTs wrap their body inside an +// ExceptionBoundary. + +// Generally all errors are reported with an associated HRESULT. These are +// represented as an HResultException. Use ExceptionBoundary() to catch +// exceptions and translate them to HRESULTs. + +// As the constructor is protected, only ThrowHR() can be used to throw an +// HResultException. This provides future flexibility in case we some day +// want to throw more varied types of exception. + +class HResultException { + HRESULT m_Hr; + + protected: + explicit HResultException(HRESULT hr) : m_Hr(hr) {} + + public: + HRESULT GetHr() const { return m_Hr; } + + __declspec(noreturn) friend void ThrowHR(HRESULT); +}; + +// +// Throws an exception for the given HRESULT. +// +__declspec(noreturn) __declspec(noinline) inline void ThrowHR(HRESULT hr) { + throw HResultException(hr); +} + +// +// Throws the appropriate exception for the given HRESULT, attaching a custom +// error message string. To avoid leaks, the message string should be owned by +// an RAII wrapper such as WinString. We don't take this parameter directly as a +// WinString to break what would otherwise be a circular dependency (WinString +// uses ErrorHandling.h in its implementation). +// +__declspec(noreturn) __declspec(noinline) inline void ThrowHR(HRESULT hr, + HSTRING message) { + RoOriginateError(hr, message); + + ThrowHR(hr); +} + +__declspec(noreturn) __declspec( + noinline) inline void ThrowHR(HRESULT hr, wchar_t const* message) { + using ::Microsoft::WRL::Wrappers::HStringReference; + + ThrowHR(hr, HStringReference(message).Get()); +} + +// +// Throws if the HR fails. This is the workhorse helper for converting calls to +// functions that return HRESULTs to exceptions. eg: +// +// ThrowIfFailed(myObj->MethodThatReturnsHRESULT()); +// +inline void ThrowIfFailed(HRESULT hr) { + if (FAILED(hr)) + ThrowHR(hr); +} + +// +// Throws if the given pointer is null. +// +template +inline void ThrowIfNullPointer(T* ptr, HRESULT hrToThrow) { + if (ptr == nullptr) + ThrowHR(hrToThrow); +} + +template +inline void ThrowIfNegative(T value) { + if (value < 0) + ThrowHR(E_INVALIDARG); +} + +inline void ThrowIfZeroOrNegative(uint32_t n) { + if (n <= 0) { + ThrowHR(E_INVALIDARG); + } +} + +// +// Checks that a given pointer argument is valid (ie non-null). This is +// expected to be used at the beginning of methods to validate pointer +// parameters that are marked as [in]. +// +template +inline void CheckInPointer(T* ptr) { + ThrowIfNullPointer(ptr, E_INVALIDARG); +} + +// +// Checks that a given output pointer parameter is valid (ie non-null), and sets +// it to null. This is expected to be used at the beginning of methods to +// validate out pointer parameters that are marked as [out]. +// +template +inline void CheckAndClearOutPointer(T** ptr) { + CheckInPointer(ptr); + *ptr = nullptr; +} + +// +// WRL's Make<>() function returns an empty ComPtr on failure rather than +// throwing an exception. This checks the result and throws bad_alloc. +// +// Note: generally we use exceptions inside constructors to report errors. +// Therefore the only way that Make() will return an error is if an allocation +// fails. +// +__declspec(noreturn) __declspec(noinline) inline void ThrowBadAlloc() { + throw std::bad_alloc(); +} + +inline void CheckMakeResult(bool result) { + if (!result) + ThrowBadAlloc(); +} + +// +// Converts exceptions in the callable code into HRESULTs. +// +__declspec(noinline) inline HRESULT ThrownExceptionToHResult() { + try { + throw; + } catch (HResultException const& e) { + return e.GetHr(); + } catch (std::bad_alloc const&) { + return E_OUTOFMEMORY; + } catch (...) { + return E_UNEXPECTED; + } +} + +template +HRESULT ExceptionBoundary(CALLABLE&& fn) { + try { + fn(); + return S_OK; + } catch (...) { + return ThrownExceptionToHResult(); + } +} + +// Element traits describe how to store and manipulate the values inside a +// collection. This default implementation is for value types. The same template +// is specialized with more interesting versions for reference counted pointer +// types and strings. +template +struct ElementTraits { + typedef T ElementType; + + static ElementType Wrap(T const& value) { return value; } + + static void Unwrap(ElementType const& value, _Out_ T* result) { + *result = value; + } + + static bool Equals(T const& value1, T const& value2) { + return value1 == value2; + } + + static void CopyTo(T const& in, ElementType* out) { *out = in; } +}; + +// Specialized element traits for reference counted pointer types. +template +struct ElementTraits { + typedef Microsoft::WRL::ComPtr ElementType; + + static ElementType Wrap(T* value) { return Microsoft::WRL::ComPtr(value); } + + static void Unwrap(ElementType const& value, _Out_ T** result) { + ThrowIfFailed(value.CopyTo(result)); + } + + static bool Equals(Microsoft::WRL::ComPtr const& value1, T* value2) { + return value1.Get() == value2; + } + + static void CopyTo(Microsoft::WRL::ComPtr const& in, ElementType* out) { + in.CopyTo(out->GetAddressOf()); + } +}; + +// Specialized element traits for strings. +template <> +struct ElementTraits { + typedef HString ElementType; + + static ElementType Wrap(HSTRING const& value) { + HString hstringValue; + hstringValue.Set(value); + return hstringValue; + } + + static void Unwrap(ElementType const& value, _Out_ HSTRING* result) { + value.CopyTo(result); + } + + static bool Equals(HString const& value1, HSTRING const& value2) { + int compareResult; + ThrowIfFailed( + WindowsCompareStringOrdinal(value1.Get(), value2, &compareResult)); + return compareResult == 0; + } + + static void CopyTo(HString const& in, ElementType* out) { + in.CopyTo(out->GetAddressOf()); + } +}; + +// Vector traits describe how the collection itself is implemented. +// This default version just uses an STL vector. +template +struct DefaultVectorTraits : public ElementTraits { + typedef std::vector InternalVectorType; + + static unsigned GetSize(InternalVectorType const& vector) { + return (unsigned)vector.size(); + }; + + static void GetAt(InternalVectorType const& vector, + unsigned index, + ElementType* element) { + if (index >= vector.size()) + ThrowHR(E_BOUNDS); + + ElementTraits::CopyTo(vector[index], element); + return; + } + + static void SetAt(InternalVectorType& vector, unsigned index, T const& item) { + if (index >= vector.size()) + ThrowHR(E_BOUNDS); + + vector[index] = Wrap(item); + } + + static void InsertAt(InternalVectorType& vector, + unsigned index, + T const& item) { + if (index > vector.size()) + ThrowHR(E_BOUNDS); + + vector.insert(vector.begin() + index, Wrap(item)); + } + + static void RemoveAt(InternalVectorType& vector, unsigned index) { + if (index >= vector.size()) + ThrowHR(E_BOUNDS); + + vector.erase(vector.begin() + index); + } + + static void Append(InternalVectorType& vector, T const& item) { + vector.push_back(Wrap(item)); + } + + static void Clear(InternalVectorType& vector) { vector.clear(); } +}; + +// Implements the WinRT IVector interface. +template class Traits = DefaultVectorTraits> +class Vector : public Microsoft::WRL::RuntimeClass< + ABI::Windows::Foundation::Collections::IVector, + ABI::Windows::Foundation::Collections::IIterable> { + InspectableClass(IVector::z_get_rc_name_impl(), BaseTrust); + + public: + // T_abi is often the same as T, but if T is a runtime class, T_abi will be + // the corresponding interface. + typedef typename ABI::Windows::Foundation::Internal::GetAbiType< + typename RuntimeClass::IVector::T_complex>::type T_abi; + + // Specialize our traits class to use the ABI version of the type. + typedef Traits Traits; + + private: + // Fields. + typename Traits::InternalVectorType mVector; + + bool isFixedSize; + bool isChanged; + + public: + // Constructs an empty vector. + Vector() : isFixedSize(false), isChanged(false) {} + + // Constructs a vector of the specified size. + template + Vector(bool isFixedSize, Args&&... args) + : mVector(std::forward(args)...), + isFixedSize(isFixedSize), + isChanged(false) {} + + // Checks whether this vector is fixed or resizable. + bool IsFixedSize() const { return isFixedSize; } + + // Checks whether the contents of the vector have changed since the last call + // to SetChanged(false). + bool IsChanged() const { return isChanged; } + + // Sets or clears the IsChanged flag. + void SetChanged(bool changed) { isChanged = changed; } + + // Expose direct access to the internal STL collection. This lets C++ owners + // bypass the ExceptionBoundary overhead of the public WinRT API surface. + typename Traits::InternalVectorType& InternalVector() { return mVector; } + + virtual HRESULT STDMETHODCALLTYPE get_Size(_Out_ unsigned* size) { + return ExceptionBoundary([&] { + CheckInPointer(size); + + *size = Traits::GetSize(mVector); + }); + }; + + virtual HRESULT STDMETHODCALLTYPE GetAt(_In_opt_ unsigned index, + _Out_ T_abi* item) { + return ExceptionBoundary([&] { + CheckInPointer(item); + ZeroMemory(item, sizeof(*item)); + + Traits::ElementType element; + Traits::GetAt(mVector, index, &element); + Traits::Unwrap(element, item); + }); + } + + virtual HRESULT STDMETHODCALLTYPE IndexOf(_In_opt_ T_abi value, + _Out_ unsigned* index, + _Out_ boolean* found) { + return ExceptionBoundary([&] { + CheckInPointer(index); + CheckInPointer(found); + + *index = 0; + *found = false; + + auto size = Traits::GetSize(mVector); + + for (unsigned i = 0; i < size; i++) { + Traits::ElementType element; + Traits::GetAt(mVector, i, &element); + + if (Traits::Equals(element, value)) { + *index = i; + *found = true; + break; + } + } + }); + } + + virtual HRESULT STDMETHODCALLTYPE SetAt(_In_ unsigned index, + _In_opt_ T_abi item) { + return ExceptionBoundary([&] { + Traits::SetAt(mVector, index, item); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE InsertAt(_In_ unsigned index, + _In_opt_ T_abi item) { + return ExceptionBoundary([&] { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::InsertAt(mVector, index, item); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE RemoveAt(_In_ unsigned index) { + return ExceptionBoundary([&] { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::RemoveAt(mVector, index); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE Append(_In_opt_ T_abi item) { + return ExceptionBoundary([&] { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::Append(mVector, item); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE RemoveAtEnd() { + return ExceptionBoundary([&] { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::RemoveAt(mVector, Traits::GetSize(mVector) - 1); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE Clear() { + return ExceptionBoundary([&] { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::Clear(mVector); + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE ReplaceAll(_In_ unsigned count, + _In_reads_(count) T_abi* value) { + return ExceptionBoundary([&] { + CheckInPointer(value); + + if (count == Traits::GetSize(mVector)) { + for (unsigned i = 0; i < count; i++) { + Traits::SetAt(mVector, i, value[i]); + } + } else { + if (isFixedSize) + ThrowHR(E_NOTIMPL); + + Traits::Clear(mVector); + + for (unsigned i = 0; i < count; i++) { + Traits::Append(mVector, value[i]); + } + } + + isChanged = true; + }); + } + + virtual HRESULT STDMETHODCALLTYPE GetView( + _Outptr_result_maybenull_ + ABI::Windows::Foundation::Collections::IVectorView** view) { + return ExceptionBoundary([&] { + CheckAndClearOutPointer(view); + + auto vectorView = Microsoft::WRL::Make>(this); + CheckMakeResult(vectorView); + + *view = vectorView.Detach(); + }); + } + + virtual HRESULT STDMETHODCALLTYPE First( + _Outptr_result_maybenull_ + ABI::Windows::Foundation::Collections::IIterator** first) { + return ExceptionBoundary([&] { + CheckAndClearOutPointer(first); + + auto iterator = Microsoft::WRL::Make>(this); + CheckMakeResult(iterator); + + *first = iterator.Detach(); + }); + } +}; + +// Implements the WinRT IVectorView interface. +template +class VectorView : public Microsoft::WRL::RuntimeClass< + ABI::Windows::Foundation::Collections::IVectorView, + ABI::Windows::Foundation::Collections::IIterable> { + InspectableClass(IVectorView::z_get_rc_name_impl(), BaseTrust); + + // Fields. + Microsoft::WRL::ComPtr mVector; + + public: + // Constructor wraps around an existing Vector. + VectorView(TVector* vector) : mVector(vector) {} + + virtual HRESULT STDMETHODCALLTYPE get_Size(_Out_ unsigned* size) { + return mVector->get_Size(size); + }; + + virtual HRESULT STDMETHODCALLTYPE GetAt(_In_opt_ unsigned index, + _Out_ typename TVector::T_abi* item) { + return mVector->GetAt(index, item); + } + + virtual HRESULT STDMETHODCALLTYPE IndexOf(_In_opt_ + typename TVector::T_abi value, + _Out_ unsigned* index, + _Out_ boolean* found) { + return mVector->IndexOf(value, index, found); + } + + virtual HRESULT STDMETHODCALLTYPE First( + _Outptr_result_maybenull_ + ABI::Windows::Foundation::Collections::IIterator** first) { + return mVector->First(first); + } +}; + +// Implements the WinRT IIterator interface. +template +class VectorIterator + : public Microsoft::WRL::RuntimeClass< + ABI::Windows::Foundation::Collections::IIterator> { + InspectableClass(IIterator::z_get_rc_name_impl(), BaseTrust); + + // Fields. + Microsoft::WRL::ComPtr mVector; + unsigned mPosition; + + public: + // Constructor wraps around an existing Vector. + VectorIterator(TVector* vector) : mVector(vector), mPosition(0) {} + + virtual HRESULT STDMETHODCALLTYPE + get_Current(_Out_ typename TVector::T_abi* current) { + return mVector->GetAt(mPosition, current); + } + + virtual HRESULT STDMETHODCALLTYPE get_HasCurrent(_Out_ boolean* hasCurrent) { + return ExceptionBoundary([&] { + CheckInPointer(hasCurrent); + + *hasCurrent = + (mPosition < TVector::Traits::GetSize(mVector->InternalVector())); + }); + } + + virtual HRESULT STDMETHODCALLTYPE MoveNext(_Out_ boolean* hasCurrent) { + return ExceptionBoundary([&] { + CheckInPointer(hasCurrent); + + auto size = TVector::Traits::GetSize(mVector->InternalVector()); + + if (mPosition >= size) { + ThrowHR(E_BOUNDS); + } + + mPosition++; + *hasCurrent = (mPosition < size); + }); + } +};