Log4NET for C++

19. June 2008 03:45 by Mrojas in General  //  Tags: , , , , , ,   //   Comments (0)

Recently my friend Yoel had just a wonderful idea. We have an old Win32 C++ application, and we wanted to add a serious logging infraestructure so we can provide better support in case the application crashes.

So Yoel came with the idea of using an existing framework for logging: LOG4NET

The only problem was, how can we integrate these two together. One way was problably exporting a .NET object as COM. But Yoel had a better idea.

Create a C++ Managed application that will comunicate with the LOG4NET assemblies, and export functions so the native applications can use that. How great is that.

Well he finally made it, and this is the code of how he did it.

First he created a C++ CLR Empty project and set its output type to Library. In the references we add a refrence to the Log4Net Library. We add a .cpp code file and we call it Bridge.cpp. Here is the code for it:

#include <atlstr.h>

using namespace System;

/// <summary>

/// Example of how to simply configure and use log4net

///
</summary>

ref class LoggingExample
{
private:
// Create a logger for use in this class
static log4net::ILog^ log = log4net::LogManager::GetLogger("LoggingExample");static LoggingExample()
{
    log4net::Config::BasicConfigurator::Configure();
}

public:static void ReportMessageWarning(char* msg)
{
String^ data = gcnew String(msg);
log->Warn(data);
}

static void ReportMessageError(char* msg)
{
String^ data = gcnew String(msg);
log->Error(data);
}

static void ReportMessageInfo(char* msg)
{
String^ data = gcnew String(msg);
log->Info(data);
}
static void ReportMessageDebug(char* msg)
{
String^ data = gcnew String(msg);
log->Debug(data);
}

};

extern "C"
{

_declspec(dllexport) void ReportMessageWarning(char* msg)
 {
LoggingExample::ReportMessageWarning(msg);
}

_declspec(dllexport) void ReportMessageError(char* msg)
{
LoggingExample::ReportMessageError(msg);
}

_declspec(dllexport) void ReportMessageInfo(char* msg)
{
LoggingExample::ReportMessageInfo(msg);
}

_declspec(dllexport) void ReportMessageDebug(char* msg)
{
LoggingExample::ReportMessageDebug(msg);
}

}

Ok. That's all. Now we have a managed C++ DLL that exposes some functions as an standard C++ DLL and we can use it with native win32 applications.

Let's do a test.

Lets create a Win32 Console application. And add this code:

// Log4NetForC++.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <atlstr.h>
extern "C"
{

_declspec(dllimport) void ReportMessageWarning(char* msg);

_declspec(dllimport) void ReportMessageError(char* msg);_declspec(dllimport) void ReportMessageInfo(char* msg); _declspec(dllimport) void ReportMessageDebug(char* msg);

}

int _tmain(int argc, _TCHAR* argv[])
{
ReportMessageWarning(
"hello for Log4NET");
ReportMessageError(
"hello for Log4NET");
ReportMessageInfo("hello for Log4NET");
ReportMessageDebug(
"hello for Log4NET");
return 0;
}

Ok. Now we just test it and we get something like:

Output

 Cool ins't it :)

Dataset save columns as Attributes

5. June 2008 11:07 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

This code is so handy that I'm posting it just to remember. I preffer to serialize my datasets as attributes instead of elements. And its just a matter of using a setting. See: 

Dim cnPubs As New SqlConnection("Data Source=<servername>;user id=<username>;" & _
"password=<password>;Initial Catalog=Pubs;")
Dim daAuthors As New SqlDataAdapter("Select * from Authors", cnPubs)

Dim ds As New DataSet()
cnPubs.Open()
daAuthors.Fill(ds, "Authors")

Dim dc As DataColumn
For Each dc In ds.Tables("Authors").Columns
dc.ColumnMapping = MappingType.Attribute
Next

ds.WriteXml("c:\Authors.xml")

Console.WriteLine("Completed writing XML file, using a DataSet")
Console.Read()

Visual Studio and the nasty No files found

