Применение в играх аналогии COM объектов.
Автор: Дмитрий Синягин
Бредисловие.
Добрый день!
Сегодня я поведаю о том, как создавать и использовать COM объекты без каких-либо инородных извращений, типа MFC или ATL. Многие, наверное, видели код, создаваемый Visual Studio App Wizard'ом, признаюсь, для неподготовленного человека - вещь просто ужасная, в какой-то мере даже опасная и без документации разобраться в ней очень проблематично (хотя и можно). А что, если уж очень хочется сделать движок с тучей фич и на плагиновой системе? Если хочется иметь 10 библиотек рендеринга и 5 скриптовых интерфейсов, и чтоб все было просто использовать. Вот тут и нужен COM.
Небольшое введение в технологию (для совсем незнающих).
Что есть COM, COM - это Component Object Model :)
На самом деле, это просто технология, позволяющая хранить реализацию какого-либо класса изолированно от того места, где он используется. То есть у вас есть UID (Уникальный идентификатор) класса и интерфейс, который описывает возможности класса, и технология COM позволяет пользоваться этим классом посредством интерфейса. Приведу пример псевдо-СОМ технологии:
У вас есть класс:
class Simple { public: Simple(); ~Simple( ); VOID print_value( ); VOID put_value( int val); private: int value; };
что мы можем сделать с ним для вторичного использования? Просто поместить этот класс в dll-файл и брать его реализацию из dll-ки, но для этого нам нужен интерфейс.
struct ISimple { virtual VOID print_value() = 0; virtual VOID put_value( int val) = 0; };
А теперь экспортируем из dll-файла функцию CreateSimple()
HRESULT CreateSimple(ISimple ** pInterface) { if( !*pInterface) { *pInterface= new Simple; return S_OK; } return E_FAIL; }
После этого можно использовать класс Simple по средствам интерфейса - то есть
struct ISimple { virtual VOID print_value() = 0; virtual VOID put_value( int val) = 0; }; void main( ) { ISimple* simple; CreateSimple( &simple); simple->put_value( 10); simple->print_value( ); }
Все, на этом и базируется технология COM.
Шаблоны и т.д.
Рассмотрим, что нужно, чтобы создать свой COM объект (мы рассматриваем только DLL сервера). Во-первых, нам нужно разработать интерфейс. К примеру, если нам нужен интерфейс для визуализации, то он должен включать в себя независимые функции (не зависящие от используемых библиотек), пусть у нас будет 2 объекта визуализации, первый - Direct3D, второй - OpenGL. Какой мы получим интерфейс:
interface IRenderer : IUnknown { virtual HRESULT Initialize(INT xSize, INT ySize) = 0; virtual HRESULT Destroy( ) = 0; virtual HRESULT DrawPolygon( Vertex *v, INT numVertex); };
Теперь нам нужно создать класс, реализующий методы интерфейса.
class D3DRenderer : public IRenderer { public: // Методы интерфейса IUnknown virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall AddRef( ); virtual ULONG __stdcall Release( ); // Конструктор D3DRenderer( ); // Деструктор virtual ~D3DRenderer( ); // Методы интерфейса HRESULT Initialize( INT xSize, INT ySize); HRESULT Destroy( ); HRESULT DrawPolygon( Vertex *v, INT numVertex); private: // Счетчик ссылок long m_cRef; // Свойства IDirect3DDevice7 * lpd3dDev; }; HRESULT D3DRenderer::Initialize( INT xSize, INT ySize) { //... } HRESULT D3DRenderer::Destroy( ) { //... } HRESULT D3DRenderer::DrawPolygon( Vertex *v, INT numVertex) { //... }
Далее нам нужно как-то идентифицировать интерфейс и класс, для этого нам нужно создать IID(Interface ID) и CLSID(Class ID), проделаем это с помощью программы guidgen.exe. Получим идентификаторы:
// {C9D2DCA0-C94A-11d5-8F91-506952C17C0B} static const IID IID_Renderer = {0xc9d2dca0, 0xc94a, 0x11d5, {0x8f, 0x91, 0x50, 0x69, 0x52, 0xc1, 0x7c, 0xb}}; // {C9D2DCA1-C94A-11d5-8F91-506952C17C0B} static const CLSID CLSID_D3DRenderer = {0xc9d2dca1, 0xc94a, 0x11d5, {0x8f, 0x91, 0x50, 0x69, 0x52, 0xc1, 0x7c, 0xb}};
Теперь нам нужно написать сам сервер. Вот его код:
// Include file needed for COM handling #include <objbase.h> // Обьявление интерфейса и идентификаторов #include "irenderer.h" // Обьявление класса #include "d3drenderer.h" // Утилиты регистрации COM обьекта #include "Registry.h" /////////////////////////////////////////////////////////// // // Глобальные переменные // static HMODULE g_hModule = NULL; // Идентификатор DLL static long g_cComponents = 0; // Количество активных компонентов static long g_cServerLocks = 0; // Количество локов сервера // Имя компонента const char g_szFriendlyName[] = "Рендеринг Через Директ3Д" ; // Версия-незавизимый ProgID const char g_szVerIndProgID[] = "Пример.Renderer" ; // ProgID const char g_szProgID[] = "Пример.Renderer.1" ; // // Конструктор // D3DRenderer::D3DRenderer() : m_cRef( 1) { // Увеличиваем количество активных компонентов InterlockedIncrement( &g_cComponents) ; } // // Деструктор // D3DRenderer::~D3DRenderer( ) { Destroy( ); // Уменьшаем количество активных компонентов InterlockedDecrement( &g_cComponents) ; } // // реализация IUnknown // HRESULT __stdcall D3DRenderer::QueryInterface( const IID& iid, void** ppv) { // Возвращение интерфейса if ( iid == IID_IUnknown) { *ppv = static_cast<IRenderer*>( this) ; } else if ( iid == IID_D3DRenderer) { *ppv = static_cast<IRenderer*>( this) ; } else { *ppv = NULL ; return E_NOINTERFACE ; } reinterpret_cast<IUnknown*>( *ppv)->AddRef( ) ; return S_OK ; } ULONG __stdcall D3DRenderer::AddRef( ) { return InterlockedIncrement( &m_cRef) ; } ULONG __stdcall D3DRenderer::Release( ) { if ( InterlockedDecrement( &m_cRef) == 0) { delete this ; return 0 ; } return m_cRef ; } /////////////////////////////////////////////////////////// // // Фабрика классов // class CFactory : public IClassFactory { public: // IUnknown virtual HRESULT __stdcall QueryInterface( const IID& iid, void** ppv); virtual ULONG __stdcall AddRef( ) ; virtual ULONG __stdcall Release( ) ; // Интерфейс IClassFactory virtual HRESULT __stdcall CreateInstance( IUnknown* pUnknownOuter, const IID& iid, void** ppv) ; virtual HRESULT __stdcall LockServer( BOOL bLock) ; // Конструктор CFactory( ) : m_cRef( 1) {} // Деструктор ~CFactory( ) {} private: long m_cRef ; } ; // // реализация IUnknown // HRESULT __stdcall CFactory::QueryInterface( const IID& iid, void** ppv) { if ( ( iid == IID_IUnknown) || ( iid == IID_IClassFactory)) { *ppv = static_cast<IClassFactory*>( this) ; } else { *ppv = NULL ; return E_NOINTERFACE ; } reinterpret_cast<IUnknown*>( *ppv)->AddRef( ) ; return S_OK ; } ULONG __stdcall CFactory::AddRef( ) { return InterlockedIncrement( &m_cRef) ; } ULONG __stdcall CFactory::Release( ) { if ( InterlockedDecrement( &m_cRef) == 0) { delete this ; return 0 ; } return m_cRef ; } // // реализация IClassFactory // HRESULT __stdcall CFactory::CreateInstance( IUnknown* pUnknownOuter, const IID& iid, void** ppv) { if ( pUnknownOuter != NULL) { return CLASS_E_NOAGGREGATION ; } // Создаем компонент D3DRenderer* pA = new D3DRenderer ; if ( pA == NULL) { return E_OUTOFMEMORY ; } // Запрашиваем интерфейс HRESULT hr = pA->QueryInterface( iid, ppv) ; // освобождаем IUnknown указатль. // (если QueryInterface завершится неудачно, компонент удалит сам себя.) pA->Release( ) ; return hr ; } // LockServer HRESULT __stdcall CFactory::LockServer( BOOL bLock) { if ( bLock) { InterlockedIncrement( &g_cServerLocks) ; } else { InterlockedDecrement( &g_cServerLocks) ; } return S_OK ; } /////////////////////////////////////////////////////////// // // Экспортируемые функции // // // Можно ли сейчас выгрузить dll? // STDAPI DllCanUnloadNow( ) { if ( ( g_cComponents == 0) && ( g_cServerLocks == 0)) { return S_OK ; } else { return S_FALSE ; } } // // Получить фабрику классов // STDAPI DllGetClassObject( const CLSID& clsid, const IID& iid, void** ppv) { // Можем мы создать такой компонент? if ( clsid != CLSID_D3DRenderer) { return CLASS_E_CLASSNOTAVAILABLE ; } // Создаем фабрику классов CFactory* pFactory = new CFactory ; if ( pFactory == NULL) { return E_OUTOFMEMORY ; } // Запрашиваем интерфейс HRESULT hr = pFactory->QueryInterface( iid, ppv) ; pFactory->Release( ) ; return hr ; } // // Регистрируем сервер // STDAPI DllRegisterServer( ) { return RegisterServer( g_hModule, CLSID_D3DRenderer, g_szFriendlyName, g_szVerIndProgID, g_szProgID) ; } // // Удаляем сервер // STDAPI DllUnregisterServer( ) { return UnregisterServer( CLSID_D3DRenderer, g_szVerIndProgID, g_szProgID) ; } /////////////////////////////////////////////////////////// // // DLL entry point // BOOL APIENTRY DllMain( HANDLE hModule, DWORD dwReason, void* lpReserved) { if ( dwReason == DLL_PROCESS_ATTACH) { g_hModule = ( struct HINSTANCE__ *)hModule ; } return TRUE ; }
Теперь мы получили свой COM объект, который можем использовать из любой программы.
Вот пример использования этого объекта:
#include "irenderer.h" #define GetMyInterface(__c,__i, x) ::CoCreateInstance \ ( __c, NULL, CLSCTX_INPROC_SERVER, __i, ( void**)&x) void main( ) { // Инициализируем OLE CoInitialize( NULL); IRenderer* render; GetMyInterface( CLSID_D3DRenderer, IID_Renderer, render); render->Initialize( 320, 200); render->DrawTriangle( ...); render->Release( ); }
Это все. Шаблоны COM объекта можно взять ТУТ.
26 октября 2001
Комментарии [2]