Monday, May 4, 2009

Cleanup artifacts in CC.Net with artifactcleanup

I got an email from the system admin that the CC.Net buildserver used a lot of diskspace. It turned out that I got 1000 buildlogs for one project on the server...

The solution was to configure a artifactcleanup in the cc.config. See the CC.Net documentation.

Saturday, May 2, 2009

Code generation for a COM wrapper assembly

For my last post, I needed some quick and dirty code generator. Basically, I've a COM Iterop assembly full with interfaces I need to wrap with a specified template.

The following code works, but still needed some handwork (like hand coding the ref parameters). Use it on your own risk ;)


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;

namespace CodeGeneration
{
class Program
{
static Dictionary<string, string> keywordType;

static void Main(string[] args)
{
FillKeywordType();

Assembly interop = Assembly.Load(@"Tridion.ContentManager.Interop.cm_tom");
foreach (Type t in interop.GetTypes())
{
//Console.WriteLine(t.Name);
if (t.Name.StartsWith("_"))
{
WriteOut(t, t.Name.Replace("_",string.Empty));
}
}
Console.ReadLine();
}

private static void FillKeywordType()
{

keywordType = new Dictionary<string, string>();
keywordType.Add("System.String", "string");
keywordType.Add("System.Void", "void");
keywordType.Add("System.Int32", "int");
keywordType.Add("System.DateTime", "DateTime");
keywordType.Add("System.Boolean", "bool");
keywordType.Add("System.Object", "object");
}

private static void WriteOut(Type t, string className)
{
Console.WriteLine("Info of {0}", t.Name);

using (StreamWriter writer = new StreamWriter(string.Format("D:\\Work\\DisposableOut\\{0}.cs", className) ))
{

WriteHeader(writer, className);


foreach (MethodInfo mi in t.GetMethods())
{
if (!(mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_") || mi.Name.StartsWith("let_")))
{
string methodSig = string.Format("{1} {0}", mi.Name, GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace));
bool isVoid = (GetTypeName(mi.ReturnType.Name, mi.ReturnType.Namespace)=="void");
List<string> parameters = new List<string>();
List<string> parameterNames = new List<string>();
foreach (ParameterInfo pi in mi.GetParameters())
{
parameters.Add(string.Format("{1} {0}", pi.Name, GetTypeName(pi.ParameterType.Name, pi.ParameterType.Namespace)));
parameterNames.Add(pi.Name);
}
string parameter = string.Join(", ", parameters.ToArray());

writer.Write(@"public {0} ({2})" + Environment.NewLine +
@" {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" {5}com{1}.{3}({4});" + Environment.NewLine +
@" }}" + Environment.NewLine + Environment.NewLine,
methodSig, className, string.Join(", ", parameters.ToArray()), mi.Name, string.Join(", ", parameterNames.ToArray()),
isVoid?string.Empty:"return ");
}
}

foreach (PropertyInfo pi in t.GetProperties())
{
writer.Write(@"public {0} {1} {{" + Environment.NewLine, GetTypeName(pi.PropertyType.Name, pi.PropertyType.Namespace), pi.Name);
if (pi.CanRead)
{
//get
writer.Write(@" get {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" return com{1}.{0};" + Environment.NewLine +
@" }}" + Environment.NewLine ,
pi.Name, className);
}
if (pi.CanWrite)
{
//set
writer.Write(@" set {{" + Environment.NewLine +
@" checkNotDisposed();" + Environment.NewLine +
@" com{1}.{0} = value;" + Environment.NewLine +
@" }}" + Environment.NewLine,
pi.Name, className);
}
writer.Write("}" + Environment.NewLine + Environment.NewLine);
}

WriteFooter(writer);
writer.Flush();
writer.Close();
}
}

private static string GetTypeName(string name, string nameSpace)
{
string typeName = string.Format("{0}.{1}", nameSpace, name);
if (keywordType.ContainsKey(typeName))
{
return keywordType[typeName];
}
return typeName;
}

private static void WriteFooter(StreamWriter writer)
{
writer.Write(@" #endregion" + Environment.NewLine +
@" }" + Environment.NewLine +
@"}" + Environment.NewLine);
}

private static void WriteHeader(StreamWriter writer, string className)
{
StringBuilder fileHeader = new StringBuilder();
fileHeader.AppendFormat(@"using System;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Collections.Generic;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Text;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using System.Runtime.InteropServices;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"using Tridion.ContentManager.Interop.TDS;{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@"namespace DisposableTridion{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public class {1} : DisposableBase {0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" private Tridion.ContentManager.Interop.TDS.{1} com{1};{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" public {1}(Tridion.ContentManager.Interop.TDS.{1} {2}){0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" com{1} = {2};{0}", Environment.NewLine, className, className.ToLower());
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" ~{1}(){0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" Dispose(false);{0}", Environment.NewLine);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" protected override void DisposeMembers(){0}", Environment.NewLine);
fileHeader.AppendFormat(@" {{{0}", Environment.NewLine);
fileHeader.AppendFormat(@" DisposableBase.ReleaseComObject(com{1});{0}", Environment.NewLine, className);
fileHeader.AppendFormat(@" }}{0}", Environment.NewLine);
fileHeader.AppendFormat(@"{0}", Environment.NewLine);
fileHeader.AppendFormat(@" #region _{1} Members{0}", Environment.NewLine, className);

writer.Write(fileHeader.ToString());
}
}
}

Friday, May 1, 2009

Disposing COM objects

The Tridion solutions at my current assignment are working great, but now and then the CM website freezes. After investigating the issue with Tridion customer support, there is a suspicion the Tridion COM objects freeze under memory load.
This load is generated because our custom solutions do not release the COM objects they use. So now the codebase should be recoded with this cleanup operation.

When you don't cleanup this code, the cleanup will be initiated by the .NET Garbage Collector (GC). But the GC isn't working continually, but will only be called under memory pressure.
Because COM objects aren't run under as managed code, the GC doesn't know how much memory is used by them. Therefore, waiting until the GC will cleanup your code can take a long time, especially because COM objects are small objects on the managed memory.

The pattern to use for cleaning up unmanaged resources is the IDisposable interface. So with this pattern, we can release a COM object after you're done with it by calling the Marshal.ReleaseComObject method.

Because the Tridion Object Model (TOM) has a lot of objects, I'm creating a managed wrapper assembly, where on each object I can implement the IDisposable interface. Besides the interface, I need some more plumbing, so the first class to create is an abstract DisposableBase class:

public abstract class DisposableBase : IDisposable
{
private bool disposed = false;

protected void checkNotDisposed()
{
if (this.disposed)
{
string message = "Object of type " + base.GetType().Name + " cannot be accessed because it was disposed.";
throw new ObjectDisposedException(base.GetType().Name, message);
}
}

public void Dispose()
{
if (!this.disposed)
{
this.Dispose(true);
}
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
this.DisposeMembers();
}
this.disposed = true;
}

protected abstract void DisposeMembers();

public static void ReleaseComObject(object interopObject)
{
if (interopObject != null)
{
Marshal.ReleaseComObject(interopObject);
}
interopObject = null;
}
}



So I can use this base class in each wrapper class for TOM classes, like the Component class:

public class Component : DisposableBase
{
private Tridion.ContentManager.Interop.TDS.Component comComponent;

public Component(Tridion.ContentManager.Interop.TDS.Component component)
{
comComponent = component;
}

~Component()
{
Dispose(false);
}

protected override void DisposeMembers()
{
DisposableBase.ReleaseComObject(comComponent);
}

public void CheckIn(bool permanentLock)
{
checkNotDisposed();
comComponent.CheckIn(permanentLock);
}

_Component Members
}


So now it's perfectly safe to use the TOM to check in a component with the following code:

using (Component component = tdse.GetObject("tcm:8-123", EnumOpenMode.OpenModeView))
{
component.CheckIn(true);
}