Skip to content

Latest commit

 

History

History
1957 lines (1401 loc) · 57 KB

Documentation.md

File metadata and controls

1957 lines (1401 loc) · 57 KB

UlibCpp Documentation

This file contains the documentation for the UlibCpp library. Note that the source code shown may not represent the actual source code. Please refer to the actual files when details are unclear.

Please use the table of contents to navigate the document:

Configuration
Core header
Logging
Stream
Threading
Win32 API

Configuration

These header files simplify the configuration of project types, e.g. WTL projects.

#include <ulib/config/Common.hpp>

Defines the following:

  • macro UNUSED(x): Can be used in function signatures to suppress the warning about unused parameters, e.g.:

    void DoSomething(int arg1, CString arg2)
    {
        UNUSED(arg2);
    }
    
  • sizeof_array(x): determines the size of the array x, in number of elements:

    int allItems[] = { 1, 2, 3, 4, 5 };
    for (size_t index = 0; index < sizeof_array(allItems); index++) ...
    

Common.hpp also includes:

#include <ulib/config/CompileTimeWarningError.hpp>

Defines the following:

  • macro ULIB_COMPILE_WARN(text): Outputs a warning in the compiler output; use it like this:

    #pragma ULIB_COMPILE_WARN("Check warning here")
    
  • macro ULIB_COMPILE_ERROR(text): Outputs an error in the compiler output; use it like this:

    #pragma #pragma ULIB_COMPILE_ERROR("C9999", "Don't forget to code here!")
    

#include <ulib/config/Win32.hpp>

Includes all sorts of Win32 API headers. This is the base of Atl.hpp. Check the file contents for details.

#include <ulib/config/Atl.hpp>

Includes for an ATL based project. This is the base of Wtl.hpp. Check the file contents for details.

#include <ulib/config/Wtl.hpp>

Includes for a WTL based project. Most WTL projects generated by the WTL wizard can just include this file instead of the many ATL and WTL files in the generated stdafx.h file. The necessary order of ATL and WTL headers are ensured. Check the file contents for details.

#include <ulib/config/BoostAsio.hpp>

Includes the Boost header <boost/asio.hpp> and prevents default linking to some Boost libraries; also disables warnings from the included headers. The header also prevents auto-generating a WinSock-init class, which automatically pulls in a dependency to the winsock2.dll.

#include <ulib/config/BoostDateTime.hpp>

Includes the Boost header <boost/date_time.hpp>, disables warnings and also prevents linking to the Boost.DateTime library.

#include <ulib/config/Android.hpp>

Defines some types like LPCTSTR and macros like ATLASSERT, ATLTRACE, etc., to be able to compile code that uses ATL or WTL data types under an Android C++ project type. This header is experimental and currently misses a CString implementation.

Core header

The root ulib include folder contains some often used classes.

#include <ulib/ulib.hpp>

This include file includes all other headers. Only use this if you use almost all of the library. Instead include single headers when you use specific classes.

Miscellaneous

Crash reporting

#include <ulib/CrashReporter.hpp>

Provides a single static function that initializes the crash reporter:

static void CrashReporter::Init(
   const CString& appName, const CString& basePath,
   T_fnShowCrashDialog fnShowCrashDialog = T_fnShowCrashDialog());

The crash reporter catches any unhandled C++ exceptions and calls to std::terminate() and writes a minidump crash dump file (extension .mdmp) to a folder. The minidump file is stored in the basePath folder and the appName is used as prefix for the filename. An optional function with the following signature can be passed:

void ShowCrashDialog(LPCTSTR filename);

The function is called after writing the minidump crash dump file in order to show the user a dialog. Depending on the cause of the crash, the function may not be called at all (e.g. due out of memory errors).

Dynamic library loading

#include <ulib/DynamicLibrary.hpp>

Provides a RAII helper class to load dynamic libraries (DLLs) and get function pointers exported by it:

class DynamicLibrary
{
public:
   /// ctor; loads module
   DynamicLibrary(LPCTSTR moduleFilename);
   /// dtor; frees module again
   ~DynamicLibrary();

   /// checks if library is loaded
   bool IsLoaded() const;

   /// checks if function with given name is available
   bool IsFunctionAvail(LPCSTR functionName) const;

   /// returns function with given function name and given function signature
   template <typename Signature>
   Signature GetFunction(LPCSTR functionName) const;
};

Exception handling

#include <ulib/Exception.hpp>

This header contains an Exception class that is used whenever ulib throws an exception and is derived from std::exception.

class Exception : public std::exception
{
public:
   Exception(LPCSTR sourceFile, UINT sourceLine);
   Exception(const CString& message, LPCSTR sourceFile, UINT sourceLine);

   CString Message() const;
   CString SourceFile() const;
   UINT SourceLine();
};

The exception message can also be retrieved with the base classes what() method.

File finder

#include <ulib/FileFinder.hpp>

The class FileFinder helps with enumerating files. It has a static convenience method that does everything:

static std::vector<CString> FindAllInPath(
   const CString& path, const CString& fileSpec, bool findFolders, bool recursive);

The fileSpec may contain wildcards, like *.*, *.png or IMG????.jpg. The flag findFolders indicates if folders should be found instead of files. The flag recursive specifies if folders should be recursively searched.

The class can also be used manually:

class FileFinder
{
public:
   FileFinder(const CString& baseFolder, const CString& fileSpec);

   /// indicates if any files were found
   bool IsValid() const;

   /// returns if the current file entry is a dot file, "." or ".."
   bool IsDot() const;

   /// returns if current file entry is a file
   bool IsFile() const;

   /// returns if current file entry is a folder
   bool IsFolder() const;

   /// returns complete filename of current file entry
   CString Filename() const;

   /// retrieves next file entry
   bool Next();
};

Constructing the class already starts the search, so it's best to use it in a do-while loop:

FileFinder finder{ baseFolder, "*.*" };
if (finder.IsValid())
do
{
   if (finder.IsDot())
      continue;
   ATLTRACE(_T("path: %s\n"), finder.Filename().GetString());
} while (!finder.Next());

IoC Container

#include <ulib/IoCContainer.hpp>

This class provides an IoC (inversion of control) container that stores references to objects in a central place.

class IoCContainer
{
public:
   /// returns current instance of IoC container
   static IoCContainer& Current();

   /// registers reference for class
   template <typename TClass>
   void Register(std::reference_wrapper<TClass> ref);

   /// resolves class to object
   template <typename TInterface>
   TInterface& Resolve();
};

At the start of the app, Register() any objects that should be globally available, and resolve them using Resolve() at a later time.

Observer pattern

#include <ulib/Observer.hpp>

This header provides an implementation of the Observer pattern, much like the C# event functionality. The Subject is the source of an event being sent to zero, one or more observer functions.

template <typename T>
class Subject
{
public:
   /// function type to be called
   typedef std::function<T> T_fnSubject;

   /// adds new observer; new ID is returned
   int Add(std::function<T> fnObserver);

   /// adds new observer
   Subject<T>& operator+=(std::function<T> fnObserver);

   /// removes an observer by ID
   void Remove(int id);

   /// calls subject with zero arguments
   void Call();

   /// calls subject with one arguments
   template <typename T1>
   void Call(T1 param1);

   /// calls subject with two arguments
   template <typename T1, typename T2>
   void Call(T1 param1, T2 param2);

   /// removes all observer
   void Clear();
};

T is the function signature of the observer functions. Define a subject like this:

Subject<void(const Widget& senderObject, const EventArgs& eventArgs)> m_buttonClicked;

In the code, set up an observer function or lambda:

m_buttonClicked += [] (senderObject, eventArgs) { /* do something here */ }

The Add() method returns an int ID that can be used to remove observers again.

Path handling

#include <ulib/Path.hpp>

This header provides the Path class with static methods to manipulate folder or file paths.

class Path
{
public:
   /// returns filename and extension
   static CString FilenameAndExt(const CString& path);

   /// returns filename without extension
   static CString FilenameOnly(const CString& path);

   /// returns extension only, with leading dot
   static CString ExtensionOnly(const CString& path);

   /// returns folder name, without filename, but ending slash
   static CString FolderName(const CString& path);

   /// returns short path name (filename in 8.3 format); file must actually exist
   static CString ShortPathName(const CString& path);

   /// make path relative to the given root path and returns it
   static CString MakeRelativeTo(const CString& path, const CString& rootPath);

   /// returns if stored path is a relative path
   static bool IsRelative(const CString& path);

   /// returns if path represents a file and if it exists
   static bool FileExists(const CString& path);

   /// returns if path represents a folder and if it exists
   static bool FolderExists(const CString& path);

   /// canonicalizes path by removing '..', etc.
   static bool Canonicalize(CString& path);

   /// adds a backslash at the end of the path
   static void AddEndingBackslash(CString& path);

   /// combine both path parts and return new path
   static CString Combine(const CString& part1, const CString& part2);

   /// returns the common root path of both given paths; returns empty string when there's no common root
   static CString GetCommonRootPath(const CString& path1, const CString& path2);

   /// returns special folder; see CSIDL_* constants
   static CString SpecialFolder(int csidl);

   /// returns the windows folder
   static CString WindowsFolder();

   /// returns the temp folder
   static CString TempFolder();

   /// returns file name of given module name (e.g. kernel32.dll); nullptr means the currently running .exe module
   static CString ModuleFilename(HMODULE moduleHandle = nullptr);

   /// creates a directory, possibly also creating non-existent parent directories
   static bool CreateDirectoryRecursive(LPCTSTR directoryName);

   // public members

   /// path separator string
   static const TCHAR Separator[2];

   /// path separator character
   static const TCHAR SeparatorCh = _T('\\');
}

Singleton

#include <ulib/Singleton.hpp>

Provides a thread-safe singleton pattern implementation.

class WidgetManager : public Singleton<WidgetManager>
{
   // ...
};

The singleton instance can then be accessed with:

WidgetManager& manager = WidgetManager::Instance();

In a .cpp file, declare the implementation of the singleton like this:

IMPLEMENT_SINGLETON(WidgetManager)

This defines the static variables needed to provide the singleton instance.

System exception

#include <ulib/SystemException.hpp>

An exception that additionally stores a Win32 error code:

class SystemException : public Exception
{
public:
   /// ctor
   SystemException(const CString& message, DWORD win32ErrorCode, LPCSTR sourceFile, UINT sourceLine);

   /// returns win32 error code
   DWORD Win32Error() const
};

UTF-8 conversion

#include <ulib/UTF8.hpp>

This header provides UTF-8 conversion functions:

/// converts string to UTF-8 encoding
void StringToUTF8(const CString& text, std::vector<char>& utf8Buffer);

/// converts from UTF-8 encoded text to CString
CString UTF8ToString(const char* utf8Text);

The UlibCpp library is compiled using the UNICODE define, so CString can always hold the UTF-8 encoded string.

Command line parsing

#include <ulib/CommandLineParser.hpp>

The CommandLineParser simplifies parsing command line parameters and also supports parsing arguments in double quotes, e.g. path names with spaces.

class CommandLineParser
{
public:
   /// parses single long command line string
   CommandLineParser(const CString& commandLine = GetCommandLine());

   /// parses _tmain parameter
   CommandLineParser(int argc, TCHAR* argv[]);

   /// returns next parameter
   bool GetNext(CString& nextParameter);
};

The first parameter is always the executable's file path. The class can easily be used in a while loop:

CommandLineParser parser;
CString parameter;
while (parser.GetNext(parameter))
{
   // do something with parameter
}

Program options

#include <ulib/ProgramOptions.hpp>

The ProgramOptions class extends the command line parsing and provides a way to specify all options of a program and then lets the class parse the command line by itself.

As a first step, register an output handler:

ProgramOptions options;
options.RegisterOutputHandler([] (const CString& text) { _tprintf(text); });

You can also use the provided OutputConsole function:

options.RegisterOutputHandler(&ProgramOptions::OutputConsole);

You can also decide to collect the output in a string and show a message box, or use any other output mechanism.

If you want to show the user help text when /h, /? or --help is specified, use:

options.RegisterHelpOption();

Now register one or more parameters that you want to handle. This call registers an option -i with long variant --ignore-case, a help text and without further arguments:

options.RegisterOption(_T("i"), _T("ignore-case"),
   _T("Ignores case on comparisons"),
   [] () { m_ignoreCase = true; return true; });

This call registers an option with a single argument:

options.RegisterOption(_T("o"), _T("output"),
   _T("Specifies the output folder"),
   [] (const CString& arg) { m_outputFolder = arg; return true; });

This registers an option with multiple arguments:

options.RegisterOption(_T("d"), _T("define"),
   _T("Defines a key with a value (2 arguments)"),
   2,
   [] (const std::vector<CString>& args) { /* do something with args */ });

The first two RegisterOptions() calls can also be written as:

options.RegisterOption(_T("i"), _T("ignore-case"),
   _T("Ignores case on comparisons"),
   m_ignoreCase);

options.RegisterOption(_T("o"), _T("output"),
   _T("Specifies the output folder"),
   m_outputFolder);

The m_ignoreCase and m_outputFolder variables are taken by reference and are assigned appropriately while parsing.

Finally, call one of the Parse() functions:

/// parses command line options, C-style
void Parse(int argc, _TCHAR* argv[]);

/// parses command line options, Win32-style
void Parse(LPCTSTR commandLine);

When the user specified one of the help options, a help text is output, based on the help texts specified in the RegisterOption() calls. You can check for the help option and quit the app without starting up by checking IsSelectedHelpOption():

if (options.IsSelectedHelpOption())
   return 0;

Date/time handling

These headers define date/time related classes.

#include <ulib/DateTime.hpp>

The class DateTime defines a point in time, with date and time parts. The point in time is time zone agnostic and usually stored as local time. Be sure to store dates as UTC in external systems (like databases) that may be running in different time zones.

A DateTime object can be created in several ways:

/// date/time status values
enum T_enStatus { valid = 0, invalid, min, max };

/// default ctor; initialized with invalid status
DateTime();

/// ctor; takes date/time components
DateTime(unsigned int year, unsigned int month, unsigned int day,
   unsigned int hour, unsigned int minute, unsigned int second,
   unsigned int millisecond = 0);

/// ctor; initialize with min or max status
DateTime(T_enStatus status);

/// returns current date/time
static DateTime Now();

/// returns current date with time part set to 00:00:00
static DateTime Today();

/// returns max time value
static DateTime MaxValue() { return DateTime(DateTime::max); }

/// returns min time value
static DateTime MinValue() { return DateTime(DateTime::min); }

The date can also be modified using these methods:

/// sets date/time components
void SetDateTime(unsigned int year, unsigned int month, unsigned int day,
   unsigned int hour, unsigned int minute, unsigned int second,
   unsigned int millisecond = 0);

/// parses ISO 8601 formatted date/time
void ParseISO8601(const CString& iso8601Timestamp);

The properties of the date/time point can also be accessed with these getters:

/// returns date/time status
T_enStatus Status() const;
/// returns year
unsigned int Year() const;
/// month of year; 1 = january
unsigned int Month() const;
/// day of month (1-31)
unsigned int Day() const;

/// hour in day (0-23)
unsigned int Hour() const;
/// minute in hour (0-59)
unsigned int Minute() const;
/// second in minute (0-59)
unsigned int Second() const;
/// millisecond in second (0-999)
unsigned int Millisecond() const;

/// day of week; 0: sunday; ... 6: saturday
unsigned int DayOfWeek() const;
/// day of year; 1-based
unsigned int DayOfYear() const;
/// returns time part of date
TimeSpan TimeOfDay();

There are also operators for addition and subtraction of TimeSpan and comparison operators with DateTime, not shown here.

Formatting dates as text can be done with these methods:

/// ISO 8601 format
enum T_enISO8601Format
{
   formatY,       // year only
   formatYM,      // year and month
   formatYMD,     // year and month
   formatYMD_HM_Z,   // year, month, day, hour, minute and timezone offset
   formatYMD_HMS_Z,  // year, month, day, hour, minute, second and timezone offset
   formatYMD_HMSF_Z, // full date/time with fraction and timezone offset
};

/// formats date/time using ISO 8601 format
CString FormatISO8601(T_enISO8601Format enFormat = formatYMD_HMS_Z, bool bBasic = false,
    const TimeZone& tz = TimeZone::System()) const;

/// formats date/time with given format, see _tcsftime
CString Format(const CString& format, const TimeZone& tz = TimeZone::System()) const;

Note: Use UTC timezone if the date should be stored in external systems (like databases) when it could be used in multiple time zones.

#include <ulib/TimeSpan.hpp>

The class TimeSpan defines a time span that can be positive or negative.

A TimeSpan object can be created in several ways:

/// time span status values
enum T_enStatus { valid = 0, invalid, min, max };

/// default ctor
TimeSpan();

/// ctor; takes date/time span components
TimeSpan(int hours, int minutes, int seconds, int milliseconds = 0);

/// ctor; initialize with min or max status only
TimeSpan(T_enStatus status);

/// sets time span components
void SetDateTimeSpan(int hours, int minutes, int seconds, int milliseconds);

The properties of the time span can also be accessed with these getters:

/// returns date/time status
T_enStatus Status() const;

/// component hours in span (-23 to 23)
int Hours() const;
/// component minutes in span (-59 to 59)
int Minutes() const;
/// component seconds in span (-59 to 59)
int Seconds() const;
/// component milliseconds in span (-999 to 999)
int Milliseconds() const;

/// span in hours (about -8.77e7 to 8.77e6)
double TotalHours() const;
/// span in minutes (about -5.26e9 to 5.26e9)
double TotalMinutes() const;
/// span in seconds (about -3.16e11 to 3.16e11)
double TotalSeconds() const;
/// span in milliseconds
double TotalMilliseconds() const;

There are also operators for addition and subtraction of TimeSpan and comparison operators with TimeSpan, not shown here.

Formatting time spans as text can be done with these methods:

/// time span format
enum T_enTimeSpanFormat
{
   formatHMS,     // "hh:mm:ss" format
   formatISO8601, // ISO 8601, "PTxxHxxMxxS" format
};

/// formats time span using specified format
CString Format(T_enTimeSpanFormat format = formatHMS) const;

/// formats time span using given format, see _tcsftime
CString Format(LPCTSTR format) const;

#include <ulib/TimeZone.hpp>

The class TimeZone defines a time zone that can be used when formatting DateTimeobjects as text.

Time zones can be accessed with these static methods:

/// returns UTC timezone
static TimeZone TimeZone::UTC();

/// returns current system's timezone
static TimeZone TimeZone::System();

/// enumerates all timezones in the system
static std::vector<TimeZone> TimeZone::EnumerateTimezones();

The TimeZone class can be accessed for further infos:

class TimeZone
{
public:
   /// returns timezone name during daylight savings time
   CString DaylightName() const;

   /// returns timezone name during standard time
   CString StandardName() const;

   /// returns display name for timezone when enumerating all system timezones
   CString DisplayName() const;

   /// gets UTC offset for given date/time
   TimeSpan GetUtcOffset(const DateTime& dt) const;

   /// returns if date/time is in daylight savings time
   bool IsDaylightSavingTime(const DateTime& dt) const;
};

Timer classes

#include <ulib/Timer.hpp>

The Timer class can be used to measure running time, e.g. for statistics or debugging purposes. The class has a precision of about 15 ms.

class Timer
{
public:
   /// ctor
   Timer();

   /// starts timer; timer must be stopped or reset
   void Start();

   /// stops timer; timer must be started
   void Stop();

   /// resets timer; timer must be stopped
   void Reset();

   /// restarts timer by resetting and starting again
   void Restart();

   /// returns elapsed time since Start() was called, in seconds
   double Elapsed() const;

   /// returns total elapsed time in seconds
   double TotalElapsed() const;

   /// returns if timer is running
   bool IsStarted() const;
};

Elapsed() only considers the last Start() call, whereas TotalElapsed() also returns the accumulated time from previous start/stop cycles.

#include <ulib/HighResolutionTimer.hpp>

The HighResolutionTimer has the exact same interface as the Timer class, but has a higher resolution, as it uses the CPU's Performance Counters.

#include <ulib/TraceOutputStopwatch.hpp>

Both Timer and HighResolutionTimer classes can be used as types in the TraceOutputStopwatch class that automatically provides trace output. Use it like this:

TraceOutputStopwatch<HighResolutionTimer> renderStopwatch{ _T("renderTime") };

The ctor and the dtor of the class both call ATLTRACE() to output start and stop of the timer, and the elapsed time in a readable format.

Logging

The log include folder contains classes for logging. It's not as thorough as e.g. log4cxx, but often enough for smaller applications.

Logging involves the following concepts:

  • Log level: Determines how severe a log message is
  • Log message: The message text itself
  • Log category: A tree-like hierarchy of loggers that determine what messages get logged where
  • Layout: Determines how the log text is to be formatted
  • Appender: Determines where the log text is going to be written or sent

Log categories are organized like a tree, and each tree node and leaf are an instance of a Logger class. Here are some log categories, the tree "branches" separated with dots:

client
client.renderer
client.import
client.import.audio
client.import.models

The logger client sits at the root and has the loggers renderer and import. The import logger again has two loggers audio and models. Depending on the use cases, I can now log to one of the listed loggers:

LOG_DEBUG(_T("rendering finished"), _T("client.renderer"));

Loggers are created on-the-fly if they are not found in the (root) logger's tree. You can get a logger directy, e.g. like this:

auto root = Log::Logger::GetRootLogger();
auto logger = root->GetLogger("client.renderer");

Each logger can have one or more Appender that determines what to do with log messages that are logged to this logger. Each appender has a Layout object that determines how to format the log message. Setting this up on a specific logger looks like this:

auto patternLayout = std::make_shared<Log::PatternLayout>(_T("%F(%L): log [%p] %m"));
auto debugAppender = std::make_shared<Log::OutputDebugStringAppender>();
debugAppender->Layout(patternLayout);

auto rendererLogger = Log::Logger::GetRootLogger()->GetLogger("client.renderer")
rendererLogger->AddAppender(debugAppender);

Note that a logger, and also the root logger, has no appender by default. You have to configure this before you can see any logging output.

The default behavior of each logger is to also send the log event to the parent logger, in this case client, which may also log the message. This can be changed on a by-logger basis and is called "additivity". For example, the client.renderer messages shouldn't reach the client logger, since they are only for debug console output:

auto logger = Log::Logger::GetRootLogger()->GetLogger("client.renderer");
logger->Additivity(false);

Each logger can also be configured to only log messages of a specific severity or higher. The client.import (and all of those below) may only get LOG_ERROR messages or above, to not degrade performance:

auto logger = Log::Logger::GetRootLogger()->GetLogger("client.import");
logger->Level(Log::Level::error);

#include <ulib/log/Log.hpp>

This header defines some common types used for logging, among them the log levels:

namespace Log
{
   enum Level
   {
      debug = 0,
      info,
      warn,
      error,
      fatal,
      none,
   };
}

Logger class

#include <ulib/log/Logger.hpp>

This header files defines the Logger class that implements a logger that can be part of a logger hierarchy.

namespace Log
{
   class Logger
   {
   public:
      // properties

      /// returns logger level
      Level Level() const;

      /// returns additivity flag
      bool Additivity() const;

      /// returns parent logger
      LoggerPtr Parent();

      /// returns full logger name
      CString Name();

      /// sets logger level
      void Level(Log::Level level);

      /// sets additivity
      void Additivity(bool additivity);

      // methods

      /// returns root logger
      static LoggerPtr GetRootLogger();

      /// returns logger with given name
      static LoggerPtr GetLogger(const CString& name);

      /// adds appender to this logger
      void AddAppender(AppenderPtr appender);

      /// removes all appender from this logger
      void RemoveAllAppender();

      /// removes given appender
      void RemoveAppender(AppenderPtr appender);
   };
}

The header file also defines macros used for logging:

LOG_DEBUG(msg, cat)
LOG_INFO(msg, cat)
LOG_WARN(msg, cat)
LOG_ERROR(msg, cat)
LOG_FATAL(msg, cat)

The msg parameter is a text that should be logged, and cat is a category to be logged under. The category determines where in the logger hierarchy the logged text is sent to.

#include <ulib/log/LoggingEvent.hpp>

The LoggingEvent class encapsulates properties of the logging event. Normally you won't get in touch with the class. See the header file for details.

Layout class

#include <ulib/log/Layout.hpp>

The Layout class formats the logging event for output in an appender. You can derive from this class in order to have a custom layout.

namespace Log
{
   class Layout
   {
   public:
      virtual ~Layout();

      /// appends header to output; override when necessary
      virtual void AppendHeader(CString& outputText);

      /// appends footer to output; override when necessary
      virtual void AppendFooter(CString& outputText);

      /// formats logging event to string
      virtual void Format(CString& outputText, const LoggingEventPtr loggingEvent) = 0;
   };
}

Only the Format() method is mandatory to override. AppendHeader() and AppendFooter() methods are called for appender that want to put a header and/or a footer line in a log file.

SimpleLayout class

#include <ulib/log/SimpleLayout.hpp>

The SimpleLayout class is derived from the Layout class and simply formats the output text using the log level in capital letters, a dash and the log text, e.g.:

DEBUG - Render thread took 12.3 ms.

PatternLayout class

#include <ulib/log/PatternLayout.hpp>

The PatternLayout class is derived from the Layout class and lets you specify a printf-like pattern to specify how the log text should be formatted.

A pattern string consists of text and pattern specifiers that start with the % char and end with a pattern character, similar to the printf notation. The following characters have meaning:

  • c: outputs logger name
  • d: date in ISO 8601 format
  • F: source filename where log message occured
  • l: not supported
  • L: source file line where log message occured
  • m: log message; not supported
  • n: platform-specific newline character
  • p: log level (priority)
  • r: not supported
  • t: thread id
  • %: percent sign

The following format modifiers are allowed:

  • The - adds left justification to the string
  • Next comes the minimum field width (excess space is padded)
  • Optional . (dot)
  • Next comes the maximum field width; if string is larger, the last n characters are shown

The class only has a ctor that lets you specify the pattern:

namespace Log
{
   class PatternLayout : public Layout
   {
   public:
      PatternLayout(const CString& pattern);
   };
}

Appender class

#include <ulib/log/Appender.hpp>

The Appender class is the base class for all appenders that can write the formatted log text to a target. Each appender can have a different Layout class set. Override this class to implement your own appender.

namespace Log
{
   class Appender
   {
   public:
      virtual ~Appender();

      /// returns layout object being used
      LayoutPtr Layout();

      /// sets new layout object
      void Layout(LayoutPtr layout);

      /// appends logging event to output
      virtual void DoAppend(const LoggingEventPtr loggingEvent) = 0;
   };
}

ConsoleAppender class

#include <ulib/log/ConsoleAppender.hpp>

The ConsoleAppender is derived from the Appender class and lets you write the log text to the console using printf.

namespace Log
{
   class ConsoleAppender : public Appender
   {
   public:
      virtual ~ConsoleAppender();
      virtual void DoAppend(const LoggingEventPtr loggingEvent);
   };
}

OutputDebugStringAppender class

#include <ulib/log/OutputDebugStringAppender.hpp>

The OutputDebugStringAppender is derived from the Appender class and uses the Win32 API function OutputDebugString to write the log text to the debugger. The log text can also be intercepted with tools like the SysInternal's Process Monitor.

namespace Log
{
   class OutputDebugStringAppender : public Appender
   {
   public:
      virtual ~OutputDebugStringAppender();
      virtual void DoAppend(const LoggingEventPtr loggingEvent);
   };
}

TextStreamAppender class

#include <ulib/log/TextStreamAppender.hpp>

The TextStreamAppender is derived from the Appender class and can write the log text to a Stream::ITextStream instance. See the "Streams" chapter for more infos on the ITextStream interface.

namespace Log
{
   class TextStreamAppender : public Appender
   {
   public:
      TextStreamAppender(std::shared_ptr<Stream::ITextStream> textStream);
      virtual ~TextStreamAppender();
      virtual void DoAppend(const LoggingEventPtr loggingEvent);
   };
}

AndroidLogcatAppender class

#include <ulib/log/AndroidLogcatAppender.hpp>

The AndroidLogcatAppender is derived from the Appender class and can write the log text to the Android logcat facility. This class can only be used when compiling and linking an Android C++ project type.

namespace Log
{
   class AndroidLogcatAppender : public Appender
   {
   public:
      virtual ~AndroidLogcatAppender();
      virtual void DoAppend(const LoggingEventPtr loggingEvent);
   };
}

Stream

The stream include folder contains classes for handing binary files, text files and other streams. The classes are inspired by C#'s System.IO.Stream classes.

IStream interface

#include <ulib/stream/IStream.hpp>

This header defines the IStream interface that describes a stream that can be read from, written to and seeked on.

namespace Stream
{
   class IStream
   {
   public:
      /// origin for Seek() operations
      enum ESeekOrigin
      {
         seekBegin = 0,
         seekCurrent = 1,
         seekEnd = 2,
      };

      virtual ~IStream();

      // stream capabilities

      /// returns true when stream can be read
      virtual bool CanRead() const = 0;
      /// returns true when stream can be written to
      virtual bool CanWrite() const = 0;
      /// returns true when seek operations are possible in the stream
      virtual bool CanSeek() const = 0;

      // read support

      /// reads amount of data into given buffer; returns if stream is at its end
      virtual bool Read(void* buffer, DWORD maxBufferLength, DWORD& numBytesRead) = 0;
      /// reads one byte
      virtual BYTE ReadByte();

      /// returns true when the stream end is reached
      virtual bool AtEndOfStream() const = 0;

      // write support

      /// writes out given data buffer
      virtual void Write(const void* dataToWrite, DWORD lengthInBytes, DWORD& numBytesWritten) = 0;
      /// writes out single byte
      virtual void WriteByte(BYTE byteToWrite);

      // seek support

      /// seeks to given position, regarding given origin
      virtual ULONGLONG Seek(LONGLONG seekOffset, ESeekOrigin origin) = 0;
      /// returns current position in stream
      virtual ULONGLONG Position() = 0;
      /// returns length of stream
      virtual ULONGLONG Length() = 0;

      /// flushes data
      virtual void Flush() = 0;
      /// closes stream
      virtual void Close() = 0;
   };
}

Stream exception

#include <ulib/stream/StreamException.hpp>

This header defines a StreamException class that is used throughout the stream classes to report errors.

namespace Stream
{
   class StreamException : public Exception
   {
   public:
      StreamException(LPCSTR sourceFile, UINT sourceLine);
      StreamException(const CString& message, LPCSTR sourceFile, UINT sourceLine)
   };
}

See the Exception class for more member functions.

File stream

#include <ulib/stream/FileStream.hpp>

The FileStream class implements IStream based on a file in storage.

namespace Stream
{
   class FileStream : public IStream
   {
   public:
      /// file open mode
      enum EFileMode
      {
         modeCreateNew = 1,   ///< creates a new file; fails when it already exists
         modeCreate = 2,      ///< create a new file; if it already exists, it is overwritten
         modeOpen = 3,        ///< open an existing file; fail when no file was found
         modeOpenOrCreate = 4,///< open an existing file; create empty file when no file was found
         modeTruncate = 5,    ///< open an existing file; truncate file to 0 bytes; fail when no file was found
         modeAppend = 6,      ///< like modeOpen, and seeks to the end of the file; no seek before end is allowed
      };

      /// file access
      enum EFileAccess
      {
         accessRead = 0x80000000,      ///< read access to file only
         accessWrite = 0x40000000,     ///< write access to file only
         accessReadWrite = 0xC0000000, ///< read and write access to file
      };

      /// file share mode
      enum EFileShare
      {
         shareNone = 0,       ///< declines reading or writing the file
         shareRead = 1,       ///< allows reading from the file
         shareWrite = 2,      ///< allows writing to the file
         shareReadWrite = 3,  ///< allows both reading and writing
         shareDelete = 4,     ///< file can be deleted by others
      };

      /// ctor; opens or creates a file
      FileStream(LPCTSTR filename, EFileMode fileMode, EFileAccess fileAccess, EFileShare fileShare);

      /// returns if the file was successfully opened
      bool IsOpen() const;

      // more overridden IStream methods...
  };
}

Read-only memory stream

#include <ulib/stream/MemoryReadStream.hpp>

The MemoryReadStream class represents a memory-based stream that can only be read from. Seeking is possible, but not beyond the buffer size. CanWrite() returns false.

namespace Stream
{
   class MemoryReadStream : public IStream
   {
   public:
      MemoryReadStream(const BYTE* data, DWORD_PTR length);

      // more overridden IStream methods...
   };
}

Read-write memory stream

#include <ulib/stream/MemoryStream.hpp>

The MemoryStream class represents a memory-based stream that can be read from and be written to. Writing beyond the current memory expands the memory space. Seeking beyond the current memory space isn't possible.

namespace Stream
{
   class MemoryStream : public IStream
   {
   public:
      /// ctor; opens an empty memory stream
      MemoryStream();

      /// ctor; provides memory contents for memory stream
      MemoryStream(const BYTE* dataToUse, DWORD_PTR lengthInBytes);

      /// returns data
      const std::vector<BYTE>& GetData() const;

      // more overridden IStream methods...
   };
}

Null stream

#include <ulib/stream/NullStream.hpp>

The NullStream class implements a stream that behaves like /dev/zero: Reading results in zeros, writing discards data, and seeking always seeks to position 0.

namespace Stream
{
   class NullStream : public IStream
   {
   public:
      NullStream();

      // more overridden IStream methods...
   };
}

Endian aware filter

#include <ulib/stream/EndianAwareFilter.hpp>

The EndianAwareFilter implements some helper methods for reading and writing 16-bit and 32-bit values in an endian aware fashion. It can be used with any IStream implementation. The method suffixes LE means little-endian and BE means big-endian.

namespace Stream
{
   class EndianAwareFilter
   {
   public:
      EndianAwareFilter(IStream& stream);

      WORD Read16LE();
      WORD Read16BE();
      DWORD Read32LE();
      DWORD Read32BE();

      void Write16LE(WORD w);
      void Write16BE(WORD w);
      void Write32LE(DWORD dw);
      void Write32BE(DWORD dw);
   };
}

ITextStream interface

#include <ulib/stream/ITextStream.hpp>

This header defines the ITextStream interface that describes a text based stream that can be read from and written to, both per-character and per-line. The interface has similarities to IStream but doesn't derive from it.

namespace Stream
{
   class ITextStream
   {
   public:
      /// text encoding that is possible for text files
      enum ETextEncoding
      {
         textEncodingNative,  ///< native encoding; compiler options decide if ANSI or Unicode is used for output
         textEncodingAnsi,    ///< ANSI text encoding; depends on the current codepage (not recommended)
         textEncodingUTF8,    ///< UTF-8 encoding
         textEncodingUCS2,    ///< UCS-2 encoding
      };

      /// line ending mode used to detect lines or is used for writing
      enum ELineEndingMode
      {
         lineEndingCRLF,   ///< a CR and LF char (\\r\\n) is used to separate lines; Win32-style
         lineEndingLF,     ///< a LF char (\\n) is used to separate lines; Linux-style
         lineEndingCR,     ///< a CR char (\\r) is used to separate lines; Mac-style
         lineEndingReadAny,///< when reading, any of the above line ending modes are detected when using ReadLine()
         lineEndingNative, ///< native mode is used
      };

      /// ctor
      ITextStream(ETextEncoding textEncoding = textEncodingNative,
         ELineEndingMode lineEndingMode = lineEndingNative);

      /// dtor
      virtual ~ITextStream();

      // stream capabilities

      /// returns text encoding currently in use
      ETextEncoding TextEncoding() const;

      /// returns line ending mode currently in use
      ELineEndingMode LineEndingMode() const;

      /// returns true when stream can be read
      virtual bool CanRead() const = 0;

      /// returns true when stream can be written to
      virtual bool CanWrite() const = 0;

      /// returns true when the stream end is reached
      virtual bool AtEndOfStream() const = 0;

      // read support

      /// reads a single character
      virtual TCHAR ReadChar() = 0;

      /// reads a whole line using line ending settings
      virtual void ReadLine(CString& line) = 0;

      // write support

      /// writes text
      virtual void Write(const CString& text) = 0;

      /// writes endline character
      virtual void WriteEndline() = 0;

      /// writes a line
      void WriteLine(const CString& line);

      /// flushes out text stream
      virtual void Flush() = 0;
   };
}

Text stream filter

#include <ulib/stream/TextStreamFilter.hpp>

The TextStreamFilter class is an implementation of ITextStream that uses an underlying IStream to read from or write to a stream of any type. The ctor needs the properties for text encoding and line ending to determine the text format.

namespace Stream
{
   class TextStreamFilter : public ITextStream
   {
   public:
      TextStreamFilter(IStream& stream,
         ETextEncoding textEncoding = textEncodingNative,
         ELineEndingMode lineEndingMode = lineEndingNative);

        /// returns underlying stream (const version)
        const IStream& Stream() const;

        /// returns underlying stream
        IStream& Stream();

        // more overridden ITextStream methods...
   };
}

Text file stream

#include <ulib/stream/TextFileStream.hpp>

The TextFileStream implements reading from a text file. It's derived from TextStreamFilter to provide the char-based and line-based read and write methods, and internally uses a FileStream to access the file in storage.

namespace Stream
{
   /// text file stream
   class TextFileStream : public TextStreamFilter
   {
   public:
      /// ctor; opens or creates text file stream
      TextFileStream(LPCTSTR filename,
         EFileMode fileMode,
         EFileAccess fileAccess,
         EFileShare fileShare,
         ETextEncoding textEncoding = textEncodingNative,
         ELineEndingMode lineEndingMode = lineEndingCRLF);

      /// returns if the file was successfully opened
      bool IsOpen() const;

      /// returns true when the file end is reached
      bool AtEndOfStream() const;

   private:
      /// file stream
      FileStream m_fileStream;
   };
}

Threading

The thread include folder contains classes for multithreading purposes.

Event classes

#include <ulib/thread/Event.hpp>

Defines two classes ManualResetEvent and AutoResetEvent that share the same interface but differ in what happens when an event is Wait()ed on. The ctor and the methods may throw a SystemException on error.

class ManualResetEvent
{
public:
   ManualResetEvent(bool initialState);

   /// sets event
   void Set();

   /// resets event
   void Reset()

   /// waits given time (or infinitely) for event to get set
   bool Wait(DWORD timeoutInMilliseconds = INFINITE);

   /// returns internal event handle
   HANDLE Handle() const;
};

Mutex common header

#include <ulib/thread/Mutex.hpp>

Includes all other mutex related header files.

Lightweight mutex

#include <ulib/thread/LightweightMutex.hpp>

Defines a light-weight, non-recursive mutex that may not call into kernel, and so performing much faster than other mutex types. This is implemented using a critical section. The ctor and the methods may throw a SystemException on error. Note that this class is always used together with the MutexLock class.

class LightweightMutex
{
public:
   /// lock type
   typedef MutexLock<LightweightMutex> LockType;

   /// ctor
   LightweightMutex();

   /// dtor
   ~LightweightMutex();
};

Mutex lock classes

#include <ulib/thread/MutexLock.hpp>

Locks are used to obtain access to the object protected by the mutex. The lock classes are templated classes that have access to the implementation of the various mutex classes. MutexLock locks the given mutex while the object is live:

template <typename T>
class MutexLock
{
public:
   /// ctor; locks mutex
   MutexLock(T& mutex);
   /// dtor; unlocks object
   ~MutexLock();
};

The MutexTryLock doesn't lock in the ctor, but provides a Try() method:

template <typename T>
class MutexTryLock
{
public:
   /// ctor; takes a lockable object, but doesn't lock it yet
   MutexTryLock(T& mutex);

   /// dtor; unlocks object
   ~MutexTryLock();

   /// tries locking mutex until timeout (in milliseconds)
   bool Try(DWORD timeoutInMilliseconds);
};

The MutexUnLocker class can temporarily unlock a locked mutex:

template <typename T>
class MutexUnLocker
{
public:
   /// ctor; takes a lockable object and unlocks it
   MutexUnLocker(T& mutex);

   /// dtor; locks object
   ~MutexUnLocker();
};

Reader-writer mutex

#include <ulib/thread/ReaderWriterMutex.hpp>

A mutex that supports locking for reading and for writing. Multiple readers can lock the mutex, with only one writer being able to lock it for writing.

class ReaderWriterMutex
{
public:
   ReaderWriterMutex();
   ~ReaderWriterMutex();
};

There are two custom lock classes that can be used with ReaderWriterMutex. The reader lock can be obtained multiple times:

class ReaderLock
{
public:
   /// ctor; locks the mutex as reader
   ReaderLock(ReaderWriterMutex& mutex);
   /// dtor; releases the mutex
   ~ReaderLock();
};

Only one writer lock can be obtained at any time:

class WriterLock
{
public:
   /// ctor; locks the mutex as writer
   WriterLock(ReaderWriterMutex& mutex);
   /// dtor; releases the mutex
   ~WriterLock();
};

Recursive mutex

#include <ulib/thread/RecursiveMutex.hpp>

A recursively lockable mutex. This is implemented using a system mutex. The ctor may throw a SystemException on error. Note that this class is always used together with the MutexLock or MutexTryLock class.

class RecursiveMutex
{
public:
   /// lock type
   typedef MutexLock<RecursiveMutex> LockType;

   /// try-lock type
   typedef MutexTryLock<RecursiveMutex> TryLockType;

   RecursiveMutex();
};

Thread handing

#include <ulib/thread/Thread.hpp>

The Thread class provides two methods for threads:

class Thread
{
public:
   /// sets thread name for current or specified thread
   static void SetName(LPCTSTR threadName, DWORD threadId = DWORD(-1))

   /// returns current thread ID
   static DWORD CurrentId();
};

The thread name appears in the debugger Threads window and may also appear in minidump crash dumps.

Unit test

The unittest include folder contains classes supporting writing unit tests.

Auto-cleanup file

#include <ulib/unittest/AutoCleanupFile.hpp>

Provides a RAII class that deletes the file specified as soon as the object goes out of scope:

namespace UnitTest
{
   class AutoCleanupFile
   {
   public:
      AutoCleanupFile(LPCTSTR filename)
      ~AutoCleanupFile();
   };
}

Auto-cleanup folder

#include <ulib/unittest/AutoCleanupFolder.hpp>

Provides a RAII class that creates a temporary folder that is deleted (as well as its contents) as soon as the object goes out of scope:

namespace UnitTest
{
   class AutoCleanupFolder
   {
   public:
      AutoCleanupFile()
      ~AutoCleanupFile();
      const CString& FolderName() const;

   };
}

Win32 API

The win32 include folder contains helper classes for the Win32 API.

Clipboard

#include <ulib/win32/Clipboard.hpp>

Provides access to the Win32 clipboard.

namespace Win32
{
   class Clipboard
   {
   public:
      Clipboard(HWND hwnd = nullptr);

      ~Clipboard();

      /// empties the clipboard
      void Clear() const;

      /// returns number of formats currently on the clipboard
      int CountFormats() const;

      /// checks if a given format is available
      static bool IsFormatAvail(UINT format);

      /// returns text format (CF_TEXT or CF_UNICODETEXT)
      CString GetText();

      /// returns binary data for given clipboard format
      void GetData(UINT format, std::vector<BYTE>& data);

      /// sets clipboard text as format CF_TEXT or CF_UNICODETEXT (depending on build options)
      void SetText(const CString& text);

      /// sets clipboard data; generic function
      void SetData(UINT format, const BYTE* dataPtr, UINT size);

      /// registers a given format name
      UINT RegisterFormat(LPCTSTR formatName) const;

      /// returns format name for given format id
      CString GetFormatName(UINT format) const;

      /// enumerates all clipboard formats currently available on the clipboard
      void EnumFormats(std::vector<UINT>& formatList) const;
   };
}

The class and its functions should be pretty self explanatory.

Embedded browser support

#include <ulib/win32/DocHostUI.hpp>

This header file provides a default implementation of the automation interface IDocHostUIHandlerDispatch that is used when customizing an IE WebBrowser control. Derive your class from IDocHostUIHandlerDispatchImpl and implement the methods that interest you. See detailed commands in the header file itself.

Win32 error messages

#include <ulib/win32/ErrorMessage.hpp>

Helps with handling Win32 error messages. Format an error code like this:

DWORD win32ErrorCode = GetLastError(); // also for HRESULTs
CString message = Win32::ErrorMessage(win32ErrorCode).ToString();

Ini file access

#include <ulib/win32/IniFile.hpp>

Encapsulates access to .ini files:

class IniFile
{
public:
   IniFile(const CString& iniFilename);

   /// returns integer value from section and key
   int GetInt(LPCTSTR sectionName, LPCTSTR keyName, int defaultValue);

   /// returns string value from section and key
   CString GetString(LPCTSTR sectionName, LPCTSTR keyName, LPCTSTR defaultValue);

   /// writes a string value to a section and key
   void WriteString(LPCTSTR sectionName, LPCTSTR keyName, LPCTSTR value);
};

Process handling

#include <ulib/win32/Process.hpp>

The Process class provides an easy way to start a process, without worrying abount missing closing any handles.

namespace Win32 { class Process { public: /// sets working directory for process void WorkingDirectory(const CString& workingDirectory);

  /// creates process with given command line
  bool Create(const CString& commandLine);

  /// returns process handle
  HANDLE ProcessHandle() const;

};

}

Resource data handling

#include <ulib/win32/ResourceData.hpp>

Helps accessing data that is stored in the resources, e.g. as RT_RCDATA type.

namespace Win32
{
   class ResourceData
   {
   public:
      ResourceData(LPCTSTR resourceName, LPCTSTR resourceType = _T("\"RT_RCDATA\""),
         HINSTANCE instanceHandle = nullptr);

      /// returns true if the resource is available
      bool IsAvailable() const;

      /// returns resource data as byte array
      bool AsRawData(std::vector<BYTE>& rawData);

      /// returns resource data as string
      CString AsString(bool storedAsUnicode = false);

      /// saves resource data as file
      bool AsFile(LPCTSTR filename);
   };
}

The ctor lets you pass the resource name, e.g. using the MAKEINTRESOURCE() macro, and the resource type. You can also specify a different instance handle, e.g. when using resource DLLs.

Version info resource

#include <ulib/win32/VersionInfoResource.hpp>

The VersionInfoResource class lets you access the version info resource stored in an .exe or .dll file. There are fixed infos and language-dependent resources.

VersionInfoResource resource{ filename };
if (!resource.IsAvail())
   return;
FixedFileInfo* info = resource.GetFixedFileInfo();

The FixedFileInfo class looks like this:

class FixedFileInfo : public VS_FIXEDFILEINFO
{
public:
   CString GetFileVersion() const;
   CString GetProductVersion() const;
   CString GetFileOS() const;
   CString GetFileType() const;
};

The four methods serve to format a displayable text from the infos stored in the Win32's VS_FIXEDFILEINFO struct.

Version info resources may also contain language-dependent strings. The languages can be queried like this:

typedef struct tagLANGANDCODEPAGE
{
   WORD wLanguage; // language code
   WORD wCodePage; // code page
} LANGANDCODEPAGE, *PLANGANDCODEPAGE;

void GetLangAndCodepages(std::vector<LANGANDCODEPAGE>& langAndCodepageList);

CString GetStringValue(const LANGANDCODEPAGE& langAndCodepage, LPCTSTR valueName);

First get all LANGANDCODEPAGE entries using GetLangAndCodepages(), then call GetStringValue(), specifying the value name. These can be e.g. "Comment", "ProductName", "SpecialBuild" or any other text values appearing in the version info resource.

Windows 7 task bar

#include <ulib/win32/Win7Taskbar.hpp>

Provides access to the Taskbar and its functionality introduced in Windows 7. The main goal is to control the task bar icon's progress bar.

This opens the taskbar for the given HWND:

Win32::Taskbar taskbar{ m_hWnd };
if (!taskbar.IsAvailable())
   return; // below Windows 7

You can open the progress bar for the taskbar icon for this process:

Win32::TaskbarProgressBar progressBar = taskbar.OpenProgressBar();

progressBar.SetState(TBPF_NORMAL);
progressBar.SetPos(40, 100); // 40 of 100 percent done

Other states that can be set are:

  • TBPF_NOPROGRESS: Stops showing progress
  • TBPF_INDETERMINATE: Shows an indeterminate progress, cycling repeatedly
  • TBPF_NORMAL: Shows normal progress using the values set via SetPos()
  • TBPF_ERROR: Shows error progress (red background color)
  • TBPF_PAUSED: Shows paused progress (yellow background color)

When TaskbarProgressBar leaves the scope and the dtor is called, the progress is also set to TBPF_NOPROGRESS.

System image list

#include <ulib/win32/SystemImageList.hpp>

Provides two static methods in the SystemImageList class:

class SystemImageList
{
   static CImageList Get(bool smallIcons);
   static int IndexFromFilename(LPCTSTR filename);
};

The Get() method returns the system's image list. Use this for list views or tree views that show file icons. Be sure to mark the image list as shared, e.g. using the LVS_SHAREIMAGELISTS style on list views.

The IndexFromFilename() returns an image list index for the given filename. The file name doesn't have to exist.