//*******************************************************************
//*******************************************************************
//***  REUSE in COM - Aggregation Demo
//***  This COM server will make use of the inner object via
//***  Aggregation!!!
//*******************************************************************
//*******************************************************************

//-------------------------------------------------------------------
// Required macros
//-------------------------------------------------------------------
#define UNICODE         // UNICODE
#define _WIN32_DCOM     // DCOM

//-------------------------------------------------------------------
// includes
//-------------------------------------------------------------------
#include <assert.h>
#include <stdio.h>
#include <windows.h>
#include <initguid.h>
#include "..\..\..\idl\ocr.h"   // the reused intefaces
#include "..\idl\aggregate.h"   // the interface we're implementing

//-------------------------------------------------------------------
// global event telling use wen to shutdown our application
//-------------------------------------------------------------------
HANDLE g_hExitEvent;

//-------------------------------------------------------------------
// Component Level Reference count for the lifetime managment of 
// the whole component.
// These specific implementations are used for out-of-process servers
// There's a pair of functions like these that are used for
// in-process servers.
//-------------------------------------------------------------------
inline ULONG ComponentAddRef()
{
   ULONG ul = CoAddRefServerProcess();
   wprintf(TEXT("ComponentAddRef(%ld)\n"), ul);
   return ul ;
}

inline ULONG ComponentRelease()
{
   ULONG ul = CoReleaseServerProcess();

   // wait 3 second to test for REGCLS_MULTIPLEUSE
   // Sleep(3000);

   wprintf(TEXT("ComponentRelease(%ld)\n"), ul);
   if (ul==0) {
      SetEvent(g_hExitEvent);
   }
   return ul ;
}


//*******************************************************************
//*******************************************************************
//***  UTILITY FUNCTIONS
//*******************************************************************
//*******************************************************************
void DisplayStatus(wchar_t *pwszMsg, HRESULT hr)
{

   if (hr == S_OK) {
      wprintf(TEXT("%s\n"), pwszMsg);
      return;
   }

   if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS) {
      hr = HRESULT_CODE(hr);
   }

   wchar_t *pwszStatus;
   FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
       FORMAT_MESSAGE_FROM_SYSTEM,
       NULL,
       hr,
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
       (LPWSTR)&pwszStatus,
       0,
       NULL );

   wprintf(TEXT("%s: %s (ECode: %lx)\n"), pwszMsg, pwszStatus, hr);

   LocalFree(pwszStatus);
}

//-------------------------------------------------------------------
// Given a unicode string, get the number of bytes needed to host it
//-------------------------------------------------------------------
inline long ByteLen(wchar_t *pwsz) 
{
   return (sizeof(wchar_t)*(wcslen(pwsz)+1));
}

//-------------------------------------------------------------------
// Automatic registration; for out-of-process server support the
// -regserver option
//-------------------------------------------------------------------
void RegisterComponent()
{
   wchar_t wszKey[MAX_PATH];
   wchar_t wszValue[MAX_PATH];
   HKEY hKey = 0;

   // HKEY_CLASSES_ROOT\CLSID\{A0F3554A-C5C1-11d1-9150-006008052F2D}
   //  @="Thesaurus"
   wcscpy(wszKey, TEXT("CLSID\\{A0F3554A-C5C1-11d1-9150-006008052F2D}"));
   RegCreateKey(HKEY_CLASSES_ROOT, wszKey, &hKey);
   wcscpy(wszValue, TEXT("Thesaurus"));
   RegSetValueEx(hKey, 0, 0, REG_SZ, (BYTE*)wszValue, ByteLen(wszValue));

   //  "AppID"="{63B53C11-C46B-11d1-83B4-006008CDD9AE}"
   wcscpy(wszValue, TEXT("{63B53C11-C46B-11d1-83B4-006008CDD9AE}"));
   RegSetValueEx(hKey, TEXT("AppID"), 0, REG_SZ, 
                 (BYTE*)wszValue, ByteLen(wszValue));
   RegCloseKey(hKey);

   // HKEY_CLASSES_ROOT\CLSID\{A0F3554A-C5C1-11d1-9150-006008052F2D}\LocalServer32
   //      @="...path...\aggregate.exe"
   wcscpy(wszKey, TEXT("CLSID\\{A0F3554A-C5C1-11d1-9150-006008052F2D}\\")
          TEXT("LocalServer32"));
   RegCreateKey(HKEY_CLASSES_ROOT, wszKey, &hKey);
   GetModuleFileName(0, wszValue, MAX_PATH);
   RegSetValueEx(hKey, 0, 0, REG_SZ, (BYTE*)wszValue, ByteLen(wszValue));
   RegCloseKey(hKey);

   // HKEY_CLASSES_ROOT\AppID\aggregate.exe
   //      "AppID"="{63B53C11-C46B-11d1-83B4-006008CDD9AE}"
   wcscpy(wszKey, TEXT("AppID\\aggregate.exe"));
   RegCreateKey(HKEY_CLASSES_ROOT, wszKey, &hKey);
   wcscpy(wszValue, TEXT("{63B53C11-C46B-11d1-83B4-006008CDD9AE}"));
   RegSetValueEx(hKey, TEXT("AppID"), 0, REG_SZ, 
                 (BYTE*)wszValue, ByteLen(wszValue));
   RegCloseKey(hKey);

   // HKEY_CLASSES_ROOT\AppID\{63B53C11-C46B-11d1-83B4-006008CDD9AE}
   //      @="Thesaurus"
   wcscpy(wszKey, TEXT("AppID\\{63B53C11-C46B-11d1-83B4-006008CDD9AE}"));
   RegCreateKey(HKEY_CLASSES_ROOT, wszKey, &hKey);
   wcscpy(wszValue, TEXT("Thesaurus"));
   RegSetValueEx(hKey, 0, 0, REG_SZ, 
                 (BYTE*)wszValue, ByteLen(wszValue));
   RegCloseKey(hKey);
}

//-------------------------------------------------------------------
// Automatic unregistration; for out-of-process server support the
// -unregserver option
//-------------------------------------------------------------------
void UnregisterComponent()
{   
   long lRc = 0 ;
   lRc = RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("CLSID\\") 
            TEXT("{A0F3554A-C5C1-11d1-9150-006008052F2D}\\") 
            TEXT("LocalServer32"));
   DisplayStatus(TEXT("Unregistered LocalServer32"), lRc);
   lRc = RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("CLSID\\") 
            TEXT("{A0F3554A-C5C1-11d1-9150-006008052F2D}"));
   DisplayStatus(TEXT("Unregistered CLSID"), lRc);
   lRc = RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AppID\\") 
            TEXT("{63B53C11-C46B-11d1-83B4-006008CDD9AE}"));
   DisplayStatus(TEXT("Unregistered AppID"), lRc);
   lRc = RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AppID\\aggregate.exe"));
   DisplayStatus(TEXT("Unregistered aggregate.exe"), lRc);
}
 
//*******************************************************************
//*******************************************************************
//***  AGGREGATION...
//***  Class CoThesaurus - A component object that implements just
//***  one interface:  IThesaurus.
//***  We are using aggregation to reuse the functionality of ISpell
//***  and IOcr that's been implemented by the inner object.
//***  If we happen to detect request for IOcr or ISpell, we 
//***  will forward these calls to the inner object, thus reusing
//***  already implemented functionality.
//*******************************************************************
//*******************************************************************
class CoThesaurus : public IThesaurus {
public:
   // constructors/destructors
   // Code to support aggregation
   CoThesaurus() : m_lRefCount(0), m_pUnkAggregate(0)
   { 
      ComponentAddRef(); 
      // creating the inner, reused object
      CreateInnerObject();
   }

   // Code to support aggregation
   ~CoThesaurus() 
   { 
       if (m_pUnkAggregate) { m_pUnkAggregate->Release(); }
       ComponentRelease(); 
   }

   // The factory will call this method to actually 
   // create this object
   static HRESULT CreateObject(LPUNKNOWN pUnkOuter, REFIID riid, 
                                void** ppv);
public: 
   // IUnknown Methods
   STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
   STDMETHODIMP_(ULONG) AddRef(void)
   { return InterlockedIncrement(&m_lRefCount); }
   STDMETHODIMP_(ULONG) Release(void)
   {
      long lCount = InterlockedDecrement(&m_lRefCount);
      if (lCount == 0) { delete this; } 
      return lCount; 
   }

   // IThesaurus
   STDMETHODIMP LookUp() 
   { wprintf(TEXT("Lookup success...\n")); return S_OK; }

private:
   // function to create the inner object for reuse
   void CreateInnerObject();

private:
   LONG m_lRefCount;
   // support aggregation...
   // the unknown of the object we're aggregating
   // must be IUnknown for aggregation...
   IUnknown *m_pUnkAggregate;  
};

//-------------------------------------------------------------------
// Creating the inner object for reuse...
// CoMyEngine::CreateInnerObject
//-------------------------------------------------------------------
void CoThesaurus::CreateInnerObject()
{
   // support aggregation...
   // The aggregation law of COM require us to create
   // the inner object, and requesting only for IUnknown
   // and nothing else!!!
   MULTI_QI mqi[] = { {&IID_IUnknown, NULL, S_OK} };

   // Notice "this" as the second argument... 
   // reuse by aggregation!!!
   // Outer unknown is this object--uses the inner object.
   // This argument is the magic that get's our 
   // object (CLSID_Thesaurus) to reuse the 
   // inner object (CLSID_OcrEngine)

   // Special support here in case the inner object
   // code happened to cause a delete of this object.
   // Remember at this moment, the reference count
   // that we are keeping for this object is 0, since
   // we're still in the constructor.
   // Here's a potential probelm:  The inner object
   // QIs for one of our (this object's) interfaces
   // (refcount=1) and releases it (refcount=0),
   // causing a premature delete of this object.
   // To prevent this, we temporarily increment and decrement our
   // reference count.  However, don't call Release, since
   // it deletes our object.  You'd have to use this technique:
   InterlockedIncrement(&m_lRefCount);
   HRESULT hr = CoCreateInstanceEx(CLSID_OcrEngine, 
        this, 
        CLSCTX_SERVER, 
        NULL,
        sizeof(mqi)/sizeof(mqi[0]), 
        mqi);
   InterlockedDecrement(&m_lRefCount);

   if (SUCCEEDED(hr) && SUCCEEDED(mqi[0].hr)) {
      m_pUnkAggregate = reinterpret_cast<IUnknown *>(mqi[0].pItf);
   } else {
      DisplayStatus(TEXT("Failed to create inner object..."), S_OK);
      wprintf(TEXT("You need to use the Aggregatable")
                TEXT("Version of CLSID_OcrEngine\n")
                TEXT("It lives in code\\basics\\reuse\\inner\n")
                TEXT("Version of CLSID_OcrEngine\n"));
      assert(false);
   }
}
 

//-------------------------------------------------------------------
// CoThesaurus::CreateObject - static function to create an
// CoThesaurus object
//-------------------------------------------------------------------
HRESULT CoThesaurus::CreateObject(LPUNKNOWN pUnkOuter, 
                                  REFIID riid, 
                                  void** ppv)
{
   *ppv = NULL;

   // CoThesaurus doesn't support aggregation
   if (pUnkOuter != NULL) { return CLASS_E_NOAGGREGATION; }

   CoThesaurus * pThesaurus = new CoThesaurus;
   if (pThesaurus == NULL) { return E_OUTOFMEMORY; }

   HRESULT hr = pThesaurus->QueryInterface(riid, ppv);
   if (FAILED(hr)) { delete pThesaurus; }

   return hr;
}

