Что такое сигнатура в программировании

Включает ли Сигнатура метода тип возвращаемого значения в Java?

Узнайте, почему сигнатуры методов состоят из имени и списка типов параметров в Java.

1. Обзор

Сигнатура метода является только подмножеством всего определения метода в Java. Таким образом, точная анатомия подписи может вызвать путаницу.

В этом уроке мы изучим элементы сигнатуры метода и ее значение в программировании на Java.

2. Сигнатура метода

Давайте подробнее рассмотрим перегрузку метода и то, как она связана с сигнатурами методов.

3. Ошибки Перегрузки

Давайте рассмотрим следующий код :

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

Теперь давайте проверим, законно ли перегружать, добавив следующий метод:

Давайте попробуем то же самое с модификаторами:

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

Последнее, что мы можем проверить, – это то, допускает ли изменение имен параметров перегрузку. Давайте добавим следующий метод:

3. Дженерики и стирание типов

Давайте рассмотрим следующий код:

Несмотря на то, что сигнатуры выглядят по-разному, компилятор не может статически привязать правильный метод после удаления типа.

4. Списки параметров и полиморфизм

Сигнатура метода учитывает точные типы. Это означает, что мы можем перегрузить метод, тип параметра которого является подклассом или суперклассом.

Давайте взглянем на следующий код:

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

Давайте рассмотрим несколько вызовов методов, которые в конечном итоге привязываются к sum(Integer, Integer) :

Для первого вызова у нас есть точные типы параметров Integer, Integer. При втором вызове Java автоматически установит int в Integer для нас . Наконец, Java преобразует значение байта 0x1 в int с помощью продвижения типа, а затем автоматически установит его в Integer.

Аналогично, у нас есть следующие вызовы, которые привязываются к sum(Number, Number) :

При первом вызове у нас есть double значения, которые автоматически преобразуются в Double. А затем, с помощью полиморфизма, Double соответствует Числу. Идентично, Float соответствует Номеру для второго вызова.

Теперь рассмотрим следующий вызов метода:

Чтобы изменить привязку по умолчанию, мы можем использовать явное приведение параметров следующим образом:

5. Параметры Vararg

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

Здесь у нас есть перегруженный метод, использующий varargs :

Итак, каковы эффективные сигнатуры методов? Мы уже видели, что sum(Объект, Объект) является сигнатурой для первого. Переменные аргументы по сути являются массивами, поэтому эффективной сигнатурой для второго после компиляции является sum(Object, Object[]).

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

Давайте рассмотрим следующие вызовы:

Последнее, что следует отметить здесь, это то, что объявление следующего метода будет конфликтовать с версией vararg:

6. Заключение

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

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

Источник

Сигнатура

Сигнатура в картографии
Условные знаки для изображения различных элементов географического ландшафта (леса, луга, болота, путей сообщения и т.д.)

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

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

Сигнатура метода
Сокращенная форма записи параметров метода и типов возвращаемого значения. Следует подчеркнуть, что в сигнатуру не входят ни имя метода, ни имена параметров. JNI формирует сигнатуры в соответствии с правилами, представленными в табл.1

Полное квалифицированное имя класса

Полное квалифицированное имя класса

(Типы аргументов) возвращаемый тип

Полная сигнатура метода

Сигнатура функции
Часть общего объявления функции, позволяющая средствам трансляции идентифицировать функцию среди других. В различных языках программирования существуют разные представления о сигнатуре функции, что также тесно связано с возможностями перегрузки функции в этих языках.
Иногда различают сигнатуру вызова и сигнатуру реализации функции. Сигнатура вызова обычно составляется по синтаксической конструкции вызова функции с учётом сигнатуры области видимости данной функции, имени функции, последовательности фактических типов аргументов в вызове и типе результата. В сигнатуре реализации обычно участвуют некоторые элементы из синтаксической конструкции объявления функции: спецификатор области видимости функции, её имя и последовательность формальных типов аргументов.
Например, в языке программирования С++ простая функция однозначно опознаётся компилятором по её имени и последовательности типов её аргументов, что составляет сигнатуру функции в этом языке. Если функция является методом некоторого класса, то в сигнатуре будет участвовать и имя класса.

Используемые источники:
1. Большая Советская Энциклопедия.
2. Толковый словарь В.Даля.
3. Фридланд А.Я., Фридланд И.А. Информатика и компьютерные технологии. Основные термины.
4. Ожегов С.И., Шведова Н.Ю. Толковый словарь русского языка.

Источник

Магические сигнатуры методов в C#

Представляю вашему вниманию перевод статьи The Magical Methods in C# автора CEZARY PIĄTEK.

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

Синтаксис инициализации коллекций

Синтаксис инициализации коллекции довольно старая фича, т. к. она существует с C# 3.0 (выпущен в конце 2007 года). Напомню, синтаксис инициализации коллекции позволяет создать список с элементами в одном блоке:

Этот код эквивалентен приведенному ниже:

Возможность использования синтаксиса инициализации коллекции не ограничивается только классами из BCL. Он может быть использован с любым типом, удовлетворяющим следующим условиям:

Мы можем добавить поддержку синтаксиса инициализации коллекции, определив Add как метод расширения:

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

Синтаксис инициализации коллекции полезен при инициализации коллекции известным числом элементов. Но что если мы хотим создать коллекцию с переменным числом элементов? Для этого есть менее известный синтаксис:

Такое возможно для типов, удовлетворяющих следующим условиям:

Благодаря этому мы можем написать следующее:

Или даже собрать коллекцию из смеси индивидуальных элементов и результатов нескольких перечислений (IEnumerable):

Без подобного синтаксиса очень сложно получить подобный результат в блоке инициализации.

Синтаксис инициализации словарей

Одна из крутых фич C# 6.0 — инициализация словаря по индексу, которая упростила синтаксис инициализации словарей. Благодаря ей мы можем писать более читаемый код:

Этот код эквивалентен следующему:

Это немного, но это определенно упрощает написание и чтение кода.

Лучшее в инициализации по индексу — это то, что она не ограничивается классом Dictionary и может быть использована с любым другим типом, определившим индексатор:

Деконструкторы

В C# 7.0 помимо кортежей был добавлен механизм деконструкторов. Они позволяют декомпозировать кортеж в набор отдельных переменных:

Что эквивалентно следующему:

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

Или использовать более краткий метод инициализации членов класса:

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

Для нашего типа Point мы можем объявить деконструктор следующим образом:

Пример использования приведен ниже:

«Под капотом» он превращается в следующее:

Деконструкторы могут быть добавлены к типам с помощью методов расширения:

Пользовательские awaitable типы

Это может быть переписано намного красивее с использованием синтаксиса async/await :

Вы можете спросить: «Каков возможный сценарий использования синтаксиса await с пользовательским awaitable типом?». Если это так, то я рекомендую вам прочитать статью Stephen Toub под названием «await anything», которая показывает множество интересных примеров.

Паттерн query expression

Разумеется, мы не обязаны реализовывать все эти методы для того, чтобы использовать синтаксис LINQ с нашим пользовательским типом. Список обязательных операторов и методов LINQ для них можно посмотреть здесь. Действительно хорошее объяснение того, как это сделать, можно найти в статье Understand monads with LINQ автора Miłosz Piechocki.

Подведение итогов

Цель этой статьи заключается вовсе не в том, чтобы убедить вас злоупотреблять этими синтаксическими трюками, а в том, чтобы сделать их более понятными. С другой стороны, их нельзя всегда избегать. Они были разработаны для того, чтобы их использовать, и иногда они могут сделать ваш код лучше. Если вы боитесь, что получившийся код будет непонятен вашим коллегам, вам нужно найти способ поделиться знаниями с ними (или хотя бы ссылкой на эту статью). Я не уверен, что это полный набор таких «магических методов», так что если вы знаете еще какие-то — пожалуйста, поделитесь в комментариях.

Источник

ОГЛАВЛЕНИЕ

1. Сигнатуры (продолжение)

Здесь приведено продолжение первой части.

1.1 LocalVarSig

Сигнатура LocalVarSig также индексируется столбцом StandAloneSig.Signature, он хранит тип всех локальных переменных, выделенных во время выполнения метода. Элемент LOCAL_SIG является прологом сигнатуры и имеет постоянное значение 0x07, элемент Count является сжатым беззнаковым целым, хранящим количество локальных переменных, принадлежащих связанному с ними методу, элемент BYREF является сокращением константы ELEMENT_TYPE_BYREF (смотрите константы в первой части) и показывает, что элемент Type(тип) указывает на реальную переменную. Элемент Constraint(ограничение) показывает, что целевой тип не будет перемещаться сборщиком мусора при выполнении восстановления памяти, так как локальные переменные находятся в стеке (где сборщик мусора не выполняет никаких действий), Type(тип) переменной должен быть ссылочным типом (как System.Object – выделяется в куче) или типом значения (как System.Decimal – выделяется в стеке), но если целевой тип (закрепленный) является типом значения, его определение должно содержать элемент BYREF, в данном случае ссылка на переменную хранится в стеке, но сама переменная выделяется в куче. Более подробно о закреплении рассказано здесь. На рисунке 1 ниже показана полная схема синтаксиса для этой сигнатуры.

Обратите особое внимание на элемент TYPEDBYREF на схеме ниже: это типизированная ссылка, она содержит не только управляемый указатель (как нормальная ссылка) на адрес, но и динамическое представление данных. Процитируем описание элемента из спецификации: «Сигнатура локальной переменной типизированной ссылки указывает, что локальная переменная будет содержать управляемый указатель на адрес и динамическое представление типа, хранящиеся по этому адресу. Сигнатура типизированной ссылки похожа на константу byref, но тогда как byref определяет тип как часть ограничения byref (и, стало быть, статически как часть описания типа), типизированная ссылка динамически предоставляет информацию о типе. Типизированная ссылка, по сути, является полной сигнатурой и не сочетается с другими ограничениями. В частности, невозможно задать byref, тип которого является типизированной ссылкой».

Рисунок 1. Схема синтаксиса сигнатуры LocalVarSig

Пример 1

Данный пример показывает объявление типов значений byref в стеке (только), пример кода написан на языке CIL (общий промежуточный язык) и выглядит так.

Сигнатура LocalVarSig для этого примера кода приведена в таблице ниже.

Смещение

Значение

Что означает

пролог сигнатуры (константа LOCAL _ SIG ).

Общее число переменных, объявленных в этом методе, равняется одной.

Тип переменной ( int 32 ), смотрите константы в первой части.

Пример 2

Пример ниже показывает, что происходит с сигнатурой при использовании типизированной ссылки: в начале объявляется переменная IntVar, в следующей строке получается типизированная ссылка с помощью ключевого слова __makeref (недокументировано и не совместимо с CLS) и сохраняется в переменной TypedByRefVar.

LocalVarSig для этого примера выглядит, как показано ниже.

Смещение

Значение

Что означает

пролог сигнатуры (константа LOCAL _ SIG )

Общее число переменных, объявленных в этом методе, равняется двум.

Тип первой переменной ( int 32 ), смотрите константы в первой части.

Тип второй переменной ( TYPEDBYREF ), смотрите константы в первой части.

Пример 3

Рассмотрим немного более сложный пример: в этом примере кода создается класс TestDataClass, имеющий лишь один член по имени StringVarToBePinned типа string(строка). В методе TestMethod (помеченный как unsafe) создается экземпляр класса TestDataClass, в строке ниже закрепляется член StringVarToBePinned и ссылка на него присваивается указателю FixedVar с помощью ключевого слова fixed. Такая обработка гарантирует, что между фигурными скобками <и>член dataClass.StringVarToBePinned не будет перемещаться действиями сборщика мусора, а значит, указатель FixedVar на член всегда будет действителен внутри фигурных скобок ключевого слова fixed. В методе нельзя непосредственно объявить переменную, которая будет закреплена, потому что такое значение уже закреплено (помещено в стек), поэтому переменная должна быть обернута классом TestDataClass (помещенным в кучу).

Данный пример сложен еще по одной причине: в какой-то момент он использует еще не описанный элемент, то есть TypeDefOrRefEncoded, этот элемент определяет, в какой строке и в какой таблице метаданных (TypeDef, TypeRef или TypeSpec) описан заданный тип. Здесь эти элементы детально не разбираются, если хотите – перейдите сразу к описанию этого элемента в подразделе 5.2 TypeDefOrRefEncoded в следующем разделе. LocalVarSig для вышеприведенного кода рассмотрена в таблице ниже.

Смещение

Значение

Что означает

пролог сигнатуры (константа LOCAL _ SIG )

Общее число переменных, объявленных в этом методе, равняется трем.

Тип указателя из предыдущего байта ( char – в конце это char * ), смотрите константы в первой части.

Третья переменная закреплена, смотрите константы.

Тип третьей закрепленной переменной ( string ), смотрите константы.

1.2 CustomAttrib

Рисунок 2a. Схема синтаксиса сигнатуры CustomAttrib

Рисунок 2b. Схема синтаксиса сигнатуры CustomAttrib

Рисунок 2c. Схема синтаксиса сигнатуры CustomAttrib

Пожалуй, эта часть – самая непонятная из всех четырех. Формат, принимаемый Elem, зависит от следующих условий (взято из спецификации).

Если тип параметра простой (первая линия на схеме выше) (bool, char, float32, float64, int8, int16, int32, int64, unsigned int8,unsigned int16, unsigned int32 или unsigned int64), то ‘блоб’ содержит его двоичное значение (Val). (bool – один байт со значением 0 (false) или 1 (true); char – двухбайтный символ Юникод; а остальные имеют свой очевидный смысл.) Данная схема также используется, если тип параметра – enum – хранит значение целого типа, лежащего в основе enum(перечисление).

Рисунок 2d. Схема синтаксиса сигнатуры CustomAttrib

Последняя часть показывает формат элемента NamedArg, являющегося именованным аргументом (полем или свойством). Так как поля и свойства могут иметь одинаковое имя, первым элементом является FIELD, имеющее постоянное однобайтовое значение 0x53, если именованный параметр ссылается на поле или PROPERTY, имеющее постоянное однобайтовое значение 0x54, если именованный параметр ссылается на свойство. Далее идет элемент FieldOrPropType, описывающий тип именованного свойства или поля в одном или двух байтах: если тип именованного параметра – распакованный простой тип значения (определен выше), то FieldOrPropType должен содержать ровно одно постоянное значение связанного с ним типа (BOOLEAN, CHAR, I1, U1, I2, U2, I4, U4, I8, U8, R4, R8, STRING – смотрите таблицу констант в первой части), но если тип именованного параметра – упакованный простой тип значения, то перед элементом FieldOrPropType стоит байт, содержащий значение 0x51, в этом случае FieldOrPropType занимает два байта. Элемент FieldOrPropName является SerString (объяснен выше), содержащим имя свойства или поля. В конце идет один элемент FixedArg, показанный выше. Элемент NamedArg является нормальным FixedArg, перед которым стоит некоторая дополнительная информация, указывающая, какое поле или свойство он обозначает.

Пример 1

Данный пример показывает формат элемента SerString и как CustomAttrib различает поля и свойства, служащие именованными параметрами. В примере ниже имеется атрибут TestAttribute, требующий передачи одного фиксированного параметра Fixed1 типа int32, дополнительно можно (и это делается) передать два дополнительных именованных параметра типа int16 и string, как показано в куске кода ниже.

Полная сигнатура CustomAttrib для этого случая имеет длину 33 байта, поэтому в некоторых точках несколько байтов были объединены в одну строку с общим описанием.

Источник

ОГЛАВЛЕНИЕ

Оглавление

Введение

1. Введение

2. Что такое сигнатуры?

Сигнатуры хранят данные, которые нельзя компактно сохранить в таблицах метаданных, например, типы параметров, аргументы, подаваемые в настраиваемые атрибуты, дескрипторы упорядочения(сортировки, формирования), и т.д. Хранение такой информации, как типы данных, в таблицах, может привести к чрезмерной фрагментации данных, неразборчивости и снизить производительность, поэтому специалисты CLI/CLR придумали сигнатуры, позволяющие хранить ранее упомянутый тип данных в компактной и хорошей форме, в следующих разделах вы ясно увидите, почему они так важны.

3. Начало работы

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

3.1 Проводник CFF

Рисунок 1.Работающий проводник CFF

3.2 Порядок следования байтов

Лучшее описание дано в Википедии:

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

В данном случае порядок следования байтов рассматривается как порядок байтов данных (обычно целочисленных), хранящихся в файле. Существует два метода (порядка) представления данных в файле, с порядком следования байтов, начиная со старшего, и с порядком следования байтов, начиная с младшего, файл PE/.NET использует оба метода, поэтому каждый из них рассматривается ниже.

Порядок следования байтов, начиная со старшего

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

Порядок следования байтов, начиная с младшего

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

3.3 Сжатое целое

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

Если значение лежит между 0 (0x00) и 127 (0x7F) включительно, кодировать в виде однобайтового целого (бит 7 пустой, значение хранится в битах от 6 до 0).

Пустая строка должна представляться с помощью зарезервированного одиночного байта 0xFF, с отсутствием последующих данных

Пример 1

Значение меньше 0x80, значит это первый случай, удаляем три ненужных байта.

Исходное значение (32-бит)

Сжатое значение

Сэкономленные байты

00000000 0000000 00000000 00000011

Пример 2

Такой же, как пример 1.

Исходное значение (32-бит)

Сжатое значение

Сэкономленные байты

00000000 0000000 00000000 01111111

Пример 3

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

Исходное значение (32-бит)

Сжатое значение

Сэкономленные байты

00000000 0000000 00000000 10000000

Пример 4

Удаляем два ненужных байта.

Исходное значение (32-бит)

Сжатое значение

Сэкономленные байты

00000000 0000000 00101110 01010111

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

3.4 Константы

Следующий список перечисляет распространенные константы, часто используемые почти во всех сигнатурах. В следующих частях статьи мы будем ссылаться на них очень часто по сокращениям, используя только последний член имени, например, ELEMENT_TYPE_I8 как I8, ELEMENT_TYPE_STRING как STRING, и т.д.

Значение

Примечания

Отмечает конец списка

Неуправляемый указатель, сопровождаемый элементом Type(тип).

Управляемый указатель, сопровождаемый элементом Type.

Модификатор типа значения, сопровождаемый меткой TypeDef или TypeRef

Модификатор типа класса, сопровождаемый меткой TypeDef или TypeRef

Обобщенный параметр в определении обобщенного типа, представленный в виде числа

Модификатор типа многомерного массива.

Указатель на функцию, сопровождаемый полной сигнатурой метода

Модификатор типа одномерного массива с нулевой нижней границей.

Обобщенный параметр в определении обобщенного метода, представленный в виде числа

Обязательный модификатор, сопровождаемый меткой TypeDef или TypeRef

Необязательный модификатор, сопровождаемый меткой TypeDef или TypeRef

Реализован внутри CLI

ORed со следующими типами элементов

Сигнальная ме(а?)тка для сигнатуры метода vararg

Обозначает локальную переменную, указывающую на закрепленный объект

Показывает аргумент типа System.Type.

Используется в специальных атрибутах, чтобы задавать упакованный объект (§23.3 в спецификации ECMA-355).

Используется в специальных атрибутах для обозначения поля (§22.10, §23.3 в спецификации ECMA-355).

Используется в специальных атрибутах для обозначения свойства (§22.10, §23.3 в спецификации ECMA-355).

Используется в специальных атрибутах для задания перечисления (§23.3 в спецификации ECMA-355).

4. Сигнатуры

Почти все приготовления завершены, и можно начинать рассматривать сигнатуры, но стоит упомянуть еще несколько вещей, не очевидных для всех. Первое – почти все целые числа в сигнатурах сжаты. Второе – все сигнатуры начинаются с размера (в байтах), занимаемого ими в куче #Blob, разумеется, это значение хранится с использованием сжатия целых. Наконец, что не менее важно, значения, показывающие местоположение сигнатуры в куче #Blob, абсолютные, т.е. вам не нужно ничего прибавлять/отнимать от основного значения (так, как в красном круге (на рисунке) на рисунке 1), чтобы найти сигнатуру в куче.

Заметьте, что когда вы перекомпилируете прикрепленный исходный код (даже не изменяя его), сигнатуры в получаемой сборке могут изменить смещение.

Так как данная статья – скорее руководство, в данном разделе по байтам рассматриваются все сигнатуры, начиная с простейших и заканчивая самыми сложными. Каждая рассматриваемая сигнатура связана с описанием, диаграммой или синтаксисом, скопированным из спецификации, и набором примеров, полные двоичные файлы и исходники которых можно загрузить вверху данной статьи, по возможности приложения написаны с помощью C#, в ином случае – с использованием CIL (раньше MSIL).

4.1 FieldSig

Рисунок 2. Схема синтаксиса сигнатуры FieldSig

Пример 1

Этот пример вполне понятный: создается простое поле типа int32, как показано ниже.

Теперь нужно загрузить двоичную сборку FieldSig\1.dll в проводник CFF и перейти в таблицу Field, чтобы найти строку, связанную с нашим полем (должна быть только одна). Рисунок ниже должен помочь вам.

Рисунок 3. Строка поля TestField в таблице метаданных Field

Мы нашли ее! Переходим на 0x000A в #Blob.

Рисунок 4. Сигнатура FieldSig, просматриваемая программой проводник CFF

Теперь разберем сигнатуру по байтам в таблице ниже.

Смещение

Значение

Что означает

Значение типа поля int32, смотрите константы

Пример 2

В этот раз мы меняем тип поля на string (строка), как показывает следующий листинг кода.

Сигнатура FieldSig для поля TestField все еще находится в 0x000A, и только последний байт изменился с 0x08 на 0x0E.

Смещение

Значение

Что означает

Значение типа поля string, смотрите константы

4.2 PropertySig

Рисунок 5. Схема синтаксиса сигнатуры PropertySig

Пример 1

Первый пример тривиальный: мы создали одно свойство экземпляра типа int32, как показано ниже.

Сигнатура начинается со смещения 0x001A в куче #Blob.

Смещение

Значение

Что означает

Пролог ORed с константой HASTHIS, так как 0x20 OR 0x08 = 0x28.

Количество параметров, передаваемых методу-получателю свойства, смотрите рисунок 5 выше.

Тип возвращаемого значения свойства (int32), смотрите константы.

Пример 2

Данный пример немного сложней, так как он использует индексированное свойство, возвращающее(го) разное значение в зависимости от параметров, переданных свойству, и, как видно ниже, такой тип свойства не имеет никакого имени (в C#), но в таблице метаданных Field оно всегда объявляется как Item(элемент). Можно определить только одно индексированное свойство на каждый класс/структуру, но его можно перегружать.

Сигнатура ранее упомянутого поля хранится со смещением 0x001B в #Blob и описана в таблице ниже.\

Смещение

Значение

Что означает

Свойство имеет тип экземпляра, поэтому вновь пролог сигнатуры объединен с помощью OR с константой HASTHIS.

Количество параметров, передаваемых методу-получателю свойства, смотрите рисунок 5 выше.

Тип возвращаемого значения свойства (int32), смотрите константы.

Тип значения первого параметра свойства (int32), смотрите константы.

Тип значения второго параметра свойства (string), смотрите константы.

Пример 3

В этом примере мы попытаемся отключить флаг HASTHIS, объявив свойство как статическое.

На этот раз вышеуказанная сигнатура свойства начинается со смещения 0x001A в #Blob.

Смещение

Значение

Что означает

Постоянное значение пролога (только).

Количество параметров, передаваемых методу-получателю свойства, смотрите рисунок 5 выше.

Тип возвращаемого значения свойства (int32), смотрите константы.

4.3 MethodDefSig

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

Рисунок 6. Схема синтаксиса сигнатуры MethodDefSig

Дополнительно используются несколько флагов (перечислены в таблице ниже), они объединяются с помощью OR и размещаются во втором байте сигнатуры (первый байт – размер сигнатуры).

Значение

Что означает

Спецификация гласит: «Обычно список параметров (всегда сопровождающий соглашение о вызовах) не предоставляет информацию о типе указателя this, так как его можно получить из другой информации. Когда явный экземпляр комбинации задан, все же, первый тип в последующем списке параметров задает тип указателя this, а следующие элементы задают типы самих параметров». Заметьте, что если EXPLICITTHIS установлен, HASTHIS тоже должен быть установлен.

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

Задает соглашение о вызовах для методов с изменяющимися аргументами.

Метод имеет один или больше обобщенных параметров.

Пример 1

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

Сигнатура MethodDefSig для примера метода хранится в смещении 0x000A и выглядит так.

Смещение

Значение

Что означает

Так как это экземпляр и обобщенный метод, флаги HASTHIS и GENERIC установлены, 0x20 OR 0x10 = 0x30.

Количество обобщенных параметров.

Количество нормальных параметров.

Тип возвращаемого значения (void), смотрите константы.

Тип первого параметра (int32), смотрите константы.

Тип второго параметра (object), смотрите константы.

Пример 2

В данном примере снова показано использование флага HASTHIS, определение рассматриваемого метода выглядит так.

Сигнатура снова хранится в 0x000A в куче #Blob и выглядит так.

Смещение

Значение

Что означает

Есть один установленный флаг, а именно DEFAULT – это означает, что метод статический, и позволяет CLR определить используемое соглашение о вызовах. Метод не обобщенный, так как флаг GENERIC не установлен, следовательно, следующий байт задает количество нормальных (не обобщенных) параметров, передаваемых методу.

Количество нормальных параметров.

Тип возвращаемого значения (void), смотрите константы.

Тип первого параметра (int32), смотрите константы.

Тип второго параметра (object), смотрите константы.

Пример 3

Теперь посмотрим, как работает флаг EXPLICITTHIS: его можно включить путем использования ключевого слова explicit в определении метода, разумеется, на языке CIL.

MethodDefSig для вышеуказанного метода выглядит так.

Смещение

Значение

Что означает

Флаги HASTHIS и EXPLICITTHIS установлены, так как 0x20 OR 0x40 = 0x60.

Количество параметров, принимаемых методом.

Тип возвращаемого значения (void), смотрите константы.

Пример 4

В этом примере был создан метод, принимающий изменяемые аргументы, т.е. кроме нормальных параметров, заданных в объявлении, он принимает переменное количество параметров изменяемого типа. Добавление vararg в ключевое слово языка CIL в определение метода заставляет метод принимать изменяемые аргументы, что видно по листингу кода ниже.
Важно: Использование ключевого слова params в C# не устанавливает флаг VARARG в связанной с методом сигнатуре. Метод, использующий ключевое слово params в C#, всего лишь оформляется компилятором C# атрибутом ParamArray, и дополнительные параметры рассматриваются как нормальный массив. Можно сделать метод истинно VARARG в C#, следуя данной инструкции, но это не совместимо с CLS.

Сигнатура метода рассмотрена в следующей таблице.

Смещение

Значение

Что означает

Метод является экземпляром и принимает изменяемые аргументы, следовательно, флаги HASTHIS и VARARG установлены, и 0x20 OR 0x05 = 0x25

Количество параметров, принимаемых методом.

Тип возвращаемого значения (void), смотрите константы.

4.4 MethodRefSig

Эта сигнатура очень похожа (если не идентична) на ранее упомянутую MethodDefSig, но MethodRefSig описывает соглашение о вызовах метода, параметры, и т.д., в точке, где метод вызывается (также называемой точка вызова). Сигнатура индексируется столбцом MemberRef.Signature, и если метод не принимает изменяемые аргументы, сигнатура идентична MethodDefSig и должна точно совпадать с сигнатурой, указанной в определении целевого метода, в ином случае – как показано ниже.

Рисунок 7. Схема синтаксиса сигнатуры MethodRefSig

Значение

Что означает

Спецификация гласит: «Обычно список параметров (всегда сопровождающий соглашение о вызовах) не предоставляет информацию о типе указателя this, так как его можно получить из другой информации. Когда явный экземпляр комбинации задан, все же, первый тип в последующем списке параметров задает тип указателя this, а следующие элементы задают типы самих параметров». Заметьте, что если EXPLICITTHIS установлен, HASTHIS тоже должен быть установлен.

Задает соглашение о вызовах для методов с изменяемыми аргументами.

Обозначает конец обязательных параметров.

Пример 1

Дабы убедить вас, что при вызове не VARARG метода нет разницы между MethodDefSig и связанной с ней сигнатурой MethodRefSig, был создан следующий код.

Теперь рассмотрим сигнатуру TestMethod’s MethodDefSig, хранящуюся в файле MethodRefSig\1a.dll.

Смещение

Значение

Что означает

Метод принимает точно два параметра.

Тип возвращаемого значения (void), смотрите константы.

Тип первого параметра (int32), смотрите константы.

Тип второго параметра (string), смотрите константы.

Связанная с ней MethodRefSig выглядит точно так же, но хранится в другом смещении.

Смещение

Значение

Что означает

Метод принимает точно два параметра.

Тип возвращаемого значения (void), смотрите константы.

Тип первого параметра (int32), смотрите константы.

Тип второго параметра (string), смотрите константы.

Пример 2

В этом примере показано, как сигнатура MethodRefSig обращается с вызовом методов VARARG. Для этой цели был создан подлинно VARARG метод, принимающий один обязательный параметр, и остальные, изменяемые параметры. Помните, что использование ключевого слова params в C# не устанавливает флаг VARARG в связанной с методом сигнатуре, так как params всего лишь оформляет метод атрибутом ParamArray, и дополнительные параметры рассматриваются как массив объектов некоторого типа. Чтобы установить флаг VARARG в сигнатуре, вы должны добавить __arglist в определение метода в качестве последнего параметра, но это не совместимо с CLS ( за дополнительной информацией идите сюда).

Теперь пришло время вызвать наш метод из отдельной сборки: метод вызывается с одним обязательным аргументом типа string, и с двумя дополнительными аргументами типа int32, как показано ниже.

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

Смещение

Значение

Что означает

Метод является экземпляром и принимает изменяемые параметры, поэтому HASTHIS OR VARARG = 0x20 OR 0x05 = 0x25.

Общее число параметров, переданных методу, равняется 3: один обязательный и два дополнительных.

Тип возвращаемого значения (void), смотрите константы.

Тип первого обязательного параметра (string), смотрите константы.

Константа SENTINEL, все параметры после данного значения дополнительные.

Тип первого дополнительного параметра (int32), смотрите константы.

Тип второго дополнительного параметра (int32), смотрите константы.

4.5 StandAloneMethodSig

Тип данной сигнатуры очень похож на MethodRefSig, он предоставляет сигнатуру точки вызова для метода, но имеет два важных отличия. Первое отличие в том, что StandAloneSig может задавать неуправляемый целевой метод, StandAloneSig обычно создается в качестве подготовки к выполнению инструкции calli, вызывающей управляемый или неуправляемый код. Второе важное отличие в том, что сигнатура StandAloneSig индексируется столбцом StandAloneSig.Signature, являющимся единственным столбцом в таблице метаданных StandAloneSig. Более того – ни на одну из строк в данной таблице не ссылается никакая другая таблица (поэтому она названа независимой), эта таблица заполнена генераторами кода. Сигнатура в столбце StandAloneSig.Signature должна быть сигнатурой StandAloneMethodSig для каждого выполнения инструкции calli, или сигнатурой LocalVarSig, описывающей локальные переменные в каждом методе, и которая будет подробнее объяснена в следующем разделе. Схема синтаксиса для сигнатуры StandAloneSig приведена ниже.

Рисунок 8. Схема синтаксиса сигнатуры StandAloneMethodSig

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

Важно: Как вскоре станет ясно, существуют разные соглашения о вызовах для вызова методов, принимающих изменяемые параметры, для управляемого и неуправляемого кода. Диаграмма для каждого случая может выглядеть по-разному: например, соглашение о вызовах VARARG вызывает управляемые методы, принимающие изменяемые параметры. В этом случае сигнатура имеет дополнительные элементы, SENTINEL и один или больше Param (закрашенные прямоугольники), однако соглашение о вызовах C также вызывает методы, принимающие изменяемые параметры (неуправляемый код), но сигнатура для этого случая заканчивается непосредственно перед элементом Param. Компилятор генерирует сигнатуры как указано выше, к сожалению, наш пример кода компилируется, но выбрасывает исключение, и невозможно понять, в чем заключается проблема. Поэтому нельзя точно судить о правильности данного заключения, более того, спецификация неясная: «Две отдельных схемы были объединены в одну в данной схеме, с использованием закраски, чтобы различать их. Таким образом, для следующих соглашений о вызовах: DEFAULT (управляемое), STDCALL, THISCALL и FASTCALL (неуправляемое), сигнатура заканчивается непосредственно перед элементом SENTINEL (все эти сигнатуры не являются vararg).

Однако для управляемого и неуправляемого соглашений о вызовах vararg – VARARG (управляемое) и C (неуправляемое) – сигнатура может содержать элемент SENTINEL и заключительные элементы Param (они не обязательные). Эти необязательные элементы обозначаются путем закрашивания прямоугольников на схеме синтаксиса». Видите ли вы это? Почему прямоугольник C не закрашен, если использование соглашения о вызовах C может добавить элементы SENTINEL и Param при вызове неуправляемого метода, принимающего изменяемые аргументы? При каких условиях элементы Param не обязательные? Инструкция calli очень редко встречается в на 100% правильно работающем коде, вызывающем неуправляемый метод (392 сборки из нашего GAC выполняют инструкцию calli лишь дважды и только по отношению к управляемым методам!), поэтому нельзя утверждать, что наши объяснения для следующего примера кода в этом подразделе абсолютно истинные. Если кто-то знает, как сигнатура StandAloneMehtodSig выглядит при правильном вызове неуправляемого метода (принимающего или не принимающего изменяемые аргументы – в обоих случаях код выбрасывает исключение), сообщите нам, мы были бы очень благодарны.

Значение

Что означает

Спецификация гласит: «Обычно список параметров (всегда сопровождающий соглашение о вызовах) не предоставляет информацию о типе указателя this, так как его можно получить из другой информации. Когда явный экземпляр комбинации задан, все же, первый тип в последующем списке параметров задает тип указателя this, а следующие элементы задают типы самих параметров». Заметьте, что если EXPLICITTHIS установлен, HASTHIS тоже должен быть установлен.

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

Задает соглашение о вызовах для управляемых методов с изменяемыми аргументами.

Соглашение о вызовах для неуправляемого целевого метода, особенности этого соглашения следующие:

Параметры передаются справа налево.

Программа, вызывающая метод, выполняет очистку стека.

Только данное соглашение о вызовах позволяет вызывать неуправляемые методы, имеющие изменяемые параметры (vararg применяется для управляемых методов).

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

Соглашение о вызовах для неуправляемого целевого метода. Особенности этого соглашения следующие:

Параметры передаются справа налево.

Вызванный метод выполняет очистку стека.

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

Соглашение о вызовах для неуправляемого целевого метода. Особенности этого соглашения следующие:

Параметры передаются справа налево.

Вызванный метод выполняет очистку стека.

Указатель this помещается в регистр ECX.

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

Соглашение о вызовах для неуправляемого целевого метода. Особенности этого соглашения следующие:

Некоторые параметры помещаются в регистры ECX и EDX, остальные аргументы помещаются (проталкиваются) в стек справа налево.

Вызванный метод выполняет очистку стека.

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

Обозначает конец обязательных параметров.

Замечание: Здесь стоит упомянуть одну вещь: в отличие от CL (компилятор Microsoft C\C++), ILASM (компилятор Microsoft CIL) не добавляет никаких специальных символов (таких как «@», «_», «?», и т.д.) в имя метода при использовании любого из соглашений о вызовах для неуправляемых целевых методов. Компилятор CIL не оформляет имена никаких методов специальными символами, так как он генерирует байтовый код, позднее компилируемый в машинный код оперативным компилятором CLR. Поэтому когда вы выбираете некоторое соглашение о вызовах при программировании в CIL, компилятор(e) ILASM не определяет, кто (вызывающая программа или вызванный метод) очищает стек, не определяет, в каком порядке аргументы передаются методу, и не меняет имена методов, все это делается во время оперативной компиляции/оптимизации. Если вы не понимаете, о чем идет речь, прочитайте статью Неманджа Трифунович под названием “Раскрытие тайны соглашений о вызовах”, подробно описывающую разные типы соглашений о вызовах для C и C++, что они означают, как работают, и т.д.

Пример 1

В листинге примера кода есть два управляемых метода: первый метод имеет один постоянный параметр типа int32 и также возвращает int32 (фактически он не возвращает ничего, так как отсутствуют данные, помещаемые в оценочный стек); второй перечисленный метод выполняет первый метод, что показано ниже.

Источник

Понравилась статья? Поделиться с друзьями:

Не пропустите наши новые статьи:

  • что такое сигнатура в программировании простыми словами
  • Что такое сжатое сообщение в программировании
  • что такое сжатие диска в windows 10
  • что такое сжатая память в виндовс 10
  • Что такое сетевые учетные данные windows 10

  • Операционные системы и программное обеспечение
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии