/*
 -  CLSFACT.CPP
 -
 *	Microsoft NetMeeting
 *	Network Audio Control DLL
 *	Generic class factory
 *
 *		Revision History:
 *
 *		When		Who					What
 *		--------	------------------  ---------------------------------------
 *		2.6.97		Yoram Yaacovi		Copied from qosfact.cpp
 *										Added handling of CInstallCodecs
 *		2.27.97		Yoram Yaacovi		Added DllRegisterServer and DllUnregisterServer
 *
 *	Functions:
 *		DllGetClassObject
 *		DllCanUnloadNow
 *		DllRegisterServer
 *		DllUnregisterServer
 *		CClassFactory::QueryInterface
 *		CClassFactory::AddRef
 *		CClassFactory::Release
 *		CClassFactory::CreateInstance
 *		CClassFactory::LockServer
 *		CreateClassFactory
 *		
 *
 *	Object types supported:
 *		CQoS
 *		CInstallCodecs
 *
 *	Notes:
 *		To add support for manufacturing objects of other types, change:
 *			DllGetClassObject
 *			DllCanUnloadNow
 *			Add the CLSID and description to aObjectInfo
 *
 */

#include <precomp.h>

int g_cObjects = 0;				// A general object count. Used for LockServer.
EXTERN_C int g_cQoSObjects;		// QoS object count. Public in qos\qos.cpp
EXTERN_C int g_cICObjects;		// CInstallCodecs object count. Public in inscodec.cpp

EXTERN_C HINSTANCE g_hInst;		// global module instance

// Untested code for registering COM objects in the NAC
// when enabled, DllRegisterServer and DllUnregisterServer should be exported
// in nac.def

#define GUID_STR_LEN    40

typedef struct
{
    const CLSID *pclsid;
	char szDescription[MAX_PATH];
} OBJECT_INFO;

static OBJECT_INFO aObjectInfo[]=
	{&CLSID_QoS, TEXT("Microsoft NetMeeting Quality of Service"),
	 &CLSID_InstallCodecs, TEXT("Microsoft NetMeeting Installable Codecs"),
	 NULL, TEXT("")};

// Internal helper functions
BOOL DeleteKeyAndSubKeys(HKEY hkIn, LPTSTR pszSubKey);
BOOL UnregisterUnknownObject(const CLSID *prclsid);
BOOL RegisterUnknownObject(LPCTSTR  pszObjectName, const CLSID *prclsid);

/***************************************************************************

    Name      : DllGetClassObject

    Purpose   : Standard COM entry point to create a COM object

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, void **ppv)
{
    HRESULT hr;
    CClassFactory *pObj;

	*ppv = 0;

	// find out object of what class we need to create and instantiate
	// the class factory with the correct create function
    if (CLSID_QoS == rclsid)
	{
    	DBG_SAVE_FILE_LINE
		pObj = new CClassFactory(CreateQoS);
	}
	else if (CLSID_InstallCodecs == rclsid)
	{
		DBG_SAVE_FILE_LINE
		pObj = new CClassFactory(CreateInstallCodecs);
	}
	else
	{
		hr = CLASS_E_CLASSNOTAVAILABLE;
		goto out;
	}

    if (!pObj)
	{
		hr = E_OUTOFMEMORY;
		goto out;
	}

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
        delete pObj;

out:
    return hr;
}

/***************************************************************************

    Name      : DllCanUnloadNow

    Purpose   : Standard COM entry point tell a DLL it can unload

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
STDAPI DllCanUnloadNow ()
{
	HRESULT hr=S_OK;
	int vcObjects = g_cObjects + g_cQoSObjects + g_cICObjects;

	return (vcObjects == 0 ? S_OK : S_FALSE);
}

/***************************************************************************

    Name      : DllRegisterServer

    Purpose   : Standard COM entry point to register a COM server

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
STDAPI DllRegisterServer(void)
{
	ULONG i=0;
	HRESULT hr=NOERROR;

	while ((aObjectInfo[i].pclsid != NULL) &&
			(lstrlen(aObjectInfo[i].szDescription) != 0))
    {
		if (!RegisterUnknownObject(aObjectInfo[i].szDescription,
								   aObjectInfo[i].pclsid))
		{
			hr = E_FAIL;
			goto out;
		}

		// next server to register
		i++;
	}

out:
	return hr;
}

/***************************************************************************

    Name      : DllUnregisterServer

    Purpose   : Standard COM entry point to unregister a COM server

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
STDAPI DllUnregisterServer(void)
{
 	ULONG i=0;
	HRESULT hr=NOERROR;

	while ((aObjectInfo[i].pclsid != NULL) &&
			(lstrlen(aObjectInfo[i].szDescription) != 0))
    {
		if (!UnregisterUnknownObject(aObjectInfo[i].pclsid))
		{
			hr = E_FAIL;
			goto out;
		}

		// next server to register
		i++;
	}

out:
	return hr;
}

/***************************************************************************

    ClassFactory: Generic implementation

***************************************************************************/
CClassFactory::CClassFactory(PFNCREATE pfnCreate)
{
	m_cRef=0;
	m_pfnCreate = pfnCreate;

	return;
}

CClassFactory::~CClassFactory(void)
{
	return;
}

/***************************************************************************

    IUnknown Methods for  CClassFactory

***************************************************************************/
HRESULT CClassFactory::QueryInterface (REFIID riid, void **ppv)
{
	HRESULT hr=NOERROR;

#ifdef DEBUG
	// parameter validation
    if (IsBadReadPtr(&riid, (UINT) sizeof(IID)))
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }

    if (IsBadWritePtr(ppv, sizeof(LPVOID)))
    {
        hr = ResultFromScode(E_INVALIDARG);
        goto out;
    }
#endif // DEBUG
	
	*ppv = 0;

    if (IID_IUnknown == riid ||
		IID_IClassFactory == riid)
	{
		*ppv = this;
	}
	else    
	{
        hr = ResultFromScode(E_NOINTERFACE);
        goto out;
    }

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

out:
	return hr;
}

ULONG CClassFactory::AddRef (void)
{
    return ++m_cRef;
}

ULONG CClassFactory::Release (void)
{
	// if the cRef is already 0 (shouldn't happen), assert, but let it through
	ASSERT(m_cRef);
	if (--m_cRef == 0)
	{
		delete this;
		return 0;
	}

	return m_cRef;
}

/***************************************************************************

    Name      : CreateInstance

    Purpose   : Standard COM class factory entry point which creates the
				object that this class factory knows to create

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
HRESULT CClassFactory::CreateInstance (	IUnknown *punkOuter,
										REFIID riid,
										void **ppv)
{
	DEBUGMSG(ZONE_VERBOSE,("CClassFactory::CreateInstance\n"));

	return (m_pfnCreate)(punkOuter, riid, ppv);
}

/***************************************************************************

    Name      : LockServer

    Purpose   : Standard COM class factory entry point which will prevent
				the server from shutting down. Necessary when the caller
				keeps the class factory (through CoGetClassObject) instead
				of calling CoCreateInstance.

    Parameters:

    Returns   : HRESULT

    Comment   : 

***************************************************************************/
HRESULT CClassFactory::LockServer (BOOL flock)
{
	if (flock)
		++g_cObjects;
	else
		--g_cObjects;

	return NOERROR;
}

/***************************************************************************

	Helper functions

***************************************************************************/
/***************************************************************************

    Name      : StringFromGuid

    Purpose   : Creates a string out of a GUID

    Parameters: riid - [in]  clsid to make string out of.
				pszBuf - [in]  buffer in which to place resultant GUID

    Returns   : int - number of chars written out

    Comment   : 

***************************************************************************/
int StringFromGuid(const CLSID *priid, LPTSTR pszBuf)
{
    return wsprintf(pszBuf, TEXT("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"),
            priid->Data1, 
            priid->Data2, priid->Data3, priid->Data4[0], priid->Data4[1], priid->Data4[2], 
            priid->Data4[3], priid->Data4[4], priid->Data4[5], priid->Data4[6], priid->Data4[7]);
}

