Windows Ruby 中的本机文件打开对话框

问题描述 投票:0回答:1

据我了解,没有 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 的人来说,我更喜欢使用本机对话框

ruby windows winapi fiddle
1个回答
0
投票

在咬牙切齿之后,我偶然发现了

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
© www.soinside.com 2019 - 2024. All rights reserved.