Кроме того, что реализации могут быть именованы с помощью CLSID, СОМ поддерживает текстовые псевдонимы, так называемые программные идентификаторы (programmatic identifiers), иначе ProgID. Эти ProgID поступают в формате libraryname.classname.version и, в отличие от CLSID, являются уникальными только по соглашению. Клиенты могут преобразовывать ProgID в CLSID и обратно с помощью API-функций СОМ CLSIDFromProgID и ProgIDFromCLSID:
HRESULT CLSIDFromProgID([in, string] const OLECHAR *pwszProgID, [out] CLSID *pclsid);
HRESULT ProgIDFromCLSID([in] REFCLSID rclsid, [out, string] OLECHAR **ppwszProgID);
Для преобразования ProgID в CLSID нужно просто вызвать CLSIDFromProgID:
HRESULT GetGorillaCLSID(CLSID& rclsid)
{
const OLECHAR wszProgID[] = OLESTR(«Apes.Gorilla.1»);
return CLSIDFromProgID(wszProgID, &rclsid);
}
На этапе выполнения будет просматриваться база данных конфигураций СОМ для преобразования ProgID Apes.Gorilla.1 в CLSID, соответствующий классу реализации СОМ.
Объекты классов
Основное требование всех СОМ-классов состоит в том, что они должны иметь объект класса. Объект класса – это единственный экземпляр (синглетон), связанный с каждым классом, который реализует функциональность класса, общую для всех его экземпляров. Объект класса ведет себя как метакласс по отношению к заданной реализации, а реализуемые им методы выполняют роль статических функций-членов из C++. По логике вещей, может быть только один объект класса в каждом классе; однако в силу распределенной природы СOМ каждый класс может иметь по одному объекту класса на каждую хост-машину (host machine), на учетную запись пользователя или на процесс, – в зависимости от того, как используется этот класс. Первой точкой входа в реализацию класса является ее объект класса.
Объекты класса являются очень полезными программистскими абстракциями. Объекты класса могут вести себя как известные объекты (когда их идентификатор CLSID выступает в качестве имени объекта), которые позволяют нескольким клиентам связываться с одним и тем же объектом, определенным с помощью данного CLSID. В то время как системы в целом могли быть созданы с использованием исключительно объектов класса, объекты класса часто используются как посредники (brokers) при создании новых экземпляров класса или для того, чтобы найти имеющиеся экземпляры, определенные с помощью какого-нибудь известного имени объекта. При использовании в этой роли объект класса обычно объявляет только один или два промежуточных интерфейса, которые позволят клиентам создать или найти те экземпляры, которые в конечном счете будут выполнять нужную работу. Например, рассмотрим описанный ранее интерфейс IАре . Объявление интерфейса IАре не нарушит законы СОМ для объекта класса:
class GorillaClass : public IApe
{
public:
// class objects are singletons, so don't delete
// объекты класса существуют в единственном экземпляре,
// так что не удаляйте их
IMPLEMENTUNKNOWNNODELETE (GorillaClass)
BEGININTERFACETABLE(GorillaClass)
IMPLEMENTSINTERFACE(IApe)
ENDINTERFACETABLE()
// IApe methods
// методы IApe
STDMETHODIMP EatBanana(void);
STDMETHODIMP SwingFromTree(void);
STDMETHODIMP getWeight(long *plbs);
};
Если для данного класса C++ может существовать лишь один экземпляр (так ведут себя все объекты классов в СОМ), то в любом заданном экземпляре может быть только одна горилла (gorilla). Для некоторых областей одноэлементных множеств достаточно. В случае с гориллами, однако, весьма вероятно, что клиенты могут захотеть создавать приложения, которые будут использовать несколько различных горилл одновременно. Чтобы обеспечить такое использование, объект класса не должен экспортировать интерфейс IApe , а вместо этого должен экспортировать новый интерфейс, который позволит клиентам создавать новых горилл и/или находить известных горилл по их имени. Это потребует от разработчика определить два класса C++: один для реализации объекта класса и другой для реализации действительных экземпляров класса. Для реализации гориллы класс C++, который определяет экземпляры гориллы, будет реализовывать интерфейс IApe:
class Gorilla : public IApe
{
public:
// Instances are heap-based, so delete when done
// копии размещены в куче, поэтому удаляем после выполнения
IMPLEMENTUNKNOWN()
BEGININTERFACETABLE()
IMPLEMENTSINTERFACE(IApe)
ENDINTERFACETABLE()
// IApe methods
// методы IApe
STDMETHODIMP EatBanana(void);
STDMETHODIMP SwingFromTree(void);
STDMETHODIMP getWeight(long *plbs):
};
Второй интерфейс понадобится для определения тех операций, которые будет реализовывать объект класса Gorilla:
[object, uuid(753A8AAC-A7FF-11d0-8C30-0080C73925BA)]
interface IApeClass : IUnknown
{
HRESULT CreateApe([out, retval] IApe **ppApe);
HRESULT GetApe([in] long nApeID, [out, retval] IApe **ppApe);
[propget]
HRESULT AverageWeight([out, retval] long *plbs);
}
Получив это определение интерфейса, объект класса будет реализовывать методы IApeClass или путем создания новых экземпляров С++-класса Gorilla (в случае CreateApe), или преобразованием произвольно выбранного имени объекта (в данном случае типа integer) в отдельный экземпляр (в случае GetApe):
class GorillaClass : public IApeClass
{
public: IMPLEMENTUNKNOWNNODELETE(GorillaClass)
BEGININTERFACETABLE(GorillaClass)
IMPLEMENTSINTERFACE(IApeClass)
ENDINTERFACETABLE()
STDMETHODIMP CreateApe(Ape **ppApe)
{
if ((*ppApe = new Gorilla) == 0) return EOUTOFMEMORY;
(*ppApe)->AddRef();
return SOK;
}
STDMETHODIMP GetApe(long nApeID, IApe **ppApe)
{
// assume that a table of well-known gorillas is
// being maintained somewhere else
// допустим, что таблица для известных горилл
// поддерживается где-нибудь еще
extern Gorilla *grgWellKnownGorillas[];
extern int gnMaxGorillas;
// assert that nApeID is a valid index
// объявляем, что nApeID – допустимый индекс
*ррАре = 0;
if (nApeID > gnMaxGorillas || nApeID < 0) return EINVALIDARG;
// assume that the ID is simply the index into the table
// допустим, что ID – просто индекс в таблице
if ((*ppApe = grgWellKnownGorillas[nApeID]) == 0) return EINVALIDARG;
(*ppApe)->AddRef();
return SOK;
}
STDMETHODIMP getAverageWeight(long *plbs)
{
extern *grgWellKnownGorillas[];
extern int gnMaxGorillas;
*plbs = 0;
long lbs;
for (int i = 0; i < gnMaxGorillas; i++)
{
grgWellKnownGorillas[i]->getWeight(&lbs);
*plbs += lbs;
}
// assumes gnMaxGorillas is non-zero
// предполагается, что gnMaxGorillas ненулевой
*plbs /= gnMaxGorillas;
return SOK;
}
};
Отметим, что в этом коде предполагается, что внешняя таблица известных горилл уже поддерживается – или самими копиями Gorilla, или каким-нибудь другим посредником (agent).