Skip to content

Instantly share code, notes, and snippets.

@timsneath
Created March 31, 2020 22:34
Show Gist options
  • Select an option

  • Save timsneath/08047e15b4ec1a72d291f89d6e22e478 to your computer and use it in GitHub Desktop.

Select an option

Save timsneath/08047e15b4ec1a72d291f89d6e22e478 to your computer and use it in GitHub Desktop.
Buggy attempt to call SHGetKnownFolder from Dart
// knownfolder.dart
// Shows usage of Shell APIs to retrieve the user's home directory
import 'dart:ffi';
import 'package:ffi/ffi.dart';
////////////////////////////////////////////////////////////////////////////////
// DEFINES /////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
const S_OK = 0;
const E_FAIL = 0x80004005;
const E_INVALIDARG = 0x80070057;
const MAX_PATH = 260;
const KF_FLAG_DEFAULT = 0;
const NULL = 0;
const FOLDERID_Documents = '{FDD39AD0-238F-46AF-ADB4-6C85480369C7}';
bool SUCCEEDED(int result) => (result == S_OK);
// typedef struct _GUID {
// unsigned long Data1;
// unsigned short Data2;
// unsigned short Data3;
// unsigned char Data4[ 8 ];
// } GUID;
class GUID extends Struct {
@Uint32()
int Data1;
@Uint16()
int Data2;
@Uint16()
int Data3;
@Uint64()
int Data4;
factory GUID.allocate() => allocate<GUID>().ref
..Data1 = 0
..Data2 = 0
..Data3 = 0
..Data4 = 0;
/// Create GUID from common {FDD39AD0-238F-46AF-ADB4-6C85480369C7} format
factory GUID.fromString(String guidString) {
assert(guidString.length == 38);
final guid = allocate<GUID>().ref;
guid.Data1 = int.parse('${guidString.substring(1, 9)}', radix: 16);
guid.Data2 = int.parse('${guidString.substring(10, 14)}', radix: 16);
guid.Data3 = int.parse('${guidString.substring(15, 19)}', radix: 16);
guid.Data4 =
(int.parse('${guidString.substring(20, 24)}', radix: 16) << 48) +
int.parse('${guidString.substring(25, 37)}', radix: 16);
return guid;
}
/// Print GUID in common {FDD39AD0-238F-46AF-ADB4-6C85480369C7} format
@override
String toString() =>
'{${Data1.toRadixString(16).padLeft(8, '0').toUpperCase()}-'
'${Data2.toRadixString(16).padLeft(4, '0').toUpperCase()}-'
'${Data3.toRadixString(16).padLeft(4, '0').toUpperCase()}-'
'${((Data4 >> 48) & 0xFFFF).toRadixString(16).padLeft(4, '0').toUpperCase()}-'
'${(Data4 & 0xFFFFFFFFFFFF).toRadixString(16).padLeft(12, '0').toUpperCase()}}';
}
////////////////////////////////////////////////////////////////////////////////
// FFI /////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// void CoTaskMemFree(
// _Frees_ptr_opt_ LPVOID pv
// );
typedef coTaskMemFreeNative = Void Function(Pointer<Void> pv);
typedef coTaskMemFreeDart = void Function(Pointer<Void> pv);
// SHFOLDERAPI SHGetFolderPathW(
// HWND hwnd,
// int csidl,
// HANDLE hToken,
// DWORD dwFlags,
// LPWSTR pszPath
// );
typedef shGetFolderPathNative = Int32 Function(Int64 hwnd, Int32 csidl,
Int64 hToken, Int32 dwFlags, Pointer<Uint16> pszPath);
typedef shGetFolderPathDart = int Function(
int hwnd, int csidl, int hToken, int dwFlags, Pointer<Uint16> pszPath);
// HRESULT SHGetKnownFolderPath(
// REFKNOWNFOLDERID rfid,
// DWORD dwFlags,
// HANDLE hToken,
// PWSTR *ppszPath
// );
typedef shGetKnownFolderPathNative = Int32 Function(
Pointer<GUID> rfid, Int32 dwFlags, Int64 hToken, Pointer<Void> ppszPath);
typedef shGetKnownFolderPathDart = int Function(
Pointer<GUID> rfid, int dwFlags, int hToken, Pointer<Void> ppszPath);
final ole32 = DynamicLibrary.open('ole32.dll');
final CoTaskMemFree = ole32
.lookupFunction<coTaskMemFreeNative, coTaskMemFreeDart>('CoTaskMemFree');
final shell32 = DynamicLibrary.open('shell32.dll');
final SHGetFolderPath =
shell32.lookupFunction<shGetFolderPathNative, shGetFolderPathDart>(
'SHGetFolderPathW');
final SHGetKnownFolderPath = shell32.lookupFunction<shGetKnownFolderPathNative,
shGetKnownFolderPathDart>('SHGetKnownFolderPath');
////////////////////////////////////////////////////////////////////////////////
// FUNC ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
String fromUtf16(Pointer pointer, int length) {
final buf = StringBuffer();
final ptr = Pointer<Uint16>.fromAddress(pointer.address);
for (var v = 0; v < length; v++) {
final charCode = ptr.elementAt(v).value;
if (charCode != 0) {
buf.write(String.fromCharCode(charCode));
} else {
return buf.toString();
}
}
return buf.toString();
}
/// Get the path for a known Windows folder, using the classic (deprecated) API
void getFolderPath() {
final CSIDL_MYDOCUMENTS = 0x0005;
var path = allocate<Uint16>(count: MAX_PATH);
final result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, 0, path);
if (SUCCEEDED(result)) {
print('SHGetFolderPath returned ${fromUtf16(path, MAX_PATH)}');
} else {
print(
'SHGetFolderPath returned error code 0x${result.toUnsigned(32).toRadixString(16)}');
}
}
/// Get the path for a known Windows folder, using the modern API
void getKnownFolderPath() {
final guidFolder = GUID.fromString(FOLDERID_Documents);
assert(sizeOf<GUID>() == 16);
print('Looking for folder with ID: $guidFolder');
final path = allocate<Uint16>(count: MAX_PATH);
final ptr = Pointer<Void>.fromAddress(path.address);
final result =
SHGetKnownFolderPath(guidFolder.addressOf, KF_FLAG_DEFAULT, 0, ptr);
if (SUCCEEDED(result)) {
print('SHGetKnownFolderPath returned ${fromUtf16(ptr, MAX_PATH)}');
CoTaskMemFree(ptr);
} else {
if (result == E_FAIL) {
print('SHGetKnownFolderPath returned error code E_FAIL');
} else if (result == E_INVALIDARG) {
print('SHGetKnownFolderPath returned error code E_INVALIDARG');
} else {
print('SHGetKnownFolderPath returned error code '
'0x${result.toUnsigned(32).toRadixString(16)}');
}
}
}
void main() {
getFolderPath();
print('');
getKnownFolderPath();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment