Как работает команда callvirt.NET для интерфейсов?

Объяснение виртуальной диспетчеризации для кого-то легко: каждый объект имеет указатель на таблицу как часть своих данных. В классе есть N виртуальных методов. Каждый вызов определенного метода я индексирует объект, когда он прибывает, и вызывает i-й метод в таблице. Каждый класс, реализующий метод X(), будет иметь код для метода X() в том же i-м индексе.

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

Я искал Интернет, и есть много дискуссий, которые я могу найти о том, как можно реализовать интерфейсную диспетчеризацию. Существуют две широкие категории: a) какая-то хэш-таблица ищет объект для поиска нужной таблицы отправки б) когда объект передается на интерфейс, создается новый указатель, который указывает на одни и те же данные, но на другую таблицу vtable.

Но, несмотря на много информации о том, как он может работать, я не могу найти ничего о том, как его работает.

Кто-нибудь знает документ, который описывает фактическую арифметику указателя, которая происходит в команде callvirt, когда тип объекта является интерфейсом?

Ответ 1

Интерфейс диспетчеризации в CLR - черная магия.

Как вы правильно заметили, отправка виртуального метода концептуально легко объяснить. И на самом деле я делаю это в этой серии статей, где описываю, как можно реализовать виртуальные методы на языке С#, который их не хватает:

http://blogs.msdn.com/b/ericlippert/archive/2011/03/17/implementing-the-virtual-method-pattern-in-c-part-one.aspx

Механизмы, которые я описываю, очень похожи на используемые механизмы.

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

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

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

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

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

Если вы хотите знать, как сохраняются таблицы для медленного поиска в памяти, см. ссылку в ответе Мэтью Уотсона.

Ответ 2

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

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

Я подозреваю, что ключевой бит, который вам интересен, - это то, как код ищет адрес метода в таблице типов методов.

Более подробная информация о таблице методов приведена в статье "JIT и Run" в выпуске журнала MSDN в мае 2005 года (который на момент написания может быть загружен как ".chm" из эта страница - но вам придется перейти к свойствам файла, чтобы разблокировать его, прежде чем он отобразится должным образом из-за ограничений безопасности.)

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