Вызов WindowsAPI CreateFile из C # - PullRequest
0 голосов
/ 15 марта 2019

При вызове CreateFile из WindowsAPI из ac #, что лучше всего делать: вызывать универсальную версию CreateFile, ANSI CreateFileA или Unicode CreateFileW?

Каждый из API имеет различную подпись для соответствующего CharSet:

// CreateFile generic
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile (
    [MarshalAs(UnmanagedType.LPTStr)] string lpFileName,
    ...

 // CreateFileA ANSI 
 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
 public static extern SafeFileHandle CreateFileA (
    [MarshalAs(UnmanagedType.LPStr)] string lpFileName,
    ...

// CreateFileW Unicode
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern SafeFileHandle CreateFileW (
    [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
    ...

Согласно документации Microsoft 1 , для C # значением CharSet по умолчанию является Charset.ANSI.Это кажется действительно странным, поскольку строки в C # являются Unicode.Если документация верна, это означает, что CreateFile в конечном итоге будет вызывать CreateFileA во время выполнения (с соответствующими преобразованиями в ANSI туда-сюда).

Другой документ Microsoft 2 говорит: «КогдаCharSet - это Unicode, или аргумент явно помечен как [MarshalAs (UnmanagedType.LPWSTR)], и строка передается по значению (не ref или out), строка будет закреплена и использована непосредственно собственным кодом (а не скопирована)."Это прекрасно подходит для того, чтобы избежать копирования потенциально больших строк и обеспечения максимальной производительности.

Предположим, что я хочу вызвать разновидность CreateFile, которая оптимально работает со строками C #, имеет лучшую производительность, минимальное приведение / преобразование, работает в ОС Windows x64и во вторую очередь имеет максимальную переносимость.

Подход 1. Вызовите универсальный CreateFile, но измените подпись на CharSet.Unicode.
Это может быть проблемой, поскольку CreateFile маршализирует lpFileName как UnmanagedType.LPTStr, тогда как CreateFileW марширует его как UnmanagedType.LPWSTR.Кажется, что маршалинг должен был бы выполнить преобразования?чтобы получить правильный тип LP (более одного раза).Другая неэффективность заключается в том, что CreateFile должен вызывать CreateFileW внутри.Кроме того, я хочу убедиться, что «закрепление» происходит для максимальной производительности, и я не уверен, что это произойдет здесь.

Подход 2. Вызов универсального CreateFile с подписью CharSet.Auto. Это, кажется, обеспечивает максимальную переносимость.для целевой ОС, но вызовет внутренний вызов CreateFileA, что неприемлемо для строк C # (Unicode).

Подход 3: Вызовите CreateFileW напрямую.Это также кажется менее чем оптимальным, потому что, если я компилирую для другой целевой ОС, такой как Win x86 (которая использует только строки ANSI), программа не сможет работать вообще.

Кажется, что подход 1 будетбудь лучшим, но MarshalAs LPTStr мне не подходит (учитывая, что версия CreateFileW маршалирует как LPWStr).

Буду признателен за любую помощь, которую вы можете оказать в этом.Я копался в десятках конфликтующих веб-страниц и не могу найти однозначного ответа.

Ссылки:

1 DllImportAttribute.CharSet Field

2 Рекомендации по собственной совместимости

3 Копирование и закрепление

Ответы [ 2 ]

2 голосов
/ 15 марта 2019

Windows использует внутреннюю кодировку UTF-16 LE 1 .Когда вы вызываете ANSI-версию Windows API, система преобразует ввод в UTF-16 (используя текущую кодовую страницу вызывающего потока), вызывает версию Unicode и преобразует вывод обратно в кодировку ANSI.Это и неоправданно дорого, так и с потерями: не каждая строка Unicode может быть представлена ​​с использованием кодировки ANSI.Преобразование также накладывает произвольные ограничения на размер входных и выходных буферов ( CreateFileA ограничивает длину имени файла до 260 единиц кода ANSI).

Учитывая это, вы всегда будете уверены, что всегдавызвать Unicode-версию Windows API.Это обеспечивает максимальную производительность во всех поддерживаемых версиях Windows, а также защищает от потери информации при преобразовании из Unicode в ANSI.Используете ли вы CharSet.Auto и MarshalAs(UnmanagedType.LPTStr) или CharSet.Unicode и MarshalAs(UnmanagedType.LPWStr) равны 2 , и это вопрос личных предпочтений. Microsoft рекомендует явным образом указывать , то есть явно указывать версию Unicode (CreateFileW) и указывать кодировку Unicode, а также типы строк широких символов (третий вариант в вашем вопросе).


1 За исключением Windows 95/98 / ME, вместе именуемой Win9x.Ни один из них официально не поддерживается.

2 CharSet.Auto "выбирает между форматами ANSI и Unicode во время выполнения на основе целевой платформы" , так что это не идентично CharSet.Unicdoe в теории.Однако все поддерживаемые платформы на практике используют кодировку Unicode.

0 голосов
/ 15 марта 2019

Звоните CreateFileW. Строки C # всегда являются Unicode, и нет никаких причин делать преобразования в ASCII и обратно в Unicode. Насчет "универсального" CreateFile - я не уверен на 100%, но для большинства функций API универсальным является макрос C. Реальные экспортируемые функции имеют версии A и W. Вы можете думать о CreateFileA (версия ASCII), только если вы используете Windows 95/98 / Me. Для 2000 / XP / 7/10 Unicode (UTF-16) строки по умолчанию.

...