Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/path_provider/path_provider_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 2.3.0

* Replaces `win32` dependency with direct FFI usage.
* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.

## 2.2.1
Expand Down
115 changes: 61 additions & 54 deletions packages/path_provider/path_provider_windows/lib/src/folders.dart

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions packages/path_provider/path_provider_windows/lib/src/guid.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ffi';
import 'dart:typed_data';

/// Representation of the Win32 GUID struct.
// For the layout of this struct, see
// https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
@Packed(4)
base class GUID extends Struct {
/// Navite Data1 field.
@Uint32()
external int data1;

/// Navite Data2 field.
@Uint16()
external int data2;

/// Navite Data3 field.
@Uint16()
external int data3;

/// Navite Data4 field.
// This should be an eight-element byte array, but there's no such annotation.
@Uint64()
external int data4;

/// Parses a GUID string, with optional enclosing "{}"s and optional "-"s,
/// into data.
void parse(String guid) {
final String hexOnly = guid.replaceAll(RegExp(r'[{}-]'), '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package:win32's GUID.parse requires the {}s and -s. Should we do that as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm removing those symbols anyway to make the implementation simple, and since https://en.wikipedia.org/wiki/Universally_unique_identifier#Textual_representation says they are all valid I figured I would just accept all of them and relaxed the API accordingly. I didn't look at the win32 implementation so I don't know if there's a reason they are stricter; the only one I could think of would be efficiency, and I'm pretty confident that it's never going to matter in this use case. (If efficiency were ever actually important the way to go would be to make an API that's not string-based in the first place, and constants that built the GUIDs directly.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks for the thoughtful reply

if (hexOnly.length != 32) {
throw ArgumentError.value(guid, 'guid', 'Invalid GUID string');
}
final ByteData bytes = ByteData(16);
for (int i = 0; i < 16; ++i) {
bytes.setUint8(
i, int.parse(hexOnly.substring(i * 2, i * 2 + 2), radix: 16));
}
data1 = bytes.getInt32(0);
data2 = bytes.getInt16(4);
data3 = bytes.getInt16(6);
// [bytes] is big endian, but the host is little endian, so a default
// big-endian read would reverse the bytes. Since data4 is supposed to be
// a byte array, the order should be preserved, so do a little-endian read.
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
data4 = bytes.getInt64(8, Endian.little);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:win32/win32.dart';

import 'folders.dart';
import 'guid.dart';
import 'win32_wrappers.dart';

/// Constant for en-US language used in VersionInfo keys.
@visibleForTesting
Expand Down Expand Up @@ -49,7 +51,7 @@ class VersionInfoQuerier {
return null;
}
final Pointer<Utf16> keyPath =
TEXT('\\StringFileInfo\\$language$encoding\\$key');
'\\StringFileInfo\\$language$encoding\\$key'.toNativeUtf16();
final Pointer<UINT> length = calloc<UINT>();
final Pointer<Pointer<Utf16>> valueAddress = calloc<Pointer<Utf16>>();
try {
Expand Down Expand Up @@ -89,7 +91,7 @@ class PathProviderWindows extends PathProviderPlatform {

if (length == 0) {
final int error = GetLastError();
throw WindowsException(error);
throw _createWin32Exception(error);
} else {
path = buffer.toDartString();

Expand Down Expand Up @@ -134,7 +136,7 @@ class PathProviderWindows extends PathProviderPlatform {
/// [WindowsKnownFolder].
Future<String?> getPath(String folderID) {
final Pointer<Pointer<Utf16>> pathPtrPtr = calloc<Pointer<Utf16>>();
final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.setGUID(folderID);
final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.parse(folderID);

try {
final int hr = SHGetKnownFolderPath(
Expand All @@ -144,9 +146,9 @@ class PathProviderWindows extends PathProviderPlatform {
pathPtrPtr,
);

if (FAILED(hr)) {
if (hr != S_OK) {
if (hr == E_INVALIDARG || hr == E_FAIL) {
throw WindowsException(hr);
throw _createWin32Exception(hr);
}
return Future<String?>.value();
}
Expand Down Expand Up @@ -179,7 +181,8 @@ class PathProviderWindows extends PathProviderPlatform {
String? companyName;
String? productName;

final Pointer<Utf16> moduleNameBuffer = wsalloc(MAX_PATH + 1);
final Pointer<Utf16> moduleNameBuffer =
calloc<WCHAR>(MAX_PATH + 1).cast<Utf16>();
final Pointer<DWORD> unused = calloc<DWORD>();
Pointer<BYTE>? infoBuffer;
try {
Expand All @@ -188,7 +191,7 @@ class PathProviderWindows extends PathProviderPlatform {
GetModuleFileName(0, moduleNameBuffer, MAX_PATH);
if (moduleNameLength == 0) {
final int error = GetLastError();
throw WindowsException(error);
throw _createWin32Exception(error);
}

// From that, load the VERSIONINFO resource
Expand Down Expand Up @@ -263,3 +266,15 @@ class PathProviderWindows extends PathProviderPlatform {
return directory.path;
}
}

Exception _createWin32Exception(int errorCode) {
return PlatformException(
code: 'Win32 Error',
// TODO(stuartmorgan): Consider getting the system error message via
// FormatMessage if it turns out to be necessary for debugging issues.
// Plugin-client-level usability isn't a major consideration since per
// https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#platform-exception-handling
// any case that comes up in practice should be handled and returned
// via a plugin-specific exception, not this fallback.
message: 'Error code 0x${errorCode.toRadixString(16)}');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// The types and functions here correspond directly to corresponding Windows
// types and functions, so the Windows docs are the definitive source of
// documentation.
// ignore_for_file: public_member_api_docs

import 'dart:ffi';

import 'package:ffi/ffi.dart';

import 'guid.dart';

typedef BOOL = Int32;
typedef BYTE = Uint8;
typedef DWORD = Uint32;
typedef UINT = Uint32;
typedef HANDLE = IntPtr;
typedef HMODULE = HANDLE;
typedef HRESULT = Int32;
typedef LPCVOID = Pointer<NativeType>;
typedef LPCWSTR = Pointer<Utf16>;
typedef LPDWORD = Pointer<DWORD>;
typedef LPWSTR = Pointer<Utf16>;
typedef LPVOID = Pointer<NativeType>;
typedef PUINT = Pointer<UINT>;
typedef PWSTR = Pointer<Pointer<Utf16>>;
typedef WCHAR = Uint16;

const int NULL = 0;

// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
const int MAX_PATH = 260;

// https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
const int S_OK = 0;
// ignore: non_constant_identifier_names
final int E_FAIL = 0x80004005.toSigned(32);
// ignore: non_constant_identifier_names
final int E_INVALIDARG = 0x80070057.toSigned(32);

// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ne-shlobj_core-known_folder_flag
const int KF_FLAG_DEFAULT = 0x00000000;

final DynamicLibrary _dllKernel32 = DynamicLibrary.open('kernel32.dll');
final DynamicLibrary _dllVersion = DynamicLibrary.open('version.dll');
final DynamicLibrary _dllShell32 = DynamicLibrary.open('shell32.dll');

// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
typedef _FFITypeSHGetKnownFolderPath = HRESULT Function(
Pointer<GUID>, DWORD, HANDLE, PWSTR);
typedef FFITypeSHGetKnownFolderPathDart = int Function(
Pointer<GUID>, int, int, Pointer<Pointer<Utf16>>);
// ignore: non_constant_identifier_names
final FFITypeSHGetKnownFolderPathDart SHGetKnownFolderPath =
_dllShell32.lookupFunction<_FFITypeSHGetKnownFolderPath,
FFITypeSHGetKnownFolderPathDart>('SHGetKnownFolderPath');

// https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfow
typedef _FFITypeGetFileVersionInfoW = BOOL Function(
LPCWSTR, DWORD, DWORD, LPVOID);
typedef FFITypeGetFileVersionInfoW = int Function(
Pointer<Utf16>, int, int, Pointer<NativeType>);
// ignore: non_constant_identifier_names
final FFITypeGetFileVersionInfoW GetFileVersionInfo = _dllVersion
.lookupFunction<_FFITypeGetFileVersionInfoW, FFITypeGetFileVersionInfoW>(
'GetFileVersionInfoW');

// https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfosizew
typedef _FFITypeGetFileVersionInfoSizeW = DWORD Function(LPCWSTR, LPDWORD);
typedef FFITypeGetFileVersionInfoSizeW = int Function(
Pointer<Utf16>, Pointer<Uint32>);
// ignore: non_constant_identifier_names
final FFITypeGetFileVersionInfoSizeW GetFileVersionInfoSize =
_dllVersion.lookupFunction<_FFITypeGetFileVersionInfoSizeW,
FFITypeGetFileVersionInfoSizeW>('GetFileVersionInfoSizeW');

// https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
typedef _FFITypeGetLastError = DWORD Function();
typedef FFITypeGetLastError = int Function();
// ignore: non_constant_identifier_names
final FFITypeGetLastError GetLastError = _dllKernel32
.lookupFunction<_FFITypeGetLastError, FFITypeGetLastError>('GetLastError');

// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
typedef _FFITypeGetModuleFileNameW = DWORD Function(HMODULE, LPWSTR, DWORD);
typedef FFITypeGetModuleFileNameW = int Function(int, Pointer<Utf16>, int);
// ignore: non_constant_identifier_names
final FFITypeGetModuleFileNameW GetModuleFileName = _dllKernel32.lookupFunction<
_FFITypeGetModuleFileNameW,
FFITypeGetModuleFileNameW>('GetModuleFileNameW');

// https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluew
typedef _FFITypeVerQueryValueW = BOOL Function(LPCVOID, LPCWSTR, LPVOID, PUINT);
typedef FFITypeVerQueryValueW = int Function(
Pointer<NativeType>, Pointer<Utf16>, Pointer<NativeType>, Pointer<Uint32>);
// ignore: non_constant_identifier_names
final FFITypeVerQueryValueW VerQueryValue =
_dllVersion.lookupFunction<_FFITypeVerQueryValueW, FFITypeVerQueryValueW>(
'VerQueryValueW');

// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw
typedef _FFITypeGetTempPathW = DWORD Function(DWORD, LPWSTR);
typedef FFITypeGetTempPathW = int Function(int, Pointer<Utf16>);
// ignore: non_constant_identifier_names
final FFITypeGetTempPathW GetTempPath = _dllKernel32
.lookupFunction<_FFITypeGetTempPathW, FFITypeGetTempPathW>('GetTempPathW');
3 changes: 1 addition & 2 deletions packages/path_provider/path_provider_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: path_provider_windows
description: Windows implementation of the path_provider plugin
repository: https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
version: 2.2.1
version: 2.3.0

environment:
sdk: ^3.2.0
Expand All @@ -21,7 +21,6 @@ dependencies:
sdk: flutter
path: ^1.8.0
path_provider_platform_interface: ^2.1.0
win32: ">=2.1.0 <6.0.0"

dev_dependencies:
flutter_test:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ffi';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_windows/src/guid.dart';

void main() {
test('has correct byte representation', () async {
final Pointer<GUID> guid = calloc<GUID>()
..ref.parse('{00112233-4455-6677-8899-aabbccddeeff}');
final ByteData data = ByteData(16)
..setInt32(0, guid.ref.data1, Endian.little)
..setInt16(4, guid.ref.data2, Endian.little)
..setInt16(6, guid.ref.data3, Endian.little)
..setInt64(8, guid.ref.data4, Endian.little);
expect(data.getUint8(0), 0x33);
expect(data.getUint8(1), 0x22);
expect(data.getUint8(2), 0x11);
expect(data.getUint8(3), 0x00);
expect(data.getUint8(4), 0x55);
expect(data.getUint8(5), 0x44);
expect(data.getUint8(6), 0x77);
expect(data.getUint8(7), 0x66);
expect(data.getUint8(8), 0x88);
expect(data.getUint8(9), 0x99);
expect(data.getUint8(10), 0xAA);
expect(data.getUint8(11), 0xBB);
expect(data.getUint8(12), 0xCC);
expect(data.getUint8(13), 0xDD);
expect(data.getUint8(14), 0xEE);
expect(data.getUint8(15), 0xFF);

calloc.free(guid);
});

test('handles alternate forms', () async {
final Pointer<GUID> guid1 = calloc<GUID>()
..ref.parse('{00112233-4455-6677-8899-aabbccddeeff}');
final Pointer<GUID> guid2 = calloc<GUID>()
..ref.parse('00112233445566778899AABBCCDDEEFF');

expect(guid1.ref.data1, guid2.ref.data1);
expect(guid1.ref.data2, guid2.ref.data2);
expect(guid1.ref.data3, guid2.ref.data3);
expect(guid1.ref.data4, guid2.ref.data4);

calloc.free(guid1);
calloc.free(guid2);
});

test('throws for bad data', () async {
final Pointer<GUID> guid = calloc<GUID>();

expect(() => guid.ref.parse('{00112233-4455-6677-88'), throwsArgumentError);

calloc.free(guid);
});
}
1 change: 0 additions & 1 deletion script/configs/allowed_unpinned_deps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# cautious about adding to this list.
- build_verify
- google_maps
- win32

## Allowed by default

Expand Down