Разъяснение по копированию строк из нативных структур - PullRequest
1 голос
/ 11 января 2012

Я использую PInvoke для того, чтобы использовать функции SetupAPI из C ++. Я использую это, чтобы получить пути к USB-устройствам, соответствующим спецификации HID. У меня все работает, но что-то, чего я не понимаю, меня озадачило. Используя эту структуру из SetupAPI:

typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
    DWORD cbSize;
    TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;

Я не получаю те же результаты, что и пример кода, который я использую. Во-первых, я использую IntPtr и выделяю память, используя Marshal.AllocHGlobal() для передачи этого туда и обратно. Я дважды вызываю SetupDiGetDeviceInterfaceDetail(), во-первых, чтобы получить размер нужного мне буфера, а во-вторых, чтобы получить данные, которые мне интересны. Я ищу путь к этому устройству, которое хранится в этой структуре.

Код, из которого я ухожу, делает это:

IntPtr pDevPath = new IntPtr(pDevInfoDetail.ToInt32() + 4);
string path = Marshal.PtrToStringAuto(pDevPath);

Который работает просто отлично. Я сделал это, и строка, которую я получил, была бредом. Я должен был изменить это на

IntPtr pDevPath = new IntPtr(pDevInfoDetail.ToInt32() + 4);
string path = Marshal.PtrToStringAnsi(pDevPath);

, чтобы заставить это работать. Почему это? Я пропускаю некоторые настройки для проекта / решения, которые информируют этого зверя, как обращаться со строками и символами? Пока что статья MSDN для PtrToStringAuto() не рассказывает мне об этом. На самом деле, похоже, что этот метод должен был принять соответствующее решение, называемое версией Unicode или Ansi для моих нужд, и все было бы хорошо.

Пожалуйста, объясните.

1 Ответ

1 голос
/ 11 января 2012

Прежде всего, +10000 при использовании реального типа взаимодействия P / Invoke, а не при сортировке данных вручную.Но так как вы спросили, вот что происходит с вашими строками.

Среда выполнения решает, как обрабатывать строки и символы в каждом конкретном случае, на основе атрибутов, которые вы применяете к декларациям взаимодействия, контекст, в которомвы используете взаимодействие, методы, которые вы вызываете, и т. д. Каждый тип объявления P / Invoke (метод extern, делегат или структура) позволяет вам указать размер символов по умолчанию для области действия этого определения.Существует три варианта:

  • Использовать CharSet.Ansi, который преобразует управляемые строки Unicode в 8-битные символы
  • Использовать CharSet.Unicode, который передает данные строки как 16-битныесимволы
  • Используйте CharSet.Auto, который решает во время выполнения, в зависимости от операционной системы хоста, какую из них использовать.

В общем, я ненавижу CharSet.Auto, потому что это в основном бессмысленно.Поскольку Framework даже не поддерживает Windows 95, единственное время, когда «Auto» не означает «Unicode», - это при работе в Windows 98. Но здесь есть более серьезная проблема, заключающаяся в том, что во время выполнения принимается решение о том, как выполнить маршалинг строкв «неподходящее время».

Неуправляемый код, который вы вызываете, принял это решение в время компиляции , поскольку компилятор должен был решить, означает ли TCHAR char или wchar - это решениеоснован на присутствии макроса препроцессора _UNICODE.Это означает, что для большинства библиотек он будет всегда использовать одну или другую, и нет смысла позволять CLR «выбирать одну».

Для системных компонентов Windows все немного лучше, потому чтоUnicode-ориентированные сборки на самом деле включают в себя две версии большинства системных функций.Например, API установки имеет два метода: SetupDiGetDeviceInterfaceDetailA и SetupDiGetDeviceInterfaceDetailW.Версия * A использует 8-битные строки "ANSI", а версия * W использует 16-битные строки "Unicode".Он также имеет ANSI и широкую версию любой структуры, которая имеет строку.

Это ситуация, когда светит CharSet.Auto, если вы используете его правильно.Когда вы применяете DllImport к функции, вы можете указать набор символов.Если вы укажете Ansi для набора символов, если среда выполнения не найдет точное совпадение с именем вашей функции, она добавит A и попытается снова.(Как ни странно, если вы укажете Unicode, он вызовет функцию * W first и попытается найти точное совпадение только в случае неудачи.)

Вот подвох: если вы неt укажите набор символов на вашем DllImport, , по умолчанию это CharSet.Ansi.Это означает, что вы получите ANSI-версию функции, если только вы не переопределите кодировку.Скорее всего, это происходит здесь: вы вызываете версию ANSI SetupDiGetDeviceInterfaceDetail по умолчанию и, таким образом, получаете строку ANSI обратно, но PtrToStringAuto хочет использовать Unicode, потому что вы, вероятно, используете хотя бы Windows XP.

Лучший вариант, при условии, что мы можем игнорировать Windows 98, будет указывать CharSet.Unicode повсеместно, поскольку SetupAPI его поддерживает, но, по крайней мере, вам нужно указать то же самое CharSet значение везде.

...