Creating a log file in Windows Runtime using C++


If you have a native C++ project running on Windows Runtime AND you want to do some logging then you are in for a surprise. You cannot simply open a file "C:\log.txt" and dump all your logs there. You have to use Windows Runtime APIs to create files and write to them. Since Windows Runtime APIs are mostly Async you will jump through different hoops to get there.

This class will allow you to create a temp file and it will allow you write formatted output to the file. You can do things like ".Write( L"param %d", parameter1 );"

It's very easy to use this class, simply create an instance and call Register in the constructor of your App/Class. Then simply call Write/WriteLine to write to the log file. When you are done call UnRegister to close the file. Register/UnRegister functions are there to provide your class ref counting. This will be helpful when you have one global instance of this Logger but multiple classes are using it. In this scenario you only want to close & flush the file when all classes using this logger are gone. So make sure to call Register/UnRegister in Constructor/Destructor respectively.

DebugLogger logger;
logger.Register();
logger.Write( L"param %d", 123 );
logger.UnRegister();

Full Source code:

#pragma once
/******************************************************************************
*
* Author: Asim Goheer
*
* DebugLogger will allow you to write logs to local folder in
* Windows Runtime C++ projects.
*
* This class will allow you to log everything to isolated storage(or local folder). Once all the
* logging is done it will do proper clean-up and release the file.
*
******************************************************************************/


#include <wrl/async.h>
#include "HStringOwner.h"
#include "CSOwner.h"
#include <time.h>

class DebugLogger
{
public:
   DebugLogger() : m_csNewLine( L"\r\n" )
   {
      InitializeCriticalSectionEx( &m_critSec, 0, 0 );
   }

   virtual ~DebugLogger()
   {
      DeleteCriticalSection( &m_critSec );
   }

   void Register()
   {
      CRITICAL_SECTION_OWNER( m_critSec );
      if( m_registerCount == 0 )
         Init();

      m_registerCount++;
   }

   void UnRegister()
   {
      if( !m_pDataWriter )
         return;

      Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IAsyncOperation< UINT32 > > pAsyncAction;
      m_pDataWriter->StoreAsync( &pAsyncAction );

      pAsyncAction->put_Completed( Microsoft::WRL::Callback< ABI::Windows::Foundation::IAsyncOperationCompletedHandler< UINT32 > >(
         [ this ] ( ABI::Windows::Foundation::IAsyncOperation< UINT32 >* pHandler, AsyncStatus status )
      {
         if( !pHandler || status != AsyncStatus::Completed )
            return S_OK;

         Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IAsyncOperation< bool > > pAsyncAction;
         m_pIIStream->FlushAsync( &pAsyncAction );

         pAsyncAction->put_Completed( Microsoft::WRL::Callback< ABI::Windows::Foundation::IAsyncOperationCompletedHandler< bool > >(
            [ this ] ( ABI::Windows::Foundation::IAsyncOperation< bool >* pHandler, AsyncStatus status )
         {
            if( !pHandler || status != AsyncStatus::Completed )
               return S_OK;

            CRITICAL_SECTION_OWNER( m_critSec );
            m_registerCount--;
            if( m_registerCount == 0 )
            {
               {
                  // close the file
                  Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IClosable > pICloseable;
                  HRESULT hr = m_pDataWriter.As< ABI::Windows::Foundation::IClosable >( &pICloseable );
                  if( SUCCEEDED( hr ) )
                     pICloseable->Close();
               }

               {
                  // close the file
                  Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IClosable > pICloseable;
                  HRESULT hr = m_pIIStream.As< ABI::Windows::Foundation::IClosable >( &pICloseable );
                  if( SUCCEEDED( hr ) )
                     pICloseable->Close();
               }

               {
                  // close the file
                  Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IClosable > pICloseable;
                  HRESULT hr = m_randomAccessStream.As< ABI::Windows::Foundation::IClosable >( &pICloseable );
                  if( SUCCEEDED( hr ) )
                     pICloseable->Close();
               }
            }

            return S_OK;
         } ).Get() );

         return S_OK;
      } ).Get() );
   }

   void WriteLine( wchar_t* wzString )
   {
      if( !m_pDataWriter )
         return;

      CRITICAL_SECTION_OWNER( m_critSec );

      HStringOwner csString( wzString );

      UINT32 length = 0;
      HRESULT hr = m_pDataWriter->WriteString( csString, &length );
      if( FAILED( hr ) )
         return;

      length = 0;
      hr = m_pDataWriter->WriteString( m_csNewLine, &length );
      if( FAILED( hr ) )
         return;
   }

   void Write( wchar_t* fmt, ... )
   {

      wchar_t szBuf[ 512 ];
      szBuf[ 0 ] = '\0';

      try
      {
         int nSize = 0;

         va_list args;
         va_start( args, fmt );
         nSize = _vsnwprintf_s( szBuf, _countof( szBuf ), _TRUNCATE, fmt, args ); // C4996
         va_end( args );

         WriteLine( szBuf );
      }
      catch( ... )
      {
         WriteLine( L"Exception" );
      }
   }

private:
   void Init()
   {
      {
         CRITICAL_SECTION_OWNER( m_critSec );

         if( initDone )
            return;

         initDone = true;
      }

      m_logFileCreatedEvent.Attach( CreateEventEx( nullptr, nullptr, CREATE_EVENT_MANUAL_RESET, WRITE_OWNER | EVENT_ALL_ACCESS ) );

      if( SUCCEEDED( CreateLogFile() ) )
      {
         WaitForSingleObjectEx( m_logFileCreatedEvent.Get(), INFINITE, FALSE );
      }
   }

   void CreateLogFileError( HRESULT hr )
   {
      SetEvent( m_logFileCreatedEvent.Get() );
   }

   HRESULT CreateLogFile()
   {
      Microsoft::WRL::ComPtr< ABI::Windows::Storage::IApplicationDataStatics > applicationDataStatics;
      HRESULT hr = Windows::Foundation::GetActivationFactory( Microsoft::WRL::Wrappers::HStringReference( RuntimeClass_Windows_Storage_ApplicationData ).Get(), &applicationDataStatics );
      if( FAILED( hr ) )
      {
         return hr;
      }

      Microsoft::WRL::ComPtr< ABI::Windows::Storage::IApplicationData > applicationData;
      hr = applicationDataStatics->get_Current( &applicationData );
      if( FAILED( hr ) )
      {
         return hr;
      }

      Microsoft::WRL::ComPtr< ABI::Windows::Storage::IStorageFolder > storageFolder;
      hr = applicationData->get_LocalFolder( &storageFolder );
      if( FAILED( hr ) )
      {
         return hr;
      }

      HStringOwner logFileName( L"log.txt" );

      Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IAsyncOperation< ABI::Windows::Storage::StorageFile* > > pAsyncAction;
      hr = storageFolder->CreateFileAsync( logFileName,  ABI::Windows::Storage::CreationCollisionOption::CreationCollisionOption_ReplaceExisting, &pAsyncAction );
      if( FAILED( hr ) )
      {
         return hr;
      }

      pAsyncAction->put_Completed( Microsoft::WRL::Callback< ABI::Windows::Foundation::IAsyncOperationCompletedHandler< ABI::Windows::Storage::StorageFile* > >(
         [ this ] ( ABI::Windows::Foundation::IAsyncOperation< ABI::Windows::Storage::StorageFile* >* pHandler, AsyncStatus status )
         {
            if( pHandler == nullptr || status != AsyncStatus::Completed )
            {
               CreateLogFileError( E_ABORT );
               return S_OK;
            }

            ABI::Windows::Storage::IStorageFile* pStorageFile = nullptr;
            HRESULT hr = pHandler->GetResults( &pStorageFile );
            if( FAILED( hr ) || !pStorageFile )
            {
               CreateLogFileError( hr );
               return S_OK;
            }

            Microsoft::WRL::ComPtr< ABI::Windows::Foundation::IAsyncOperation< ABI::Windows::Storage::Streams::IRandomAccessStream* > > pAsyncAction;
            hr = pStorageFile->OpenAsync(  ABI::Windows::Storage::FileAccessMode::FileAccessMode_ReadWrite, &pAsyncAction );
            if( FAILED( hr ) )
            {
               CreateLogFileError( hr );
               return S_OK;
            }

            pAsyncAction->put_Completed( Microsoft::WRL::Callback< ABI::Windows::Foundation::IAsyncOperationCompletedHandler< ABI::Windows::Storage::Streams::IRandomAccessStream* > >(
               [ this ] ( ABI::Windows::Foundation::IAsyncOperation< ABI::Windows::Storage::Streams::IRandomAccessStream* >* pHandler, AsyncStatus status )
               {
                  if( pHandler == nullptr || status != AsyncStatus::Completed )
                  {
                     CreateLogFileError( E_ABORT );
                     return S_OK;
                  }

                  HRESULT hr = pHandler->GetResults( &m_randomAccessStream );
                  if( FAILED( hr ) || !m_randomAccessStream )
                  {
                     CreateLogFileError( hr );
                     return S_OK;
                  }

                  hr = m_randomAccessStream->GetOutputStreamAt( 0, &m_pIIStream );
                  if( FAILED( hr ) || !m_pIIStream )
                  {
                     CreateLogFileError( hr );
                     return S_OK;
                  }

                  CreateDataWriter( m_pIIStream );

                  wchar_t buffer[ 256 ];
                  GetCurrentDateTime( buffer, sizeof( buffer ) / sizeof( wchar_t ) );
                  Write( L"Log file created %s", buffer );

                  return S_OK;
               } ).Get() );

            return S_OK;

         } ).Get() );

      return S_OK;
   }

   void CreateDataWriter( Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IOutputStream >& pStream )
   {
      Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IDataWriter > dataWriter;

      Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IDataWriterFactory > data_writer_factory;
      HRESULT hr = Windows::Foundation::GetActivationFactory( Microsoft::WRL::Wrappers::HStringReference( RuntimeClass_Windows_Storage_Streams_DataWriter ).Get(), &data_writer_factory );
      if( FAILED( hr ) )
      {
         CreateLogFileError( hr );
         return;
      }

      hr = data_writer_factory->CreateDataWriter( *pStream.GetAddressOf(), &m_pDataWriter );
      if( FAILED( hr ) )
      {
         CreateLogFileError( hr );
         return;
      }

      SetEvent( m_logFileCreatedEvent.Get() );
   }

   void GetCurrentDateTime( wchar_t* wzBuffer, int wcBufferLen )
   {
      time_t now = time( 0 );
      struct tm tstruct;
      localtime_s( &tstruct, &now );
      wcsftime( wzBuffer, wcBufferLen, L"%Y-%m-%d.%X", &tstruct );
   }


private:
   CRITICAL_SECTION  m_critSec;
   HStringOwner m_csNewLine;

   Microsoft::WRL::Wrappers::Event m_logFileCreatedEvent;
   Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IDataWriter > m_pDataWriter;
   Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IRandomAccessStream > m_randomAccessStream;
   Microsoft::WRL::ComPtr< ABI::Windows::Storage::Streams::IOutputStream > m_pIIStream;

   int m_registerCount { 0 };
   bool initDone { false };
};


Comments (3)
  1. Anonymous says:

    Asim,

    Thank you for this educational and valuable code sample.  Although my WRL needs were different, your code enabled me to figure out how to write code to access WinRT interfaces through asynchronous APIs.  Many thanks and keep up the nice work!

  2. Anonymous says:

    I really appreciate you presenting this code to us, but I have a stubborn problem about C++/WRL code. Basically whenever it comes to using an event to wait for the continuations to finish (like you have done) the code blocks there. I have just pasted your code above into a plain C++ dll and used it in a Win8.1 test in visual studio. The problem showed itself again, with the code jamming permanently at WaitForSingleObjectEx(m_logFileCreatedEvent.Get(), INFINITE, FALSE); . It's as if there is only one thread in play. Strangely the problem is absent if I start windows runtime in a plain old .exe console app and run the same code from there. Please comment if you know anything about this.

  3. Anonymous says:

    Follow-up to my other comment: I don't think that your code is working, except in a plain old console application (for some reason). The callbacks execute on the same thread because they are not agile objects, and the code blocks.  You need to make them agile like this:

    Callback<Implements<RuntimeClassFlags<ClassicCom>, IWorkItemHandler, FtmBase>>

    as explained here:

    social.msdn.microsoft.com/…/thread-pool-task-blocking-ui-thread

Comments are closed.

Skip to main content