// NativeLib DLL (for DotNetHelpers) | C+- (SysRoutine) | Author: Sergei Y.Kitáev
#include "Precompiled.h"
#include "SharedIncludes.h"
#include <HelpersRT.h>
//==================================================================================================
// MODULE ENTRY POINT (LINK-OPEN FUNCTION):
BOOL APIENTRY DllMain( HMODULE hModule, DWORD dwReason, PVOID pvReserved )
{
   return DLL::Main( hModule, dwReason, pvReserved );
}
//==================================================================================================
volatile HMODULE DLL::_ModuleHandle_;
bool DLL::_InitCSInitialized_;
//==================================================================================================

bool DLL::Main( HMODULE hModule, DWORD dwReason, PVOID pvReserved )
{
   if (!( _ModuleHandle_ = hModule ))
      return false;
   switch ( dwReason )
   {
   case DLL_PROCESS_ATTACH:
      $RT::SpecifyDefaultModuleName( LIB::dll_name );
      if ( !AllocateTLS( LIB::error_pointer_tls ) )
         return false;
      if ( !InitializeCS( LIB::initialization_cs ) )
         return false;
      _InitCSInitialized_ = true;
      goto PREPARE_THREAD;
   case DLL_PROCESS_DETACH:
      LIB::is_initialized = false;
      $RT::Finalize();
      if ( _InitCSInitialized_ )
         $CS::Delete( LIB::initialization_cs );
      $TLS::Free($, &LIB::error_pointer_tls );
      break;
   case DLL_THREAD_ATTACH:
      if ( !$RT::IsInitialized() )
         break; // successful thread start without NativeLib initialization
      goto PREPARE_THREAD;
   case DLL_THREAD_DETACH:
      FinalizeThread();
      break;
   default:
      return false;
   } // switch
   return true;
PREPARE_THREAD:
   if ( !PrepareThread($, dwReason, nullptr ) )
   {
      FinalizeThread();
      return false;
   }
   return true;
}

//--------------------------------------------------------------------------------------------------

bool DLL::PrepareThread($AM, DWORD dwReason, PWSTR* pErrorMessagePtr )
{
   bool isDllEntryReason =
      dwReason != (DWORD)-1;
   if ( !isDllEntryReason )
      $VERIFY_POINTER( pErrorMessagePtr );
   PWSTR &errorMessagePtr = *pErrorMessagePtr;
   if ( dwReason == DLL_THREAD_ATTACH )
   {
      $DynText errorMessage;
      if ( !$Thread::InitializeThread($, errorMessage ) )
         return TraceError($, dwReason, errorMessage.T($) );
   }
   auto errorDataPtr = $memory::CleanAllocNS<NLError>($);
   if ( !errorDataPtr )
   {
      PCWSTR errorText = $StaticMessages::MemoryAllocationFailure();
      if ( isDllEntryReason )
         return TraceError($, dwReason, errorText );
      errorMessagePtr = HelpersRT::DuplicateSZ($ci, errorText );
      return false;
   }
   $TLS::SetPointer($, &LIB::error_pointer_tls, errorDataPtr );
   NLFailure::ResetError( dwReason == DLL_THREAD_ATTACH );
   return true;
}

//--------------------------------------------------------------------------------------------------

void DLL::FinalizeThread()
{
   if ( !$TLS::IsInitialized($, &LIB::error_pointer_tls ) )
      return;
   auto errorDataPtr = $tls::GetPointer<NLError>($, &LIB::error_pointer_tls );
   if ( errorDataPtr )
   {
      if ( errorDataPtr->IsAllocatedMsg )
         $Memory::Free( errorDataPtr->MessagePtr );
      $Memory::Free( errorDataPtr );
   }
   $Thread::FinalizeThread();
}

//==================================================================================================

void DLL::TraceInformation($A, DWORD dwReason, PCWSTR message )
{
   $TracerNS::TraceInformationFmt($,
      L"%s (%s - %s).", message,
      LIB::dll_name, EntryReasonIdName( dwReason ) );
}
bool DLL::TraceError($A, DWORD dwReason, PCWSTR message ) $ret_false
{
   $TracerNS::TraceErrorFmt($,
      L"%s (%s - %s).", message,
      LIB::dll_name, EntryReasonIdName( dwReason ) );
   return false;
}
/*
void DLL::TraceInformationFmt($A, DWORD dwReason, PCWSTR messageFmt, ... )
{
   $VERIFY_POINTER_ARG( messageFmt );
   va_list argList;
   PWSTR pMessage = $SZ::FormatAllocVANS($ci, $VA_COMPLEMENT( messageFmt, argList ) );
   va_end( argList );
   $AUTO_FREE( pMessage );
   if ( pMessage )
      TraceInformation($, dwReason, pMessage );
}
bool DLL::TraceErrorFmt($A, DWORD dwReason, PCWSTR messageFmt, ... ) $ret_false
{
   $VERIFY_POINTER_ARG( messageFmt );
   va_list argList;
   PWSTR pMessage = $SZ::FormatAllocVANS($ci, $VA_COMPLEMENT( messageFmt, argList ) );
   va_end( argList );
   $AUTO_FREE( pMessage );
   if ( pMessage )
      TraceError($, dwReason, pMessage );
   return false;
}
*/
PCWSTR DLL::EntryReasonIdName( DWORD dwReason )
{
   switch ( dwReason )
   {
   case DLL_PROCESS_ATTACH: return $STRINGIZE( DLL_PROCESS_ATTACH );
   case DLL_PROCESS_DETACH: return $STRINGIZE( DLL_PROCESS_ATTACH );
   case DLL_THREAD_ATTACH : return $STRINGIZE( DLL_THREAD_ATTACH );
   case DLL_THREAD_DETACH : return $STRINGIZE( DLL_THREAD_DETACH );
   default: return L"DLL_???";
   }
}

//--------------------------------------------------------------------------------------------------

bool DLL::AllocateTLS(DWORD& idx $out)
{
   if ( !$TLS::AllocateNS($, &idx ) )
      return TraceError($, DLL_PROCESS_ATTACH, $TEXT(
         L"Can not allocate TLS.",
         L"Не удаётся выделить память под TLS-слот." ));
   return true;
}

bool DLL::InitializeCS(CRITICAL_SECTION& criticalSection)
{
   if ( !$CS::Initialize( criticalSection ) )
      return TraceError($, DLL_PROCESS_ATTACH, $TEXT(
         L"Can not initialize critical section.",
         L"Не удаётся проинициализировать критическую секцию." ));
   return true;
}

//==================================================================================================
