Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

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

Anna | 17.06.2014 | нет комментариев
Это НЕ очередная статья о том что такое 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 которые будут использованы. Полное изложение IDL есть в msdn

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

[uuid(00000000-0000-0000-0000-000000000001)]
library Import
{
}

Но если все-таки нужно принудительно включить тип объявленный вне блока, то дозволено внутри library написать

typedef MY_TYPE MY_TYPE; 

Внутри блока идут объявления типов. Нам потребуются struct, union, enum, interface и module. Первые три безусловно то же что и в С, следственно не будем на них детально останавливаться. Следует подметить только одну специфика, заключающуюся в том, что при таком объявлении:

typedef struct tagTEST
{
    int i;
} TEST;

именем конструкции будет tagTEST, а TEST это alias тот, что будет в результате заменен именем. От того что во многих заголовочных файлах в объявлениях конструкций присутствуют разные гнусные префиксы, то во избежание беспорядка в именах отменнее принять какие-нибудь меры. А в целом, в IDL как и в C дозволено создавать всякое число alias-ов директивой typedef.

Для объявления интерфейсов применяется блок interface. Внутри этого блока функции:

[uuid(38BF1A5B-65EE-4C5C-9BC3-0D8BE47E8A1F)]
interface IXAudio2MasteringVoice : IXAudio2Voice
{
    HRESULT GetChannelMask(DWORD* pChannelmask);
};

Все достаточно видимо. Из признаков в нашем случае значим только uuid, являющийся идентификатором интерфейса.

Еще есть блок module. В нем дозволено, к примеру, размещать функции из DLL, либо какие-нибудь константы.

[dllname("kernel32.dll")]
module NativeMethods_kernel32
{ 
    const UINT DONT_RESOLVE_DLL_REFERENCES = 0x00000001;

    [entry("RtlMoveMemory")]
    void RtlMoveMemory(
        void *Destination,
        const void *Source,
        SIZE_T Length);
}

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

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

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

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

Сейчас нужно определиться как это все копировать в IDL.

  • Конструкции и объединения: Копируем как есть, при желании убирая только лишние префиксы из имен.
  • Перечисления: Подобно конструкциям.
  • Объявления функций импортируемых из DLL: Копируем как есть в блок module для соответствующей DLL. Видимо, что для всякой DLL потребуется сделать правда бы по одному блоку module.
  • Константы (объявленные через #define): Здесь безусловно не дюже отлично получается – придется добавлять тип, т.е. константа из примера выше это на самом деле
    #define DONT_RESOLVE_DLL_REFERENCES 0x00000001
    

    вариантов немножко – макросы то безусловно никак не могут попасть в библиотеку типов.
    Иная задача это любые конструкции как бы GUID-ов объявленных с поддержкой DEFINE_GUID. Ну если быть точным, то реально это никакие не константы, а всеобщии переменные, но применяются традиционно в качестве констант. Здесь увы никак. GUID-ы то мы еще можем в виде строк объявить, но со каждому остальным придется иметь дело вручную.

  • Alias-ы типов объявленные через typedef (т.е. каждые там DWORD-ы и.т.п.): Копируем как есть.
  • Интерфейсы: От того что ни C ни C не поддерживают интерфейсы, то в большинстве заголовочных файлов они объявлены через условную компиляцию двумя методами – как класс для C с __declspec(uuid(x)) в том либо другом виде и как конструкция со списком указателей на функции для C. Нас волнуют объявления для C . Они выглядят традиционно так:
    MIDL_INTERFACE("0c733a30-2a1c-11ce-ade5-00aa0044773d")
    ISequentialStream : public IUnknown
    {
    public:
        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Read( 
            /* [annotation] */ 
            _Out_writes_bytes_to_(cb, *pcbRead)  void *pv,
            /* [annotation][in] */ 
            _In_  ULONG cb,
            /* [annotation] */ 
            _Out_opt_  ULONG *pcbRead) = 0;
    
        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Write( 
            /* [annotation] */ 
            _In_reads_bytes_(cb)  const void *pv,
            /* [annotation][in] */ 
            _In_  ULONG cb,
            /* [annotation] */ 
            _Out_opt_  ULONG *pcbWritten) = 0;
    };

    Нужно почистить отсель все лишнее, Дабы интерфейс выглядел так:

    [uuid(0c733a30-2a1c-11ce-ade5-00aa0044773d)]
    interface ISequentialStream : IUnknown
    {
        HRESULT Read(
            void *pv,
            ULONG cb,
            ULONG *pcbRead);
    
        HRESULT Write(
            void const *pv,
            ULONG cb,
            ULONG *pcbWritten);
    };

    При желании дозволено не трогать комментарии, а SAL-аннотации спрятать в признак [annotation(…)].
    Да, ряд операций проделывать все-таки доводится, но ключевой момент, как и основная суть статьи, тут в том что мы не трогаем доводы функций и возвращаемые значения. Т.е. даже невзирая на то что начальное объявление несколько изменяется, дозволено с довольной уверенностью гарантировать его корректность, так как все типы и indirection level указателей остаются постоянными. Если что то позабудем почистить, то оно не скомпилируется, но если скомпилируется то итог будет правилен от того что “сигнатуры” не меняются.

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

    typedef LRESULT (CALLBACK* WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    

    будет выглядеть так:

    [uuid(C17B0B13-6E49-4268-B699-2D083BAE88F9)
    interface WNDPROC : __Delegate
    {
        LRESULT WNDPROC(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    }
    

    В данном случае __Delegate это объявленный нами пустой интерфейс по которому мы будем отличать такой “указатель на функцию” от обыкновенных интерфейсов. Признак uuid содержит случайное значение (Дабы не конфликтовать ни с чем), легко без него не скомпилируется. Дозволено безусловно было бы заменить все указатели на функции на void*, но вследствие такому хаку мы сбережем суровую типизацию, скажем поле WNDPROC lpfnWndProc у конструкции WNDCLASSEX в библиотеке типов будет также сурово типизированным, а нам необходима информация только об имени типа и indirection level указателей, потому тот факт, что это интерфейс значения не имеет.

  • Битовые поля: Правда это и относится к конструкциям, я перенес их в обособленный пункт от того что тут тоже придется хитрить. Нужно к всякому каким-либо образом привязать информацию о числе бит. Скажем, дозволено сделать это с поддержкой массивов. А Дабы при конвертации библиотеки типов осознать, что это битовое поле, добавить какой-нибудь непотребный признак. Скажем это:
    struct DWRITE_LINE_BREAKPOINT
    {
        UINT8 breakConditionBefore : 2;
        UINT8 breakConditionAfter : 2;
        UINT8 isWhitespace : 1;
        UINT8 isSoftHyphen : 1;
        UINT8 padding : 2;
    };
    

    объявим так:

    typedef struct DWRITE_LINE_BREAKPOINT
    {
        [replaceable]
        UINT8 breakConditionBefore[2];
        [replaceable]
        UINT8 breakConditionAfter[2];
        [replaceable]
        UINT8 isWhitespace[1];
        [replaceable]
        UINT8 isSoftHyphen[1];
        [replaceable]
        UINT8 padding[2];
    } DWRITE_LINE_BREAKPOINT;
    

    И для простоты условимся что если в структуре есть битовые поля то обыкновенных полей там быть не должно. Тогда такие объявления:

    typedef struct TEST 
    {
        int i1 : 1;
        int i2 : 31;
        float f1;
    } TEST;
    

    Нужно будет преобразовать в:

    typedef struct TEST
    {
        struct
        {
            int i1 : 1;
            int i2 : 31;
        };
        float f1;
    } TEST;
    

    Но битовые поля это дюже огромная редкость, потому в тезисе их дозволено бы было и вообще не поддерживать, а заменять на базовый тип и теснее в C# вручную делать все остальное:

    typedef struct TEST
    {
        int i;
        float f1;
    } TEST;
    

Вышеизложенного должно быть довольно Дабы перенести в 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__.
    Конструкция

    typedef struct TEST
    {
        struct
        {
             int i;
        };
    } TEST;
    

    На самом деле будет чем то таким:

    typedef struct TEST
    {
        struct __MIDL___MIDL_itf_Win32_0001_0001_0001
        {
            int i;
        } __MIDL____MIDL_itf_Win32_0001_00010000;
    } TEST;
    

    Соответственно если импортировать в C# как есть, то получим следующее:

    [StructLayout(LayoutKind.Sequential)]
    public unsafe struct TEST
    {
        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct __MIDL___MIDL_itf_Win32_0001_0001_0001
        {
            public int i;
        }
    
        public __MIDL___MIDL_itf_Win32_0001_0001_0001 __MIDL____MIDL_itf_Win32_0001_00010000;
    }
    

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

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public unsafe struct TEST
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public unsafe struct __MIDL___MIDL_itf_Win32_0001_0001_0001
        {
            public int i;
        }
    
        public __MIDL___MIDL_itf_Win32_0001_0001_0001 __MIDL____MIDL_itf_Win32_0001_00010000;
    
        public int i
        {
            get
            {
                return __MIDL____MIDL_itf_Win32_0001_00010000.i;
            }
            set
            {
                __MIDL____MIDL_itf_Win32_0001_00010000.i = value;
            }
        }
    }
    

    Допустимо такой подход не лишен недостатков, но это разрешает добиться максимального соответствия объявлений и правильно обрабатывать к примеру такие конструкции:

    typedef struct TEST
    {
        union
        {
            struct 
            {
                int i1;
                int i2;
            };
            struct
            {
                float f1;
                float f2;
            };
        };
        char c1;
    } TEST;
    

    Доступ напрямую через свойства дозволит трудиться со конструкцией примерно верно так же как в С. Исключением является только случай когда нужен адрес вложенных полей, тогда придется все-таки указывать полный путь.

  • Перечисления. Здесь все легко, лишь незначительные отличия в синтаксисе.
  • Битовые поля. Выглядеть они будут так – целочисленная private переменная (тип зависит от того какого суммарно размера конструкция с битовыми полями) и сгенерированные свойства исполняющие битовые операции для чтения/установки только соответствующих бит:
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
    public unsafe struct DWRITE_LINE_BREAKPOINT
    {
        private byte __bit_field_value;
    
        public byte breakConditionBefore
        {
            get
            {
                return (byte)((__bit_field_value >> 8) & 3);
            }
            set
            {
                __bit_field_value = (byte)((value & 3) << 8);
            }
        }
    
        public byte breakConditionAfter
        {
            get
            {
                return (byte)((__bit_field_value >> 8) & 3);
            }
            set
            {
                __bit_field_value = (byte)((value & 3) << 8);
            }
        }
    
        ...
    
    }
    
  • Объявления функций импортируемых из 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 типом и следственно не может быть напрямую использован). Также нужно там же объявить базовый интерфейс для типов обозначающих указатели на функции.

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

Бочкой дегтя была дальнейшая специфика: http://support.microsoft.com/default.aspx?scid=kb;en-us;220137. Microsoft позиционирует это как feature. С одной стороны разумно – основное призвание библиотек типов это OLE Automation, что подразумевает поддержку регистронезависимых языков. С иной стороны реализация мягко говоря необычная – между именами доводов и именами способов либо типов нет никакой связи, для чего применять один всеобщий список строк взамен отдельных списков для имен типов, отдельных списков для имен способов в всяком типе и.т.п.? В любом случае, нас такой “by design” не устраивает, потому как итогом является Жуткая помойка в именах, да и с механическим тестированием (см. ниже) будут задачи, от того что для этого нужно точное соответствие имен тем что в начальных файлах.

Регистронезависимое сопоставление строк традиционно даже самые отъявленные индусы редко станут писать с нуля, потому с огромный долей вероятности применяется API-функция.

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

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

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

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB