////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This is a part of the COM Serialization SDK VC++ Library.
//  
// Copyright 2009 BPM Microsystems
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//                                                                                                                    //
// $Header: //sesca/src/Winprog/COMSerializationSDK/VC++/HeaderLib/ComSerSdk/Detail/rcs/Module.h 1.6 2010/01/04 09:03:42 arturoc Exp $
//                                                                                                                    //
///////// 120 columns, Tab = Insert 3 Spaces ///////////////////////////////////////////////////////////////////////////

/**
   This header file defines the ATL library-required "module" class.
*/

#pragma once

#include <vector>
#include "Preproc.h"
#include "Exception.h"
#include "Debug.h"
#include "Detail\LastError.h"

namespace ComSerSdk
{
   namespace Detail
   {
      /**
         Forward-declared so that ComSerSdk_Detail_CSerializationModule can declare it as a friend.
      */
      class COMSERSDK_ESS_CPP_CLASS_NAME;

      /**
         Defined in SerialDataServerComObject.h, declared here to ensure it is visible to its client code here.
      */
      CString GetClassClsid();

   }

   /**
      Forward-declared so that ComSerSdk_Detail_CSerializationModule can declare it as a friend and refer to
      it by reference.
   */
   class CApplicationBase;

}//end namespace ComSerSdk

/**
   We are forced to use string-literals to specify GUIDs in the ATL atttibutes, below.  We also must specify these in
   the registration information.  The one way that we can define them once and only once and use them in all necessary
   locations is with macros, therefore we reluctantly resort to defining them with macros.

   This one is the CLSID of the ESS, which is specified in the SDK documentation, item 3.1 of "Appendix: ESS
   Requirements".
*/
#define COMSERSDK_ESS_CLSID "7A61987B-66D9-41c8-973B-846CC37FC1A5"

/**
   Unique CLSID used in the "module" attribute, therefore it also identifies the type library in the registry.
   BPWin does not require this to be a specific GUID, nor does it require a type library.
   However, the ATL library uses registered type libraries to invoke methods via IDispatch, which requires us to specify
   a GUID that identifies the type library.
*/
#define COMSERSDK_TLB_CLSID "CAACA545-BB06-4490-8611-2EA464E75D4F"

/**
   Compiler doesn't allow this class to be in a namespace, thus it has a name that emulates the namespace heirarchy
   we would like for it, especially since the user of the library should never refer to it (if we could, we would have
   it in the user-unreferenceable "Detail" namespace).

   This is the class that represents the "module", as required by the ATL library ("Module classes implement the basic
   functionality required by ATL" - MSDN).  The "exe" passed to the "module" attribute below results in this class
   inheriting from ATL's CAtlExeModuleT class, which "represents the module for an application (EXE) and contains code
   that supports creating an EXE, processing the command line, registering class objects, running the message loop, and
   cleaning up on exit." - MSDN.  See the documentation on CAtlExeModuleT in MSDN for more information.

   Via use of ATL attributes the compiler generates code that declares a global instance of this class.  That global
   instance is given the name "_AtlModule" and is used elsewhere in this library.

   We have given our concrete of CAtlExeModuleT the following additional responsibilites:

   - Customize the component registration otherwise done by default by ATL - see the PrepareRegistrar() member
     function for important information regarding what behavior is overriden.

   - Store a reference to the user's ComSerSdk::CApplicationBase-derived class.  ComSerSdk::CApplicationBase, upon
     construction, uses the _AtlModule instance to invoke our SetApp() method to register itself with us.  Then later,
     when the ESS COM object is created, we define it to retrieve that instance so that it may invoke the 
     ComSerSdk-library-user's CreateServer() override.
*/

[module(exe, 
#if !defined(COMSERSDK_USER_SPECIFIED_ESS_NAME)
        uuid=COMSERSDK_TLB_CLSID,
#endif
        name = COMSERSDK_LIBRARY_NAME_STRING)]

class COMSERSDK_ESS_MODULE_NAME
{
public:

   /**
      Constructor.  No heavy-lifting is done here.  Instead, most of our code is executed on registration via
      overriding CAtlExeModuleT's RegisterServer() and UnregisterServer() member functions.
   */
   COMSERSDK_ESS_MODULE_NAME():
      m_poApp(NULL),
      m_sObjRegScript
      (
         "%ROOT%"                                                                    "\n"
         "{"                                                                         "\n"
         "   NoRemove Software"                                                      "\n"
         "   {"                                                                      "\n"
         "      NoRemove Classes"                                                    "\n"
         "      {"                                                                   "\n"
         "         " COMSERSDK_ESS_PROGID ".1 = s '" COMSERSDK_ESS_NAME_STRING " Object'""\n"
         "         {"                                                                "\n"
         "            CLSID = s '%ESS_CLSID%'"                                       "\n"
         "         }"                                                                "\n"
         "         " COMSERSDK_ESS_PROGID " = s '" COMSERSDK_ESS_NAME_STRING " Object'"  "\n"
         "         {"                                                                "\n"
         "            CLSID  = s '%ESS_CLSID%'"                                      "\n"
         "            CurVer = s '" COMSERSDK_ESS_PROGID ".1'"                         "\n"
         "         }"                                                                "\n"
         "         NoRemove CLSID"                                                   "\n"
         "         {"                                                                "\n"
         "            ForceRemove %ESS_CLSID% = s '" COMSERSDK_ESS_NAME_STRING " Object'\n"
         "            {"                                                             "\n"
         "               ProgID = s '" COMSERSDK_ESS_PROGID ".1'"                      "\n"
         "               VersionIndependentProgID = s '" COMSERSDK_ESS_PROGID "'"      "\n"
         "               ForceRemove 'Programmable'"                                 "\n"
         "               LocalServer32 = s '%MODULE%'"                               "\n"
         "               'TypeLib' = s '%TLB_CLSID%'"                                "\n"
         "            }"                                                             "\n"
         "         }"                                                                "\n"
         "         NoRemove AppID"                                                   "\n"
         "         {"                                                                "\n"
         "            ForceRemove %ESS_CLSID% = s '" COMSERSDK_ESS_NAME_STRING " Object'\n"
         "         }"                                                                "\n"
         "      }"                                                                   "\n"
         "   }"                                                                      "\n"
         "}"                                                                         "\n"
      ),
      m_sTlbRegScript
      (
         "%ROOT%"                                    "\n"
         "{"                                         "\n"
           "NoRemove Software"                       "\n"
           "{"                                       "\n"
             "NoRemove Classes"                      "\n"
             "{"                                     "\n"
               "NoRemove TypeLib"                    "\n"
               "{"                                   "\n"
                "ForceRemove %TLB_CLSID%"            "\n"
                "{"                                  "\n"
                 "ForceRemove 1.0"                   "\n"
                 "{"                                 "\n"
                  "ForceRemove 0"                    "\n"
                  "{"                                "\n"
                   "ForceRemove win32 = s '%MODULE%'""\n"
                  "}"                                "\n"
                  "ForceRemove FLAGS = s 0"          "\n"
                  "ForceRemove HELPDIR = s '%HELPDIR%'\n"
                 "}"                                 "\n"
                "}"                                  "\n"
               "}"                                   "\n"
             "}"                                     "\n"
           "}"                                       "\n"
         "}"                                         "\n"
      )
   {}

// Only the code above this location needs this defined.
//
#undef COMSERSDK_TLB_CLSID

   /**
   Helper for setting an arbitrary set of ASCII text into the windows clipboard.  No specific information about errors
   is transmitted from this function, other than whether or not the post-condition (that the text was set
   to the clipboard) has been met; this is done through the return value.
   @return True if successful, false otherwise.
   */
   bool SetTextToClipboard(const CStringA & sTextToSet)
   {
      using namespace ComSerSdk::Detail;

      if(0 == ::OpenClipboard(NULL)) return false;
      struct CClipCloser{~CClipCloser(){::CloseClipboard();}} oClipCloser;

      if(0 == ::EmptyClipboard()) return false;
      
      const SIZE_T dwTextLength = sTextToSet.GetLength() + 1;
      
      const HGLOBAL hGlobalMem = ::GlobalAlloc(GMEM_MOVEABLE, dwTextLength);
      if(NULL == hGlobalMem) return false;
      CScopeExitLogic oHglobFreer = MakeScopeExitLogic(::GlobalFree, hGlobalMem);
      
      LPSTR pszLocked = reinterpret_cast<LPSTR>(::GlobalLock(hGlobalMem));
      if(NULL == pszLocked) return false;
      CScopeExitLogic oHglobUnlocker = MakeScopeExitLogic(::GlobalUnlock, hGlobalMem);

      std::string sStringWithCheckedIterators = sTextToSet.GetString();

      memcpy_s(pszLocked, dwTextLength, sTextToSet.GetString(), sTextToSet.GetLength());
      
      if(NULL == ::SetClipboardData(CF_TEXT, hGlobalMem)) return false;

      return true;
   }

   /**
      Examines the command line and if the getclsid switch is found displays a dialog with identity information
      about the ESS COM object.
   */
   void CheckForGetClsidSwitch()
   {
      for(int i = 0; i < __argc; ++i)
      {
         const CString sArg         = __wargv ? CString(__wargv[i]) : CString(__argv[i]);
         const CString sFirstChar   = sArg.GetAt(0);
         const CStringW sSwitchName = sArg.Right(sArg.GetLength() - 1);
         if((sFirstChar == '/' ||
             sFirstChar == '-') &&
             sSwitchName.CompareNoCase(L"GetClsid") == 0)
         {
            using namespace ComSerSdk::Detail;
            const CString sClsId = ComSerSdk::Detail::GetClassClsid();
            std::wostringstream oOutStrm;

            oOutStrm << L"This ESS:\r\n"
                     << GetModulePath() << L"\r\n"
                        L"has the name \"" << CStringW(COMSERSDK_ESS_NAME_STRING).GetString() << L"\"\r\n"
                        L"a ProgID of \"" << CStringW(COMSERSDK_ESS_PROGID).GetString() << L"\"\r\n"
                        L"and has a CLSID of " << sClsId.GetString() << L".\r\n";

            if(SetTextToClipboard(CStringA(sClsId)))
            {
               oOutStrm << L"The CLSID has been copied";
            }
            else
            {
               oOutStrm << L"There was a problem copying the CLSID";
            }
            oOutStrm << L" to the windows clipboard.";
            ::MessageBoxW(NULL, oOutStrm.str().c_str(), L"CLSID", MB_OK);
         }
      }
   }

   /**
      /override
      Customize CAtlExeModuleT's functionality to merely forward to RegUnReg(), below, specifying to it appropriate
      member functions in order to ADD our information TO the registry.
   */
   HRESULT RegisterServer(BOOL bRegTypeLib = FALSE, const CLSID* pCLSID = NULL) throw( )
   {
      CheckForGetClsidSwitch();
      return RegUnReg(&IRegistrar::StringRegister, &CAtlModuleT::RegisterServer, bRegTypeLib, pCLSID);
   }

   /**
      /override
      Customize CAtlExeModuleT's functionality to merely forward to RegUnReg(), below, specifying to it appropriate
      member functions in order to REMOVE our information FROM the registry.
   */
   HRESULT UnregisterServer(BOOL bUnRegTypeLib, const CLSID* pCLSID = NULL) throw( )
   {
      return RegUnReg(&IRegistrar::StringUnregister, &CAtlModuleT::UnregisterServer, bUnRegTypeLib, pCLSID);
   }

   ComSerSdk::Detail::CLastError GetLastError()
   {
      return m_oLastError;
   }

   void SetLastError(const std::string & sLastError, HRESULT hResult)
   {
      m_oLastError = { sLastError, hResult };
   }

private:

   /**
      Type of method of the IRegristrar interface that either adds information to the registry or removes it.
   */
   typedef HRESULT(STDMETHODCALLTYPE IRegistrar::*RegFuncType)(LPCOLESTR);

