Страница:
В предыдущем сценарии описывалось, что происходит в том случае, когда хост-машина становится недоступной в сети. Больший интерес представляет сценарий, когда происходит преждевременный выход процесса, в котором остались неосвобожденные заместители. Если процесс закрывается, не вызвав CoUninitialize нужное число раз (например, процесс аварийно завершился), то у библиотеки СОМ нет возможности восстановить утерянные ссылки. Когда это происходит, локальный OR обнаружит гибель процесса и удалит импортированные им ссылки из последующих передаваемых сообщений, что в конце концов заставит OR-экспортера освободить хранящиеся там ссылки. Если в процессе хранились импортированные ссылки на объекты локальной машины, то они могут быть освобождены вскоре после установления смерти клиента[1].
Распределенный сборщик мусора СОМ иногда критикуют за неэффективность. На самом деле, если объектам нужно надежно установить жизнеспособность клиента, то СОМ сделает это значительно более эффективно, чем модели, специфические для приложения. Дело в том, что сборщик мусора СОМ может агрегировать сохраненные сообщения для всех ссылок на определенной машине в единое периодическое сообщение. Модели же, специфические для приложений, не имеют столь полной информации, и от них можно ожидать единого сообщения для каждого приложения, но не для каждой хост-машины. Для тех сценариев, когда сборщик мусора СОМ действительно влияет на производительность, тестовый опрос для конкретной заглушки может быть отключен с помощью флага MSHLFLAGS_NOPING. Тем не менее, стандартное поведение сборщика мусора пригодно для большинства приложений и превосходит множество специальных моделей, специфических для приложений.
Администратор заглушек следит за тем, сколько внешних ссылок еще не выполнено. Когда заглушка создана, этот счетчик устанавливается в нуль. Если сделан вызов CoMarshalInterface с флагом MSHLFLAGS_NORMAL, этот счетчик увеличивается на некоторое число n, которое записано в маршалированной объектной ссылке. Администратор заместителей, демаршалируя ссылку, добавляет n к своему счетчику хранимых ссылок. Если CoMarshalInterface вызван для администратора заместителей для передачи копии ссылки в другой апартамент, то администратор заместителей может выделить некоторое количество ссылок для инициализации второго заместителя. Если в заместителе осталась только одна ссылка, он должен вернуться в администратор заглушек для запроса дополнительных ссылок.
Часто бывает полезно сохранить маршалированные интерфейсные ссылки в центральной области, доступной для одного или более клиентов. Классическим примером этого является Таблица Исполняемых Объектов (Running Object Table), используемая некоторыми реализациями моникеров. Если бы маршалированные интерфейсные указатели должны были создаваться с использованием флага MSHLFLAGS_NORMAL, то только один клиент смог бы когда-либо демаршалировать объектную ссылку. Если предполагается, что объектную ссылку будут демаршалировать несколько клиентов, то ссылка должна маршалироваться с применением либо MSHLFLAGS_TABLESTRONG, либо MSHLFLAGS_TABLEWEAK. В обоих случаях маршалированная объектная ссылка может быть демаршалирована несколько раз.
Разница между сильным (strong) и слабым (weak) табличными маршалингами заключается во взаимоотношениях между маршалированой объектной ссылкой и администратором заглушек. Когда маршалированная объектная ссылка создается с флагом MSHLFLAGS_TABLEWEAK, то внешний счетчик ссылок в администраторе заглушек не увеличивается. Это означает, что маршалированная объектная ссылка будет содержать нуль ссылок, и каждому администратору заместителей для получения одной или более внешних ссылок придется связываться с администратором заглушек. Маршалированная с помощью слабой таблицы ссылка не представляет сосчитанную внешнюю ссылку на администратор заглушек. Поэтому, когда последний администратор заместителей отсоединится от администратора заглушек, администратор заглушек самоуничтожится и, конечно, освободит все хранившиеся ссылки СОМ на объект. Если ни один администратор заместителей ни разу не свяжется с администратором заглушек, то последний останется жить в течение неопределенного времени. Отрицательной стороной является то, что неосвобожденная маршалированная объектная ссылка не заставляет оставаться в живых администратор заглушек или объект. Напротив, когда маршалированная объектная ссылка создана с применением флага MSHLFLAGS_TABLESTRONG , то есть с помощью сильной таблицы, то внешний счетчик ссылок увеличивается на единицу. Это означает, что маршалированная объектная ссылка представляет сосчитанную внешнюю ссылку на администратор заглушек. Как и в случае маршалинга по слабой таблице, каждому администратору заместителей понадобится связаться с администратором заглушек, чтобы получить одну или более дополнительных внешних ссылок. Поскольку маршалированная по сильной таблице ссылка представляет внешний счетчик ссылок на администратор заглушек, то при отсоединении последнего администратора заместителей от администратора заглушек он не будет самоуничтожаться и фактически продолжит хранение ссылок СОМ на объект. Отрицательная сторона маршалинга по сильной таблице заключается в том, что неосвобожденная маршалированная объектная ссылка влияет на жизненный цикл администратора заглушек или объекта. Это означает, что должен существовать какой-нибудь механизм для освобождения ссылок, хранящихся в объектной ссылке, маршалированной по сильной таблице. В СОМ предусмотрена API-функция CoReleaseMarshalData, которая информирует администратор заглушек о том, что маршалированная объектная ссылка уничтожается:
HRESULT CoReleaseMarshalData([in] IStream *pStm);
Подобно CoUnmarshalInterface, CoReleaseMarshalData принимает интерфейсный указатель IStream на маршалированную объектную ссылку. Если таблица маршалинга более не нужна, для ее аннулирования следует вызвать функцию CoReleaseMarshalData . Если по некоторым причинам нормально маршалированная объектная ссылка не будет демаршалироваться с помощью CoUnmarshalInterface , то должна вызываться также функция CoReleaseMarshalData .
Разработчики объектов могут обратиться к счетчику внешних ссылок администратора заглушек вручную, чтобы убедиться в том, что администратор заглушек продолжает жить во время критических фаз жизненного цикла объекта. В СОМ предусмотрена функция CoLockObjectExternal , которая увеличивает или уменьшает на единицу счетчик внешних ссылок администратора заглушек:
HRESULT CoLockObjectExternal([in] IUnknown *pUnkObject,
[in] BOOL bLock,
[in] BOOL bLastUnlockKillsStub);
Первый параметр CoLockObjectExternal должен указывать на действительный объект, он не может указывать на заместитель. Второй параметр, bLock, показывает, увеличивать или уменьшать на единицу счетчик внешних ссылок администратора заглушек. Третий параметр показывает, нужно или нет уничтожать администратор заглушек, если этот вызов удаляет последнюю внешнюю ссылку. Чтобы понять, для чего необходима функция CoLockObjectExternal , рассмотрим объект, который контролирует некоторое аппаратное устройство и зарегистрирован в таблице исполняемых объектов (Running Object Table) с использованием маршалинга по слабой таблице. Пока объект осуществляет активный контроль, он хочет быть уверенным, что его администратор заглушек действительно существует, чтобы новые клиенты могли соединяться с объектом для проверки состояния устройства. Если же, однако, объект не осуществляет активный контроль, то он мог бы пожелать, чтобы администратор заглушек исчез, если нет соединенных с ним неосвобожденных заместителей. Для реализации такой функциональной возможности объект должен иметь метод, который начинает осуществлять контроль:
STDMETHODIMP Monitor::StartMonitoring(void) {
// ensure that stub manager/object stays alive
// убеждаемся, что администратор заглушек/объект остается жив
HRESULT hr = CoLockObjectExternal(this, TRUE, FALSE);
// start hardware monitoring
// начинаем контроль за аппаратным устройством
if (SUCCEEDED(hr))
hr = this->EnableHardwareProbe();
return hr; }
а также другой метод, который предписывает объекту закончить активный контроль:
STDMETHODIMP Monitor::StopMonitoring(void)
{
// stop hardware monitoring
// прекращаем контроль за устройством
this->DisableHardwareProbe();
// allow stub manager/object to die when no clients exist
// разрешаем администратору заглушек/объекту прекратить
// существование, когда нет клиентов
hr = CoLockObjectExternal(this, FALSE, TRUE);
return hr; }
Если принять, что объект был изначально маршалирован с помощью слабой таблицы маршалинга, то данный код обеспечивает жизнь администратора заглушек и объекта до тех пор, пока хотя бы один неосвобожденный заместитель или объект активно контролируют основное устройство.
Кроме предоставления разработчикам объектов возможности устанавливать счетчик внешних ссылок в администраторе заглушек, СОМ также позволяет разработчикам явно уничтожать администратор заглушек, независимо от числа неосвобожденных объектных ссылок. В СОМ предусмотрена API-функция CoDisconnectObject, которая находит администратор заглушек объекта и уничтожает его, отсоединяя все существующие в данный момент заместители:
HRESULT CoDisconnectObject(
[in] Unknown * pUnkObject,
// ptr to object
// указатель на объект
[in] DWORD dwReserved
// reserved, must be zero
// зарезервировано, должно равняться нулю
);
Подобно CoLockObjectExternal, функция CoDisconnectObject должна вызываться из процесса действующего объекта и не может быть вызвана на объект. Для того чтобы применить CoDisconnectObject к показанному выше объекту контроля за устройством, рассмотрим, что произошло бы, если бы состояние объекта было испорчено. Для предотвращения дополнительных вызовов методов объекта, которые могут возвращать ошибочные результаты, объект мог бы вызвать CoDisconnectObject , чтобы резко отсоединить все существующие заместители:
STDMETHODIMP Monitor::GetSample(/*[out]*/ SAMPLEDATA *ps) {
HRESULT hr = this->GetSampleFromProbe(ps);
if (FAILED(hr))
// probe or object may be corrupted
// образец или объект могут быть испорчены
CoDisconnectObject(this, 0);
return hr; }
Функция CoDisconnectObject используется также в случаях, когда процесс хочет отключиться, хотя один или более его объектов могут иметь неосвобожденные заместители. При явном вызове CoDisconnectObject до уничтожения любых объектов, которые могут иметь оставшиеся заместители, нет риска, что исходящие ORPC-запросы будут обслуживаться после того, как объект уже уничтожен. Если бы входящий ORPC-запрос должен был бы поступить после того, как объект уже уничтожен, но администратор заглушек еще жив, то небрежность привела бы к вызову интерфейсной заглушкой соответствующего метода из участка памяти, ранее известного как данный объект. Это вызвало бы лишние неприятности, связанные с тщетными усилиями по отладке.
Обе функции – CoLockObjectExternal и CoDisconnectObject – могут быть использованы разработчиком объектов для манипулирования администратором заглушек. Часто бывает полезно знать, есть ли в администраторе заглушек в наличии какие-либо заместители или объектные ссылки, маршалированные по сильной таблице (strong marshals ). Для информирования объектов о том, что имеются неосвобожденные внешние ссылки на администратор заглушек, в СОМ определен интерфейс IExternalConnection , который может быть экспортирован объектами:
[ uuid(00000019-0000-0000-C000-000000000046), object, local ]
interface IExternalConnection : IUnknown {
DWORD AddConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved
// reserved, must be zero
// зарезервировано, должно быть равно нулю
);
DWORD ReleaseConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved,
// reserved, must be zero
// зарезервировано, должно быть равно нулю
[in] BOOL fLastReleaseCloses
// should kill stub?
// нужно ли убить заглушку?
); }
При первом подсоединении администратора заглушек к объекту он спрашивает объект, желает ли тот, чтобы его уведомляли о создании или уничтожении внешних ссылок. Он делает это посредством запроса интерфейса IExternalConnection для QueryInterface. Если объект не реализует IExternalConnection, то администратор заглушек будет использовать свой собственный счетчик ссылок при решении вопроса, когда уничтожать администратор заглушек. Если же объект предпочтет реализовать IExternalConnection, то в этом случае администратор заглушек будет жить до тех пор, пока объект явно не уничтожит его путем вызова CoDisconnectObject.
Ожидается, что в объектах, которые реализуют IExternalConnection, поддерживается счетчик блокировок, записывающий число вызовов функций AddConnection и ReleaseConnection. Для большей эффективности СОМ не вызывает AddConnection всякий раз, когда создается заместитель. Это означает, что если объект поддерживает счетчик блокировок, основанный на вызовах функций AddConnection и ReleaseConnection, то этот счетчик блокировок объекта не будет точно отражать число существующих в данный момент объектных ссылок. В то же время СОМ гарантирует, что в том случае, когда счетчик блокировок не равен пулю, существует хотя бы одна неосвобожденная ссылка, а если счетчик блокировок равен нулю, то не существует ни одной неосвобожденной ссылки. Вызовы функции CoLockObjectExternal также будут влиять на этот счетчик. Эта информация особенно полезна для тех объектов, которые заботятся о существовании внешних клиентов. Предположим для примера, что представленный ранее объект для контроля над аппаратным устройством порождает подпроцесс для выполнения фоновой регистрации выборочных данных. Также допустим, что если регистрация произойдет в тот момент, когда объект осуществляет активный контроль данных или, напротив, находится под контролем внешних клиентов, то может возникнуть ошибка выборки. Для предотвращения этой ситуации регистрационный поток мог бы проверять счетчик блокировок, поддерживаемый объектной реализацией IExternalConnection и осуществлять операцию регистрации только тогда, когда не существует внешних ссылок. Это предполагает, что объект реализует IExternalConnection следующим образом:
class Monitor : public IExternalConnection, public IMonitor {
LONG m_cRef;
// normal COM reference count
// обычный счетчик ссылок СОМ
LONG m_cExtRef;
// external reference count
// счетчик внешних ссылок
Monitor(void) : m_cRef(0), m_cExtRef(0) { … }
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
if (extconn & EXTCONN_STRONG)
// must check for this bit
// нужно проверить этот бит
return InterlockedIncrement(&m_cExtRef);
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD,
BOOL bLastUnlockKillsStub) {
DWORD res = 0;
if (extconn & EXTCONN_STRONG) {
// must check for this bit
// нужно проверить этот бит
res = InterlockedDecrement(&m_cExtRef);
if (res == 0 && bLastUnlockKillsStub)
CoDisconnectObject(this, 0);
}
return res;
} }
: : :
: : : }
Получив эту реализацию, подпрограмма потока могла бы проверить состояние объекта и решить, выполнять или нет операцию регистрации, основываясь на уровне активности объекта:
DWORD WINAPI ThreadProc(void *pv) {
// assume ptr to real object is passed to CreateThread
// пусть указатель на действительный объект передается в CreateThread
Monitor *pm = (Monitor*)pv;
while (1) {
// sleep for 10 seconds
// ожидаем 10 секунд
Sleep(1OOOO);
// if object is not in use, perform a log operation
// если объект не используется, то выполняем операцию регистрации
if (pm->m_cExtRef == 0)
pm->TryToLogSampleData();
}
return 0; }
Если принять, что метод объекта TryToLogSampleData корректно поддерживает параллелизм, то данная поточная процедура будет регистрировать данные только при условии, что объект не используется внешними клиентами или не осуществляет активный контроль (напомним, что при контроле объект увеличивает счетчик внешних ссылок посредством CoLockObjectExternal). Хотя данный пример может показаться несколько запутанным, имеются случаи, когда отслеживание внешних ссылок является решающим для обеспечения правильности операции. Один классический пример описан в главе 6 и относится к регистрации объектов класса на внепроцессных серверах.
Специальный маршалинг
Распределенный сборщик мусора СОМ иногда критикуют за неэффективность. На самом деле, если объектам нужно надежно установить жизнеспособность клиента, то СОМ сделает это значительно более эффективно, чем модели, специфические для приложения. Дело в том, что сборщик мусора СОМ может агрегировать сохраненные сообщения для всех ссылок на определенной машине в единое периодическое сообщение. Модели же, специфические для приложений, не имеют столь полной информации, и от них можно ожидать единого сообщения для каждого приложения, но не для каждой хост-машины. Для тех сценариев, когда сборщик мусора СОМ действительно влияет на производительность, тестовый опрос для конкретной заглушки может быть отключен с помощью флага MSHLFLAGS_NOPING. Тем не менее, стандартное поведение сборщика мусора пригодно для большинства приложений и превосходит множество специальных моделей, специфических для приложений.
Администратор заглушек следит за тем, сколько внешних ссылок еще не выполнено. Когда заглушка создана, этот счетчик устанавливается в нуль. Если сделан вызов CoMarshalInterface с флагом MSHLFLAGS_NORMAL, этот счетчик увеличивается на некоторое число n, которое записано в маршалированной объектной ссылке. Администратор заместителей, демаршалируя ссылку, добавляет n к своему счетчику хранимых ссылок. Если CoMarshalInterface вызван для администратора заместителей для передачи копии ссылки в другой апартамент, то администратор заместителей может выделить некоторое количество ссылок для инициализации второго заместителя. Если в заместителе осталась только одна ссылка, он должен вернуться в администратор заглушек для запроса дополнительных ссылок.
Часто бывает полезно сохранить маршалированные интерфейсные ссылки в центральной области, доступной для одного или более клиентов. Классическим примером этого является Таблица Исполняемых Объектов (Running Object Table), используемая некоторыми реализациями моникеров. Если бы маршалированные интерфейсные указатели должны были создаваться с использованием флага MSHLFLAGS_NORMAL, то только один клиент смог бы когда-либо демаршалировать объектную ссылку. Если предполагается, что объектную ссылку будут демаршалировать несколько клиентов, то ссылка должна маршалироваться с применением либо MSHLFLAGS_TABLESTRONG, либо MSHLFLAGS_TABLEWEAK. В обоих случаях маршалированная объектная ссылка может быть демаршалирована несколько раз.
Разница между сильным (strong) и слабым (weak) табличными маршалингами заключается во взаимоотношениях между маршалированой объектной ссылкой и администратором заглушек. Когда маршалированная объектная ссылка создается с флагом MSHLFLAGS_TABLEWEAK, то внешний счетчик ссылок в администраторе заглушек не увеличивается. Это означает, что маршалированная объектная ссылка будет содержать нуль ссылок, и каждому администратору заместителей для получения одной или более внешних ссылок придется связываться с администратором заглушек. Маршалированная с помощью слабой таблицы ссылка не представляет сосчитанную внешнюю ссылку на администратор заглушек. Поэтому, когда последний администратор заместителей отсоединится от администратора заглушек, администратор заглушек самоуничтожится и, конечно, освободит все хранившиеся ссылки СОМ на объект. Если ни один администратор заместителей ни разу не свяжется с администратором заглушек, то последний останется жить в течение неопределенного времени. Отрицательной стороной является то, что неосвобожденная маршалированная объектная ссылка не заставляет оставаться в живых администратор заглушек или объект. Напротив, когда маршалированная объектная ссылка создана с применением флага MSHLFLAGS_TABLESTRONG , то есть с помощью сильной таблицы, то внешний счетчик ссылок увеличивается на единицу. Это означает, что маршалированная объектная ссылка представляет сосчитанную внешнюю ссылку на администратор заглушек. Как и в случае маршалинга по слабой таблице, каждому администратору заместителей понадобится связаться с администратором заглушек, чтобы получить одну или более дополнительных внешних ссылок. Поскольку маршалированная по сильной таблице ссылка представляет внешний счетчик ссылок на администратор заглушек, то при отсоединении последнего администратора заместителей от администратора заглушек он не будет самоуничтожаться и фактически продолжит хранение ссылок СОМ на объект. Отрицательная сторона маршалинга по сильной таблице заключается в том, что неосвобожденная маршалированная объектная ссылка влияет на жизненный цикл администратора заглушек или объекта. Это означает, что должен существовать какой-нибудь механизм для освобождения ссылок, хранящихся в объектной ссылке, маршалированной по сильной таблице. В СОМ предусмотрена API-функция CoReleaseMarshalData, которая информирует администратор заглушек о том, что маршалированная объектная ссылка уничтожается:
HRESULT CoReleaseMarshalData([in] IStream *pStm);
Подобно CoUnmarshalInterface, CoReleaseMarshalData принимает интерфейсный указатель IStream на маршалированную объектную ссылку. Если таблица маршалинга более не нужна, для ее аннулирования следует вызвать функцию CoReleaseMarshalData . Если по некоторым причинам нормально маршалированная объектная ссылка не будет демаршалироваться с помощью CoUnmarshalInterface , то должна вызываться также функция CoReleaseMarshalData .
Разработчики объектов могут обратиться к счетчику внешних ссылок администратора заглушек вручную, чтобы убедиться в том, что администратор заглушек продолжает жить во время критических фаз жизненного цикла объекта. В СОМ предусмотрена функция CoLockObjectExternal , которая увеличивает или уменьшает на единицу счетчик внешних ссылок администратора заглушек:
HRESULT CoLockObjectExternal([in] IUnknown *pUnkObject,
[in] BOOL bLock,
[in] BOOL bLastUnlockKillsStub);
Первый параметр CoLockObjectExternal должен указывать на действительный объект, он не может указывать на заместитель. Второй параметр, bLock, показывает, увеличивать или уменьшать на единицу счетчик внешних ссылок администратора заглушек. Третий параметр показывает, нужно или нет уничтожать администратор заглушек, если этот вызов удаляет последнюю внешнюю ссылку. Чтобы понять, для чего необходима функция CoLockObjectExternal , рассмотрим объект, который контролирует некоторое аппаратное устройство и зарегистрирован в таблице исполняемых объектов (Running Object Table) с использованием маршалинга по слабой таблице. Пока объект осуществляет активный контроль, он хочет быть уверенным, что его администратор заглушек действительно существует, чтобы новые клиенты могли соединяться с объектом для проверки состояния устройства. Если же, однако, объект не осуществляет активный контроль, то он мог бы пожелать, чтобы администратор заглушек исчез, если нет соединенных с ним неосвобожденных заместителей. Для реализации такой функциональной возможности объект должен иметь метод, который начинает осуществлять контроль:
STDMETHODIMP Monitor::StartMonitoring(void) {
// ensure that stub manager/object stays alive
// убеждаемся, что администратор заглушек/объект остается жив
HRESULT hr = CoLockObjectExternal(this, TRUE, FALSE);
// start hardware monitoring
// начинаем контроль за аппаратным устройством
if (SUCCEEDED(hr))
hr = this->EnableHardwareProbe();
return hr; }
а также другой метод, который предписывает объекту закончить активный контроль:
STDMETHODIMP Monitor::StopMonitoring(void)
{
// stop hardware monitoring
// прекращаем контроль за устройством
this->DisableHardwareProbe();
// allow stub manager/object to die when no clients exist
// разрешаем администратору заглушек/объекту прекратить
// существование, когда нет клиентов
hr = CoLockObjectExternal(this, FALSE, TRUE);
return hr; }
Если принять, что объект был изначально маршалирован с помощью слабой таблицы маршалинга, то данный код обеспечивает жизнь администратора заглушек и объекта до тех пор, пока хотя бы один неосвобожденный заместитель или объект активно контролируют основное устройство.
Кроме предоставления разработчикам объектов возможности устанавливать счетчик внешних ссылок в администраторе заглушек, СОМ также позволяет разработчикам явно уничтожать администратор заглушек, независимо от числа неосвобожденных объектных ссылок. В СОМ предусмотрена API-функция CoDisconnectObject, которая находит администратор заглушек объекта и уничтожает его, отсоединяя все существующие в данный момент заместители:
HRESULT CoDisconnectObject(
[in] Unknown * pUnkObject,
// ptr to object
// указатель на объект
[in] DWORD dwReserved
// reserved, must be zero
// зарезервировано, должно равняться нулю
);
Подобно CoLockObjectExternal, функция CoDisconnectObject должна вызываться из процесса действующего объекта и не может быть вызвана на объект. Для того чтобы применить CoDisconnectObject к показанному выше объекту контроля за устройством, рассмотрим, что произошло бы, если бы состояние объекта было испорчено. Для предотвращения дополнительных вызовов методов объекта, которые могут возвращать ошибочные результаты, объект мог бы вызвать CoDisconnectObject , чтобы резко отсоединить все существующие заместители:
STDMETHODIMP Monitor::GetSample(/*[out]*/ SAMPLEDATA *ps) {
HRESULT hr = this->GetSampleFromProbe(ps);
if (FAILED(hr))
// probe or object may be corrupted
// образец или объект могут быть испорчены
CoDisconnectObject(this, 0);
return hr; }
Функция CoDisconnectObject используется также в случаях, когда процесс хочет отключиться, хотя один или более его объектов могут иметь неосвобожденные заместители. При явном вызове CoDisconnectObject до уничтожения любых объектов, которые могут иметь оставшиеся заместители, нет риска, что исходящие ORPC-запросы будут обслуживаться после того, как объект уже уничтожен. Если бы входящий ORPC-запрос должен был бы поступить после того, как объект уже уничтожен, но администратор заглушек еще жив, то небрежность привела бы к вызову интерфейсной заглушкой соответствующего метода из участка памяти, ранее известного как данный объект. Это вызвало бы лишние неприятности, связанные с тщетными усилиями по отладке.
Обе функции – CoLockObjectExternal и CoDisconnectObject – могут быть использованы разработчиком объектов для манипулирования администратором заглушек. Часто бывает полезно знать, есть ли в администраторе заглушек в наличии какие-либо заместители или объектные ссылки, маршалированные по сильной таблице (strong marshals ). Для информирования объектов о том, что имеются неосвобожденные внешние ссылки на администратор заглушек, в СОМ определен интерфейс IExternalConnection , который может быть экспортирован объектами:
[ uuid(00000019-0000-0000-C000-000000000046), object, local ]
interface IExternalConnection : IUnknown {
DWORD AddConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved
// reserved, must be zero
// зарезервировано, должно быть равно нулю
);
DWORD ReleaseConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved,
// reserved, must be zero
// зарезервировано, должно быть равно нулю
[in] BOOL fLastReleaseCloses
// should kill stub?
// нужно ли убить заглушку?
); }
При первом подсоединении администратора заглушек к объекту он спрашивает объект, желает ли тот, чтобы его уведомляли о создании или уничтожении внешних ссылок. Он делает это посредством запроса интерфейса IExternalConnection для QueryInterface. Если объект не реализует IExternalConnection, то администратор заглушек будет использовать свой собственный счетчик ссылок при решении вопроса, когда уничтожать администратор заглушек. Если же объект предпочтет реализовать IExternalConnection, то в этом случае администратор заглушек будет жить до тех пор, пока объект явно не уничтожит его путем вызова CoDisconnectObject.
Ожидается, что в объектах, которые реализуют IExternalConnection, поддерживается счетчик блокировок, записывающий число вызовов функций AddConnection и ReleaseConnection. Для большей эффективности СОМ не вызывает AddConnection всякий раз, когда создается заместитель. Это означает, что если объект поддерживает счетчик блокировок, основанный на вызовах функций AddConnection и ReleaseConnection, то этот счетчик блокировок объекта не будет точно отражать число существующих в данный момент объектных ссылок. В то же время СОМ гарантирует, что в том случае, когда счетчик блокировок не равен пулю, существует хотя бы одна неосвобожденная ссылка, а если счетчик блокировок равен нулю, то не существует ни одной неосвобожденной ссылки. Вызовы функции CoLockObjectExternal также будут влиять на этот счетчик. Эта информация особенно полезна для тех объектов, которые заботятся о существовании внешних клиентов. Предположим для примера, что представленный ранее объект для контроля над аппаратным устройством порождает подпроцесс для выполнения фоновой регистрации выборочных данных. Также допустим, что если регистрация произойдет в тот момент, когда объект осуществляет активный контроль данных или, напротив, находится под контролем внешних клиентов, то может возникнуть ошибка выборки. Для предотвращения этой ситуации регистрационный поток мог бы проверять счетчик блокировок, поддерживаемый объектной реализацией IExternalConnection и осуществлять операцию регистрации только тогда, когда не существует внешних ссылок. Это предполагает, что объект реализует IExternalConnection следующим образом:
class Monitor : public IExternalConnection, public IMonitor {
LONG m_cRef;
// normal COM reference count
// обычный счетчик ссылок СОМ
LONG m_cExtRef;
// external reference count
// счетчик внешних ссылок
Monitor(void) : m_cRef(0), m_cExtRef(0) { … }
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
if (extconn & EXTCONN_STRONG)
// must check for this bit
// нужно проверить этот бит
return InterlockedIncrement(&m_cExtRef);
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD,
BOOL bLastUnlockKillsStub) {
DWORD res = 0;
if (extconn & EXTCONN_STRONG) {
// must check for this bit
// нужно проверить этот бит
res = InterlockedDecrement(&m_cExtRef);
if (res == 0 && bLastUnlockKillsStub)
CoDisconnectObject(this, 0);
}
return res;
} }
: : :
: : : }
Получив эту реализацию, подпрограмма потока могла бы проверить состояние объекта и решить, выполнять или нет операцию регистрации, основываясь на уровне активности объекта:
DWORD WINAPI ThreadProc(void *pv) {
// assume ptr to real object is passed to CreateThread
// пусть указатель на действительный объект передается в CreateThread
Monitor *pm = (Monitor*)pv;
while (1) {
// sleep for 10 seconds
// ожидаем 10 секунд
Sleep(1OOOO);
// if object is not in use, perform a log operation
// если объект не используется, то выполняем операцию регистрации
if (pm->m_cExtRef == 0)
pm->TryToLogSampleData();
}
return 0; }
Если принять, что метод объекта TryToLogSampleData корректно поддерживает параллелизм, то данная поточная процедура будет регистрировать данные только при условии, что объект не используется внешними клиентами или не осуществляет активный контроль (напомним, что при контроле объект увеличивает счетчик внешних ссылок посредством CoLockObjectExternal). Хотя данный пример может показаться несколько запутанным, имеются случаи, когда отслеживание внешних ссылок является решающим для обеспечения правильности операции. Один классический пример описан в главе 6 и относится к регистрации объектов класса на внепроцессных серверах.
Специальный маршалинг
До сих пор внимание в данной главе было сосредоточено на стандартном маршалинге и вызове методов на основе ORPC. Для большого класса объектов этого было достаточно, чтобы достичь нужного баланса между производительностью, семантической корректностью и простотой реализации. В то же время существуют объекты, для которых ORPC-вызов по умолчанию является неэффективным и даже непригодным. Для таких объектов в СОМ предусмотрен специальный маршалинг (custom marshaling). Как уже упоминалось в этой главе, специальный маршалинг позволяет разработчику объекта обеспечить реализацию специальных заместителей (custom proxies ), которые будут созданы в импортирующих апартаментах. Объекты сообщают о своем желании поддерживать специальный маршалинг путем экспорта интерфейса IMarshal:
[uuid(00000003-0000-0000-C000-000000000046), local, object] interface IMarshal : IUnknown {
// get CLSID for custom proxy (CoMarshalInterface)
// получаем CLSID для специального заместителя (CoMarshalInterface)
HRESULT GetUnmarshalClass( [in] REFIID riid, [in, iid_is(riid) ] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags, [out] CLSID *pclsid);
// get size of custom marshaled objref (CoGetMarshalSizeMax)
// получаем размер специально маршалированной объектной ссылки (CoGetMarshalSizeMax)
HRESULT GetMarshalSizeMax( [in] REFIID riid, [in, iid_is(riid)] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags, [out] DWORD *pSize);
// write out custom marshaled objref (CoMarshalInterface)
// выполняем контрольное считывание специально маршалированной объектной ссылки (CoMarshalInterface)
HRESULT MarshalInterface([in] IStream *pStm, [in] REFIID riid, [in, iid_is(riid)] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags);
// read objref and return proxy (CoUnmarshalInterface)
// читаем объектную ссылку и возвращаем заместитель (CoUnmarshalInterface)
HRESULT UnmarshalInterface([in] IStream *pStm, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
// revoke a marshal (CoReleaseMarshalData)
// аннулируем маршалер (CoReleaseMarshalData)
HRESULT ReleaseMarshalData([in] IStream *pStm);
// tear down connection-state (CoDisconnectObject)
// разрываем связь между объектами (CoDisconnectObject)
HRESULT DisconnectObject([in] DWORD dwReserved);
}
Комментарии, предваряющие определения методов, показывают, какие именно API-функции вызывает каждый из них.
Когда метод CoMarshalInterface вызывается на объект, поддерживающий специальный маршалинг, маршалированная объектная ссылка имеет несколько другой (формат, как показано на рис. 5.7. Заметим, что после стандартного заголовка MEOW маршалированная объектная ссылка просто содержит CLSID, используемый для создания специального заместителя и непрозрачного байтового массива, предназначенного для инициализации этого специального заместителя. CoMarshalInterface находит CLSID специального заместителя посредством вызова на объект метода IMarshal::GetUnmarshalСlass. CoMarshalInterface заполняет непрозрачный байтовый массив, вызывая реализацию метода IMarshal::MarshalInterface объекта. Именно в MarshalInterface объект получает свой первый и единственный шанс послать инициализационное сообщение новому специальному заместителю, просто записав его в подаваемый байтовый поток.
При вызове CoUnmarshalInterface это сообщение будет передано вновь созданному специальному заместителю через его метод IMarshal::UnmarshalInterface. Это означает, что и объект, и специальный заместитель должны реализовать IMarshal . Метод объекта MarshalInterface записывает инициализационное сообщение. Метод заместителя UnmarshalInterface читает инициализационное сообщение. Когда метод UnmarshalInterface возвращается, СОМ больше не участвует ни в каких связях заместитель/объект. Реализация интерфейсных методов семантически корректным способом является делом специального заместителя. Если нужно произвести удаленный вызов метода на объект, то сделать это – задача заместителя. Если же метод может быть реализован в апартаменте клиента, то заместитель может сделать и это.
Преимуществом специального маршалинга является то, что клиент не имеет понятия о его использовании. Фактически клиент не может достоверно определить, является ли интерфейс стандартным заместителем, специальным заместителем или настоящим объектом. Специальный маршалинг является решением на уровне объект-объект. Два экземпляра одного и того же класса могут независимо друг от друга избрать стандартный или специальный маршалинг. Если объект выбирает реализацию специального маршалинга, то он должен делать это для всех интерфейсов. Если объект желает специально маршалировать только для части всех возможных контекстов, подлежащих маршалингу – например, внутрипроцессный, локальный, с другой машины, – то он может получить экземпляр стандартного маршалера и направить его методы IMarshal для маршалинга неподдерживаемых контекстов, так чтобы могли поддерживаться все контексты. Если бы объект мог безоговорочно направить все методы IMarshal к стандартному маршалеру, то он практически всегда использовал бы стандартный маршалинг.
Для получения указателя на стандартный маршалер объекты могут вызывать метод CoGetStandardMarshal:
HRESULT CoGetStandardMarshal( [in] REFIID riid,
// type of itf marshaled?
// тип, которым маршалирован интерфейс?
[in, iid_is(riid)] IUnknown *pUnk,
// the itf to marshal
// интерфейс для маршалинга
[in] DWORD dwDestCtx,
// MSHCTX [in] void *pvDestCtx,
// reserved // зарезервировано [in] DWORD mshlflags,
// normal vs. table // нормальный или табличный маршалинг
[out] IMarshal **ppMarshal); // ptr to std. Marshal
// указатель на стандартный маршалер
Предположим, что объект использует технологию специального маршалинга, которая работает только на локальном хосте, но не при связи с внехостовыми апартаментами. Реализация объектом метода GetMarshalSizeMax могла бы выглядеть примерно так:
STDMETHODIMP CustStd::GetMarshalSizeMax(
ULONG *pcb, REFIID riid, void *pv, DWORD dwDestCtx, void *pvDestCtx, DWORD mshlflags) {
// if context is supported, do work!
// если контекст поддерживается, то действуем!
if (dwDestCtx == MSHCTX_LOCAL || dwDestCtx == MSHCTX_INPROC) return this->MyCustomMarshalingRoutine(pcb);
// unsupported context, delegate to std marshal
// контекст не поддерживается, обращаемся к стандартному маршапингу
IMarshal *pMsh = 0;
HRESULT hr = CoGetStandardMarshal (riid, pv, dwDestCtx, pvDestCtx, mshlflags, &pMsh);
if (SUCCEEDED(hr)) {
hr = pMsh->GetMarshalSizeMax(pcb, riid, pv, dwDestCtx, pvDestCtx, mshlflags);
pMsh->Retease();
}
return hr;
}
В этом фрагменте кода не показано, как писать инициализационное сообщение для случая, когда действительно желателен специальный маршалинг. Дело в том, что не существует стандартной реализации каждого из методов IMarshal (отсюда и термин специальный (custom) маршалинг). Существует, однако, несколько общих сценариев, в которых специальный маршалинг чрезвычайно выигрышен и реализация IMarshal в этих сценариях – довольно обычное явление. Безусловно, наиболее общим приложением IMarshal является реализация маршалинга по значению (marshal-by-value).
Маршалинг по значению наиболее удобен для таких объектов, которые после инициализации никогда не изменяют своего состояния. Обертки СОМ для структур – вот типичный пример объекта, который просто инициализирован, передан другому объекту для запроса и затем уничтожен. Такой объект является первым кандидатом для специального маршалинга. При реализации маршалинга по значению реализация объекта почти всегда является внутрипроцессным сервером. Это позволяет объекту и заместителю разделять один и тот же класс реализации. Идея маршалинга по значению состоит в том, что специальный заместитель становится клоном исходного объекта. Из этого следует, что маршалированная объектная ссылка должна содержать все состояние исходного объекта, а также (для простоты) то, что CLSID специального заместителя должен быть тем же, что и у исходного объекта.
Представим следующее определение класса СОМ-обертки вокруг простой двумерной точки:
class Point : public IPoint, public IMarshal
{
long m_x;
long m_y;
public:
Point(void) : m_x(0), m_y(0) {}
IMPLEMENT_UNKNOWN (Point)
BEGIN_INTERFACE_TABLE(Point)
IMPLEMENTS_INTERFACE(IPoint)
IMPLEMENTS_INTERFACE(IMarshal)
END_INTERFACE_TABLE()
// IPoint methods
// методы IPoint
// IMarshal methods
// методы IMarshal
};
Для поддержки маршалинга по значению метод MarshalInterface класса должен преобразовать состояние объекта в последовательную форму в качестве инициализационного сообщения для заместителя:
STOMETHODIMP Point::MarshalInterface(IStream *pStm, REFIID, void *, DWORD, void *, DWORD)
{
// write out endian header
// переписываем завершающий заголовок
DWORD dw = OxFF669900;
HRESULT hr = pStm->Write(&dw, sizeof(DWORD), 0);
if (FAILED(hr)) return hr; dw = m_x;
hr = pStm->Write(&dw, sizeof(DWORD), 0);
if (FAILED(hr)) return hr; dw = m_y;
return pStm->Write(&dw, sizeof (DWORD), 0);
}
Если допустить, что класс объекта реализован как внутрипроцессный сервер, то специальный заместитель может стать просто вторым экземпляром того же класса, из чего вытекает следующая реализация GetUnmarshalClass:
STDMETHODIMP Point::GetUnmarshalClass(REFIID, void *, DWORD, void *, DWORD, CLSID *pclsid)
{
*pclsid = CLSID_Point;
// this class's CLSID
// CLSID этого класса return hr;
}
Для обеспечения того, чтобы для инициализационного сообщения было выделено достаточно места, методу объекта GetMarshalSizeMax требуется возвратить правильное количество байт:
STDMETHODIMP Point::GetMarshalSizeMax(REFIID, void *, DWORD, void *, DWORD, DWORD *pcb)
{
*pcb = 3 * sizeof (DWORD);
// m_x + m_y + header
return hr;
}
Когда маршалированная объектная ссылка демаршалируется с помощью CoUnmarshalInterface, тот факт, что она была маршалирована специальным образом, вызовет создание нового специального заместителя. Объектная ссылка содержит CLSID специального заместителя, возвращенный исходным объектом в своем методе GetUnmarshalClass. Когда создан новый специальный заместитель, его метод UnmarshalInterface получает инициализационное сообщение, которое объект записал в своей реализации MarshalInterface:
[uuid(00000003-0000-0000-C000-000000000046), local, object] interface IMarshal : IUnknown {
// get CLSID for custom proxy (CoMarshalInterface)
// получаем CLSID для специального заместителя (CoMarshalInterface)
HRESULT GetUnmarshalClass( [in] REFIID riid, [in, iid_is(riid) ] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags, [out] CLSID *pclsid);
// get size of custom marshaled objref (CoGetMarshalSizeMax)
// получаем размер специально маршалированной объектной ссылки (CoGetMarshalSizeMax)
HRESULT GetMarshalSizeMax( [in] REFIID riid, [in, iid_is(riid)] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags, [out] DWORD *pSize);
// write out custom marshaled objref (CoMarshalInterface)
// выполняем контрольное считывание специально маршалированной объектной ссылки (CoMarshalInterface)
HRESULT MarshalInterface([in] IStream *pStm, [in] REFIID riid, [in, iid_is(riid)] void *pv, [in] DWORD dwDestCtx, [in] void *pvDestCtx, [in] DWORD mshlflags);
// read objref and return proxy (CoUnmarshalInterface)
// читаем объектную ссылку и возвращаем заместитель (CoUnmarshalInterface)
HRESULT UnmarshalInterface([in] IStream *pStm, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
// revoke a marshal (CoReleaseMarshalData)
// аннулируем маршалер (CoReleaseMarshalData)
HRESULT ReleaseMarshalData([in] IStream *pStm);
// tear down connection-state (CoDisconnectObject)
// разрываем связь между объектами (CoDisconnectObject)
HRESULT DisconnectObject([in] DWORD dwReserved);
}
Комментарии, предваряющие определения методов, показывают, какие именно API-функции вызывает каждый из них.
Когда метод CoMarshalInterface вызывается на объект, поддерживающий специальный маршалинг, маршалированная объектная ссылка имеет несколько другой (формат, как показано на рис. 5.7. Заметим, что после стандартного заголовка MEOW маршалированная объектная ссылка просто содержит CLSID, используемый для создания специального заместителя и непрозрачного байтового массива, предназначенного для инициализации этого специального заместителя. CoMarshalInterface находит CLSID специального заместителя посредством вызова на объект метода IMarshal::GetUnmarshalСlass. CoMarshalInterface заполняет непрозрачный байтовый массив, вызывая реализацию метода IMarshal::MarshalInterface объекта. Именно в MarshalInterface объект получает свой первый и единственный шанс послать инициализационное сообщение новому специальному заместителю, просто записав его в подаваемый байтовый поток.
При вызове CoUnmarshalInterface это сообщение будет передано вновь созданному специальному заместителю через его метод IMarshal::UnmarshalInterface. Это означает, что и объект, и специальный заместитель должны реализовать IMarshal . Метод объекта MarshalInterface записывает инициализационное сообщение. Метод заместителя UnmarshalInterface читает инициализационное сообщение. Когда метод UnmarshalInterface возвращается, СОМ больше не участвует ни в каких связях заместитель/объект. Реализация интерфейсных методов семантически корректным способом является делом специального заместителя. Если нужно произвести удаленный вызов метода на объект, то сделать это – задача заместителя. Если же метод может быть реализован в апартаменте клиента, то заместитель может сделать и это.
Преимуществом специального маршалинга является то, что клиент не имеет понятия о его использовании. Фактически клиент не может достоверно определить, является ли интерфейс стандартным заместителем, специальным заместителем или настоящим объектом. Специальный маршалинг является решением на уровне объект-объект. Два экземпляра одного и того же класса могут независимо друг от друга избрать стандартный или специальный маршалинг. Если объект выбирает реализацию специального маршалинга, то он должен делать это для всех интерфейсов. Если объект желает специально маршалировать только для части всех возможных контекстов, подлежащих маршалингу – например, внутрипроцессный, локальный, с другой машины, – то он может получить экземпляр стандартного маршалера и направить его методы IMarshal для маршалинга неподдерживаемых контекстов, так чтобы могли поддерживаться все контексты. Если бы объект мог безоговорочно направить все методы IMarshal к стандартному маршалеру, то он практически всегда использовал бы стандартный маршалинг.
Для получения указателя на стандартный маршалер объекты могут вызывать метод CoGetStandardMarshal:
HRESULT CoGetStandardMarshal( [in] REFIID riid,
// type of itf marshaled?
// тип, которым маршалирован интерфейс?
[in, iid_is(riid)] IUnknown *pUnk,
// the itf to marshal
// интерфейс для маршалинга
[in] DWORD dwDestCtx,
// MSHCTX [in] void *pvDestCtx,
// reserved // зарезервировано [in] DWORD mshlflags,
// normal vs. table // нормальный или табличный маршалинг
[out] IMarshal **ppMarshal); // ptr to std. Marshal
// указатель на стандартный маршалер
Предположим, что объект использует технологию специального маршалинга, которая работает только на локальном хосте, но не при связи с внехостовыми апартаментами. Реализация объектом метода GetMarshalSizeMax могла бы выглядеть примерно так:
STDMETHODIMP CustStd::GetMarshalSizeMax(
ULONG *pcb, REFIID riid, void *pv, DWORD dwDestCtx, void *pvDestCtx, DWORD mshlflags) {
// if context is supported, do work!
// если контекст поддерживается, то действуем!
if (dwDestCtx == MSHCTX_LOCAL || dwDestCtx == MSHCTX_INPROC) return this->MyCustomMarshalingRoutine(pcb);
// unsupported context, delegate to std marshal
// контекст не поддерживается, обращаемся к стандартному маршапингу
IMarshal *pMsh = 0;
HRESULT hr = CoGetStandardMarshal (riid, pv, dwDestCtx, pvDestCtx, mshlflags, &pMsh);
if (SUCCEEDED(hr)) {
hr = pMsh->GetMarshalSizeMax(pcb, riid, pv, dwDestCtx, pvDestCtx, mshlflags);
pMsh->Retease();
}
return hr;
}
В этом фрагменте кода не показано, как писать инициализационное сообщение для случая, когда действительно желателен специальный маршалинг. Дело в том, что не существует стандартной реализации каждого из методов IMarshal (отсюда и термин специальный (custom) маршалинг). Существует, однако, несколько общих сценариев, в которых специальный маршалинг чрезвычайно выигрышен и реализация IMarshal в этих сценариях – довольно обычное явление. Безусловно, наиболее общим приложением IMarshal является реализация маршалинга по значению (marshal-by-value).
Маршалинг по значению наиболее удобен для таких объектов, которые после инициализации никогда не изменяют своего состояния. Обертки СОМ для структур – вот типичный пример объекта, который просто инициализирован, передан другому объекту для запроса и затем уничтожен. Такой объект является первым кандидатом для специального маршалинга. При реализации маршалинга по значению реализация объекта почти всегда является внутрипроцессным сервером. Это позволяет объекту и заместителю разделять один и тот же класс реализации. Идея маршалинга по значению состоит в том, что специальный заместитель становится клоном исходного объекта. Из этого следует, что маршалированная объектная ссылка должна содержать все состояние исходного объекта, а также (для простоты) то, что CLSID специального заместителя должен быть тем же, что и у исходного объекта.
Представим следующее определение класса СОМ-обертки вокруг простой двумерной точки:
class Point : public IPoint, public IMarshal
{
long m_x;
long m_y;
public:
Point(void) : m_x(0), m_y(0) {}
IMPLEMENT_UNKNOWN (Point)
BEGIN_INTERFACE_TABLE(Point)
IMPLEMENTS_INTERFACE(IPoint)
IMPLEMENTS_INTERFACE(IMarshal)
END_INTERFACE_TABLE()
// IPoint methods
// методы IPoint
// IMarshal methods
// методы IMarshal
};
Для поддержки маршалинга по значению метод MarshalInterface класса должен преобразовать состояние объекта в последовательную форму в качестве инициализационного сообщения для заместителя:
STOMETHODIMP Point::MarshalInterface(IStream *pStm, REFIID, void *, DWORD, void *, DWORD)
{
// write out endian header
// переписываем завершающий заголовок
DWORD dw = OxFF669900;
HRESULT hr = pStm->Write(&dw, sizeof(DWORD), 0);
if (FAILED(hr)) return hr; dw = m_x;
hr = pStm->Write(&dw, sizeof(DWORD), 0);
if (FAILED(hr)) return hr; dw = m_y;
return pStm->Write(&dw, sizeof (DWORD), 0);
}
Если допустить, что класс объекта реализован как внутрипроцессный сервер, то специальный заместитель может стать просто вторым экземпляром того же класса, из чего вытекает следующая реализация GetUnmarshalClass:
STDMETHODIMP Point::GetUnmarshalClass(REFIID, void *, DWORD, void *, DWORD, CLSID *pclsid)
{
*pclsid = CLSID_Point;
// this class's CLSID
// CLSID этого класса return hr;
}
Для обеспечения того, чтобы для инициализационного сообщения было выделено достаточно места, методу объекта GetMarshalSizeMax требуется возвратить правильное количество байт:
STDMETHODIMP Point::GetMarshalSizeMax(REFIID, void *, DWORD, void *, DWORD, DWORD *pcb)
{
*pcb = 3 * sizeof (DWORD);
// m_x + m_y + header
return hr;
}
Когда маршалированная объектная ссылка демаршалируется с помощью CoUnmarshalInterface, тот факт, что она была маршалирована специальным образом, вызовет создание нового специального заместителя. Объектная ссылка содержит CLSID специального заместителя, возвращенный исходным объектом в своем методе GetUnmarshalClass. Когда создан новый специальный заместитель, его метод UnmarshalInterface получает инициализационное сообщение, которое объект записал в своей реализации MarshalInterface: