Click here to Skip to main content
15,897,273 members
Articles / Web Development / HTML

Integrating Crash Reporting into Your Application - A Beginners Tutorial

Rate me:
Please Sign up or sign in to vote.
4.91/5 (30 votes)
5 Feb 2012CPOL12 min read 90.8K   4.4K   170  
This article shows how to use CrashRpt error reporting library with an MFC application
<!-- This comment will put IE 6, 7 and 8 in quirks mode -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" href="../favicon.ico" type="image/x-icon" />
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<title>CrashRpt: Sending Error Reports</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javaScript" src="search/search.js"></script>
<link href="doxygen.css" rel="stylesheet" type="text/css"/>
</head>
<body onload='searchBox.OnSelectItem(0);'>

<table border="0" bgcolor="#FFFFFF" cellspacing="5" width="100%">
 <tr>
  <td width="24px" rowspan="2"><img src="../logo.png" alt="Logo" /></td>
  <td><font family="Arial" size="+2">crashrpt</font></td>
  <td rowspan="2" align="right"><a href="http://sourceforge.net/donate/index.php?group_id=279722"><img src="../donate_small.png" alt="Donate" /></a></td>
 </tr>
 <tr>
  <td colspan="2"><i>A crash reporting system for Windows applications</i></td>
 </tr>


</table>


</body>
<!-- Generated by Doxygen 1.5.9 -->
<script type="text/javascript">
<!--
function changeDisplayState (e){
  var num=this.id.replace(/[^[0-9]/g,'');
  var button=this.firstChild;
  var sectionDiv=document.getElementById('dynsection'+num);
  if (sectionDiv.style.display=='none'||sectionDiv.style.display==''){
    sectionDiv.style.display='block';
    button.src='open.gif';
  }else{
    sectionDiv.style.display='none';
    button.src='closed.gif';
  }
}
function initDynSections(){
  var divs=document.getElementsByTagName('div');
  var sectionCounter=1;
  for(var i=0;i<divs.length-1;i++){
    if(divs[i].className=='dynheader'&&divs[i+1].className=='dynsection'){
      var header=divs[i];
      var section=divs[i+1];
      var button=header.firstChild;
      if (button!='IMG'){
        divs[i].insertBefore(document.createTextNode(' '),divs[i].firstChild);
        button=document.createElement('img');
        divs[i].insertBefore(button,divs[i].firstChild);
      }
      header.style.cursor='pointer';
      header.onclick=changeDisplayState;
      header.id='dynheader'+sectionCounter;
      button.src='closed.gif';
      section.id='dynsection'+sectionCounter;
      section.style.display='none';
      section.style.marginLeft='14px';
      sectionCounter++;
    }
  }
}
window.onload = initDynSections;
-->
</script>
<div class="navigation" id="top">
  <div class="tabs">
    <ul>
      <li><a href="index.html"><span>Contents</span></a></li>
      <li><a href="modules.html"><span>API&nbsp;Reference</span></a></li>
      <li><a href="files.html"><span>File&nbsp;Reference</span></a></li>
    <li>
      <form action="search.php" method="get">
        <table cellspacing="0" cellpadding="0" border="0">
          <tr>
            <td><label>&nbsp;<u>S</u>earch&nbsp;for&nbsp;</label></td>
            <td><input type="text" name="query" value="" size="20" accesskey="s"/></td>
          </tr>
        </table>
      </form>
    </li>
    </ul>
  </div>
  <div class="navpath"><a class="el" href="index.html">CrashRpt Documentation</a>&nbsp;&raquo&nbsp;<a class="el" href="using_crashrpt.html">Using CrashRpt in Your Project</a>
  </div>
</div>
<div class="contents">
<h1><a class="anchor" name="sending_error_reports">Sending Error Reports </a></h1>When crash occurs, CrashRpt runs the <b>CrashSender.exe</b> process that collects crash information (desktop screen shot, minidump, application-defined files). The parent process is then terminated, except in the case when you generate error report manually.<p>
By default, <b>CrashSender.exe</b> saves error report files to <em>%LOCAL_APP_DATA%\CrashRpt\UnsentErrorReports\%AppName%_%AppVersion%</em> folder. Optionally, you can specify another location where to store files using <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#51955d296cf629bc7d537d87f8f639cb" title="Directory where to save error reports.">CR_INSTALL_INFO::pszErrorReportSaveDir</a> structure member.<p>
When crash report files are collected, CrashRpt does one of the following: sends report immediately (by default) or exits (<a class="el" href="_crash_rpt_8h.html#07d53781e0ea5636b4d0218619460050">CR_INST_DONT_SEND_REPORT</a> flag for <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#1798fdbe4869846814ebe004a316037c" title="Flags.">CR_INSTALL_INFO::dwFlags</a> member is specified).<p>
If error report delivery fails, CrashRpt can save (queue) error report data locally for later delivery.<p>
You can ask CrashRpt to send all queued reports by specifying <a class="el" href="_crash_rpt_8h.html#1d2fdb48ae4bf477109baf07b29dd26a">CR_INST_SEND_QUEUED_REPORTS</a> flag for <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#1798fdbe4869846814ebe004a316037c" title="Flags.">CR_INSTALL_INFO::dwFlags</a> member. On application start up, if this flag is specified, CrashRpt checks if it is time to remind user about error reports ready for delivery, shows notification balloon and offers user to deliver them. CrashRpt shows notification if at least one week elapsed since the last notification.<h2><a class="anchor" name="delivery_methods">
Delivery Methods</a></h2>
CrashRpt attempts to deliver the error report using any of the following delivery methods:<ul>
<li>via HTTP (or HTTPS) request to a server-side script</li><li>via direct connection to an SMTP server</li><li>via Simple MAPI programming interface (user's default E-mail client application)</li></ul>
<p>
You can specify the order of trials by using <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#01d5bd46223beabcced7329662ef9837" title="Array of error sending transport priorities.">CR_INSTALL_INFO::uPriorities</a> structure member. You can disable certain delivery method by using the <a class="el" href="_crash_rpt_8h.html#8b1fa457e0f62bc3fae070dd403d32a0">CR_NEGATIVE_PRIORITY</a> constant for that member.<h2><a class="anchor" name="error_report_size_limitations">
Error Report Size Limitations</a></h2>
<b>Since v.1.2.2</b>, CrashRpt provides support of large error reports being sent using HTTP (or HTTPS) connection. You can send error reports having arbitrary size, however check your web-server settings to ensure it won't reject reports larger than some limit. For example, if you use PHP, check the <em>upload_max_filesize</em> configuration parameter (the default in 2 megabytes), <em>post_max_size</em> parameter and possibly others (see PHP documentation for details).<p>
In case you plan to deliver error reports over E-mail (SMTP or Simple MAPI), than error reports should be as small as possible. This is because of limitations of email attachment size. If the error report is larger than the limit (which may be different for different mail servers), it is possible that the error report will be rejected.<h2><a class="anchor" name="httpsend">
Sending Crash Report Using HTTP Connection</a></h2>
Many software products have web sites on the Internet. Such web servers typically have some scripting engine enabled, for example, PHP and so on. CrashRpt can establish HTTP (or HTTPS) connection to a server-side script and send the error report as a script parameter.<p>
Older versions of CrashRpt used "application/x-www-form-urlencoded" form encoding type in conjunction with Base-64 encoding for file attachment. The legacy way is enabled by default for backwards compatibility, however it is not recommended to use. The encoding type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters.<p>
The equivalent HTML form is presented below:<p>
<div class="fragment"><pre class="fragment">&lt;html&gt;
&lt;form action=<span class="stringliteral">"http://someserver.net/crashrpt.php"</span> method=<span class="stringliteral">"POST"</span> enctype=<span class="stringliteral">"application/x-www-form-urlencoded"</span>&gt;
 MD5:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"md5"</span>&gt;
 Base64-encoded ZIP file data:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"crashrpt"</span>&gt;
 &lt;input type=<span class="stringliteral">"submit"</span> name=<span class="stringliteral">"Submit"</span>&gt;
&lt;/form&gt;
&lt;/html&gt;
</pre></div><p>
<b>Since v.1.2.2</b>, CrashRpt supports HTTP file uploads as described in <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC-1867.</a> This mechanism allows to upload large files by using binary content transfer encoding. The "multipart/form-data" encoding type is utilized for this purpose.<p>
The equivalent HTML form is presented below:<p>
<div class="fragment"><pre class="fragment">&lt;html&gt;
&lt;form action=<span class="stringliteral">"http://someserver.net/crashrpt.php"</span> method=<span class="stringliteral">"POST"</span> enctype=<span class="stringliteral">"multipart/form-data"</span>&gt;
 Application name:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"appname"</span>&gt;
 Application version:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"appversion"</span>&gt;
 Email from:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"emailfrom"</span>&gt;
 Email subject:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"emailsubject"</span>&gt;
 Crash GUID:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"crashguid"</span>&gt;
 MD5:&lt;input type=<span class="stringliteral">"text"</span> name=<span class="stringliteral">"md5"</span>&gt;
 Attach ZIP file:&lt;input type=<span class="stringliteral">"file"</span> name=<span class="stringliteral">"crashrpt"</span>&gt;
 &lt;input type=<span class="stringliteral">"submit"</span> name=<span class="stringliteral">"Submit"</span>&gt;
&lt;/form&gt;
&lt;/html&gt;
</pre></div><p>
This transfer type is disabled by default to ensure backwards compatibility. To opt this in, specify <a class="el" href="_crash_rpt_8h.html#322f7b37078003b52728dad7b26e1785">CR_INST_HTTP_BINARY_ENCODING</a> flag in <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#1798fdbe4869846814ebe004a316037c" title="Flags.">CR_INSTALL_INFO::dwFlags</a> structure member.<h3><a class="anchor" name="script_params">
Script Parameters</a></h3>
Below is the summary of parameters the server-side script may receive. The parameters actually received may depend on what version of CrashRpt sent the report.<p>
The server-side script may receive the following POST parameters. In PHP, these parameters are part of <b>$_POST</b> global array.<p>
<table border="1" cellspacing="3" cellpadding="3">
<tr>
<td><b>Parameter Name</b> </td><td><b>Example value</b> </td><td><b>Remarks</b><p>
</td></tr>
<tr>
<td>md5 </td><td>"af89e902b42cd301092bb34530984e59" </td><td>This parameter contains the MD5 hash of error report data. This can be used to check error report integrity.<p>
</td></tr>
<tr>
<td>crashrpt </td><td>This will be a Base-64 encoded character sequence. </td><td>This parameter contains error report data encoded with Base-64 encoding. This legacy parameter is have to be used if you need to receive reports being sent by an older (pre-v.1.2.2) version of CrashRpt.<p>
The crash report ZIP file data are converted to base64 encoding to replace all restricted characters with a set of 64 predefined characters. The server-side script should base64-decode the ZIP file data.<p>
Since v.1.2.2, error reports are recommended to send using binary encoding (see <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#1798fdbe4869846814ebe004a316037c" title="Flags.">CR_INSTALL_INFO::dwFlags</a> member for more information).<p>
</td></tr>
<tr>
<td>appname </td><td>"MyApp" </td><td>Application name (as passed to <b><a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#626bed9c6b114c92e3bcfa16990d6c0c" title="Name of application.">CR_INSTALL_INFO::pszAppName</a></b> structure member).<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
<tr>
<td>appversion </td><td>"1.0.0" </td><td>Application version (as passed to <b><a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#4e341f0ae33c88f65e770a69dbba300c" title="Application version.">CR_INSTALL_INFO::pszAppVersion</a></b> structure member).<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
<tr>
<td>crashguid </td><td>"37b4d6da-1211-4e62-adc6-174acb53ddf5" </td><td>Crash GUID (globally unique identifier).<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
<tr>
<td>emailsubject </td><td>"MyApp 1.0.0 Error Report" </td><td>E-mail subject (as passed to <b><a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#3ae88305c70b042bf0ea4a0a5131d4fd" title="Subject of crash report e-mail.">CR_INSTALL_INFO::pszEmailSubject</a></b> structure member).<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
<tr>
<td>emailfrom </td><td>"user@example.com" </td><td>E-mail of the user who sent this report.<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
<tr>
<td>description </td><td>"I just started the app and then it crashed. Help!" </td><td>User-provided problem description.<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
</table>
<p>
The server side script may receive the following file attachments. In PHP, these parameters are part of <b>$_FILES</b> global array.<p>
<table border="1" cellspacing="3" cellpadding="3">
<tr>
<td><b>Attachment Name</b> </td><td><b>Remarks</b><p>
</td></tr>
<tr>
<td>crashrpt </td><td>This file attachment contains the error report file data.<p>
<b>This parameter is available in error report sent by CrashRpt v.1.2.2 or later.</b> </td></tr>
</table>
<h3><a class="anchor" name="script_return">
Return Value</a></h3>
The script should return status of request completion as server responce header. In HTTP/1.0 and since, the first line of the HTTP response is called the status line and includes a numeric status code (such as "404") and a textual reason phrase (such as "Not Found").<p>
If the script succeeds in saving the error report, it should return the "200 Success" as server responce header. If the script encounters an error, it should return a 4xx error code, for example "450 Invalid parameter". Note that some error codes are reserved by HTTP specification. If the script uses custom error codes, they shouldn't intersect with standard predefined HTTP codes.<p>
<dl class="note" compact><dt><b>Note:</b></dt><dd></dd></dl>
When creating your own script, be careful with the script's return code. If your script succeeds in saving error report it should return '200 Success'. If crash sending process encounters another error code, it attempts sending the error report using another way. In such situation you may receive the same error report several times through different transport.<h3><a class="anchor" name="script_example">
Sample PHP Script</a></h3>
Below is an example server-side PHP script (reporting/scripts/crashrpt.php) that can receive a crash report and write it to a file. The script can receive error reports from any version of CrashRpt.<p>
<div class="fragment"><pre class="fragment">&lt;?php

<span class="comment">// Specify the directory where to save error reports</span>
$file_root = <span class="stringliteral">"/home/username/crash_reports/"</span>;

<span class="comment">// This is to avoid PHP warning</span>
date_default_timezone_set(<span class="stringliteral">'UTC'</span>);

<span class="comment">// Writes error code and text message and then exits</span>
function done($return_status, $message)
{
  <span class="comment">// Write HTTP responce code</span>
  header(<span class="stringliteral">"HTTP/1.0 "</span>.$return_status.<span class="stringliteral">" "</span>.$message);
  <span class="comment">// Write HTTP responce body (for backwards compatibility)</span>
  echo $return_status.<span class="stringliteral">" "</span>.$message; 
  exit(0);
}

<span class="comment">// Checks that text fild doesn't contain inacceptable symbols</span>
function checkOK($field)
{
  <span class="keywordflow">if</span> (stristr($field, <span class="stringliteral">"\\r"</span>) || stristr($field, <span class="stringliteral">"\\n"</span>)) 
  {
    done(450, <span class="stringliteral">"Invalid input parameter."</span>);
  }
}

$md5_hash = <span class="stringliteral">""</span>;    <span class="comment">// MD5 hash for error report ZIP</span>
$file_name = <span class="stringliteral">""</span>;   <span class="comment">// Destination file name                                  </span>
$crash_guid = <span class="stringliteral">""</span>;  <span class="comment">// Crash GUID</span>

<span class="comment">// Check that MD5 hash exists </span>
<span class="keywordflow">if</span>(!isset($_POST[<span class="stringliteral">'md5'</span>]))
{
  done(450, <span class="stringliteral">"MD5 hash is missing."</span>);
}

<span class="comment">// Get MD5 hash</span>
$md5_hash = $_POST[<span class="stringliteral">'md5'</span>];
checkOK($md5_hash);
<span class="keywordflow">if</span>(strlen($md5_hash)!=32)
{
  done(450, <span class="stringliteral">"MD5 hash value has wrong length."</span>);
}

<span class="comment">// Get CrashGUID</span>
<span class="keywordflow">if</span>(array_key_exists(<span class="stringliteral">"crashguid"</span>, $_POST))
{
  $crash_guid = $_POST[<span class="stringliteral">"crashguid"</span>];
  checkOK($crash_guid);
  <span class="keywordflow">if</span>(strlen($crash_guid)!=36)
  {
    done(450, <span class="stringliteral">"Crash GUID has wrong length."</span>);
  }  
}

<span class="comment">// Get file attachment</span>
<span class="keywordflow">if</span>(array_key_exists(<span class="stringliteral">"crashrpt"</span>, $_FILES))
{
  <span class="comment">// Check upload error code</span>
  $error_code = $_FILES[<span class="stringliteral">"crashrpt"</span>][<span class="stringliteral">"error"</span>];
  <span class="keywordflow">if</span>($error_code!=0)
  {
    done(450, <span class="stringliteral">"File upload failed with code $error_code."</span>);
  }

  <span class="comment">// Get temporary name uploaded file is stored currently</span>
  $tmp_file_name = $_FILES[<span class="stringliteral">"crashrpt"</span>][<span class="stringliteral">"tmp_name"</span>];
  checkOK($tmp_file_name);

  <span class="comment">// Check that uploaded file data have correct MD5 hash</span>
  $my_md5_hash = strtolower(md5_file($tmp_file_name));
  $their_md5_hash = strtolower($md5_hash);
  <span class="keywordflow">if</span>($my_md5_hash!=$their_md5_hash)
  {
    done(451, <span class="stringliteral">"MD5 hash is invalid (yours is "</span>.$their_md5_hash.<span class="stringliteral">", but mine is "</span>.$my_md5_hash.<span class="stringliteral">")"</span>);
  }

  <span class="keywordflow">if</span>(!empty($crash_guid))
  { 
    <span class="comment">// If crash GUID presents, use it as file name</span>
    $file_name = $file_root.$crash_guid.<span class="stringliteral">".zip"</span>;
  }
  <span class="keywordflow">else</span>
  {
    <span class="comment">// Generate random file name </span>
    $date_time = date(DATE_RFC822);
    $file_name = $file_root.md5(md5_file($tmp_file_name).$date_time).<span class="stringliteral">".zip"</span>;
  }

  <span class="comment">// Move uploaded file to an appropriate directory</span>
  <span class="keywordflow">if</span>(!move_uploaded_file($tmp_file_name, $file_name))
  {
    done(452, <span class="stringliteral">"Couldn't save data to local storage"</span>); 
  }
}
<span class="keywordflow">else</span>
{
  <span class="comment">// Assume legacy way is used</span>
  <span class="keywordflow">if</span>(!isset($_POST[<span class="stringliteral">"crashrpt"</span>]))  
  {
    done(450, <span class="stringliteral">"Error report data is missing."</span>);
  }

  <span class="comment">// Get BASE64-encoded file data</span>
  $encoded_data = $_POST[<span class="stringliteral">"crashrpt"</span>];

  <span class="comment">// Decode file data</span>
  $file_data = base64_decode($encoded_data);

  <span class="comment">// Check that decoded data have correct MD5 hash</span>
  $my_md5_hash = strtolower(md5($file_data));
  $their_md5_hash = strtolower($md5_hash);
  <span class="keywordflow">if</span>($my_md5_hash!=$their_md5_hash)
  {
    done(451, <span class="stringliteral">"MD5 hash is invalid (yours is "</span>.$their_md5_hash.<span class="stringliteral">", but mine is "</span>.$my_md5_hash.<span class="stringliteral">")"</span>);
  }

  <span class="keywordflow">if</span>(!empty($crash_guid))
  { 
    <span class="comment">// If crash GUID presents, use it as file name</span>
    $file_name = $file_root.$crash_guid.<span class="stringliteral">".zip"</span>;
  }
  <span class="keywordflow">else</span>
  {
    <span class="comment">// Generate random file name </span>
    $date_time = date(DATE_RFC822);
    $file_name = $file_root.md5($file_data.$date_time).<span class="stringliteral">".zip"</span>;
  }
  
  <span class="comment">// Write decoded data to file</span>
  $f = fopen($file_name, <span class="stringliteral">"w"</span>);
  <span class="keywordflow">if</span>($f==FALSE)
  {
    done(452, <span class="stringliteral">"Couldn't save data to local storage"</span>); 
  }
         
  fwrite($f, $file_data);
  fclose($f);
}

<span class="comment">// OK.</span>
done(200, <span class="stringliteral">"Success."</span>);

?&gt;
</pre></div><h2><a class="anchor" name="smtpsend">
Sending Crash Report Using SMTP Connection</a></h2>
CrashRpt has a simple built-in SMPT client. It can try to send an error report to recipient using SMTP connection without any user interaction. The error report is sent as an E-mail message with attachments.<p>
If user provides his/her email address, CrashRpt tries to use the address's MX domain record to determine local SMTP server name and relay the email message to that server.<p>
If user doesn't provide his/her email address, CrashRpt tries to send the email directly to recipient using the MX record of recipent's domain. Another way to specify the SMTP server address and port is by using <a class="el" href="struct_c_r___i_n_s_t_a_l_l___i_n_f_o_a.html#31f5db1808f060b09df3305ce7b85bd1">CR_INSTALL_INFO::pszSmtpProxy</a> member.<p>
Many SMTP servers may block direct access to them to avoid spam (for example Google does so). This way may also fail if firewall blocks outgoing connections on port 25.<h2><a class="anchor" name="smapisend">
Sending Crash Report Using Simple MAPI</a></h2>
CrashRpt can use the default E-mail client, for example, Mozilla Thunderbird or Microsoft Outlook, to send an error report as email. The error report is sent as an E-mail message with attachments. This requires some user interaction.<p>
This delivery method has the lowest priority by default.<h2><a class="anchor" name="sending_error_reports_faq">
FAQ</a></h2>
<b>Does crashrpt support bidirectional communication (the user gets an email with updates on his issue)?</b><p>
We currently do not support bidirectional communication, because this would require some complex server-side functionality. You can add such integration on your server for users of your software if you wish. </div>
<hr size="1"><address style="text-align: right;"><small>Generated on Sat Oct 22 17:37:43 2011 for CrashRpt by&nbsp;
<a href="http://www.doxygen.org/index.html">
<img src="doxygen.png" alt="doxygen" align="middle" border="0"></a> 1.5.9 </small></address>
</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Russian Federation Russian Federation
I am a software developer currently living in Tomsk, Russia. I received a PhD degree in Computer Science from Tomsk Polytechnic University in 2010. I have been professionally developing C/C++ and PHP software since 2005. I like contributing to open-source and writing programming articles for popular web resources, like CodeProject. Besides writing, I love skiing and watching Formula-1.

Comments and Discussions