Что такое код w32api_invoke_function

Содержание

FPublisher

Web-технологии: База знаний

Документация PHP

w32api_invoke_function

w32api_invoke_function — Invokes function funcname with the arguments passed after the function name

Описание

w32api_invoke_function() tries to find the previously registered function, passing the parameters you provided.

Список параметров

The function name.

Any of the arguments can be of any PHP type or w32api_deftype() defined type, as needed.

Возвращаемые значения

The return type is the one you set when you registered the function, the value is the one returned by the function itself.

Примечания

Эта функция является ЭКСПЕРИМЕНТАЛЬНОЙ. Поведение этой функции, ее имя и относящаяся к ней документация могут измениться в последующих версиях PHP без уведомления. Используйте эту функцию на свой страх и риск.

w32api_invoke_function

w32api_invoke_function — Invokes function funcname with the arguments passed after the function name

Описание

w32api_invoke_function() tries to find the previously registered function, passing the parameters you provided.

Список параметров

The function name.

Any of the arguments can be of any PHP type or w32api_deftype() defined type, as needed.

Возвращаемые значения

The return type is the one you set when you registered the function, the value is the one returned by the function itself.

Примечания

Эта функция является ЭКСПЕРИМЕНТАЛЬНОЙ. Поведение этой функции, ее имя и относящаяся к ней документация могут измениться в последующих версиях PHP без уведомления. Используйте эту функцию на свой страх и риск.

User Contributed Notes 1 note

As of PHP 4.2.0, this function doesn’t appear to do anything useful (nor does it even work). Instead, use w32api_register_function to register your function and then simply call the function you registered just like you would call any normal PHP function. For example, the following snippet will display a standard messagebox:

w32api_register_function(«User32.dll»,
«MessageBoxA»,
«long»);
MessageBoxA(NULL,
«Hello World!»,
«PHP MessageBox»,
0);

Рекомендации по организации кода .NET P/Invoke для API Win32

Я реорганизую большую и сложную базу кода в .NET, которая сильно использует API P/Invoke для Win32. Структура проекта не самая большая, и я нахожу нахождении DllImport-операторов повсеместно, очень часто дублируется для одной и той же функции, а также объявляется различными способами:

Директивы и методы импорта иногда объявляются публичными, иногда частными, иногда статическими, а иногда и методами экземпляра. Мое беспокойство заключается в том, что рефакторинг может иметь непреднамеренные последствия, но это может быть неизбежным.

Есть ли документальные рекомендации, которые я могу последовать, что может помочь мне?

Мой инстинкт состоит в том, чтобы организовать статический/общий класс API Win/P/Invoke API, в котором перечислены все эти методы и связанные константы в одном файле. РЕДАКТИРОВАТЬ В DLL пользователя32 содержится более 70 импортных файлов.

(База кода состоит из более чем 20 проектов с большим количеством передачи сообщений Windows и сквозных вызовов. Это также проект VB.NET, обновленный с VB6, если это имеет значение.)

Вы можете подумать о том, как это было сделано в .NET Framework. Он неизменно объявляет статический класс (Module in VB.NET) с именем NativeMethods, который содержит объявления P/Invoke. Вы можете быть более организованными, чем программисты Microsoft, существует много повторяющихся деклараций. Различные команды, работающие в разных частях структуры.

Однако, если вы хотите поделиться этим среди всех проектов, вы должны объявить эти объявления Public вместо Friend. Что не очень важно, это должна быть детальность реализации. Я думаю, вы можете решить это, повторно используя файл исходного кода в каждом проекте, который ему нужен. Как правило, табу, но все в порядке, я думаю.

Я лично объявляю их по мере необходимости в файле исходного кода, который им нужен, делая их приватными. Это также очень помогает при лжи о типах аргументов, особенно для SendMessage.

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

Расширение выше по запросу.

Учитывая характер P/Invoke, особенно если требуется несколько вызовов и имеют разные области реализации, мне кажется, что лучше группировать подобные предметы вместе, таким образом вы не тянете много других беспорядков или другие DLL-импорт, если это не требуется.

Желание избегать статических методов связано с вызовами неуправляемых ресурсов и возможностью утечки памяти и т.д.

w32api_invoke_function

Invokes function funcname with the arguments passed after the function name ( PHP 4 >= 4.2.0 )

w32api_invoke_function() tries to find the previously registered function, passing the parameters you provided.

Parameters

The function name.

Any of the arguments can be of any PHP type or w32api_deftype() defined type, as needed.

Return Values

The return type is the one you set when you registered the function, the value is the one returned by the function itself.

Notes

This function is EXPERIMENTAL . The behaviour of this function, the name of this function, and anything else documented about this function may change without notice in a future release of PHP. Use this function at your own risk.

Рекомендации по организации .NET P / Invoke кода Win32 API,

December 2020

2.2k раз