   /**
      Implementation of the RegisterServer() and UnregisterServer() overrides; in other words, all those overrides
      are defined to do here is forward to this member function.  They specify pointers to member functions
      that we bind here to appropriate objects - it's done this way since both registration and unregistration are
      very similar, with the only difference between them being the names of the member functions that need to be
      called.
      Therefore we have this member function, which defines that code only once, and is parameterized on the only
      difference:  the member functions to execute.
      Note that upon error detection, a dialog box is displayed to notify the user as to the nature of the error.
      This is done since registration occurs without a way of sending BPWin detailed error information since, at this
      point, BPWin is merely executing the .exe with /regserver or /unregserver on the command line.

      @param pfnRegFunc Pointer to either IRegistrar::StringRegister or IRegistrar::StringUnregister, depending
                        on the type of registry operation to perform.
      @param pfnModFunc Pointer to either CAtlModuleT::RegisterServer or CAtlModuleT::UnregisterServer, depending
                        on the type of registry operation to perform.
      @param bRegTypeLib Whether to also register the type library - this value is merely forwarded from the argument
                         provided by the framework.
      @param pCLSID Just like the bRegTypeLib, this is merely forwarded from the value provided by the framework.
      @return The value returned when pfnModFunc was invoked or, if an exception occurred, the result of handling
              the exception.  In either case, this return value is what is returned to the framework.
   */
   HRESULT RegUnReg(RegFuncType pfnRegFunc,
                    HRESULT (CAtlModuleT::*pfnModFunc)(BOOL, const CLSID*) throw(),
                    BOOL bRegTypeLib,
                    const CLSID* pCLSID) throw( )
   {
      try
      {
         CComPtr<IRegistrar> spoRegistrar = PrepareRegistrar();

         PerformRegOperation(spoRegistrar, pfnRegFunc, m_sObjRegScript, "the ESS COM object");

         if(bRegTypeLib)
         {
            PerformRegOperation(spoRegistrar, pfnRegFunc, m_sTlbRegScript, "the type library");
         }

         return (this->*pfnModFunc)(bRegTypeLib, pCLSID);
      }
      catch(...)
      {  
         return ComSerSdk::Detail::HandleExceptions(GUID_NULL, true/*show an error dialog*/);
      }
   }

   /**
      Invokes pfnRegFunc on spoRegistrar, passing to pfnRegFunc the sRegScript string.  sType is used in error information.
   */
   void PerformRegOperation(CComPtr<IRegistrar> & spoRegistrar,
                            RegFuncType pfnRegFunc,
                            const _bstr_t & sRegScript,
                            const std::string & sType)
   {
      const HRESULT hObjRegResult = (spoRegistrar->*pfnRegFunc)(sRegScript);

      if(FAILED(hObjRegResult))
      {
         throw ComSerSdk::Detail::CHresultException("Could not perform registry operation for " + sType + ".",
                                                    hObjRegResult,
                                                    "IRegistrar.StringRegister/StringUnregister",
                                                    COMSERSDK_CODELOCATION);
      }
   }
         
   /**
      Creates and returns an instance of a Registrar object (see "Creating Registrar Scripts" in MSDN).
      Also, initializes the registrar object with the registry script substitutions we want to perform based 
      on runtime data (path to the current module, the "helpdir", and whether to add reg info to HKLM or HKCU).
   */
   CComPtr<IRegistrar> PrepareRegistrar()
   {
      CComPtr<IRegistrar> spoRegistrar;

      HRESULT hResult = ::CoCreateInstance(CLSID_Registrar,   
                                           NULL,
                                           CLSCTX_INPROC_SERVER,
                                           IID_IRegistrar,
                                           reinterpret_cast<void**>(&spoRegistrar));

      if(FAILED(hResult))
      {
         throw ComSerSdk::Detail::CHresultException("Could not create instance of IRegistrar object to perform "
                                                    "registration.",
                                                    hResult,
                                                    "CoCreateInstance(CLSID_Registrar,...)",
                                                    COMSERSDK_CODELOCATION);
      }

      const std::wstring sModulePath = GetModulePath();

      LPOLESTR pszTlbClsid = NULL;
      ::StringFromCLSID(__uuidof(COMSERSDK_LIBRARY_NAME), &pszTlbClsid);
      using namespace ComSerSdk::Detail;
      CScopeExitLogic oMemFreer = MakeScopeExitLogic(::CoTaskMemFree, pszTlbClsid);

		// From "Application Compatibility: UAC: COM Per-User Configuration" in MSDN
      //    http://msdn.microsoft.com/en-us/library/bb756926.aspx
      //
      //       "Beginning with Windows VistaŽ and Windows ServerŽ 2008, if the integrity level of a process is higher
      //        than Medium, the COM runtime ignores per-user COM configuration and accesses only per-machine COM
      //        configuration. This action reduces the surface area for elevation of privilege attacks, preventing a
      //        process with standard user privileges from configuring a COM object with arbitrary code and having this
      //        code called from an elevated process."
      // 
      // Instead of bothering to additionally detect whether we're running Vista or later, we will only detect
      // administrator, since we know that this kind of registration will also work correctly under all supported
      // operating systems.
      //
      AddReplacement(spoRegistrar, L"ROOT",      IsUserAdmin() ? L"HKLM" : L"HKCU");
      AddReplacement(spoRegistrar, L"ESS_CLSID", CStringW(ComSerSdk::Detail::GetClassClsid()).GetString());
      AddReplacement(spoRegistrar, L"TLB_CLSID", pszTlbClsid);
      AddReplacement(spoRegistrar, L"MODULE",    L"\"" + sModulePath             + L"\"");
      AddReplacement(spoRegistrar, L"HELPDIR",   L"\"" + GetHelpDir(sModulePath) + L"\"");

      return spoRegistrar;
	}

   /**
      Determines the current path to this executable; this is for adding it to the registry information
      for the LocalServer32 value.
   */
   std::wstring GetModulePath()
   {
      std::vector<wchar_t> oModule(_MAX_PATH + 1);
      if(0 == ::GetModuleFileNameW(_AtlBaseModule.m_hInst, &oModule.at(0), _MAX_PATH))
		{
         const DWORD dwLastError = ::GetLastError();
         throw ComSerSdk::CLibraryException("Could not get path to executable to use in registration data.  " + 
                                            ComSerSdk::Detail::GetWin32ErrorMessage(dwLastError),
                                            COMSERSDK_CODELOCATION);
		}

      return &oModule.at(0);
   }

   /**
      Creates the value of the helpdir registry item based on the module path.
   */
   std::wstring GetHelpDir(const std::wstring & sModulePath)
   {
      std::vector<wchar_t> oDrive(_MAX_DRIVE + 1);
      std::vector<wchar_t> oDir(_MAX_DIR + 1);
      _wsplitpath(sModulePath.c_str(), &oDrive.at(0), &oDir.at(0), NULL, NULL);
      return std::wstring(&oDrive.at(0)) + &oDir.at(0);
   }

   /**
      Adds a replacement key/value pair to the registrar and detects errors with that operation
      and throws exceptions based on those errors.
   */
   void AddReplacement(CComPtr<IRegistrar> & spoRegistrar,
                       const std::wstring & sKey,
                       const std::wstring & sValue)
   {
      const HRESULT hResult = spoRegistrar->AddReplacement(_bstr_t(sKey.c_str()), _bstr_t(sValue.c_str()));

      if(FAILED(hResult))
      {
         std::wostringstream oOutStrm;
         oOutStrm << "Could not add replacement with key \"" << sKey << "\" and value \""
                  << sValue << "\" to registrar to perform registration.";

         throw ComSerSdk::Detail::CHresultException(Narrow(oOutStrm.str()),
                                                    hResult,
                                                    "IRegistrar.AddReplacement()",
                                                    COMSERSDK_CODELOCATION);
      }
   }

   /**
      Invoked by CApplicationBase upon its construction to register itself (and by extension the user's application
      object) with us.  See the description of the m_poApp member for justification of raw pointer usage.
   */
   void SetApp(ComSerSdk::CApplicationBase * poApp)
   {
      m_poApp = poApp;
   }

   /**
      Provides access to the application object to the CSerialDataServerComObject instance so that it can invoke
      CreateServer().  See the description of the m_poApp member for justification of raw pointer usage.
   */
   ComSerSdk::CApplicationBase * GetApp()
   {
      return m_poApp;
   }

   /**
      Converts a wide standard string to a narrow standard string, based on advice found here:
      http://groups.google.com/group/comp.lang.c++/browse_frm/thread/74b2a17081f9356d/95d9b403fa213d58?#95d9b403fa213d58
   */
   std::string Narrow(std::wstring const& sSource)
   {
      typedef std::ctype<std::wstring::value_type> CType;
      std::vector<std::string::value_type> oResult(sSource.size() + 1);
      CType const& oCtype = std::use_facet<CType>(std::locale());
      oCtype.narrow(sSource.data(), sSource.data() + sSource.size(), ' ', &oResult.at(0));
      return &oResult.at(0);
  } 

   /**
      Determines if we're currently running as administrator.  See the MSDN documentation for API 
      CheckTokenMembership(), the example given there is very similar to what is done here.
   */
   bool IsUserAdmin()
   {
      SID_IDENTIFIER_AUTHORITY oNtAuthority = SECURITY_NT_AUTHORITY;
      PSID pAdministratorsGroup = NULL;
      if(0 == ::AllocateAndInitializeSid(&oNtAuthority,
                                         2,
                                         SECURITY_BUILTIN_DOMAIN_RID,
                                         DOMAIN_ALIAS_RID_ADMINS,
                                         0, 0, 0, 0, 0, 0,
                                         &pAdministratorsGroup))
      {
         const DWORD dwLastError = ::GetLastError();

         std::ostringstream oOutStrm;
         oOutStrm << "AllocateAndInitializeSid() failed, could not determine if user is running as administrator.  "
                  << ComSerSdk::Detail::GetWin32ErrorMessage(dwLastError);
                     
         throw ComSerSdk::CLibraryException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }
      using namespace ComSerSdk::Detail;
      CScopeExitLogic oSidFreer = MakeScopeExitLogic(::FreeSid, pAdministratorsGroup);

      BOOL bIsMember = FALSE;
      if(0 == ::CheckTokenMembership(NULL, pAdministratorsGroup, &bIsMember))
      {
         const DWORD dwLastError = ::GetLastError();

         std::ostringstream oOutStrm;
         oOutStrm << "CheckTokenMembership() failed, could not determine if user is running as administrator.  "
                  << ComSerSdk::Detail::GetWin32ErrorMessage(dwLastError);
                     
         throw ComSerSdk::CLibraryException(oOutStrm.str().c_str(), COMSERSDK_CODELOCATION);
      }

      return bIsMember != FALSE;
   }

   /**
      We need the COM object implementation to be able to call GetApp().
   */
   friend class ComSerSdk::Detail::COMSERSDK_ESS_CPP_CLASS_NAME;
   
   /**
      We need the CApplicationBase class to be able to call SetApp() so that it can register itself with us.
   */
   friend class ComSerSdk::CApplicationBase;
   
   /**
      We need a reference to the CApplicationBase class instance.  We cannot use a C++ reference because it must be set
      after construction.  We cannot use a std::auto_ptr because it is not allocated on the heap.
   */
   ComSerSdk::CApplicationBase * m_poApp;
   
   /**
      Contains the registration script for adding information about the COM object to the registry.
   */
   const _bstr_t m_sObjRegScript;
   
   /**
      Contains the registration script for adding information about the type library to the registry.
   */
   const _bstr_t m_sTlbRegScript;

   ComSerSdk::Detail::CLastError m_oLastError = { std::string(), S_OK };
};

namespace ComSerSdk
{
   namespace Detail
   {
      

      void SetLastError(const std::string & sMsg, HRESULT hRresult)
      {
         _AtlModule.SetLastError(sMsg, hRresult);
      }

      CLastError GetLastError()
      {
         return _AtlModule.GetLastError();
      }
   }
}