プログラミング

C++で作るCOMサーバ

2005年12月20日

 .NET を用いればregasm.exeでCOMサーバを簡単に登録することが出来るのだが、やはり.NETは重い。加えて、.NET Framework がインストールされていないコンピュータの割合はそれほど低くないと思われる。
 他方で、簡単な構造のCOMサーバなら、C++を用いてWin32プログラムとして製作すればよい。この場合、動作は軽いし汎用性もある。ただし、複雑なプログラムを組むのはやはり骨が折れるのだが…。
 今回C++を用いたCOMサーバを製作することになったので、その方法をここにまとめておくことにした。

 COMサーバの基本となるインターフェース部分を一つのcppファイルにした。以下の通りである。このファイルはいっさい変更することなく用いることが出来る。C++プロジェクトにインクルードすればよい。

comserver.cppの内容
#include <windows.h>
#include <shlobj.h>
#include "comserver.h"

//Standard COM server
//See "comserver.h" for usage

// CServer class holds global object count
class CServer
{
public:
  CServer() : m_hInstance(NULL), m_dwRef(0)
  {}
  HINSTANCE m_hInstance;
  DWORD m_dwRef;
};
CServer m_Server;

// Template class that provides basic IUnknown implementation
template <class T, const IID* piid>
class CInterface : public T
{
public:
  CInterface() : m_dwRef(0)
  { m_Server.m_dwRef++; }
  virtual ~CInterface()
  { m_Server.m_dwRef--; }

  STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject)
  {
    if ((riid == IID_IUnknown) || (riid == *piid))
    {
      *ppvObject = (T*)static_cast<T*>(this);
      m_dwRef++;
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  STDMETHOD_(ULONG,AddRef)()
  { return ++m_dwRef; }
  STDMETHOD_(ULONG,Release)()
  {
    if (!(--m_dwRef))
    {
      delete this;
      return 0;
    }
    return m_dwRef;
  }
  DWORD m_dwRef;
};

// The COM class
class ComClass : public CInterface<IDispatch,&IID_IDispatch>
{
// IDispatch interface implementation
public:
    // These methods not implemented
    STDMETHOD(GetTypeInfoCount)(UINT* pctinfo)
    { return E_NOTIMPL;  }
    STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo**)
    { return E_NOTIMPL;  }

    //Method definition
    STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR* rgszNames,
               UINT cNames, LCID, DISPID* rgDispId)
    {
      return COM_GetIDsOfNames(rgszNames, cNames, rgDispId, data);
    }

    //Method Invoke
    STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
            DISPPARAMS* pDispParams, VARIANT* pVarResult,
            EXCEPINFO* pExcepInfo, UINT *puArgErr)
    {
      return COM_Invoke(dispIdMember, riid, lcid, wFlags,
         pDispParams, pVarResult, pExcepInfo, puArgErr, data);
    }
    ComClass(){
        data=new COMDATA;
    }
    ~ComClass(){
        delete data;
    }
    COMDATA* data;
};

// Class factory to create COM objects
class CClassFactory :
  public CInterface<IClassFactory,&IID_IClassFactory>
{
public:
// IClassFactory interface implementation
  STDMETHOD(CreateInstance)(IUnknown* pUnkOuter, REFIID riid,
                void** ppvObject)
  {
    if (pUnkOuter)
      return CLASS_E_NOAGGREGATION;
    ComClass* pObject = new ComClass;
    HRESULT hr = pObject->QueryInterface(riid,ppvObject);
    if (FAILED(hr))
      delete pObject;
    return hr;
  }
  STDMETHOD(LockServer)(BOOL fLock)
  { return CoLockObjectExternal(this,fLock,TRUE); }
};

// DllMain
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    m_Server.m_hInstance = hInstance;
    DisableThreadLibraryCalls(hInstance);
  }
  return TRUE;
}

// Required COM in-proc server exports
STDAPI DllUnregisterServer(void)
{
    HRESULT hr = S_OK;
    HKEY key = NULL;
    if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,CLSIDKEY,0,NULL,REG_OPTION_NON_VOLATILE,
                        KEY_ALL_ACCESS,NULL,&key,NULL))
    {
        char szModulePath[_MAX_PATH];
        GetModuleFileName(m_Server.m_hInstance,szModulePath,_MAX_PATH);
        if(!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)szModulePath,
                          strlen(szModulePath)+1))
        {
          if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,PRODKEY,0,NULL,REG_OPTION_NON_VOLATILE,
                            KEY_ALL_ACCESS,NULL,&key,NULL))
          {
            if (RegDeleteKey(HKEY_CLASSES_ROOT,PRODIDKEY)) hr = E_FAIL;
            if (RegDeleteKey(HKEY_CLASSES_ROOT,PRODKEY)) hr = E_FAIL;
          }
          RegCloseKey(key);
        }
        if (RegDeleteKey(HKEY_CLASSES_ROOT,CLASSKEY)) hr = E_FAIL;
        if (RegDeleteKey(HKEY_CLASSES_ROOT,CLSIDKEY)) hr = E_FAIL;
    }
    RegCloseKey(key);
    return hr;
}

STDAPI DllRegisterServer(void)
{
    HRESULT hr = E_FAIL;
    HKEY key = NULL;
    if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,CLASSKEY,0,NULL,REG_OPTION_NON_VOLATILE,
                        KEY_ALL_ACCESS,NULL,&key,NULL))
    {
        char szModulePath[_MAX_PATH];
        GetModuleFileName(m_Server.m_hInstance,szModulePath,_MAX_PATH);
        if(!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)szModulePath,
                          strlen(szModulePath)+1))
        {
          RegCloseKey(key);
          if (!RegCreateKeyEx(HKEY_CLASSES_ROOT,PRODIDKEY,0,NULL,REG_OPTION_NON_VOLATILE,
                            KEY_ALL_ACCESS,NULL,&key,NULL))
          {
            if (!RegSetValueEx(key,NULL,0,REG_SZ,(const unsigned char*)CLSIDVAL,
                          strlen(CLSIDVAL)+1))
              hr = S_OK;
          }
        }
    }
    RegCloseKey(key);
    return hr;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
  HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
  if (rclsid == CLSID_COM)
  {
    CClassFactory* pFactory = new CClassFactory;
    if (FAILED(hr = pFactory->QueryInterface(riid,ppv)))
      delete pFactory;
    hr = S_OK;
  }
  return hr;
}

STDAPI DllCanUnloadNow()
{ return (m_Server.m_dwRef) ? S_FALSE : S_OK; }

 最低限必要な4つの関数DllRegisterServer, DllUnregisterServer, DllGetClassObject, DllCanUnloadNowはここに含まれている。ソース半ばにあるComClass クラスからグローバル関数COM_GetIDsOfNamesとCOM_Invokeを呼び出す形になっているので、この2つのグローバル関数を独自に製作することになる。次に、ヘッダファイルを見てみる。

comserver.hの内容
// {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
static const GUID CLSID_COM =
{ 0xXXXXXXXX, 0xXXXX, 0xXXXX, { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX} };
static const LPCSTR CLSIDVAL = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}";
static const LPCSTR CLSIDKEY = "CLSID\\{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}";
static const LPCSTR CLASSKEY = "CLSID\\{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\\InProcServer32";
static const LPCSTR PRODKEY   = "YourComName";
static const LPCSTR PRODIDKEY = "YourComName\\CLSID";

class COMDATA
{
  //This class is used for storing the data of each COM object.
};

DWORD COM_GetIDsOfNames(LPOLESTR* rgszNames, UINT cNames, DISPID* rgDispId,COMDATA *data);

DWORD COM_Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
            DISPPARAMS* pDispParams, VARIANT* pVarResult,
            EXCEPINFO* pExcepInfo, UINT *puArgErr,COMDATA *data);

 {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}は製作するCOMサーバを認識するGUIDである。GUIDGen.exeで製作した値をペーストして用いる。 "YourComName" には、製作するCOMサーバの名称を指定する。これは、例えばvbscriptでは、『Set xx=CreateObject("YourComName")』のように用いる物である。すでに登録されている・或いは一般に用いられている他の名称と重ならないように命名しなければならない。また、各COMオブジェクトで保存されるデータを格納するためのCOMDATAクラスの定義をここに書いておく。

 続けて、独自のコードを次のように書いてプロジェクトにインクルードする。

yourserver.cppの内容
#include <windows.h>
#include "comserver.h"

//Method definition
DWORD COM_GetIDsOfNames(LPOLESTR* rgszNames, UINT cNames, DISPID* rgDispId,COMDATA *data)
{
  for (UINT i = 0; i < cNames; i++)
  {
    rgDispId[i] == DISPID_UNKNOWN;
    if (!lstrcmpiW(rgszNames[i],L"Method1"))            rgDispId[i]=1;
    else if (!lstrcmpiW(rgszNames[i],L"Method2")) rgDispId[i]=2;
    else if (!lstrcmpiW(rgszNames[i],L"Method3"))    rgDispId[i]=3;
    else return DISP_E_MEMBERNOTFOUND;
  }
  return S_OK;
}

//Method Invoke
DWORD COM_Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
            DISPPARAMS* pDispParams, VARIANT* pVarResult,
            EXCEPINFO* pExcepInfo, UINT *puArgErr,COMDATA *data)
{
  int cArgs = pDispParams->cArgs;
  VARIANTARG* rgvarg = pDispParams->rgvarg;
  switch (dispIdMember)
  {
  case 1: //Method1

      //rgvarg[i].vt:
      //                VT_UI1    byte, use rgvarg[i].bVal
      //                VT_I2     short, use rgvarg[i].iVal
      //                VT_I4     long, use rgvarg[i].lVal
      //                VT_BSTR   unicode, use rgvarg[i].bstrVal
      //                etc

      // when (rgvarg[i].vt & VT_BYREF)
      //rgvarg[i].pvarVal->vt:
      //                VT_I2     short, use rgvarg[i].pvarVal->iVal
      //                VT_I4     long, use rgvarg[i].pvarVal->lVal
      //                etc

      return S_OK; //if everything is all right.
      return DISP_E_BADPARAMCOUNT;  //if wrong parameter(s)

  case 2: //Method2

  case 3: //Method3

  default:return (DWORD)DISPID_UNKNOWN;
  }
}


 最後に、yourserver.defを次の様に書いて、プロジェクトにインクルードする。

LIBRARY      "yourserver.dll"

EXPORTS
    DllRegisterServer PRIVATE
    DllCanUnloadNow   PRIVATE
    DllGetClassObject PRIVATE
    DllUnregisterServer PRIVATE


 comserver.cpp, yourserver.cpp, yourserver.defの3つのファイルがプロジェクトに含まれていて、comserver.hが依存ファイルになっていることを確認する。 ビルドして『regsvr32.exe yourserver.dll』で登録すれば、使えるようになる。

コメント

コメントはありません

コメント送信