Я рефакторинг большой и сложный код базы в .NET, которая делает интенсивное использование P / Invoke для Win32 API. Структура проекта не является самым большим, и я нахожу заявление DllImport повсюду, очень часто дублируется для одной и той же функции, а также объявлено в различных формах:

Директивы и методы импорта иногда объявлены публично, иногда частные, иногда как статические, а иногда и как методы экземпляра. Мое беспокойство в том, что рефакторинг может иметь непредсказуемые последствия, но это может быть неизбежным.

Существуют документально лучшие практики я могу следовать, что может помочь мне?

Мой instict должен организовать статический / общий Win32 P / Invoke класса API , который перечисляет все из этих методов и связанных с ними констант в одном файле . EDIT Есть более 70 импорта в user32 DLL.

(Код база состоит из более чем 20 проектов с большим количеством окон передачи сообщений и кроссом-нить вызовов. Это также проект VB.NET повышен с VB6, если это делает разницу.)

6 ответы

Вы могли бы рассмотреть, как это было сделано в рамках .NET. Он неизменно заявляет статический класс (модуль в VB.NET) с именем NativeMethods, который содержит P / Invoke деклараций. Вы могли бы быть более организованными, чем программисты Microsoft, есть много повторяющихся деклараций. Различные команды, работающие на различных частях каркаса.

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

Я лично объявить их по мере необходимости в коде файла источника, который нуждается в них, что делает их Private. Это также очень помогает, когда лгут о типах аргументов, особенно для SendMessage.

Организуйте их в [Safe|Unsafe]NativeMethods классе . Отметить класс как internal static . Если вам нужно , чтобы выставить их на свои собственные сборки, вы можете использовать InternalsVisibleTo — хотя бы более уместно , если вы могли бы группировать связанные из них в каждую сборку.

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

В качестве первого шага — я бы , вероятно , переместить все к ядру сборки (если он есть), или создать Product.Native сборку. Тогда вы можете найти простофили и перекрывается легко, и искать управляемые эквиваленты. Если р / вызывает беспорядок, я не подозреваю , у вас есть много на пути наслоения в других сборках , которые будут направлять вашу группировку.

Почему бы не создать уникальный файл с именем Win32.vb и в этом логически группировать pinvokes в отдельные пространства имен, например , пространство имен GDI могут использовать все pinvokes GDI, User32 имена могут использовать все pinvokes , который находится в ядре User32, и так далее .. ..Это может быть болезненными в первом, но , по крайней мере , вы будете иметь централизованные имена все содержащиеся в этом файл? Посмотрите здесь , чтобы посмотреть , что я имею в виду . Что вы думаете?

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

Расширение на выше по запросу.

Учитывая характер P / Invoke, особенно если число вызовов необходимы и варьирования области реализации я считаю, что лучше группы как предметы вместе, таким образом, вы не тянет во многих других помех, или другие импорта DLL когда не требуется.

Илон Маск рекомендует:  Получение текущих дисков в системе на PHP.

Желание держаться подальше от статических методов, связано с вызовами неуправляемых ресурсов и потенциала для утечек памяти и т.д ..

Являются ли ваши P / Invoke вызывает артефакт миграции из VB6? Я мигрировали 300000 строк кода из VB6 в C # (Windows.Forms и System.EnterpriseServices), и устранить все, кроме горстки P / Запускает звонки — есть почти всегда управляемый эквивалент. Если вы рефакторинга, вы можете рассмотреть возможность сделать что-то подобное. Полученный код должен быть справедливым легче поддерживать.

Рекомендуемый способ иметь класс NativeMethods за сборку со всеми методами DllImported в нем, с внутренней видимостью. Таким образом, вы всегда знаете, где ваша импортируемая функция есть и во избежание дублирования деклараций.

Что такое код w32api_invoke_function

w32api_invoke_function — Invokes function funcname with the arguments passed after the function name

Description mixed w32api_invoke_function ( string funcname, mixed argument [, mixed . ] )

w32api_invoke_function() tries to find the previously registered function, passing the parameters you provided.

Parameters

The function name.

Any of the arguments can be of any PHP type or w32api_deftype() defined type, as needed.

Return Values

The return type is the one you set when you registered the function, the value is the one returned by the function itself.

Notes

This function is EXPERIMENTAL . The behaviour of this function, the name of this function, and anything else documented about this function may change without notice in a future release of PHP. Use this function at your own risk.

Что такое код w32api_invoke_function

w32api_invoke_function — вызывает функцию funcname с аргументами, переданными после имени функции.

Описание

mixed w32api_invoke_function (string funcname)

Warning

Эта функция — ЭКСПЕРИМЕНТАЛЬНАЯ. Поведение, имя и всё остальное, что задокументировано для данной функции может быть изменено в будущих релизах РНР без предупреждения. Вы можете использовать эту функцию только на свой страх и риск.

Предупреждение!

Эта функция в настоящее время ещё не задокументирована; имеется только список аргументов.

Что такое код w32api_invoke_function

w32api_invoke_function — Invokes function funcname with the arguments passed after the function name

Description mixed w32api_invoke_function ( string funcname, mixed argument [, mixed . ] )

Предупреждение!

This function is EXPERIMENTAL . The behaviour of this function, the name of this function, and anything else documented about this function may change without notice in a future release of PHP. Use this function at your own risk.

w32api_invoke_function() tries to find the previously registered function, named funcname , passing the parameters you prov >w32api_deftype() defined type, as needed.

Что такое код w32api_invoke_function

w32api_invoke_function — Invokes function funcname with the arguments passed after the function name

Description mixed w32api_invoke_function ( string funcname, mixed argument [, mixed . ] )

Warning

Эта функция является ЭКСПЕРИМЕНТАЛЬНОЙ . Поведение этой функции, ее имя и относящаяся к ней документация могут измениться в последующих версиях PHP без уведомления. Используйте эту функцию на свой страх и риск.

w32api_invoke_function() tries to find the previously registered function, named funcname , passing the parameters you provided. The return type is the one you set when you registered the function, the value is the one returned by the function itself. Any of the arguments can be of any PHP type or w32api_deftype() defined type, as needed.

Внимание
Пред. Начало След.
w32api_init_dtype Уровень выше w32api_register_function

Если Вы не нашли что искали, то рекомендую воспользоваться поиском по сайту:

Генерация P/Invoke сигнатур в C#. Нецелевое использование Interface Definition Language и OLE Automation Type Libraries

Это НЕ очередная статья о том что такое P/Invoke.

Итак, допустим в сферическом C# проекте необходимо использовать какую-либо технологию, отсутствующую в .NET, и все что у нас есть это Windows SDK 8.1 в котором имеется лишь набор заголовочных файлов для C/С++. Придется объявлять кучу типов, проверять корректность выравнивания структур и писать различные обертки. Это большое количество рутинной работы, и риск допустить ошибку. Можно конечно написать парсер заголовочных файлов… Тут просто и понятно все кроме количества требуемых на это человекочасов. Поэтому этот вариант отбрасываем и постараемся как либо иначе свести к минимуму количество необходимых действий для взаимодействия с unmanaged кодом.

Кроме того, полученный в результате код не будет зависеть от разрядности процесса, будет сохранена строгая типизация, будет применено автоматическое тестирование.

Взаимодействие Managed и Unmanaged кода.

Как известно, в .NET существует 2 основных способа взаимодействия с unmanaged кодом:

  1. С++/CLI: Можно написать враппер – обернуть unmanaged вызовы в managed методы, вручную преобразовывать native структуры, строки и массивы в managed объекты. Бесспорно это максимально гибко, но недостатков больше.
    Во-первых это куча кода, в том числе unmanaged, соответственно потенциальный риск допустить ошибку (без багов пишут только боги и лжецы).
    Во-вторых полученные сборки гвоздями приколочены к архитектуре – x64, x86 и.т.п., соответственно если у нас весь проект AnyCPU то придется собирать врапперы под несколько платформ и тащить их все с собой, распаковывая при установке или загружая при запуске сборку соответствующую конфигурации.
    В-третьих это C++, а он не нужен.
  2. P/Invoke и COM: Множество компонентов windows реализовано с использованием COM. В общем случае .net приемлемо работает с этой технологией. Необходимые интерфейсы и структуры можно либо объявлять вручную самостоятельно, либо, при наличии библиотеки типов, импортировать их оттуда автоматически с использованием специальной утилиты tlbimp.
    А вызывать экспортируемые функции из динамических библиотек можно объявив extern методы с атрибутом DllImport. Есть даже целый сайт где выложены объявления для основных winapi функций.

Остановимся подробнее на библиотеках типов. Библиотеки типов, как можно догадаться из названия, содержат информацию о типах, и получаются путем компиляции IDL – interface definition language – языка синтаксис которого чертовски схож с С. Библиотеки типов обычно поставляются либо в виде отдельных файлов с расширением .tlb либо встроены в ту же DLL где находятся описываемые объекты. Упомянутая выше утилита tlbimp генерирует из библиотек типов специальную interop-сборку содержащую необходимые объявления для .NET.
Поскольку синтаксис IDL схож объявлениями в заголовочных файлах языка C, то первая мысль которая приходит в голову – а не сгенерировать ли каким-либо образом библиотеку типов чтобы в дальнейшем импортировать ее в .net проект? Если в IDL файл можно скопировать все необходимые объявления из заголовочных файлов практически как есть, не задумываясь о конвертировании всяких там DWORD в uint, то это как раз то что нужно. Но есть ряд проблем: во-первых IDL не все поддерживает, а во-вторых tlbimp не все импортирует. В частности:

  • В IDL нельзя использовать указатели на функции
  • В IDL нельзя объявлять битовые поля
  • tlbimp не использует unsafe-код, поэтому на выходе подавляющее число указателей будут представлены нетипизированным IntPtr
  • Если в качестве аргумента в метод передается структура по ссылке, то tlbimp объявит такой аргумент как ref. И если в теории подразумевается, что туда на самом деле передавать надо адрес массива, то мы идем лесом. Конечно можно передать как ref нулевой элемент pinned-массива, оно даже будет работать, но выглядит такое несколько по-индусски. В любом случае из-за ref мы не сможем передать нулевой указатель если аргумент вдруг опциональный
  • Указатели на C-style null-terminated строки (а ля LPWSTR) tlbimp преобразует в string, и если вдруг нехороший COM объект вздумает что то записать в этот кусок памяти, приложение скажет “кря”
  • tlbimp импортирует только интерфейсы и структуры. Методы из DLL придется объявлять вручную
  • tlbimp генерирует сборку но не код. Хотя это и не так критично