5. June 2008 08:52 by Mrojas in General  //  Tags: , ,   //   Comments (0)

The find in files options of the IDE is part of my daily bread tasks. I use it all day long to get to the darkest corners of my code and kill some horrible bugs.

But from time to time it happens that the find in files functionality stops working. It just starts as always but shows and anoying "No files found..." and i really irritated me because the files where there!!!! !@#$!@#$!@#$

Well finally a fix for this annoyance is (seriously is not a joke, don't question the dark forces):

1.  Spin your chair 3 times for Visual Studio 2003 and 4 times for Visual Studio 2005

2. In Visual Studio 2003 press CTRL SCROLL-LOCK and in Visual Studion 2005 press CTRL and BREAK.

3. Dont laugh, this is serious! It really works.

 

ActiveX Controls in .NET and the Enabled bug

18. April 2008 07:04 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

We found and interesting bug during a migration. The issue was that when there was an iteration through the controls in the forms, and you set the Enabled property, the property didn't get set.

After some research my friend Olman found this workaroung

 

foreach(Control c in Controls)

  ctrl.Enabled = true;
  if (ctrl is AxHost) ((AxHost)ctrl).Enabled = true;
}

Install Assembly in GAC with C#

9. April 2008 05:42 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

 

Do you want to create a program to install your assembly in the GAC using C#. Well if you had that requirement or you are just curious, here is how.

I read these three articles:

Demystifying the .NET Global Assembly Cache

GAC API Interface 

Undocumented Fusion

What I wanted just a straight answer of how to do it. Well here is how:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.GAC;
//// Artinsoft
//// Author: Mauricio Rojas orellabac@gmail.com mrojas@artinsoft.com
//// This program uses the undocumented GAC API to perform a simple install of an assembly in the GAC
namespace AddAssemblyToGAC
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an FUSION_INSTALL_REFERENCE struct and fill it with data
            FUSION_INSTALL_REFERENCE[] installReference = new FUSION_INSTALL_REFERENCE[1];
            installReference[0].dwFlags = 0;
            // Using opaque scheme here
            installReference[0].guidScheme = System.GAC.AssemblyCache.FUSION_REFCOUNT_OPAQUE_STRING_GUID;
            installReference[0].szIdentifier = "My Pretty Aplication Identifier";
            installReference[0].szNonCannonicalData= "My other info";
 
            // Get an IAssemblyCache interface
 
            IAssemblyCache pCache = AssemblyCache.CreateAssemblyCache();
            String AssemblyFilePath = args[0];
 
            if (!System.IO.File.Exists(AssemblyFilePath))
            {
                Console.WriteLine("Hey! Please use a valid path to an assembly, assembly was not found!");
            }
            int result = pCache.InstallAssembly(0, AssemblyFilePath,installReference);
            //NOTE recently someone reported a problem with this code and I tried this:
            // int result = pCache.InstallAssembly(0, AssemblyFilePath,null); and it worked. I think is a marshalling issue I will probably review it later
 
            Console.WriteLine("Process returned " + result);
            Console.WriteLine("Done!");
 
        }
 
    }
}

And here's the complete source code for this application: DOWNLOAD SOURCE CODE AND BINARIES

Useful MSBuild Custom Tasks

3. April 2008 08:48 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

I present here the implementation of some useful tasks
In Artinsoft we perform massive migrations of VB6 code to VB.Net
and C#.

And sometimes after migration there are customizations to be
performed on the code, to add new functionality or to set certain new
properties.

 The idea was to provide a couple of very simple and puntual MSBuildTask
 to illustrate how easy it is to create custom tasks and to provide a starting
 point to create new one.

 You can freely use this code, just keep this comments and remember this is just
 a sample code. There are not warranties. ;) And i made it a rush I know it could have
 been written better

 Artinsoft
 mrojas@artinsoft.com
 

The implemented tasks are:

RemoveCOMReference 
 Removes COMReferences from your project. COM references are for when you are using things thru Interop
FixOutputPath 
 Resets the output paths to bin\Release and bin\Debug
AddProjectReference Add a reference to another project. A nice feature is that it generates RelativePaths the way Visual Studio does
AddSimpleReference Add a reference to a very simple references like the ones you add when you click Add Reference and add System.EnterpriseServices
ChangeCurrentBuildSetting This can be used for a lot of things.

For example to turn on or off the RegisterForComInterop setting

To set conditional compilation variables

To set debug info to pdbonly

The sky is the limit jeje

The following is a sample project file

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- make sure that the Assembly is in a place where msbuild can find it, a simple way is just to put it 
in the same directory of your .proj file -->
<UsingTask TaskName="SomeUsefulTasks.MSBuild.RemoveCOMReference"    
  AssemblyFile="SomeUsefulTasks.dll"/> 
<UsingTask TaskName="SomeUsefulTasks.MSBuild.FixOutputPath"         
  AssemblyFile="SomeUsefulTasks.dll"/> 
<UsingTask TaskName="SomeUsefulTasks.MSBuild.AddProjectReference"   
  AssemblyFile="SomeUsefulTasks.dll"/> 
<UsingTask TaskName="SomeUsefulTasks.MSBuild.AddSimpleReference"    
  AssemblyFile="SomeUsefulTasks.dll"/> 
<UsingTask TaskName="SomeUsefulTasks.MSBuild.ChangeProjectBuildSetting"    
  AssemblyFile="SomeUsefulTasks.dll"/> 
 
   <ItemGroup>
    <VSProjects Include="$(Start)\**\*.*proj" />
  </ItemGroup>
 
<!--
Run with 
MSBUILD SampleProject.proj /target:COMReference /p:Start="C:\MyCode"
-->
  <Target Name="COMReference">
    <RemoveCOMReference SourceFiles="@(VSProjects)" ComReferenceName="MSXML2" />
  </Target> 
  
  
<!-- 
Adds a project reference  
Run with 
MSBUILD SampleProject.proj /target:AddProjectReference /p:Start="C:\MyCode" /p:ProjectPath="C:\MyCode\MyNewSuperProject\Project1.csproj"
-->
   <Target Name="AddProjectReference">
      <AddProjectReference SourceFiles="@(VSProjects)"  AbsolutePathToProject="$(ProjectPath)"/>
   </Target> 
   
   
<!-- 
Adds a reference to a standard assembly 
Run with 
MSBUILD SampleProject.proj /target:AddSimpleReference /p:Start="C:\MyCode" /p:Reference="System.EnterpriseServices"   
-->
<Target Name="AddSimpleReference">
      <AddSimpleReference SourceFiles="@(VSProjects)" Reference="$(Reference)" />
</Target> 
 
  
<!-- 
Resets the OutputPaths to .\bin\Debug and .\bin\Release 
Run with 
MSBUILD SampleProject.proj /target:FixOutput /p:Start="C:\MyCode" /p:Reference="System.EnterpriseServices"   
-->
<Target Name="FixOutput">
    <FixOutputPath SourceFiles="@(VSProjects)"  />
</Target> 
  
<!-- 
Adds a reference to a standard assembly 
There are several options here for example to set the project debug info to pdb-only do this:
Run with 
MSBUILD SampleProject.proj /target:ChangeSettingToPDBOnly /p:Start="C:\MyCode" 
Or run with 
MSBUILD SampleProject.proj /target:ChangeSettingAddAConstant /p:Start="C:\MyCode" 
Or run with 
MSBUILD SampleProject.proj /target:SettingComInterop /p:Start="C:\MyCode" 
-->
 
<Target Name="ChangeSettingToPDBOnly">
      <ChangeProjectBuildSetting 
          SourceFiles="@(VSProjects)" 
          ConfigurationType="All" 
          Setting="DebugType" 
          NewValue="pdbonly" />
</Target> 
   
<Target Name="ChangeSettingAddAConstant">
      <ChangeProjectBuildSetting 
          SourceFiles="@(VSProjects)" 
          ConfigurationType="All" 
          Setting="DefineConstants" 
          NewValue="MYNEWVAL" 
          Add="True"/>
</Target> 
 
 
<Target Name="SettingComInterop">
      <ChangeProjectBuildSetting 
          SourceFiles="@(VSProjects)" 
          ConfigurationType="All" 
          Setting="RegisterForComInterop" 
         NewValue="true" />
</Target> 
 
  
</Project>

DOWNLOAD CODE AND BINARIES

Get Relative Path

3. April 2008 04:24 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

I had the requirement of creating a MSBuild custom task that opens a .csproj
adds a reference to another project.

The problem I faced is that references in VisualStudio are generated as relative paths,
so I needed something to help me generate relative paths.

After some Googleing I finally found this code. It was in a long forum discussion
and was posted by a guy named something like Marcin Grzabski. And here it is for posterity.

        private static string EvaluateRelativePath(string mainDirPath, string absoluteFilePath)
        {
            string[]
            firstPathParts = 
             mainDirPath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
            string[]
            secondPathParts = 
             absoluteFilePath.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
 
            int sameCounter = 0;
            for (int i = 0; i < Math.Min(firstPathParts.Length,secondPathParts.Length); i++)
            {
                if (
                !firstPathParts[i].ToLower().Equals(secondPathParts[i].ToLower()))
                {
                    break;
                }
                sameCounter++;
            }
 
            if (sameCounter == 0)
            {
                return absoluteFilePath;
            }
 
            string newPath = String.Empty;
            for (int i = sameCounter; i < firstPathParts.Length; i++)
            {
                if (i > sameCounter)
                {
                    newPath += Path.DirectorySeparatorChar;
                }
                newPath += "..";
            }
            if (newPath.Length == 0)
            {
                newPath = ".";
            }
            for (int i = sameCounter; i < secondPathParts.Length; i++)
            {
                newPath += Path.DirectorySeparatorChar;
                newPath += secondPathParts[i];
            }
            return newPath;
        }

And to use is just do somelines like:

 

            String test = EvaluateRelativePath(@"E:\Source_Code\Code\ProjectsGroup1\Project1", @"E:\Source_Code\Code\ProjecstGroup2\Project2");
 
//This will genearate something like ..\..\ProjectGroup2\Project2

Pretty Printers / Format Code / for C# VB.NET

4. March 2008 09:49 by Mrojas in General  //  Tags: , , ,   //   Comments (0)

In my past life I spent a few eons writing Java code. And it wasn't bad. We had nice tools like Jalopy! that allowed us to have
code in a very standard way.

And I missed that. I've been looking around for something similar but I havent found anything like that :(

Until I found a great post from Chris Eargle, he improved the original solution from Kelvinpinch

Well here's the code.

Public Sub FormatSolution()
Dim sol As Solution = DTE.Solution
For i As Integer = 1 To sol.Projects.Count
FormatProject(sol.Projects.Item(i))
Next
End Sub 
Private Sub FormatProject(ByVal proj as Project)
     For i As Integer = 1 To proj.ProjectItems.Count
FormatProjectItem(proj.ProjectItems.Item(i))
Next
End Sub
 
Private Sub FormatProjectItem(ByVal projectItem As ProjectItem)
     If projectItem.Kind = Constants.vsProjectItemKindPhysicalFile Then
          If projectItem.Name.LastIndexOf(".cs") = projectItem.Name.Length - 3 Then
Dim window As Window = projectItem.Open(Constants.vsViewKindCode)
window.Activate()
projectItem.Document.DTE.ExecuteCommand("Edit.FormatDocument")
window.Close(vsSaveChanges.vsSaveChangesYes)
End If
End If
'Be sure to format all of the ProjectItems.
If Not projectItem.ProjectItems Is Nothing Then
For i As Integer = 1 To projectItem.ProjectItems.Count
FormatProjectItem(projectItem.ProjectItems.Item(i))
Next
End If
'Format the SubProject if it exists.
     If Not projectItem.SubProject Is Nothing Then
FormatProject(projectItem.SubProject)
End If
End Sub

To use it perform the following steps:

  •  Go to the VS IDE Tools Option
  • Then Select the Macros option and select Macros IDE...
  • This will open the macros IDE
  • In the Macros IDE navigate to the Module1, and Insert the code

To run the Macro go to Tools\Macros\Macro Explorer

And select FormatAll :)

And last but not least if you want to runit from the command line just do:

devenv /command "Macros.MyMacros.Module1.FormalAll" MyProject.csproj or

devenv /command "Macros.MyMacros.Module1.FormalAll" MySol.sln or


 

ActiveX exceptions when running in .NET

5. February 2008 04:11 by Mrojas in General  //  Tags: , , , ,   //   Comments (0)

During migration to C# or .NET it is easier to keep the same ActiveX.
The VBCompanion does a great work in migrating the ActiveX control using the .NET ActiveX wrappings and fixing all method calls. 
Sadly sometimes those ActiveX do not work properly in .NET.

Well we have good news.
Recently my friend Jose David (who we keep bothering because he is now 
a Project Manager and now he only programs in MS Excel and MS Project, I added the MS by his request :P) fixed a curious bug
we had with an aplication we migrated from VB6 to C#.

The thing is that the aplication had an ActiveX control with a strange runtime behaviour.
We migrated the application keeping the ActiveX control and in most ocasions it worked ok.

But randomly it started throwing exceptions.

During the testing he discovered that if he repeated the steps slowly the bug did not reproduced.

So his idea was that it was due a garbage collection issue. And SURPRINSINLY he was right :P

He added this:

System.GC.Collect();

System.

GC.WaitForPendingFinalizers();

 And the application started to work.

It seems like some of the COM objects needed a little more time for releasing all references :)

 

IsMissing migration in VB.NET or C#

Recently  we added some support for migrating the IsMissing function to VB.NEt or C#

The thing is. In VB6 the IsMissing Function is TRUE only if you have something like:

Public Sub Foo(Optional str)

 

Where you dont specify the variable type, or if you have

 

Public Sub Foo(Optional str as Variant)

 

And is IsMissing is FALSE for any other case. Including Optional variables whose definition type is not Variant.

 

So let's see some examples to illustrate the idea:
 Example 1:

Public Sub Foo(str, a As Integer, b As Integer, Optional c As Integer)
    MsgBox (str & "Foo Is missing a " & IsMissing(a))
    MsgBox (str & "Foo Is missing b " & IsMissing(b))
    MsgBox (str & "Foo Is missing c " & IsMissing(c))
End Sub

 

It occurs that IsMissing is really FALSE in all cases. So it is equivalent to:

 

Public Sub Foo(str, a As Integer, b As Integer, Optional c As Integer)
    MsgBox (str & "Foo Is missing a " & false)
    MsgBox (str & "Foo Is missing b " & false)
    MsgBox (str & "Foo Is missing c " & false)
End Sub

 

 

Example 2:

 

Public Sub Goo(str, a As Integer, b As Integer, Optional c As Object, Optional d As Byte, Optional e)
    MsgBox (str & "Goo Is missing a" & IsMissing(a))
    MsgBox (str & "Goo Is missing b" & IsMissing(b))
    MsgBox (str & "Goo Is missing c" & IsMissing(c))
    MsgBox (str & "Goo Is missing d" & IsMissing(d))
    MsgBox (str & "Goo Is missing e" & IsMissing(e))
End Sub

 

All cases EXCEPT "e" are equivalent to FALSE

 

Public Sub Goo(str, a As Integer, b As Integer, Optional c As Object, Optional d As Byte, Optional e)
    MsgBox (str & "Goo Is missing a" & false)
    MsgBox (str & "Goo Is missing b" & false)
    MsgBox (str & "Goo Is missing c" &false)
    MsgBox (str & "Goo Is missing d" & false)
    MsgBox (str & "Goo Is missing e" & IsMissing(e))
End Sub

 

So if you are migrating your VB6 Code to C# put attention to these little details it can save you a lot of time.And remember that this is just one feature of VBCompanion tool ;)