Изменение интерфейсов с сохранением обратной совместимости
Методы, описанные в Теории управления версиями для RPC и COM могут быть неприемлемыми по многим причинам. Изменение версии интерфейса в соответствии с правилами по сути требует, чтобы новые клиенты не взаимодействовали со старыми серверами. Это часто невозможно с помощью коммерческого программного обеспечения, развернутого в поле. Иногда Windows вносила изменения интерфейса, не изменяя идентификаторов GUID или версий. Это было результатом того, что новым клиентам требовалось взаимодействовать с устаревшими серверами, поскольку решение поддерживать как старые, так и новые интерфейсы новым клиентом считалось нежелательным.
Лучшие практики
Это разумные методы обхода проблемы несовместимости провода, когда не удается изменить GUID интерфейса и версию.
Позвольте приложению учитывать возможности другой стороны.
Клиент и сервер имеют протокол, позволяющий каждому (или по крайней мере новому клиенту) установить удостоверение партнера. Как правило, необходимо иметь в виду, что новый клиент должен знать о функциях, поддерживаемых старыми и новыми серверами. Это можно легко сделать, если приложение удерживает контекст подключения и это поддерживается с помощью вызова функции типа XxxGetInfo, выполняемого клиентом перед осуществлением любых операций RPC. Когда приложение управляет функциями на основе релизов для каждого сервера, вызов, несовместимый со старым сервером или клиентом, никогда не может произойти, так как приложение контролирует, какие вызовы направляются на какой сервер. Суть в том, что приложение активно предотвращает несоответствие. Это может быть выполнено в сочетании со второй практикой.
Введите новый удаленный API.
Новый удаленный метод не сталкивается с существующими методами, если он добавляется в самом конце интерфейса. Старые клиенты могут вызывать новые серверы, как они делали это всегда. Новый клиент может вызывать новый метод, не зная удостоверения сервера, при условии, что он отслеживает ошибки, поступающие от вызываемого сервера. Среда выполнения RPC всегда проверяет номер метода для каждого интерфейса перед отправкой, чтобы убедиться, что метод входит в соответствующую таблицу v-table. Для метода, неизвестного серверу, время выполнения RPC вызывает исключение RPC_S_PROCNUM_OUT_OF_RANGE. Это исключение возникает только в этой конкретной ситуации. Таким образом, новый клиент может отслеживать возникновение исключения как признак того, что вызов пошел на старый сервер, и сможет соответственно адаптировать своё поведение.
Введите новые параметры или новые типы данных только в новых методах.
Одна из причин внедрения нового метода заключается в том, чтобы избежать несовместимости данных. Если новый тип данных введен или просто изменен, в принципе его следует использовать только в новом методе (или методах). Смотрите примеры изменений несовместимых типов данных в разделе "Примеры несовместимых изменений". Единственное недопустимое исключение для этого правила описано в элементе четыре.
Сопоставление новых параметров или новых типов данных с помощью оболочки.
Это решение применяется, когда новый параметр или тип данных должен быть обозначен для пользователя, но на самом деле не требуется обрабатывать удаленно отдельно или можно сопоставить со старыми типами данных или параметрами. Например, многие системные API сразу начинают выполнять удаленный вызов. Они могут осуществлять или не осуществлять определённое сопоставление от известных пользователю типов данных к типам данных, фактически используемым в базовом вызове RPC. Поэтому всегда стоит изучить, необходимо ли изменение пользовательского интерфейса распространяться в виде изменения в удаленном интерфейсе.
Аналогичная ситуация может произойти, когда пользователь вызывает удаленный API напрямую, но можно использовать оболочку для создания нового сопоставления типов или выполнения других дополнительных действий, которые стали необходимы. Язык определения интерфейса (IDL) имеет несколько способов облегчения такого переназначения, а именно [call_as], [transmit_as], и [wire_marshal]. Атрибут [call_as] вводит оболочку функции на клиенте и сервере. Оба помещаются между пользовательским кодом и маршалером. Другие атрибуты имеют дело с сопоставлением прямых типов. Для проблем расширений, [call_as] наиболее часто используется и его проще всего понять и манипулировать, избегая подводных камней.
Изменяйте типы данных с помощью объединения без значения по умолчанию.
Изменение атрибута или типа данных обычно приводит к несовместимости провода. См. примеры в "Примеры несовместимых изменений". Однако в случае объединения без предложения по умолчанию несовместимость может управляться таким образом, как в случае процедуры вне диапазона, как описано ранее. Эта схема легко применима к популярным типам XxxINFO, которые используют профсоюзы.
Например, вызов, как показано ниже
XxxGetInfo( [in] level, [out] XxxINFO * pInfo );
может возвращать информацию на уровне 1, 2 или 3, при этом XxxINFO является объединением с тремя ветвями: 1, 2 и 3.
Используйте атрибут [диапазон] для указания диапазона.
Атрибут [диапазон] можно указать в простом типе масштабирования, не нарушая обратную совместимость. Этот атрибут не влияет на формат передачи данных, но во время демаршалинга RPC проверяет значение на канале, чтобы убедиться, что оно находится в диапазоне, указанном в IDL-файле. В противном случае возникает исключение RPC_X_INVALID_BOUND. Это особенно полезно, если сервер знает максимальный размер массива.
Например:
HRESULT Method1( [in, range(0,100)] ULONG m, [size_is(m)] ULONG *plong);
Поведение RPC, если указанный уровень равен 4, а рука отсутствует, зависит от определения объединения. Для объединения с условием по умолчанию, RPC передает тип, указанный в этом условии, для случая, когда метка не является одной из известных (в данном случае не 1, 2 или 3). Для объединения без значения по умолчанию unmarshaler вызывает исключение, поскольку, по определению, нет значения по умолчанию, к которому можно вернуться. Исключение — RPC_S_INVALID_TAG.
Снова, новый клиент может изменить своё поведение, обнаружив, что подключился к устаревшему серверу.
Что следует из этих рекомендуемых практик, заключается в том, что если необходимо создать тип данных, который можно расширить в будущем, используйте союз без значений по умолчанию в IDL-файле. Учитывая выбор, инкапсулированный союз немного более чистый.
Из-за особенностей внутреннего представления проводного протокола NDR64, рекомендацию по добавлению ветвей, предоставленную ранее в этом разделе, необходимо уточнить следующим образом: новая добавленная ветвь не должна изменять выравнивание объединения, и, в частности, самое крупное выравнивание ветвей не должно изменяться. Как правило, это не проблема, поскольку указатель в процессе выравниванию принуждает к 8. Дизайн, в котором каждая рука является указателем на тип руки является одним из чистых способов удовлетворения требования.