Все проблемы с tlbimp решаются легко – мы не будем использовать эту утилиту, а напишем свою. А вот с IDL дело обстоит сложнее – придется шаманить. Предупреждаю сразу: поскольку библиотека типов будет являться лишь промежуточным звеном, то забудем о совместимости с какими-либо стандартами, хорошим тоном и.т.п. и будем хранить в ней все в том виде в котором удобнее нам.

Я не буду подробно останавливаться на описании этого языка, а лишь вкратце перечислю ключевые элементы IDL которые будут использованы. Полное описание IDL есть в msdn

Основной блок в IDL файле это library. Все типы, которые находится внутри него, будут включены в библиотеку. Типы объявленные вне блока library будут включены только если на них ссылается кто-либо из блока library. По хорошему блок library должен иметь имя и уникальный идентификатор. Есть и ряд других атрибутов, но нам ничего из этого не нужно.
Но если все-таки необходимо принудительно включить тип объявленный вне блока, то можно внутри library написать

Внутри блока идут объявления типов. Нам понадобятся struct, union, enum, interface и module. Первые три абсолютно то же что и в С, поэтому не будем на них подробно останавливаться. Следует отметить только одну особенность, заключающуюся в том, что при таком объявлении:
именем структуры будет tagTEST, а TEST это alias который будет в итоге заменен именем. Поскольку во многих заголовочных файлах в объявлениях структур присутствуют различные мерзкие префиксы, то во избежание бардака в именах лучше принять какие-нибудь меры. А в целом, в IDL как и в C можно создавать любое количество alias-ов директивой typedef.

Для объявления интерфейсов используется блок interface. Внутри этого блока функции:
Все довольно очевидно. Из атрибутов в нашем случае важен только uuid, являющийся идентификатором интерфейса.

Еще есть блок module. В нем можно, к примеру, размещать функции из DLL, или какие-нибудь константы.
Здесь важны атрибуты dllname и entry, указывающие откуда будет загружаться метод. В качестве entry можно указывать ordinal функции вместо имени.

Объявления в IDL

Составим список того что надо брать из заголовочного файла:

  • Структуры и объединения, в.т.ч. с битовыми полями
  • Перечисления
  • Объявления функций импортируемых из DLL
  • Интерфейсы
  • Константы (макросы объявленные с помощью #define)
  • Указатели на функции
  • Alias-ы типов объявленные через typedef (т.е. всякие там DWORD-ы и.т.п.)

Теперь надо определиться как это все копировать в IDL.

  • Структуры и объединения: Копируем как есть, при желании убирая только лишние префиксы из имен.
  • Перечисления: Аналогично структурам.
  • Объявления функций импортируемых из DLL: Копируем как есть в блок module для соответствующей DLL. Очевидно, что для каждой DLL понадобится создать хотя бы по одному блоку module.
  • Константы (объявленные через #define): Тут конечно не очень хорошо получается – придется добавлять тип, т.е. константа из примера выше это на самом деле
    вариантов немного – макросы то естественно никак не могут попасть в библиотеку типов.
    Другая проблема это всякие структуры вроде GUID-ов объявленных с помощью DEFINE_GUID. Ну если быть точным, то фактически это никакие не константы, а глобальные переменные, но используются обычно в качестве констант. Тут увы никак. GUID-ы то мы еще можем в виде строк объявить, но со всем остальным придется иметь дело вручную.
  • Alias-ы типов объявленные через typedef (т.е. всякие там DWORD-ы и.т.п.): Копируем как есть.
  • Интерфейсы: Поскольку ни C ни C++ не поддерживают интерфейсы, то в большинстве заголовочных файлов они объявлены через условную компиляцию двумя способами – как класс для C++ с __declspec(uuid(x)) в том или ином виде и как структура со списком указателей на функции для C. Нас интересуют объявления для C++. Они выглядят обычно так:
    Необходимо почистить отсюда все лишнее, чтобы интерфейс выглядел так:
    При желании можно не трогать комментарии, а SAL-аннотации спрятать в атрибут [annotation(…)].
    Да, ряд операций проделывать все-таки приходится, но ключевой момент, как и основная суть статьи, здесь в том что мы не трогаем аргументы функций и возвращаемые значения. Т.е. даже несмотря на то что исходное объявление несколько изменяется, можно с достаточной уверенностью гарантировать его корректность, так как все типы и indirection level указателей остаются неизменными. Если что то забудем почистить, то оно не скомпилируется, но если скомпилируется то результат будет корректен поскольку “сигнатуры” не меняются.
  • Указатели на функции: Здесь начинаются костыли. Объявим интерфейс с одним методом, а при конвертации библиотеки типов такие интерфейсы будем преобразовывать в делегаты. Таким образом по-прежнему не будем трогать аргументы, да и остальной код использующий этот указатель не будет выдавать ошибок компиляции.
    Т.е. к примеру это:
    будет выглядеть так:
    В данном случае __Delegate это объявленный нами пустой интерфейс по которому мы будем отличать такой “указатель на функцию” от обычных интерфейсов. Атрибут uuid содержит случайное значение (чтобы не конфликтовать ни с чем), просто без него не скомпилируется. Можно конечно было бы заменить все указатели на функции на void*, но благодаря такому хаку мы сохраним строгую типизацию, например поле WNDPROC lpfnWndProc у структуры WNDCLASSEX в библиотеке типов будет также строго типизированным, а нам нужна информация только об имени типа и indirection level указателей, потому тот факт, что это интерфейс значения не имеет.
  • Битовые поля: Хотя это и относится к структурам, я вынес их в отдельный пункт поскольку здесь тоже придется хитрить. Надо к каждому каким-либо образом привязать информацию о числе бит. Например, можно сделать это с помощью массивов. А чтобы при конвертации библиотеки типов понять, что это битовое поле, добавить какой-нибудь ненужный атрибут. Например это:
    объявим так:
    И для простоты условимся что если в структуре есть битовые поля то обычных полей там быть не должно. Тогда такие объявления:
    Надо будет преобразовать в:
    Но битовые поля это очень большая редкость, потому в принципе их можно бы было и вообще не поддерживать, а заменять на базовый тип и уже в C# вручную делать все остальное:

Вышеизложенного должно быть достаточно чтобы перенести в IDL информацию обо всем что может понадобиться при работе с native библиотеками. Конечно здесь не учитываются различные классы и шаблоны для C++, но во всяком случае процентов девяносто пять содержимого заголовочных файлов от Windows API таким образом перенести можно. Несмотря на наличие нескольких грязных хаков, копирование в IDL все равно проще, быстрее и безопаснее чем написание врапперов на CLI или ручного объявления типов в .NET.

Объявления в С#

Рассмотрим теперь как это все должно выглядеть в C#.

Генерировать мы будем unsafe код. Во-первых для строгой типизации указателей, во-вторых, чтобы не гонять данные туда-сюда всяческими там Marshal.PtrToStructure. Не столько из-за ловли блох на производительности, а просто потому что с расово-верными указателями код получается тупо проще. Маршалинг сложных типов иначе лаконично не сделать — это будут тонны кода. Я пробовал все варианты и очень долго пытался найти универсальный способ не использующий unsafe код. Его нет, и отказ от unsafe это палки себе в колеса – надежнее и безопаснее код не станет, а проблем добавится.

Разницу лучше всего видно когда надо в функцию передать структуру содержащую указатель на другую структуру, или на строку, или вообще рекурсивную ссылку. А если в unmanaged коде один указатель затем будет заменен на другой и надо чтобы эти изменения отразились на исходной структуре в managed коде… тут даже custom marshaling не особо поможет. Да, и кстати атрибут MarshalAs не нужен и использоваться не будет.

Кроме того, использование импортированных объявлений будет максимально приближено к таковому в С, что возможно сможет облегчить перенос уже написанного кода. Следует сразу отметить что чтобы в C# получить адрес переменной, она должна иметь blittable-тип. Все наши структуры будут соответствовать этим требованиям. Поля с массивами объявим как fixed, для строк будем использовать char*/byte*, но вот тип bool не является blittable, поэтому в нашем случае для его представления будет использоваться структура с int полем и implicit операторами для приведения от/к bool. На массивах внутри структур надо остановиться чуть подробнее. Есть ограничения: во-первых ключевое слово fixed применимо только к массивам примитивных типов, поэтому массивы структур так не объявить, а во-вторых поддерживаются только одномерные массивы. Обычные массивы (с атрибутом MarshalAs и опцией SizeConst) хоть и могут содержать структуры, но они не являются blittable-типом, кроме того они также могут быть только одномерными. Чтобы решить этот вопрос, для массивов мы будем создавать специальные структуры с private полями по числу элементов. Такие структуры будут иметь indexer property для доступа к элементам, а также implicit операторы для копирования из/в managed массивы. Псевдомногомерность будет обеспечиваться через доступ по нескольким индексам. Т.е. матрица 4х4 это будет структура с 16 полями, а indexer property будет брать адрес первого элемента и высчитывать смещение по такой формуле: индекс1 * длина1 + индекс2, где длина1 равна 4, а оба индекса – числа от 0 до 3.

    Структуры и объединения: Структуры как структуры, ничего особенного. Для объединений LayoutKind.Explicit и FieldOffset(0) для всех полей. Особо следует отметить безымянные поля со структурами и объединениями. Дело в том что библиотеки типов такое не поддерживают, вместо этого им будут назначены сгенерированые имена, начинающиеся на __MIDL__.
    Структура

На самом деле будет чем то таким:
Соответственно если импортировать в C# как есть, то получим следующее:
В принципе и черт бы с ним, но доступ к полю i в C выполняется напрямую, как будто это поле основной структуры, т.е. myVar.i, а здесь будет жутковатое myVar. __MIDL____MIDL_itf_Win32_0001_00010000.i. Не годится, поэтому для таких случаев будем генерировать свойства для доступа напрямую к полям вложенных безымянных структур:
Возможно такой подход не лишен недостатков, но это позволяет добиться максимального соответствия объявлений и корректно обрабатывать к примеру такие структуры:
Доступ напрямую через свойства позволит работать со структурой почти точно так же как в С. Исключением является только случай когда необходим адрес вложенных полей, тогда придется все-таки указывать полный путь.

  • Перечисления. Тут все просто, лишь незначительные различия в синтаксисе.
  • Битовые поля. Выглядеть они будут так – целочисленная private переменная (тип зависит от того какого суммарно размера структура с битовыми полями) и сгенерированные свойства выполняющие битовые операции для чтения/установки только соответствующих бит:

  • Объявления функций импортируемых из DLL: Как обычно, static extern методы с атрибутом DllImport в классе NativeMethods
  • Alias-ы типов объявленные через typedef: Если в IDL случайно не затесались никакие лишние атрибуты то alias-ы будут заменены на сам тип при компиляции библиотеки типов (см. тут). А если все таки они туда попадут, то вместо них подставим тип который они представляют.
  • Константы: константы в классе NativeConstants. Строки или числа.
  • Указатели на функции (которые в виде специальных интерфейсов): Генерируем 2 основных типа: делегат и структуру, которая будет представлять собой сам указатель. В структуре одно private-поле имеющее тип void*. А через оператор implicit неявно приводить типы от/к делегату путем вызова Marshal.GetFunctionPointerForDelegate и Marshal.GetDelegateForFunctionPointer
  • Интерфейсы: Тут казалось бы все просто – объявил интерфейс с атрибутом ComImport и дело в шляпе, и в классе Marshal навалом методов для дополнительной функциональности.
    А вот нет, это работает только для COM-интерфейсов. А нам запросто могут вернуть нечто не наследующее IUnknown. Например IXAudio2Voice. И вот тут-то стандартные механизмы .NET скажут вам “кря”. Ну не страшно, в запасе есть хитрый ход конем – будем генерировать таблицы виртуальных методов сами и вызывать их через Marshal.GetFunctionPointerForDelegate и Marshal.GetDelegateForFunctionPointer. Здесь нет ничего особенного – интерфейсы будут представлены структурами, внутри которых есть private структуры с набором указателей. Для каждой функции интерфейса у основной структуры генерируется метод, вызывающий соответствующий указатель через Marshal.GetDelegateForFunctionPointer. А также набор implicit операторов чтобы поддержать приведение типов в случае наследования интерфейсов. Пример занял бы слишком много места чтобы привести его здесь, поэтому все можно посмотреть в приложенном архиве.
  • Утилита для преобразования

    С теорией на этом все. Переходим к практике.

    За преобразование IDL в библиотеку типов будет отвечать компилятор midl входящий в комплект Windows SDK.

    За преобразование библиотеки типов в C# код будет отвечать собственная утилита (но из нее же будем запускать и компилятор).

    Начну со второго. Для чтения содержимого библиотеки типов используются стандартные интерфейсы ITypeLib2 и ITypeInfo2. Документацию можно посмотреть здесь. Они же используются и в утилите tlbimp. Реализация конвертера ничего интересного из себя не представляет, поэтому больше про него рассказывать нечего. Исходный код в приложенном архиве (и да, я знаю, что существуют библиотеки для генерации C# кода, но без них проще).

    Теперь о компиляции IDL.

    Скопируем файлы компилятора в отдельную папку. Во-первых потому что придется их модифицировать, а во-вторых чтобы отвязаться от Windows 8.1 SDK и не прописывать нигде никаких абсолютных путей вида C:\Program Files (x86)\блаблабла.
    Понадобятся следующие файлы:
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\1033\clui.dll
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\c1.dll
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\cl.exe
    C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\mspdb120.dll
    C:\Program Files (x86)\Windows Kits\8.1\bin\x64\midl.exe
    C:\Program Files (x86)\Windows Kits\8.1\bin\x64\midlc.exe
    Все кроме clui.dll сваливаем в одну кучу. А clui.dll должен располагаться в подпапке 1033.

    Процесс midl.exe запускает другой процесс – midlc.exe, который и выполняет всю работу.

    Компилятор требует обязательное наличие файла с именем oaidl.idl где-либо в пределах досягаемости, с объявленым там интерфейсом IUnknown. Для удобства настройки создадим копию этого файла и скопируем туда основные объявления из исходного oaidl.idl и файлов на которые он ссылается. Хотя можно ограничиться и лишь интерфейсом IUnknown, а остальные объявления добавлять уже по мере использования. Разместим полученный файл рядом с компилятором.
    Необходимо это затем, что часть системных типов придется немного подправить. К примеру BOOL и BOOLEAN нам нужны в виде структур с одним полем чтобы не возиться с int и byte, а поддержать приведение такой структуры к bool (который как уже было упомянуто выше, не является blittable типом и поэтому не может быть напрямую использован). Также надо там же объявить базовый интерфейс для типов обозначающих указатели на функции.

    Исправление багов в компиляторе Обход ограничений компилятора

    Вооружившись отладчиком наблюдаем практическое подтверждение описанного в KB220137 поведения:

    Внутри компилятора есть глобальный словарь в который добавляются строки с именами. Если в файле хоть раз попалась строка “msg” (к примеру в качестве аргумента в какой-либо функции), то она будет добавлена в словарь. Если в дальнейшем в исходном файле попадется строка “Msg” (к примеру имя структуры), то выполнится проверка наличия этой строки в словаре с помощью CompareStringA и флагом NORM_IGNORECASE. Проверка вернет результат что строки одинаковы, текст “Msg” будет проигнорирован и компилятор в библиотеку типов в обоих случаях (и имя аргумента и имя структуры) запишет “msg”, хотя по факту они никак не связаны. Эта логика выполняется в зависимости от значения глобальной переменной.

    Кроме того, для создания файла с библиотекой типов используются COM-объекты из oleaut32.dll (ICreateTypeLib, ICreateTypeInfo и.т.п.), которые также используют CompareStringA для проверки повторяющихся имен. К примеру, функция ICreateTypeInfo::SetVarName вернет результат TYPE_E_AMBIGUOUSNAME при попытке добавить поле в структуру отличающееся только регистром от существующего. Хотя там похоже глобальных словарей нет и такие проверки выполняются только для полей и методов в пределах содержащего их типа.

    Из вышеизложенного становится очевидной задача – перехватить вызов CompareStringA и убрать из аргумента dwCmpFlags флаг NORM_IGNORECASE.

    Midlc.exe импортирует CompareStringA из kernel32.dll, которая в свою очередь вызывает CompareStringA из kernelbase.dll, а oleaut32.dll использует сразу CompareStringA из kernelbase.dll. Поскольку подменить системную библиотеку не получится, будем перехватывать в рантайме.

    Делается это элементарно: надо внедрить свой код в процесс и, получив адрес функции, модифицировать код так, чтобы передать управление в перехватчик, где выполнить необходимые операции и передать управление обратно. Для этого можно воспользоваться к примеру этой библиотекой: http://www.codeproject.com/Articles/44326/MinHook-The-Minimalistic-x86-x64-API-Hooking-Libra (В приложенном архиве слегка модифицированный вариант – код переписан на нормальный язык и почищен от лишней функциональности).

    Для внедрения в процесс создадим DLL и модифицируем таблицы импорта файла midlc.exe чтобы при запуске он загружал нашу библиотеку. Инициализация перехватчика будет выполняться в точке входа DllMain.

    Модифицировать таблицы импорта можно и вручную, но лучше воспользоваться готовыми утилитами, к примеру вот http://www.ntcore.com/exsuite.php. В утилите CFF Explorer надо открыть exe файл и выбрав слева Import Adder добавить нашу библиотеку и указать какую-н функцию для импорта (придется создать одну пустую функцию для этого, на практике ее никто никогда не вызовет) и нажав Rebuild Import Table сохранить файл.

    Подключение файлов к проекту

    Для снижения количества бесполезных файлов и левых build-event-ов применим известную технологию T4. Это мощный инструмент для генерации текста по шаблонам. Нам же в данном случае важна лишь возможность выполнения произвольного C# кода при сохранении файла. Шаблоном будет сам IDL файл. Суть в том что блок который будет распознан T4 помещаем в комментарий IDL файла и он будет проигнорирован midl-ом, а все что вне этого блока будет проигнорировано T4. Чтобы не дублировать код, вынесем в общий подключаемый файл весь запуск процесса и работу с файлами, оставив только директиву с подключаемым файлом. Таким образом где-н в начале каждого IDL файла будет всегда комментарий вроде
    А в свойствах IDL файла указываем TextTemplatingFileGenerator в качестве Custom Tool.

    В подключаемом файле ничего интересного – просто запускается наша утилита с нужными параметрами. C# код сгенерированный нашей утилитой во временный файл считывается в скрипте T4-шаблона и возвращается в качестве результата. Если скрипт в T4 возвращает конкретную строку, то результирующий файл будет содержать только ее, и таким образом содержимое шаблона никогда не попадет в выходной файл и может быть произвольным.

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

    Следует отметить что в T4 есть ограничения на размеры непрерывных блоков текста (по слухам

    64кб), поэтому при попытке сохранить очень большой файл можно поймать ошибку “Compiling transformation: An expression is too long or complex to compile ”. В этом случае в файл надо периодически добавлять такие строки:

    Настройки

    Наши промежуточные библиотеки типов будут тащить за собой кучу типов, которые возможно будут повторяться если у нас несколько IDL файлов. К примеру объявление интерфейса IUnknown. Кроме того неплохо бы где то указывать пространство имен в котором лежат сгенерированные классы, а также перечислить используемые пространства имен. Список типов которые надо проигнорировать при кодогенерации и перечень пространств имен можно разместить в виде комментариев в начале IDL файла и считывать перед конвертацией.

    Тестирование

    Тестировать будем следующие вещи:

    • Наличие функций в указанных DLL
    • Размеры структур
    • Смещения всех полей в структурах

    Причем поскольку описываемое решение позиционируется как не привязанное к разрядности ОС, то тестироваться все будет и в 32 разрядном и в 64 разрядном режимах.

    Можно также тестировать размеры перечислений. Но в 99% случаев они занимают 4 байта. Поэтому возможность генерации перечислений с базовым типом отличным от int не рассматривается.

    Информацию о размерах и смещениях надо получать из native кода. Для этого создадим две сборки на CLI (32 и 64). По сгенерированным утилитой managed-типам сгенерируем файл с кодом для получения необходимых данных. Генерировать будем макросы с инициализаторами:
    для массивов структур:
    Без патча компилятора этот шаг автоматизировать бы не удалось!

    Пробегаясь по массивам в цикле преобразуем содержимое в Dictionary . В первом случае ключом будет являться имя структуры а значением ее размер. Во втором – ключ это нечто вроде ‘полного пути’ к полю, а значение – смещение этого поля в структуре.

    Данные будут различаться для 32 и 64 разрядных версий, именно поэтому нам необходимы две сборки. Эти данные подцепим из тестовых классов на C#. Далее тест будет сравнивать эти размеры и смещения с аналогами для managed структур, полученными с помощью Marshal.SizeOf и Marshal.OffsetOf.

    Наличие методов в dll будем проверять вызывая LoadLibrary и GetProcAddress. Если они отработали, то все в порядке, если нет то или такой функции нет или накосячено в атрибутах в IDL.

    Таким образом при добавлении новых объявлений тесты менять не придется. Ну точнее иногда надо будет только добавить директивы #include с файлами где объявлены исходные структуры, чтобы тест скомпилировался.

    Но тут поджидает очередная проблема – VisualStudio не умеет одновременно искать и 32-разрядные и 64-разрядные тесты. Либо одно либо другое. По этой причине тесты будут запускать отдельные процессы которые и будут выполнять всю тестирующую логику, а сами тестовые классы лишь покажут результат выполнения.

    Тесты иногда будут выявлять несоответствие выравнивания структур для какой-либо платформы. Поскольку для сохранения совместимости мы не можем указывать явные ненулевые смещения полей атрибутами FieldOffset или размеры структур (и то и другое будет отличаться для разных платформ), то придется химичить. Вот пример:
    В x64 у массива Address будет смещение 8, т.е. после поля iAddressCount необходим padding из 4 байт. На х86 его быть не должно. Аналог в .NET будет выровнен по 4 байтам на обоих платформах. Хитрый ход конем заключается в следующем:
    С точки зрения использования в коде, структура остается эквивалентной, но в генерируемых .NET структурах это даст необходимый эффект – дополнительное поле будет занимать 4 байта в 32-разрядном режиме и 8 байт в 64-разрядном, тем самым “смещая” массив на 4 байта только в 64-разрядном режиме.
    Маргинальные настройки выравнивания через условную компиляцию (а ля #pragma pack(2) для x86 и #pragma pack(16) для х64) здесь не рассматриваются — 99% структур выровнены либо по умолчанию либо по 1 байту, все остальное не нужно.

    Изредка попадаются структуры кардинально отличающиеся на x86 и x64, например WSADATA. Для таких случаев у меня решения нет. С ними придется иметь дело вручную, но такие структуры попадаются крайне редко.

    На этом все. Весь исходный код с примером использования в прилагаемом архиве.
    Чтобы не нарушать никаких лицензионных соглашений, компилятор midl не прилагается. Его можно взять установив VisualStudio и пропатчить самостоятельно (была использована 64-разрядная версия).

    Илон Маск рекомендует:  Синтаксис CSS
    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL