October 11, 2008

Exposing .NET assembly as a COM object.

Exposing .NET Assemblies as COM components. - Trouble is that he suggest placing the .NET component in the GAC. The problem with this is that the assembly and all its dependencies must then have a strong name! Better expose on deploying .NET Assemblies as COM components Sourcing .Net events for COM Sinks Really Raising Events Handled by a C++ COM Sink Sinking events from managed code in unmanaged C++ This is the simple method that avoids the use of the GAC. In the 'AssemblyInfo.cs' file add the following:
// Setting ComVisible to false makes the types in this assembly not visible 
// to COM components.  If you need to access a type in this assembly from 
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(true)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f23114e6-5754-4793-b2c2-8cbe299421ad")]
The .Net component should have the following form:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace TestNETComComponent
{
   // Declare the interface
   [ComVisible(true), Guid("23661D34-4D5E-42e7-9992-CA64923E2221")]
   // No IDIspatch, only the simplest IUknown derived COM interface
   [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
   public interface ISomeInterface
   {
      int DoSomething();
      int DoSomethingElse(string arg);
   }

   // Need a class to implement the interface 
   [Guid("2AD6213C-0632-450b-8742-224913943C87")]
   [ProgId("Test.TestNETComComponent")]
   [ClassInterface(ClassInterfaceType.None)]
   public class TestNETCom : ISomeInterface
   {
      #region ISomeInterface Members

      public int  DoSomething()
      {
         Debug.WriteLine("TestNETCom.DoSomething()");
         return 1;
      }

      public int DoSomethingElse(string arg)
      {
     Debug.WriteLine("TestNETCom.DoSomethingElse() - " + m_SomeString);
         return 1;
      }

      #endregion
   }
}
Use this command line to register the assembly with COM and create the type library: regasm.exe /codebase /tlb TestNETComponent.dll To unregister the assembly: regasm.exe /unregister TestNETComponent.dll

October 7, 2008

Batch File To Clean A Build

Batch file to clean a development environment Add /f to the 'del ...' statements to force deletion of readonly files as well
cd "c:\SomeDevPath"
del /q /s *.exe
del /q /s *.dll
del /q /s *.pdb
del /q /s *.cache
del /q /s *.resources
del /q /s *.proj.user
pause

Interlocked.CompareExchange Usage

To protect an area of code without blocking:
int m_InUse = 0;
...
// Only allow one thread to enter but do not queue up other threads
if (System.Threading.Interlocked.CompareExchange(ref m_InUse, 1, 0) == 0)
{  
  DoSomething();
  // No longer in use, reset it.
  // NOTE: Interlocked.Exchange not needed here as this is a write of a literal value!
  m_InUse = 0;
} 
Is semantically the equivalent of:
static object Lock = new System.Object();
public int CompareExchange(ref int loc, int value, int comp)
{
    Monitor.Enter(Lock);
    int ret = loc;
    if (ret == comp)  
        loc = value;
    Monitor.Exit(Lock);
    return ret;
}
although it does NOT use Monitors internally.
There is a good example of this being used in the Windows Service Class Example

Boxing and Interfaces

public static void TestBoxing()
{
    // Create value
    SomeValueType myval = new SomeValueType();
    myval.X = 123;
    Debug.WriteLine("1. myval: " + myval.ToString());

    // box it
    object obj = myval;
    Debug.WriteLine("2. boxed: " + obj.ToString());

    // modify the contents in the box.
    ISomeValue iface = (ISomeValue)obj;
    iface.X = 456;
    Debug.WriteLine("3. After interface call: " + obj.ToString());

    // unbox it and see what it is.
    SomeValueType newval = (SomeValueType)obj;
    Debug.WriteLine("4. unboxed: " + newval.ToString());
    Debug.WriteLine("5. myval: " + myval.ToString());
    ((ISomeValue)myval).X = 789;
    Debug.WriteLine("6. myval: " + myval.ToString() + " obj: " + obj.ToString());
    ((ISomeValue)obj).X = 789;
    Debug.WriteLine("7. obj: " + obj.ToString());
    myval = (SomeValueType)obj;
    Debug.WriteLine("8. myval: " + myval.ToString());
}
Output:
1. myval: X=123
2. boxed: X=123
3. After interface call: X=456
4. unboxed: X=456
5. myval: X=123
6. myval: X=123 obj: X=456
7. obj: X=789
8. myval: X=789
Conclusion: To use an interface to modify a value type you must explicitly box and unbox it.

Invoke/BeginInvoke On A GUI Thread

Good article on BeginInvoke and Invoke differences
Here is a code template for it. On the GUI class (form or control derived object):
  private delegate void DataChanged(Object Sender, BindingEventArgs Args);

  private void HandleDataChanged(Object
Sender, BindingEventArgs Args)
  {
    // If this delegate is invoked on another thread
    if (this.InvokeRequired)
    {
      // Asynchronously call THIS method later
      // but this time on our own GUI thread
      // BeginInvoke translates to a PostMessage call on
      // our window.
      BeginInvoke(new DataChangedDelegate(HandleDataChanged),
 new object[] { Sender, Args });
    }
    else // when the method is invoked on the GUI thread
    { // Update our forms/controls as required
      UpdateGui(Args);
    }
  }
Simpler form of the InvokeRequired/BeginInvoke or Invoke pattern using the MethodInvoker
private void OnHandleDataChanged()
{
  // If this delegate is invoked on another thread
  if (this.InvokeRequired)
  {
    // Asynchronously call THIS method later
    // but this time on our own GUI thread
    // BeginInvoke translates to a PostMessage call on
    // our window.
    // Invoke maps to a SendMessage call, it will BLOCK 
    // the calling thread until finished
    BeginInvoke/Invoke(new MethodInvoker(HandleDataChanged));
  }
  else // when the method is invoked on the GUI thread
  { 
    // Update our forms/controls as required
  }
}
Simplest InvokeRequired Pattern, again this is within the GUI object:
// Define the delegate
private delegate void SomeDelegate(string arg1, int arg2);
// or (easier) use Action<string, int>, see below

private void HandleDataChanged(string arg1, int arg2)
{
  // IF this delegate is invoked on another thread
  if (this.InvokeRequired)
  { // THEN Call it again asynchronously on the GUI thread 
    this.BeginInvoke(
     new Action<string, int>(HandleDataChanged),
     // Make sure the number of args here matches the number 
     // of args in the delegate!
     new object[] { arg1, arg2 });
    return;
  }
  // From this point onwards we are definitely on the GUI thread again
  
  // Update our forms/controls as required
  ...    
}
As a rule use BeginInvoke unless Synchronization is critical when marshalling to the GUI thread. Note that BeginInvoke() in this context (marshalling to a GUI thread) is a fire and forget thing, no need to call EndInvoke() and all that shenanigans.

BackgroundWorker Usage

Better Sample BackgroundWorker usage Create a class that derives from the BackgroundWorker class that implements the threading routine by overriding the OnDoWork method rather than implementing the work method using the 'DoWork' event. The class user only needs to hook up the 'ProgressChanged' and 'RunWorkerThread' completed events. This style is more object orientated than the conventional usage where the worker code is specified in the 'DoWork' event. All the routines executed on the background thread can be separated from the rest of the code and encapsulated in the worker class. This makes it easier to see what shared data the threading routine is accessing.
// This sample deletes all the 'bin' and 'obj' directories 
// under a specified directory
internal class CleanDirWorker : BackgroundWorker
{
    string m_TargetDirectory = string.Empty;

    float total = 0;
    float count = 0;


    public CleanDirWorker() :
        base()
    {
        this.WorkerReportsProgress = true;
    }

    public string TargetDirectory
    {
        get { return m_TargetDirectory; }
        set { m_TargetDirectory = value; }
    }

    private void CleanDirectories(string dirTarget)
    {
        if (Directory.Exists(dirTarget))
        {
            DirectoryInfo diTarg = new DirectoryInfo(dirTarget);
            DirectoryClean(diTarg);
        }
        else
        {
            string msg = "Directory \'" + dirTarget + "\' does not exist";
        }
    }

    private void DirectoryClean(DirectoryInfo diTarg)
    {
        DirectoryInfo[] binDirs = diTarg.GetDirectories("bin", 
    SearchOption.AllDirectories);
        DirectoryInfo[] objDirs = diTarg.GetDirectories("obj", 
    SearchOption.AllDirectories);
        total = binDirs.Length + objDirs.Length;
        this.ReportProgress(10);
        DeleteDirs(binDirs);
        DeleteDirs(objDirs);
    }

    private void DeleteDirs(DirectoryInfo[] dis)
    {
        foreach (DirectoryInfo di in dis)
        {
            try
            {
                Debug.WriteLine("Deleting \'" + di.FullName + "\'");
                //di.Delete(true);
                count = count + 1.0f;
                int progress = 10 + (int)Math.Round(0.9f * total / (total - count));
                this.ReportProgress(progress);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Exception occured trying to delete \'" + 
    di.FullName + "\' :" + ex.ToString());
            }
        }
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        try
        {
            if (!string.IsNullOrEmpty(m_TargetDirectory))
            {
                //DirectoryCleaner dc = new DirectoryCleaner();
                CleanDirectories(m_TargetDirectory);
            }
        }
        catch (Exception ex)
        {
            Trace.WriteLine("Exception occured :" + ex.ToString());
        }
    }
}
To use it we use the events to monitor the progress of the worker thread and to find out when the worker thread has completed
// NOTE pbClean is a progress bar in this sample 

private void button_Click(object sender, RoutedEventArgs e)
{
    if (!string.IsNullOrEmpty(tbCleanTarg.Text))
    {
        AsyncCleanDirectories();
    }
}

#region CleanDirWorker Usage
CleanDirWorker m_CleanDirWorker = null;

private void InitializeCleanWorkerDir()
{
    m_CleanDirWorker = new CleanDirWorker()
    m_CleanDirWorker.RunWorkerCompleted += new
         RunWorkerCompletedEventHandler(m_CleanDirWorker_RunWorkerCompleted);
    m_CleanDirWorker.ProgressChanged += 
      new ProgressChangedEventHandler(m_CleanDirWorker_ProgressChanged);
}

private void TerminateCleanWorkerDir()
{
 m_CleanDirWorker.Dispose();
}

private void AsyncCleanDirectories()
{
    if (m_CleanDirWorker == null)
    {
 InitializeCleanWorkerDir();
        pbClean.Value = 0;
        m_CleanDirWorker.TargetDirectory = tbCleanTarg.Text;
        m_CleanDirWorker.RunWorkerAsync(null);
    }
}

void m_CleanDirWorker_ProgressChanged(
  object sender, 
  ProgressChangedEventArgs e)
{
    pbClean.Value = e.ProgressPercentage;
}

private void m_CleanDirWorker_RunWorkerCompleted(
  object sender,
  RunWorkerCompletedEventArgs e)
{
    TerminateCleanWorkerDir();
}
#endregion CleanDirWorker Usage