据我了解,没有 gems 可以帮助打开本机文件对话框,所以我有兴趣编写一个,专门针对 Windows
我陷入了第一步,即获取文件打开对话框的 CLSID,我在某处读到我需要
SysAllocString
并将结果 BSTR
传递给 CLSIDFromString
require 'fiddle'
require 'fiddle/import'
require 'fiddle/types'
include Fiddle
include Fiddle::CParser
clsid = "{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}\0".encode 'UTF-16' # <= must be WCHAR according to winapi specs
clsidptr = Pointer[clsid]
oleaut_dll = Fiddle.dlopen 'OleAut32'
sysallocstring = Function.new oleaut_dll['SysAllocString'], [parse_ctype('const char* string')], parse_ctype('char* bstr')
bstr = sysallocstring.call(clsid) # <= the string here is 0 length which should not be the case
p bstr.to_s
ole_dll = Fiddle.dlopen 'Ole32'
clsidfromstring = Function.new ole_dll['CLSIDFromString'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_INT
buf = '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
bufptr = Pointer[buf]
value = clsidfromstring.call(clsidptr, bufptr)
begin
if value == -2_147_221_005
raise 'CO_E_CLASSSTRING'
elsif value == -2_147_024_808
raise 'E_INVALIDARG'
else
puts 'NOERROR'
puts buf
end
ensure
sysfreestring = Function.new oleaut_dll['SysFreeString'], [parse_ctype('char* string')], parse_ctype('void')
sysfreestring.call(bstr)
end
但是我只得到了CO_E_CLASSSTRING,我尝试了不同的编码,但没有找到任何解决方案。
任何和所有帮助将不胜感激
对于那些说用 tk 制作 GUI 的人来说,我更喜欢使用本机对话框
在咬牙切齿之后,我偶然发现了
commdlg32
和 GetOpenFileName 并想出了这个:
# Gems needed: ffi, ffi_wide_char
require 'ffi'
require 'ffi_wide_char'
module ComdlgAPI
extend FFI::Library
ffi_lib 'comdlg32'
ffi_convention :stdcall
class OPENFILENAME < FFI::Struct
layout :lStructSize, :ulong,
:hwndOwner, :pointer,
:hInstance, :pointer,
:lpstrFilter, :pointer,
:lpstrCustomFilter, :pointer,
:nMaxCustFilter, :ulong,
:nFilterIndex, :ulong,
:lpstrFile, :pointer,
:nMaxFile, :ulong,
:lpstrFileTitle, :pointer,
:nMaxFileTitle, :ulong,
:lpstrInitialDir, :pointer,
:lpstrTitle, :pointer,
:Flags, :ulong,
:nFileOffset, :ushort,
:nFileExtension, :ushort,
:lpstrDefExt, :pointer,
:lCustData, :pointer,
:lpfnHook, :pointer,
:lpTemplateName, :pointer,
:pvReserved, :pointer,
:dwReserved, :ulong,
:FlagsEx, :ulong
end
attach_function :GetOpenFileNameW, [OPENFILENAME.by_ref], :bool
end
def open_file_dialog
ofn = ComdlgAPI::OPENFILENAME.new
ofn[:lStructSize] = ComdlgAPI::OPENFILENAME.size
ofn[:lpstrFile] = FFI::MemoryPointer.new(:char, 260 * 2)
ofn[:nMaxFile] = 260
# Use UTF-16LE encoding for wide strings
filters = "All Files\0*.*\0Text Files\0*.TXT\0\0".encode('UTF-16LE')
ofn[:lpstrFilter] = FFI::MemoryPointer.from_string(filters)
ofn[:nFilterIndex] = 1
ofn[:Flags] = 0x00000800 | 0x00001000 # OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
if ComdlgAPI.GetOpenFileNameW(ofn)
# Use ffi_wide_char helper to convert
# memory to UTF-16LE, and then encode as UTF-8
return FfiWideChar.read_wide_string(ofn[:lpstrFile]).encode('UTF-8')
else
return nil
end
end
if file_path = open_file_dialog
puts "Selected file: #{file_path}"
else
puts "No file selected"
end