Как конвертировать Ruby строковое кодирование в win32 и обратно - PullRequest
0 голосов
/ 18 октября 2018

У меня есть ситуация, когда я создаю временные файлы из Ruby в папке пользователя.Я использую Dir.mktmpdir() для создания временной папки, в которую я помещаю свои файлы.

По какой-то причине эта функция дает мне короткое имя папки Windows, поэтому вместо, например, C:\Users\very long username\AppData\Local\Temp\20181018-5548 я получаю что-то вродеC:\Users\VERYLO~1\AppData\Local\Temp\20181018-5548.Это изначальная причина моей проблемы, но у меня сложилось впечатление, что я не могу это исправить, потому что мне приходится работать в среде, над которой у меня нет большого контроля (Ruby 2.0 и 2.2 встроены в SketchUpесли быть точным).Это также ограничивает количество внешних библиотек Ruby, которые я могу разумно ввести.

Чтобы получить действительное длинное имя папки, я вызывал функцию Win32 GetLongPathName() через класс WIN32API, и это было в значительной степениуспешно.

Однако у меня проблемы со спецсимволами.Похоже, что буферы, которые я отправляю в Win32 API (и также буферы возврата), должны находиться в определенной кодировке, а строки Ruby - это UTF-8 (или предполагается, что возвращаемые значения - UTF-8).Я бы с радостью облегчил любые изменения в кодировке, но я несколько потерял в плане того, какую кодировку использовать.Я даже не уверен, используется ли широкоформатная версия Win32 API.

Есть что-то, что добавляет странности и заставляет меня задуматься, могу ли я лаять неправильное дерево: использование международного и/ или английская версия Windows Я могу без проблем отправлять и получать всевозможные специальные символы в вызов Win32 API.Однако, как только я использую версию Windows на другом языке (я пробовал бразильский португальский, но у нас были люди с ивритом, и некоторые восточно-европейские издания Windows также сообщали об этой проблеме), она перестала работать.

def self.get_long_win32_filename(short_name)
    require 'Win32API'
    max_path = 1024
    long_name = " " * max_path
    lfn_size = Win32API.new("kernel32", "GetLongPathName", ['P','P','L'],'L').call(short_name, long_name, max_path)
    return (1..max_path).include?(lfn_size) ? long_name[0..lfn_size-1] : short_name
end 

Вот код, который я использую:

Любая помощь в выяснении того, как подходить к проблеме кодирования при передаче строк в Win32 API и из него, очень ценится!

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Вместо того, чтобы запускать обходы с Win32 для этого, я бы рекомендовал использовать Sketchup.temp_dir (http://ruby.sketchup.com/Sketchup.html#temp_dir-class_method), чтобы получить системный временный путь и сгенерировать свое собственное уникальное имя для своего собственного временного подкаталога. Это будетнамного менее хрупкий, чем преобразование между локалями и вызовами Win32.

0 голосов
/ 22 октября 2018

Не уверен, что это лучший способ, но вот то, что мне показалось подходящим.По сути, я просто изменил функцию из вопроса, чтобы преобразовать входные данные в текущий языковой стандарт системы и преобразовать результат обратно:

def self.get_long_win32_filename(short_name)
    max_path = 1024
    long_name = " " * max_path
    # Make sure the short_name is in the current system locale encoding,
    # because the Win32 API appears to always expect strings like that.
    short_name = short_name.encode(Encoding::find('locale'))
    lfn_size = Win32API.new("kernel32", "GetLongPathName", ['P','P','L'],'L').call(short_name, long_name, max_path)
    # If lfn_size is a valid value, shorten the long_name to the actual length,
    # otherwise use short_name (e.g. when a zero lfn_size indicates an error).
    long_name = (1..max_path).include?(lfn_size) ? long_name[0..lfn_size-1] : short_name
    # Make sure the return string is in the correct encoding again.
    long_name.force_encoding(Encoding::find('locale'))
    return long_name.encode(Encoding::UTF_8)  
end 

В одном я не уверен, должен ли я использовать Encoding::find('locale'), как яделаю или Encoding::find('filesystem'), потому что мои строки - это имена файлов.

0 голосов
/ 18 октября 2018

Для работы с этими языками необходимо использовать GetLongPathNameW.Единственная проблема в том, что эта функция использует широкие строки, поэтому вам нужно будет вызывать функции WinAPI, которые преобразуют многобайтовые строки в широкие строки и наоборот.

В геме windows-pr эти функции уже четко определеныкак multi_to_wide и wide_to_multi.Я не знаю, можете ли вы использовать драгоценные камни в вашей среде, если вы не можете, то попробуйте эти «упрощенные» версии:

def mb_to_wide(str)
  # CP_UTF8 = 65001
  wsize = Win32API.new("kernel32", "MultiByteToWideChar", 'ILSIPI', 'I').call(65001, 0, str, -1, nil, 0)
  if wsize > 0
    wstr = " " * wsize * 2
    Win32API.new("kernel32", "MultiByteToWideChar", 'ILSIPI', 'I').call(65001, 0, str, -1, wstr, wsize)
    wstr
  end
end

def wide_to_mb(wstr)
  wstr << "\000\000" if wstr[-1].chr != "\000" # add wide null terminators if not found
  size = Win32API.new("kernel32", "WideCharToMultiByte", 'ILSIPIPP', 'I').call(65001, 0, wstr, -1, 0, 0, nil, nil)
  if size > 0
    str = " " * wstr.length
    Win32API.new("kernel32", "WideCharToMultiByte", 'ILSIPIPP', 'I').call(65001, 0, wstr, -1, str, wstr.length, nil, nil)
    str[/^[^\0]*/] # up to \0
  end
end

Тогда вам просто нужно будет адаптировать свою функцию к чему-то вродеэто:

def self.get_long_win32_filename(short_name)
  max_path = 1024
  long_name = " " * max_path
  wshort_name = mb_to_wide(short_name)
  size = Win32API.new("kernel32", "GetLongPathNameW", 'PPL', 'L').call(wshort_name, long_name, max_path)
  wide_to_mb(long_name[/.+?(?=\0\0)/]) # up to \0\0
end

Это может решить проблему, указанную в заголовке вопроса и в большей части вашего сообщения, но, возможно, вы задаете неправильный вопрос, и вам следует найти более простое решение для создания временной папкибез возвращения короткого пути для начала.Таким образом, вам не нужно проходить через все это только для решения вашей проблемы.

...