ПрограммированиеСтатьиОбщее

Применение в играх аналогии 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 объекта можно взять ТУТ.

#COM

26 октября 2001

Комментарии [2]