C++/CLI Logger

In our previous post we learnt about C++/CLI concepts, here is an example how to implement a logger library using C++/CLI which can be used both C# and C++.

Every software product development needs a mechanism to track execution of it application or to report glitches if any. Therefore, a logger becomes an indispensable module of a software package. Moreover, it becomes more rewarding if all logs are saved in coherent and centralized manner irrespective of which language (C# or C++) it is logged from.

C++/CLI based logger therefore turns into a single useful trick to log all important events (not C# events of course) from both C# and C++. Lets see how we can implement one now.

Here is a C++/CLI based logger class which implements from a pure abstract class ILogger which is again C++/CLI based; see the usage of caret added as suffix in ILogger name. Also ref keyword is used in Logger class declaration to make this class a .NET type instead of native C++ class.

public ref class Logger : public ILogger
{
public:
Logger(ILogWriter^ logWriter);
void LogInfo(String^ MESSAGE, [CallerMemberName][Optional] String^ memberName, [CallerFilePath][Optional] String^ callerFilePath) override;
void LogWarning(String^ MESSAGE, [CallerMemberName][Optional] String^ memberName, [CallerFilePath][Optional] String^ callerFilePath) override;
void LogError(String^ MESSAGE, [CallerMemberName][Optional] String^ memberName, [CallerFilePath][Optional] String^ callerFilePath) override;
private:
ILogWriter^ myLogWriter;
};
view raw Logger.h hosted with ❤ by GitHub

Also notice that this class depends on ILogWriter which is pure C# interface and and .NET String and Caller info attributes; these attributes in C# provide a powerful method to associate metadata, or declarative information with code (we can discuss later), here they are used to get source function name (CallerMemberName) and file path (CallerFilePath) from where a particular log is created.

Next find the implementation of Logger class below. It creates the instance of a simple C# data structure class LogData using gcnew keyword. It also uses .NET DateTime struct to fetch time of log creation.

Logger::Logger(ILogWriter^ logWriter)
{
myLogWriter = logWriter;
}
void Logger::LogInfo(String^ message, String^ memberName, String^ callerFilePath)
{
String^ currentDate = DateTime::Now.ToString("g");
myLogWriter->WriteLog(gcnew LogData(currentDate, LogLevel::INFO, message, memberName, callerFilePath));
}
void Logger::LogWarning(String^ message, String^ memberName, String^ callerFilePath)
{
String^ currentDate = DateTime::Now.ToString("g");
myLogWriter->WriteLog(gcnew LogData(currentDate, LogLevel::WARNING, message, memberName, callerFilePath));
}
void Logger::LogError(String^ message, String^ memberName, String^ callerFilePath)
{
String^ currentDate = DateTime::Now.ToString("g");
myLogWriter->WriteLog(gcnew LogData(currentDate, LogLevel::ERROR, message, memberName, callerFilePath));
}
view raw Logger.cpp hosted with ❤ by GitHub

Now our logger class can be instantiated in any C# class to start logging information. Usually a provider class is implemented to provide a singleton logger instance with desired logging destination (e.g. file, event or database logging) across application.

Logger class we have seen above is of course a .NET compatible type can be used in C# directly but it cannot be used in native C++ as it is; native C++ compiler does not recognize special character caret (^) which is used to refer our managed Logger class. We need to create a wrapper written again in C++/CLI using native C++ class declaration. We can call this class as a LoggerProxy; it only delegates a logging call to our original managed Logger class.

void LoggerProxy::LogInfo(const string MESSAGE)
{
myManagedLogger->LogInfo(ConvertToManagedString(MESSAGE));
}
void LoggerProxy::LogWarning(const string MESSAGE)
{
myManagedLogger->LogWarning(ConvertToManagedString(MESSAGE));
}
void LoggerProxy::LogError(const string MESSAGE)
{
myManagedLogger->LogError(ConvertToManagedString(MESSAGE));
}
String^ LoggerProxy::ConvertToManagedString(string input)
{
return msclr::interop::marshal_as<String^>(input);
}
view raw LoggerProxy.cpp hosted with ❤ by GitHub

Here one more concept is used, Marshalling; it is a process of exchanging data between managed and unmanaged code provided by CLR. One data type in managed context can be marshalled in corresponding data type in unmanaged context or vice versa. So, in above wrapper class, a C++ string is passed to LoggerProxy functions, it is converted into .NET string using marshalling.

msclr::interop::marshal_as<String^>(input);

So, this LoggerProxy class instance can be created in C++ classes to log information. Again, we can use a provider class to get singleton instance of logger with homogeneous logging destination settings. If we use this mechanism, we are ultimately logging information from both C# and C++ software modules using a single logger library in a coherent and centralized manner.

Find complete implementation in my Github repository here.

C++/CLI programming

With .NET framework Microsoft created Common Language Infrastructure (CLI) which lays down specifications for a language to become compatible with .NET framework; to be executed under Common Language Runtime (CLR). CLR is a virtual machine which provides an software environment for programs written using CLI specification to execute. It basically provides a managed environment where memory allocation-release, optimization and interoperability between different .NET langauges, all these are taken care by the environment.

So, here we are going to describe C++/CLI programming technique, a .NET extension of native C++ programming with the help of which we create programs executing within CLR. In other words, with the help of this technique a program can access all .NET managed components in C++. And additionally, as C++/CLI programs written execute within CLR, managed code written in C# can also access C++/CLI classes and invoke its APIs.

This technique first appeared with Visual Studio 2005 as a substitute for the Managed C++ programming which became deprecated. It is usually used as a bridge between C# and C++ in the large production development setup where an application is created using both managed and unmanaged components.

Syntax for C++/CLI classes

A class written in C++/CLI is decalared with ‘ref‘ keyword.

public ref class MyClass

ref keyword here indicates that this class is of reference type and thus can accessed from .NET runtime.

To access above class in C++ Caret symbol (^) is added as a suffix to a class name, symbolizing that this class is a .NET environment class not a native C++ class.

For e.g., following snippet shows way to access .NET string class in C++/CLI class.

String^ myString;

For creating instance of managed types, gcnew keyword is used, ‘gc’ of course symbolizes ‘garbage collected’.

String^ myString = gcnew String();

Note that caret is not used on right side of assignment operator.

Next, C++ scope resolution operator (::) is used instead of dot(.) for accessing public properties/methods of a .NET class and in importing managed namespaces.

Console::ReadLine();

using namespace System::Diagnostics;

Now, that you are familiarized with differences in C++/CLI from native C++ syntax, lets brush up some C++ concepts before starting to build a C++/CLI program.

Object creation in C++

In C++, if you have a class as MyNativeClass and we declare its object like

MyNativeClass myNativeClass;

this myNativeClass is not a reference to the object (which is the usual case in C#), it is a value type object. C++ objects are referred to as static variables which means they are created in Stack unless they are created with pointer reference.

Second, when you have myNativeClass1 and myNativeClass2 and you assign one to another using assignment operator (=), you are not referencing one object to another, you are actually doing a MEMBER-WISE ASSIGNMENT.

So, after this you can appreciate caret(^) and gcnew usage above to create reference type objects in C++ context.

Now, that we have explored all aspects of C++/CLI programming, in our next post we will demonstrate its example by creating a library which can be accessed in both native C++ and C#.

Design a site like this with WordPress.com
Get started