/////////////////////////////////////////////////////////////////////////////
// DynaLib.cpp | Implementation of the TCDynaLib class used to wrap the
// loading of DLL's and resolving a set of API's that may or may not exist
// at runtime.
//

#include "pch.h"
#include "..\Inc\TCLib.h"
#include "DynaLib.h"


/////////////////////////////////////////////////////////////////////////////
// TCDynaLib


/////////////////////////////////////////////////////////////////////////////
// Group=Construction / Destruction

/////////////////////////////////////////////////////////////////////////////
// When using the default constructor, the LoadLibrary member function must
// be used to load a DLL.
TCDynaLib::TCDynaLib()
  : m_hinst(NULL)
{
}

/////////////////////////////////////////////////////////////////////////////
// Parameters:
//   pszLibName - The name of the DLL to be loaded into this instance.
//
// Remarks:
// When using the second form of the constructor, use the IsLibraryLoaded
// method to determine if the library was successfully loaded.
TCDynaLib::TCDynaLib(LPCTSTR pszLibName)
  : m_hinst(NULL)
{
  // Attempt to load the specified library
  LoadLibrary(pszLibName);
}

/////////////////////////////////////////////////////////////////////////////
// If a DLL is loaded into this instance, it is freed.
TCDynaLib::~TCDynaLib()
{
  CLock lock(this);
  if (NULL != m_hinst)
    FreeLibrary(m_hinst);
}


/////////////////////////////////////////////////////////////////////////////
// Group=Attributes

/////////////////////////////////////////////////////////////////////////////
// Description: Determines if a library is loaded.
//
// Return Value: true if a DLL is loaded into this instance, otherwise false.
bool TCDynaLib::IsLibraryLoaded()
{
  CLock lock(this);
  return NULL != m_hinst;
}

/////////////////////////////////////////////////////////////////////////////
// Description: Determines if a library is loaded and a set of API exports
// have been resolved.
//
// Return Value: true if a DLL is loaded into this instance and all required
// API exports can be resolved, otherwise false.
//
// Remarks: A derived class can override this method using the following set
// of macros:
//  + TCDynaLib_BEGIN_REQUIRED_MAP
//  + TCDynaLib_REQUIRED_FUNCTION
//  + TCDynaLib_END_REQUIRED_MAP
bool TCDynaLib::IsLibraryLoadedAndResolved()
{
  return IsLibraryLoaded();
}


/////////////////////////////////////////////////////////////////////////////
// Group=Operations

/////////////////////////////////////////////////////////////////////////////
// Description: Loads the specified dynamic link library (DLL).
//
// Parameters:
//   pszLibName - The name of the DLL to be loaded into this instance.
//
// Return Value: true if the DLL was successfully loaded into this instance.
// false if an error occurred.
// 
// Remarks: Each instance can be associated with only one DLL.  Once
// LoadLibrary has been called, either directly or indirectly (using the
// second form of the constuctor), subsequent attempts to call this method
// will fail.
bool TCDynaLib::LoadLibrary(LPCTSTR pszLibName)
{
  bool bResult = false;
  __try
  {
    // Lock the object
    Lock();

    // LoadLibrary can only be successfully called once
    assert(!m_hinst);

    // Load the specified library and indicate whether it was loaded or not
    bResult = NULL != (m_hinst = ::LoadLibrary(pszLibName));
  }
  __except(1)
  {
    bResult = false;
  }

  // Unlock the object
  Unlock();

  // Return the last result
  return bResult;
}

/////////////////////////////////////////////////////////////////////////////
// Description: Attempts to resolve the specified API export name.
//
// Parameters:
//   pszFn - The API export name to be resolved. This parameter is
//     intentionally an ANSI string because the Win32 ::GetProcAddress API
//     does not accept Unicode strings.
//   chDecoration - The calling convention used to decorate the exported
// name. See the remarks for possible values.
//   cbArgs - The number of bytes of arguments passed to the export. This is
// only used when the __stdcall or __fastcall calling convention decoration
// is specified 's' or 'f'.
//
// Return Value: The function pointer of the resolved export if successful,
// otherwise NULL.
//
// Remarks: This method will always fail if no DLL has been loaded.
//
// The possible values for the /chDecoration/ parameter are as follows:
//   'c' - Decorated according to the __cdecl calling convention.
//   's' - Decorated according to the __stdcall calling convention.
//   'f' - Decorated according to the __fastcall calling convention.
//
// Typically, this function is not called directly, but rather, used by the
// TCDynaLib_Fn macros.
//
// See Also: TCDynaLib::LoadLibrary, TCDynaLib_Fn macros
FARPROC TCDynaLib::GetProcAddress(LPCSTR pszFn, char chDecoration, int cbArgs)
{
  // Apply the specified name decoration
  char* szDecorated = (char*)_alloca(strlen(pszFn) + 8);
  switch (tolower(chDecoration))
  {
    case 'c':
    {
      sprintf(szDecorated, "_%s", pszFn);
      break;
    }
    case 's':
    {
      sprintf(szDecorated, "_%s@%d", pszFn, cbArgs);
      break;
    }
    case 'f':
    {
      sprintf(szDecorated, "@%s@%d", pszFn, cbArgs);
      break;
    }
    default:
      strcpy(szDecorated, pszFn);
  }

  // Resolve the address of the specified function name
  return GetProcAddress(szDecorated);
}

FARPROC TCDynaLib::GetProcAddress(LPCSTR pszFn)
{
  // Get the library instance handle
  HINSTANCE hinst;
  {
    CLock lock(this);
    hinst = m_hinst;
  }

  // Can't resolve if library couldn't be loaded
  if (NULL == hinst)
    return NULL;

  // Resolve the address of the specified function name
  return ::GetProcAddress(m_hinst, pszFn);
}


/////////////////////////////////////////////////////////////////////////////
// Group=Implementation

/////////////////////////////////////////////////////////////////////////////
// {Secret}
// Description: Called internally when an unresolved API is invoked and
// cannot be resolved in the loaded DLL.
void TCDynaLib::FunctionNotFound(LPCSTR pszFn)
{
  LPCSTR pszFmt = "TCDynaLib: Function \"%s\" could not be resolved\n";
  LPSTR pszMsg = LPSTR(_alloca(strlen(pszFmt) + strlen(pszFn)));
  sprintf(pszMsg, pszFmt, pszFn);
  OutputDebugStringA(pszMsg);
  assert(pszMsg);
}