/*
    Crystal Space COM Emulation Support
    Copyright (C) 1998 by Jorrit Tyberghein
    Written by Dan Ogles

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/**
 * Note NeXT:
 *   The NextStep 3.3 compiler disables use of the keyword 'new' in functions
 *   which have been declared extern "C", such as those prefixed by STDAPI.
 *   To work around this problem, we define a simple stub function of type
 *   STDAPI which calls the real function which has not been prefixed with
 *   extern "C". This is done with the macro NeXT_STDAPI(name,args,fullargs)
 *   which is effectively a NOP for non-NeXT platform.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define SYSDEF_ACCESS
#include "sysdef.h"
#include "cscom/com.h"
#include "csutil/csvector.h"
#include "csutil/inifile.h"
#include "debug/memory.h"

#if defined (OS_NEXT)
#  define NeXT_STDAPI(name,args,fullargs) 	\
   static HRESULT name##__ fullargs;		\
   STDAPI name fullargs				\
   {						\
     return name##__ args;			\
   }						\
   static HRESULT name##__ fullargs
#  define NeXT_STDAPI_(type,name,args,fullargs) \
   static type name##__ fullargs;		\
   STDAPI_(type) name fullargs			\
   {						\
     return name##__ args;			\
   }						\
   static type name##__ fullargs
#  define NeXT_STDAPI_VOID(name,args,fullargs)	\
   static void name##__ fullargs;		\
   STDAPI_(void) name fullargs			\
   {						\
     name##__ args;				\
   }						\
   static void name##__ fullargs
#else
#  define NeXT_STDAPI(name,args,fullargs)	\
   STDAPI name fullargs
#  define NeXT_STDAPI_(type,name,args,fullargs)	\
   STDAPI_(type) name fullargs
#  define NeXT_STDAPI_VOID(name,args,fullargs)	\
   STDAPI_(void) name fullargs
#endif

// Implement support for dynamic libraries no matter whenever
// we have native COM or not.
#ifndef CS_STATIC_LINKED

//-------------------------------------------------------- Library management --

// system-dependent functions used to dynamically load a library.
extern CS_HLIBRARY SysLoadLibrary (char *szLibName);
extern PROC SysGetProcAddress (CS_HLIBRARY Handle, char *szProcName);
extern bool SysFreeLibrary (CS_HLIBRARY Handle);

class LibraryVector : public csVector
{
public:
  LibraryVector () : csVector (8, 8) {}
  virtual ~LibraryVector () { DeleteAll (); }
  virtual bool FreeItem (csSome Item)
  { return SysFreeLibrary ((CS_HLIBRARY)Item); }
};

static LibraryVector* gLibList = NULL;

CS_HLIBRARY csLoadLibrary (char* szLibName)
{
  CS_HLIBRARY Handle = SysLoadLibrary (szLibName);
  if (Handle)
  {
    if (!gLibList) { CHK (gLibList = new LibraryVector ()); }
    gLibList->Push ((csSome)Handle);
  }
  return Handle;
}

PROC csGetProcAddress (CS_HLIBRARY Handle, char* szProcName)
{
  return SysGetProcAddress (Handle, szProcName);
}

bool csFreeLibrary (CS_HLIBRARY Handle)
{
  if (!gLibList) { CHK (gLibList = new LibraryVector ()); }
  int idx = gLibList->Find ((csSome)Handle);
  if (idx >= 0)
    return gLibList->Delete (idx);
  else
    return false;
}

void csFreeAllLibraries ()
{
  if (!gLibList) { CHK (gLibList = new LibraryVector ()); }
  gLibList->DeleteAll ();
}

#endif // !CS_STATIC_LINKED

#if defined (NO_COM_SUPPORT)

/**
 *  RegistryEntry</p>
 *
 *  This is how csCoCreateInstance() determines what DLL to load, or if we
 *  are working statically linked, which singleton class object that we
 *  are linked to. The CLSID is the globally unique identifier for the
 *  class object. The ProgID is a human-readable version of the CLSID which
 *  can also map to the DLL name or the singleton object (if we are statically
 *  linked).
 */
struct RegistryEntry
{
  /// the ProgID of the CoClass.
  char szProgID[128];
  /// the CLSID of the CoClass.
  CLSID clsid;

#if defined(CS_STATIC_LINKED)
  /// a pointer to the singleton CoClass.
  IUnknown* pClass;
#else
  /// the InProc server name.
  char szDllName [256];
#endif
};

/// This class holds a handful of RegistryEntries
class ClassRegistryVector : public csVector
{
public:
  ClassRegistryVector () : csVector (16, 16) {}
  virtual ~ClassRegistryVector () { DeleteAll (); }
  virtual bool FreeItem (csSome Item)
  { if (Item) { CHK (delete (RegistryEntry *)Item); } return true; }
  virtual bool Equal (csSome Item, csConstSome Key) const
  { return (((RegistryEntry *)Item)->clsid == *(CLSID *)Key); }
};

/// the registry
static ClassRegistryVector* gClassRegistry = NULL;

#if defined (CS_STATIC_LINKED)
//---------------------------- Static linking emulation for non-COM platforms --

IID IID_IUnknown =
{ 0x00000000, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

IID IID_IClassFactory =
{ 0x00000001, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

/*
 *  csCoInitialize ()
 *
 *  This method must be called before any COM calls are made.
 */
extern "C" void csInitializeStaticCOM ();
NeXT_STDAPI (csCoInitialize, (lpReserved), (void *lpReserved))
{
  (void) lpReserved;
  // Initialize registry vector
  if (!gClassRegistry)
  {
    CHK (gClassRegistry = new ClassRegistryVector ());
    // make at least a single reference to static.cpp
    csInitializeStaticCOM ();
  }

  return S_OK;
}

/**
 *  csCoUninitialize ()
 *
 *  This must be called before exit.
 */
NeXT_STDAPI_VOID (csCoUninitialize, (), ())
{
}

/**
 * csRegisterServer ()
 * <p>This entry is called at initialization time
 * to register all statically linked COM objects
 */
NeXT_STDAPI (csRegisterServer, (DllRegData), (const DllRegisterData *DllRegData))
{
  static bool initialized = false;
  if (!initialized)
  {
    csCoInitialize (NULL);
    initialized = true;
  }

  CHK (RegistryEntry *Reg = new RegistryEntry);
  strcpy (Reg->szProgID, DllRegData->szProgID);
  memcpy (&Reg->clsid, DllRegData->clsid, sizeof (Reg->clsid));
  Reg->pClass = DllRegData->pClass;
  gClassRegistry->Push (Reg);
  return S_OK;
}

NeXT_STDAPI (csUnregisterServer, (DllRegData), (const DllRegisterData *DllRegData))
{
  int idx = gClassRegistry->FindKey (DllRegData->clsid);
  if (idx >= 0)
    gClassRegistry->Delete (idx);
  return S_OK;
}

/**
 *  csCoCreateInstance ()
 *
 *  Creates an instance of a COM object given the CLSID and the
 *  IID. This is the statically-linked version.
 */
NeXT_STDAPI (csCoCreateInstance,
  (rclsid, pUnkOuter, dwClsContext, riid, ppv),
  (REFCLSID rclsid, IUnknown * pUnkOuter, DWORD dwClsContext, REFIID riid, void **ppv))
{
  (void) dwClsContext;

  int idx = gClassRegistry->FindKey (&rclsid);
  if (idx < 0)
    return REGDB_E_CLASSNOTREG;

  IUnknown *pClass = ((RegistryEntry *)(*gClassRegistry) [idx])->pClass;
  pClass->AddRef ();

  IClassFactory *piCF = NULL;
  HRESULT hRes = pClass->QueryInterface (IID_IClassFactory, (void **) &piCF);

  if (FAILED (hRes))
  {
    FINAL_RELEASE (piCF);
    FINAL_RELEASE (pClass);
    return hRes;
  }

  hRes = piCF->CreateInstance (pUnkOuter, riid, ppv);
  if (FAILED (hRes))
  {
    FINAL_RELEASE (piCF);
    FINAL_RELEASE (pClass);

    if (*ppv)
    {
      ((IUnknown *) * ppv)->Release ();
      *ppv = NULL;
    }
    return hRes;
  }

  pClass->Release ();
  return S_OK;
}

NeXT_STDAPI (csCoGetClassObject,
  (rclsid, dwClsContext, pServerInfo, riid, ppv),
  (REFCLSID rclsid, DWORD dwClsContext, COSERVERINFO *pServerInfo,
   REFIID riid, void **ppv))
{
  (void) dwClsContext;
  (void) pServerInfo;

  *ppv = NULL;

  int idx = gClassRegistry->FindKey (&rclsid);
  if (idx < 0)
    return REGDB_E_CLASSNOTREG;

  IUnknown *pClass = ((RegistryEntry *)(*gClassRegistry) [idx])->pClass;
  pClass->AddRef ();

  HRESULT hRes = pClass->QueryInterface (riid, ppv);

  if (FAILED (hRes))
  {
    pClass->Release ();
    if (*ppv)
    {
      ((IUnknown *) *ppv)->Release ();
      *ppv = NULL;
    }
    return hRes;
  }

  pClass->Release ();
  return S_OK;
}

/**
 *  csCLSIDFromProgID
 *
 *  This converts a CLSID to a ProgID. A CLSID is a 64-bit unique identifier
 *  that identifies a coclass. A coclass is used to create instances of COM
 *  objects. A coclass exists as a singleton ('static') instance. A ProgID
 *  is a human-readable version that corresponds to a CLSID. It often comes
 *  in the form "libraryname.classname.version".
 *  For example: "CrystalSpace.Graphics3DDirect3D.1"
 */
NeXT_STDAPI (csCLSIDFromProgID,
  (lpszProgID, pclsid), (char **lpszProgID, LPCLSID pclsid))
{
  if (!gClassRegistry) { CHK (gClassRegistry = new ClassRegistryVector ()); }
  for (int i = 0; i < gClassRegistry->Length (); i++)
  {
    RegistryEntry *Reg = (RegistryEntry *)(*gClassRegistry) [i];
    if (!strcmp (*lpszProgID, Reg->szProgID))
    {
      memcpy (pclsid, &Reg->clsid, sizeof (*pclsid));
      return S_OK;
    } /* endif */
  } /* endfor */

  return E_UNEXPECTED;			// couldn't find the matching CLSID.
}

NeXT_STDAPI (csProgIDFromCLSID,
  (clsid, lpszProgID), (REFCLSID clsid, char **lpszProgID))
{
  if (!gClassRegistry) { CHK (gClassRegistry = new ClassRegistryVector ()); }
  int idx = gClassRegistry->FindKey (&clsid);
  if (idx < 0)
    return E_UNEXPECTED;		// couldn't find the matching CLSID.

  RegistryEntry *Reg = (RegistryEntry *)(*gClassRegistry) [idx];
  CHK (*lpszProgID = new char [strlen (Reg->szProgID)]);
  strcpy (*lpszProgID, Reg->szProgID);

  return S_OK;
}

#else
//--------------------------- Dynamic linking emulation for non-COM platforms --

IID IID_IUnknown =
{ 0x00000000, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

IID IID_IClassFactory =
{ 0x00000001, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

typedef HRESULT (*GETCLASSOBJPROC)(const CLSID&, const IID&, void**);

//--------------------------------------------- COM class registry management --

#define COM_CONFIG_FILENAME	"csCOM.cfg"
#if defined (OS_OS2)
#  define COM_CONFIG_SECTION	"registry.os2"
#elif defined (OS_UNIX)
#  define COM_CONFIG_SECTION	"registry.unix"
#elif defined (OS_WIN32)
#  define COM_CONFIG_SECTION	"registry.win32"
#elif defined (OS_BE)
#  define COM_CONFIG_SECTION	"registry.win32"
#elif defined (OS_MACOS)
#  define COM_CONFIG_SECTION	"registry.mac"
#elif defined (OS_AMIGAOS)
#  define COM_CONFIG_SECTION	"registry.amiga"
#else
#  error "Please set the COM_CONFIG_SECTION macro in com/cscom.cpp for your system!"
#endif

/// This is used to keep track of instantiated objects
struct RunningObjectEntry
{
  /// the CLSID of this class
  CLSID clsid;
  /// a handle to the dynamic library.
  CS_HLIBRARY Handle;
  /// a pointer to the DllGetClassObject function in the library.
  GETCLASSOBJPROC pfnGCO;
};

/// This is a vector of running object entries
class RunningObjectVector : public csVector
{
public:
  RunningObjectVector () : csVector (16, 16) {}
  virtual ~RunningObjectVector () { DeleteAll (); }
  virtual bool FreeItem (csSome Item)
  { if (Item) { CHK (delete (RunningObjectEntry *)Item); } return true; }
  virtual bool Equal (csSome Item, csConstSome Key) const
  { return (((RunningObjectEntry *)Item)->clsid == *(CLSID *)Key); }
};

/// the list of loaded coclasses.
static RunningObjectVector* gRunningObjectTable = NULL;

// Utility function: find out the name of registry file
char *GetRegistryFileName ()
{
  // Look in environment
  char *fn = getenv ("CS_COM");
  if (fn)
    return fn;
  else
  {
    // try some system-wide values
    if (access ("/etc/" COM_CONFIG_FILENAME, R_OK) == 0)
      return "/etc/" COM_CONFIG_FILENAME;
    // return the default value
    return COM_CONFIG_FILENAME;
  } /* endif */
}

// Called by csIniFile::EnumData for each entry in [COM] section
bool ConfigIterator (csSome /*Parm*/, char *Name, size_t /*DataSize*/, csSome Data)
{
  // Look for the end of shared library name
  char *cur = strchr ((char *)Data, ':');
  // no shared library name???
  if (!cur)
    return false;

  // Create a new registry entry object
  CHK (RegistryEntry *reg = new RegistryEntry);

  // The "Name" is right the name of COM class
  strncpy (reg->szProgID, Name, sizeof (reg->szProgID));

  // The szDllName is at the beginning of Data string
  memcpy (reg->szDllName, (char *)Data, cur - (char *)Data);
  reg->szDllName [cur - (char *)Data] = 0;

  // now we have the GUID string: convert from ASCII to binary
  unsigned short tmp [8];
  sscanf (cur + 1, "%08lx-%04hx-%04hx-%02hx%02hx-%02hx%02hx%02hx%02hx%02hx%02hx",
    &reg->clsid.Data1, &reg->clsid.Data2, &reg->clsid.Data3,
    &tmp [0], &tmp [1], &tmp [2], &tmp [3], &tmp [4], &tmp [5], &tmp [6], &tmp [7]);
  for (int i = 0; i <= 7; i++)
    reg->clsid.Data4 [i] = tmp [i];

  // Add the new entry to the registry
  gClassRegistry->Push (reg);
  return false;
}

/**
 *  csCoInitialize ()
 *
 *  This method must be called before any COM calls are made.
 */
NeXT_STDAPI (csCoInitialize, (lpReserved), (void *lpReserved))
{
  // Argument is unused (reserved for great future m$ enhancements)
  (void)lpReserved;
  // Initialize registry vector
  if (!gClassRegistry)
    CHKB (gClassRegistry = new ClassRegistryVector ());
  // Initialize running objects vector
  if (!gRunningObjectTable)
    CHKB (gRunningObjectTable = new RunningObjectVector ());
  // load the COM database from the configuration file
  csIniFile config (GetRegistryFileName ());
  config.EnumData (COM_CONFIG_SECTION, ConfigIterator, NULL);

  return S_OK;
}

/**
 *  csCoUninitialize ()
 *
 *  This must be called before exit.
 */
NeXT_STDAPI_VOID (csCoUninitialize, (), ())
{
  gRunningObjectTable->DeleteAll ();
  gClassRegistry->DeleteAll ();
  csFreeAllLibraries ();
}

// Utility procedure
static HRESULT FlushConfig (csIniFile &config, char *fname)
{
  // Save registry
  if (!config.Save (fname))
    return E_FAIL;

  // delete and load again the COM database from the configuration file
  gClassRegistry->DeleteAll ();
  config.EnumData (COM_CONFIG_SECTION, ConfigIterator, NULL);

  return S_OK;
}

// csRegisterServer () -- Adds entries to CRYST_REGFILE.
NeXT_STDAPI (csRegisterServer,
  (DllRegData), (const DllRegisterData * DllRegData))
{
  if (!DllRegData)
    return E_UNEXPECTED;

  char *rfn = GetRegistryFileName ();
  csIniFile config (rfn);
  char tmp[200];
  sprintf (tmp, "%s:%08lx-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x",
    DllRegData->szInProcServer,
    DllRegData->clsid->Data1, DllRegData->clsid->Data2, DllRegData->clsid->Data3,
    DllRegData->clsid->Data4[0], DllRegData->clsid->Data4[1],
    DllRegData->clsid->Data4[2], DllRegData->clsid->Data4[3],
    DllRegData->clsid->Data4[4], DllRegData->clsid->Data4[5],
    DllRegData->clsid->Data4[6], DllRegData->clsid->Data4[7]);

  bool SetComment = !config.KeyExists (COM_CONFIG_SECTION, DllRegData->szProgID);
  config.SetStr (COM_CONFIG_SECTION, DllRegData->szProgID, tmp);
  if (SetComment)
  {
    sprintf (tmp, " %.199s", DllRegData->szFriendlyName);
    config.SetComment (COM_CONFIG_SECTION, DllRegData->szProgID, tmp);
  }

  return FlushConfig (config, rfn);
}

NeXT_STDAPI (csUnregisterServer,
  (DllRegData), (const DllRegisterData * DllRegData))
{
  char *rfn = GetRegistryFileName ();
  csIniFile config (rfn);
  config.Delete (COM_CONFIG_SECTION, DllRegData->szProgID);
  return FlushConfig (config, rfn);
}

/**
 *  csCoCreateInstance ()
 *
 *  Creates an instance of a COM object given the CLSID and the
 *  IID. This is the dynamic-linking version for non-COM platforms.
 */
NeXT_STDAPI (csCoCreateInstance,
  (rclsid, pUnkOuter, dwClsContext, riid, ppv),
  (REFCLSID rclsid, IUnknown *pUnkOuter, unsigned long dwClsContext, REFIID riid, void **ppv))
{
  (void)dwClsContext;
  // In the event we will fail ...
  *ppv = NULL;

  // first determine if the coclass is already loaded
  // look in running objects table
  if (!gRunningObjectTable) { CHK (gRunningObjectTable = new RunningObjectVector ()); }
  int idx = gRunningObjectTable->FindKey (&rclsid);
  // is class already loaded?
  if (idx >= 0)
  {
    RunningObjectEntry *Class = (RunningObjectEntry *)gRunningObjectTable->Get (idx);
    IClassFactory *piCF = NULL;
    HRESULT hRes = Class->pfnGCO (rclsid, IID_IClassFactory, (void **) &piCF);
    if (FAILED (hRes))
    {
      if (*ppv)
      {
        ((IUnknown *) *ppv)->Release ();
        *ppv = NULL;
      }
      return hRes;
    }

    hRes = piCF->CreateInstance (pUnkOuter, riid, ppv);
    if (FAILED (hRes))
    {
      if (*ppv)
      {
        ((IUnknown *) *ppv)->Release ();
        *ppv = NULL;
      }
      return hRes;
    }
    return S_OK;
  }

  // Now look in registry
  idx = gClassRegistry->FindKey (&rclsid);
  if (idx < 0)
    return REGDB_E_CLASSNOTREG;

  RegistryEntry *Class = (RegistryEntry *)gClassRegistry->Get (idx);
  CS_HLIBRARY Handle = csLoadLibrary (Class->szDllName);
  if (!Handle)
    return CO_E_DLLNOTFOUND;

  GETCLASSOBJPROC pfnGCO = (GETCLASSOBJPROC) csGetProcAddress (Handle, "DllGetClassObject");
  if (!pfnGCO)
    return CO_E_ERRORINDLL;		// couldn't find 'DllGetClassObject' entry

  // fill in data for running objects table
  CHK (RunningObjectEntry * NewClass = new RunningObjectEntry);
  memcpy (&NewClass->clsid, &rclsid, sizeof (NewClass->clsid));
  NewClass->Handle = Handle;
  NewClass->pfnGCO = pfnGCO;

  // insert the class in the running objects table.
  gRunningObjectTable->Push (NewClass);

  // make the call to DllGetClassObject.
  IClassFactory *piCF = NULL;
  HRESULT hRes = pfnGCO (rclsid, IID_IClassFactory, (void **) &piCF);
  if (FAILED (hRes))
  {
    if (piCF)
      piCF->Release ();
    return hRes;
  }

  hRes = piCF->CreateInstance (pUnkOuter, riid, ppv);
  if (FAILED (hRes))
  {
    if (*ppv)
    {
      ((IUnknown *) * ppv)->Release ();
      *ppv = NULL;
    }
    return hRes;
  }

  return S_OK;
}

NeXT_STDAPI (csCoGetClassObject,
  (rclsid, dwClsContext, pServerInfo, riid, ppv),
  (REFCLSID rclsid, DWORD dwClsContext, COSERVERINFO* pServerInfo, REFIID riid, void **ppv))
{
  (void)dwClsContext;
  (void)pServerInfo;
  // In the event we will fail ...
  *ppv = NULL;

  // first determine if the coclass is already loaded
  // look in running objects table
  if (!gRunningObjectTable) { CHK (gRunningObjectTable = new RunningObjectVector ()); }
  int idx = gRunningObjectTable->FindKey (&rclsid);
  // is class already loaded?
  if (idx >= 0)
  {
    RunningObjectEntry *Class = (RunningObjectEntry *)gRunningObjectTable->Get (idx);
    HRESULT hRes = Class->pfnGCO (rclsid, riid, (void **)ppv);
    if (FAILED (hRes))
    {
      if (*ppv)
      {
        ((IUnknown *) *ppv)->Release ();
        *ppv = NULL;
      }
      return hRes;
    }
    return S_OK;
  }

  // it's not in the running object table, so load the appropriate library
  idx = gClassRegistry->FindKey (&rclsid);
  if (idx < 0)
    return REGDB_E_CLASSNOTREG;

  RegistryEntry *Class = (RegistryEntry *)gClassRegistry->Get (idx);
  CS_HLIBRARY Handle = csLoadLibrary (Class->szDllName);
  if (!Handle)
    return CO_E_DLLNOTFOUND;

  GETCLASSOBJPROC pfnGCO = (GETCLASSOBJPROC) csGetProcAddress (Handle, "DllGetClassObject");
  if (!pfnGCO)
    return CO_E_ERRORINDLL;		// couldn't find 'DllGetClassObject' entry

  // fill in data for running objects table
  CHK (RunningObjectEntry * NewClass = new RunningObjectEntry);
  memcpy (&NewClass->clsid, &rclsid, sizeof (NewClass->clsid));
  NewClass->Handle = Handle;
  NewClass->pfnGCO = pfnGCO;

  // insert the class in the running objects table.
  gRunningObjectTable->Push (NewClass);

  // make the call to DllGetClassObject.
  HRESULT hRes = pfnGCO (rclsid, riid, (void **)ppv);
  if (FAILED (hRes))
  {
    if (*ppv)
    {
      ((IUnknown *) * ppv)->Release ();
      *ppv = NULL;
    }
    return hRes;
  }

  return S_OK;
}

/**
 *  csCLSIDFromProgID
 *
 *  This converts a CLSID to a ProgID. A CLSID is a 64-bit unique identifier
 *  that identifies a coclass. A coclass is used to create instances of COM
 *  objects. A coclass exists as a singleton ('static') instance. A ProgID
 *  is a human-readable version that corresponds to a CLSID. It often comes
 *  in the form "libraryname.classname.version".
 *  For example: "CrystalSpace.Graphics3DDirect3D.1"
 */
NeXT_STDAPI (csCLSIDFromProgID,
  (lpszProgID, pclsid),
  (char **lpszProgID, LPCLSID pclsid))
{
  for (int idx = 0; idx < gClassRegistry->Length (); idx++)
  {
    RegistryEntry *Class = (RegistryEntry *)gClassRegistry->Get (idx);
    if (!strcmp (*lpszProgID, Class->szProgID))
    {
      memcpy (pclsid, &Class->clsid, sizeof (*pclsid));
      return S_OK;
    }
  }

  // couldn't find the matching CLSID.
  return E_UNEXPECTED;
}

NeXT_STDAPI (csProgIDFromCLSID,
  (clsid, lpszProgID), (REFCLSID clsid, char **lpszProgID))
{
  int idx = gClassRegistry->FindKey (&clsid);
  if (idx >= 0)
  {
    RegistryEntry *Class = (RegistryEntry *)gClassRegistry->Get (idx);
    CHK (*lpszProgID = new char [strlen (Class->szProgID)]);
    strcpy (*lpszProgID, Class->szProgID);
    return S_OK;
  }

  // couldn't find the matching CLSID.
  return E_UNEXPECTED;
}

#endif // CS_STATIC_LINKED

#else  // not NO_COM_SUPPORT

// Direct to COM for platforms that support it.

/**
 *  csCoInitialize ()
 *
 *  This method must be called before any COM calls are made.
 */
NeXT_STDAPI (csCoInitialize, (lpReserved), (void *lpReserved))
{
  return CoInitialize (lpReserved);
}

/**
 *  csCoUninitialize ()
 *
 *  This must be called before exit.
 */
NeXT_STDAPI_VOID (csCoUninitialize, (), ())
{
  CoUninitialize ();
}

#define MAX_KEY_LEN 128

static long WriteRegValue (const char *szKey, const char * /*szValueName*/, const char *szValue)
{
  HKEY hKey;

  long err = RegCreateKey (HKEY_CLASSES_ROOT, szKey, &hKey);

  if (err == ERROR_SUCCESS)
  {
    err = RegSetValue (hKey, NULL, REG_SZ, szValue, (strlen (szValue) + 1));
    RegCloseKey (hKey);
  }

  return err;
}

HINSTANCE ModuleHandle = NULL;

// csRegisterServer () -- Adds entries to the Windows registry.
NeXT_STDAPI (csRegisterServer, (DllRegData), (const DllRegisterData *DllRegData))
{
  LPOLESTR wszGUID;
  char szGUID[50];
  char szKey[MAX_KEY_LEN];
  const char szKeys[][256] =
  {
    "CLSID\\%s",
    "CLSID\\%s\\InprocServer32",
    "CLSID\\%s\\ProgID",
    "%s\\CLSID"
  };
  int i;
  long err;

  // ASSERT (DllRegData);

  // get the registry-style GUID representation, and convert to ANSI.
  VERIFY_SUCCESS (StringFromCLSID (*DllRegData->clsid, &wszGUID));
  WideCharToMultiByte (CP_ACP, 0, wszGUID, wcslen (wszGUID), szGUID, 50, NULL, NULL);
  LocalFree (wszGUID);

  // turn the brackets into braces
  for (i = 0; i < (int) strlen (szGUID); i++)
  {
    if (szGUID[i] == '[')
      szGUID[i] = '{';
    else if (szGUID[i] == ']')
      szGUID[i] = '}';
  }
  szGUID[38] = '\0';

  // now write the keys

  // [HKCR\CLSID\{guid}]
  // @=friendlyname
  sprintf (szKey, szKeys[0], szGUID);

  err = WriteRegValue (szKey, 0, DllRegData->szFriendlyName);
  if (err != ERROR_SUCCESS)
  {
    csUnregisterServer (DllRegData);
    return E_UNEXPECTED;
  }

  // [HKCR\CLSID\{guid}\InprocServer32]
  // @=filename
  sprintf (szKey, szKeys[1], szGUID);

  char szFilename[MAX_PATH];

  //Module handle needs to be set! (Make sure, you included dllentry.cpp in your dll!)
  ASSERT(ModuleHandle);
  GetModuleFileName (ModuleHandle, szFilename, MAX_PATH);

  err = WriteRegValue (szKey, 0, szFilename);
  if (err != ERROR_SUCCESS)
  {
    csUnregisterServer (DllRegData);
    return E_UNEXPECTED;
  }

  // [HKCR\CLSID\{guid}\ProgID]
  // @=progID
  sprintf (szKey, szKeys[2], szGUID);

  err = WriteRegValue (szKey, 0, DllRegData->szProgID);
  if (err != ERROR_SUCCESS)
  {
    csUnregisterServer (DllRegData);
    return E_UNEXPECTED;
  }

  // [HKCR\progID]
  // @=friendlyname
  err = WriteRegValue (DllRegData->szProgID, 0, DllRegData->szFriendlyName);
  if (err != ERROR_SUCCESS)
  {
    csUnregisterServer (DllRegData);
    return E_UNEXPECTED;
  }

  // [HKCR\progID\CLSID]
  sprintf (szKey, szKeys[3], DllRegData->szProgID);

  err = WriteRegValue (szKey, 0, szGUID);
  if (err != ERROR_SUCCESS)
  {
    csUnregisterServer (DllRegData);
    return E_UNEXPECTED;
  }

  return S_OK;
}

NeXT_STDAPI (csUnregisterServer, (DllRegData), (const DllRegisterData * DllRegData))
{
  OLECHAR wszGUID[MAX_KEY_LEN];
  char szGUID[40];
  char szKey[MAX_KEY_LEN];
  const char *szKeys[] =
  {
    {"CLSID\\%s"},
    {"CLSID\\%s\\InprocServer32"},
    {"CLSID\\%s\\ProgID"},
    {"%s\\CLSID"}
  };

  // ASSERT (DllRegData);

  // get the registry-style GUID representation, and convert to ANSI.
  StringFromGUID2 (*DllRegData->clsid, wszGUID, 40);
  wcstombs (szGUID, wszGUID, 40);

  // turn the brackets into braces
  int i;
  for (i = 0; i < (int) strlen (szGUID); i++)
  {
    if (szGUID[i] == '[')
      szGUID[i] = '{';
    else if (szGUID[i] == ']')
      szGUID[i] = '}';
  }
  szGUID[38] = '\0';

  // now delete the entries.

  for (i = 0; i < 3; i++)
  {
    sprintf (szKey, szKeys[i], szGUID);
    RegDeleteKey (HKEY_CLASSES_ROOT, szKey);
  }

  RegDeleteKey (HKEY_CLASSES_ROOT, DllRegData->szProgID);

  sprintf (szKey, szKeys[3], DllRegData->szProgID);
  RegDeleteKey (HKEY_CLASSES_ROOT, szKey);

  return S_OK;
}

/**
 *  csCoCreateInstance ()
 *
 *  Creates an instance of a COM object given the CLSID and the
 *  IID. This is the dynamic-linking version for non-COM platforms.
 */
NeXT_STDAPI (csCoCreateInstance,
  (rclsid, pUnkOuter, dwClsContext, riid, ppv),
  (REFCLSID rclsid, IUnknown *pUnkOuter, DWORD dwClsContext, REFIID riid, void **ppv))
{
  return CoCreateInstance (rclsid, pUnkOuter, dwClsContext, riid, ppv);
}

NeXT_STDAPI (csCoGetClassObject,
  (rclsid, dwClsContext, pServerInfo, riid, ppv),
  (REFCLSID rclsid, DWORD dwClsContext, COSERVERINFO * pServerInfo, REFIID riid, void **ppv))
{
  return CoGetClassObject (rclsid, dwClsContext, pServerInfo, riid, ppv);
}

/**
 *  csCLSIDFromProgID
 *
 *  This converts a CLSID to a ProgID. A CLSID is a 64-bit unique identifier
 *  that identifies a coclass. A coclass is used to create instances of COM
 *  objects. A coclass exists as a singleton ('static') instance. A ProgID
 *  is a human-readable version that corresponds to a CLSID. It often comes
 *  in the form "libraryname.classname.version".
 *  For example: "CrystalSpace.Graphics3DDirect3D.1"
 */
NeXT_STDAPI (csCLSIDFromProgID,
  (lpszProgID, pclsid), (char **lpszProgID, LPCLSID pclsid))
{
  HRESULT hRes = S_OK;
  int nSize = MultiByteToWideChar (CP_ACP, NULL, *lpszProgID, -1, NULL, 0);
  CHK (LPOLESTR lpwcProgID = new OLECHAR[nSize]);

  hRes = MultiByteToWideChar (CP_ACP, NULL, *lpszProgID, -1, lpwcProgID, nSize);
  if (FAILED (hRes))
    goto OnError;

  hRes = CLSIDFromProgID (lpwcProgID, pclsid);

OnError:

  CHK (delete[] lpwcProgID);
  return hRes;
}

NeXT_STDAPI (csProgIDFromCLSID,
  (clsid, lpszProgID), (REFCLSID clsid, char **lpszProgID))
{
  HRESULT hRes;
  int nSize = strlen (*lpszProgID);
  CHK (LPOLESTR lpwcProgID = new OLECHAR[nSize]);

  hRes = ProgIDFromCLSID (clsid, &lpwcProgID);
  if (FAILED (hRes))
    goto OnError;

  hRes = WideCharToMultiByte (CP_ACP, NULL, lpwcProgID, nSize, *lpszProgID, nSize, NULL, 0);
  if (FAILED (hRes))
    goto OnError;

OnError:
  CHK (delete lpwcProgID);
  return hRes;
}

void csErrorMessageFromResult (HRESULT hRes)
{
  LPVOID lpMsgBuf;

  FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
    hRes, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
    (LPTSTR) & lpMsgBuf, 0, NULL);

  MessageBox (NULL, (const char *) lpMsgBuf, "GetLastError", MB_OK | MB_ICONINFORMATION);

  LocalFree (lpMsgBuf);
}

#endif      // !NO_COM_SUPPORT