//-------------------------------------------------------------------
// CoThesaurus::QueryInterface
//-------------------------------------------------------------------
STDMETHODIMP
CoThesaurus::QueryInterface(REFIID riid, void** ppv)
{
   if (ppv==NULL) { return E_INVALIDARG; }

   if (riid==IID_IUnknown) {
      *ppv= static_cast<IThesaurus *>(this);
   } else if (riid==IID_IThesaurus) {
      *ppv= static_cast<IThesaurus *>(this);
   }  else if (riid==IID_IOcr||riid==IID_ISpell) {
      // Support Aggregation...
      // interfaces belong to the reused object
      // let the inner object handle this request!
      return m_pUnkAggregate->QueryInterface(riid, ppv) ;
   } else {
      *ppv=NULL; return E_NOINTERFACE ;
   }

   reinterpret_cast<IUnknown *>(*ppv)->AddRef();

   return S_OK;
}

//*******************************************************************
//*******************************************************************
//***  Class Factory: manufacturing CoThesaurus objects
//*******************************************************************
//*******************************************************************
class CoThesaurusFactory : public IClassFactory 
{
public:
   // IUnknown Methods
   STDMETHODIMP QueryInterface (REFIID riid, void** ppv);
   STDMETHODIMP_(ULONG) AddRef(void)
   { return 1; }
   STDMETHODIMP_(ULONG) Release(void) 
   { return 1; }

   // IClassFactory Methods
   STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, 
                               REFIID riid, 
                               void **ppv)
  {
     // call CoThesaurus's static function to create
     // a CoThesaurus component object
     return CoThesaurus::CreateObject(pUnkOuter, riid, ppv);
  }

   STDMETHODIMP LockServer(BOOL fLock)
   {
      if (fLock) {
         ComponentAddRef();
      } else {
         ComponentRelease();
      }
      return S_OK;        
   }   
};

//-------------------------------------------------------------------
//  Singleton factory instance that manufacture CoCorEngine
//  component objects
//-------------------------------------------------------------------
CoThesaurusFactory g_ThesaurusClassFactory;

//-------------------------------------------------------------------
//  CoThesaurusFactory::QueryInterface
//-------------------------------------------------------------------
STDMETHODIMP 
CoThesaurusFactory::QueryInterface(REFIID riid, void** ppv)
{
   if (ppv==NULL) { return E_INVALIDARG; }

   if (riid==IID_IUnknown) {
      *ppv= static_cast<IClassFactory *>(this);
   } else if (riid==IID_IClassFactory) {
      *ppv= static_cast<IClassFactory *>(this);
   } else {
      *ppv=NULL; return E_NOINTERFACE ;
   }

   reinterpret_cast<IUnknown *>(*ppv)->AddRef();

   return S_OK;
}

//*******************************************************************
//*******************************************************************
//***  Main Program
//*******************************************************************
//*******************************************************************
void main(int argc, char **argv)
{
   DisplayStatus(TEXT("Server: Started"), S_OK);

   g_hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
   assert(g_hExitEvent);

   // registration if required
   if (argc > 1) {
      if (_stricmp(argv[1], "-RegServer")==0){
         RegisterComponent();
         DisplayStatus(TEXT("Registered..."), S_OK);
         return ;
      }
      if (_stricmp(argv[1], "-UnRegServer")==0){
         UnregisterComponent();
         DisplayStatus(TEXT("Unregistered..."), S_OK);
         return ;
      }
   }

   // Init COM
   HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
   assert(SUCCEEDED(hr));

   // Register Thesaurus Class Factory with COM
   DWORD dwRegister;
   hr = CoRegisterClassObject(CLSID_Thesaurus, 
                              &g_ThesaurusClassFactory,
                              CLSCTX_SERVER, 
                              REGCLS_MULTIPLEUSE, 
                              &dwRegister);
   assert(SUCCEEDED(hr));
   // we don't have to do this, but it's a good habit
   g_ThesaurusClassFactory.Release();  

   // sit and wait until CoThesaurus object is used
   WaitForSingleObject(g_hExitEvent, INFINITE);

   // revoke the factory from public view
   CoRevokeClassObject(dwRegister);

   // uninitialize
   CoUninitialize();

   DisplayStatus(TEXT("Server shutting down in 5 seconds..."), S_OK);

   Sleep(5000);
}