/***************************************************************************

    Name      : RegisterUnknownObject

    Purpose   : Registers a simple CoCreatable object
				We add the following information to the registry:

				HKEY_CLASSES_ROOT\CLSID\<CLSID> = <ObjectName> Object
				HKEY_CLASSES_ROOT\CLSID\<CLSID>\InprocServer32 = <path to local server>
				HKEY_CLASSES_ROOT\CLSID\<CLSID>\InprocServer32  @ThreadingModel = Apartment

    Parameters: pszObjectName - [in] Object Name
				prclsid - [in] pointer to the CLSID of the object

    Returns   : BOOL - FALSE means couldn't register it all

    Comment   : 

***************************************************************************/
BOOL RegisterUnknownObject(LPCTSTR  pszObjectName, const CLSID *prclsid)
{
    HKEY  hk = NULL, hkSub = NULL;
    TCHAR szGuidStr[GUID_STR_LEN];
    DWORD dwPathLen, dwDummy;
    TCHAR szScratch[MAX_PATH];
	BOOL bRet = FALSE;
    long  l;

    // clean out any garbage
    UnregisterUnknownObject(prclsid);

    if (!StringFromGuid(prclsid, szGuidStr))
		goto out;

	// CLSID/<class-id>
    wsprintf(szScratch, TEXT("CLSID\\%s"), szGuidStr);
    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szScratch, 0, TEXT(""), REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hk, &dwDummy);
	if (l != ERROR_SUCCESS)
		goto out;

	// CLSID/<class-id>: class name 
    wsprintf(szScratch, TEXT("%s Object"), pszObjectName);
    l = RegSetValueEx(hk, NULL, 0, REG_SZ, (BYTE *)szScratch,
                      (lstrlen(szScratch) + 1)*sizeof(TCHAR));
	if (l != ERROR_SUCCESS)
		goto out;

	// CLSID/<class-id>/InprocServer32
    l = RegCreateKeyEx(hk, TEXT("InprocServer32"), 0, TEXT(""), REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
	if (l != ERROR_SUCCESS)
		goto out;

	// CLSID/<class-id>/InprocServer32:<file name>
    dwPathLen = GetModuleFileName(g_hInst, szScratch, sizeof(szScratch)/sizeof(TCHAR));
    if (!dwPathLen)
		goto out;
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szScratch, (dwPathLen + 1)*sizeof(TCHAR));
	if (l != ERROR_SUCCESS)
		goto out;

	// CLSID/<class-id>/InprocServer32: ThreadingModel = Apartment
    l = RegSetValueEx(hkSub, TEXT("ThreadingModel"), 0, REG_SZ, (BYTE *)TEXT("Apartment"),
                      sizeof(TEXT("Apartment")));
	if (l != ERROR_SUCCESS)
		goto out;

    bRet = TRUE;

out:
	// clean the keys if we failed somewhere
	if (!bRet)
		UnregisterUnknownObject(prclsid);
    if (hk)
		RegCloseKey(hk);
    if (hkSub)
		RegCloseKey(hkSub);
    return bRet;
}

/***************************************************************************

    Name      : UnregisterUnknownObject

    Purpose   : cleans up all the stuff that RegisterUnknownObject puts in the
				registry.

    Parameters: prclsid - [in] pointer to the CLSID of the object

    Returns   : BOOL - FALSE means couldn't register it all

    Comment   : 

***************************************************************************/
BOOL UnregisterUnknownObject(const CLSID *prclsid)
{
	TCHAR szScratch[MAX_PATH];
	HKEY hk=NULL;
	BOOL f;
	long l;
	BOOL bRet = FALSE;

	// delete everybody of the form
	//   HKEY_CLASSES_ROOT\CLSID\<CLSID> [\] *
	//
	if (!StringFromGuid(prclsid, szScratch))
		goto out;

	l = RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("CLSID"), 0, KEY_ALL_ACCESS, &hk);
	if (l != ERROR_SUCCESS)
		goto out;

	// Delete the object key and subkeys
	bRet = DeleteKeyAndSubKeys(hk, szScratch);

out:
    if (hk)
		RegCloseKey(hk);
	return bRet;
}

/***************************************************************************

    Name      : DeleteKeyAndSubKeys

    Purpose   : delete's a key and all of it's subkeys.

    Parameters: hkIn - [in] delete the descendant specified
				pszSubKey - [in] i'm the descendant specified

    Returns   : BOOL - TRUE = OK

    Comment   : Despite the win32 docs claiming it does, RegDeleteKey doesn't seem to
				work with sub-keys under windows 95.
				This function is recursive.

***************************************************************************/
BOOL DeleteKeyAndSubKeys(HKEY hkIn, LPTSTR pszSubKey)
{
    HKEY  hk;
    TCHAR szTmp[MAX_PATH];
    DWORD dwTmpSize;
    long  l;
    BOOL  f;
    int   x;

    l = RegOpenKeyEx(hkIn, pszSubKey, 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    // loop through all subkeys, blowing them away.
    //
    f = TRUE;
    x = 0;
    while (f) {
        dwTmpSize = MAX_PATH;
        l = RegEnumKeyEx(hk, x, szTmp, &dwTmpSize, 0, NULL, NULL, NULL);
        if (l != ERROR_SUCCESS) break;
        f = DeleteKeyAndSubKeys(hk, szTmp);
        x++;
    }

    // there are no subkeys left, [or we'll just generate an error and return FALSE].
    // let's go blow this dude away.
    //
    RegCloseKey(hk);
    l = RegDeleteKey(hkIn, pszSubKey);

    return (l == ERROR_SUCCESS) ? TRUE : FALSE;
}