Wednesday, October 11, 2006

VB.NET COM Interface Issues

I am a fan of VB.NET, but sometimes, for no apparent reason, it lacks a feature that its brother C# supports. I recently ran across the following issues that affect VB.NET, but not C#.

Both VB.NET and C# allow you to create classes with COM interfaces. This allows unmanaged code (VB 6, C++, etc.) to interact with your VB.NET or C# class. This can be very useful for environments were you have old legacy code that must be still be maintained, but you would like to do new development in a .NET language.

Issue #1 – No COM Help Strings on Interface Properties
One of the things COM allows you to do is specify a help string for any just about anything in a type library. An easy way to view these help strings is to use the Object Browser in VB 6. (You can get to the Object Browser by pressing the F2 key in VB6.) VB.NET and C# support COM help string through the use of the DescriptionAttribute class. The example below shows how you use this class to add help strings to class methods and properties.

Public Property Name() As String
    <Description("This is a sample property.")> _
    Get
        Return mName
    End Get
    <Description("This is a sample property.")> _
    Set(ByVal value As String)
        mName = value
    End Set
End Property

<Description("This is a sample method.")> _
Public Sub SampleMethod()
End Sub


This is how the descriptions look in the VB6 Object Brower.

Property SampleProperty As String
    Member of VbComExample.SimpleSampleClass
    This is a sample property.

Sub SampleMethod()
    Member of VbComExample.SimpleSampleClass
    This is a sample method.


If you take a look at the library in OLE View, you will see a coclass with the name of your class and an interface with the same name as your class preceded by an underscore (_). My class was named SimpleSampleClass, so the automatically generated interface name was _SimpleSampleClass.

This technique works fine, but this technique is discouraged by Microsoft. In the “.NET Framework Developer's Guide” section of the Visual Studio 2005 help on the page titled “Qualifying .NET Types for Interoperation” the following guideline appears:

- Classes should implement interfaces explicitly.

Although COM interop provides a mechanism to automatically generate an interface containing all members of the class and the members of its base class, it is far better to provide explicit interfaces. The automatically generated interface is called the class interface.


Once again in the “.NET Framework Developer's Guide” section of the Visual Studio 2005 help on the page titled “Introducing the Class Interface” it states:

Caution
Using the class interface, instead of explicitly defining your own, can complicate the future versioning of your managed class.


The following example implements the same class shown in the example above, only this time explicitly defining the COM interface separately:

<ComVisible(True)> _
<Guid(SampleClass.InterfaceId)> _
<Description("This is the interface for the SampleClass class.")> _
Public Interface ISampleClass

    <Description("This will not be used.")> _
    Property Name() As String

    <Description("This is a sample method.")> _
    Sub SampleMethod()

End Interface

<Guid(SampleClass.ClassId)> _
<ClassInterface(ClassInterfaceType.None)> _
<Description("This is a sample COM class.")> _
Public Class SampleClass
    Implements ISampleClass

    Public Property Name() As String Implements ISampleClass.Name
        <Description("Sample property description.")> _
        Get
            Return mName
        End Get
        <Description("Sample property description.")> _
        Set(ByVal value As String)
            mName = value
        End Set
    End Property
    Private mName As String

    <Description("This string will not be used.")> _
    Public Sub SampleMethod() Implements ISampleClass.SampleMethod

    End Sub

End Class


This is how the descriptions look in the VB6 Object Brower.

Property Name As String
    Member of VbComExample.SampleClass

Sub SampleMethod()
    Member of VbComExample.SimpleSampleClass
    This is a sample method.


If you take a look at the library in OLE View, you will see a coclass with the name of SampleClass and an interface with the name ISampleClass.

The descriptions on the Name property are lost because there is no way to specify the DescriptionAttribute on the Get and Set declarations in a VB.NET interface. C# does not have this problem, because it includes the get and set keywords in its interface definitions.

The following is the same example in C#:

[ComVisible(true)]
[Description("This is the interface for the SampleClass class.")]
[Guid(SampleClass.InterfaceId)]
public interface ISampleClass
{
    string Name
    {
        [Description("Name property description.")]
        get;
        [Description("Name property description.")]
        set;
    }

    [Description("This is a sample method.")]
    void SampleMethod();
}

[ClassInterface(ClassInterfaceType.None)]
[Description("This is a sample C# COM class.")]
[Guid(SampleClass.ClassId)]
public class SampleClass : ISampleClass
{


    string ISampleClass.Name
    {
        [Description("This won't be used.")]
        get
        {
            return mName;
        }
        [Description("This won't be used.")]
        set
        {
            mName = value;
        }
    }
    private string mName;

    [Description("This won't be used.")]
    void ISampleClass.SampleMethod()
    {
    }
}


Unlike its VB.NET brother the C# version includes the helper string, even when the interface is explicitly defined. This issue is really just annoying, but the next one actually prevents one from explicitly defining an interface properly.

Issue #2 – Broken Currency Support on Interface Properties
Consider the following property declared in a class with an automatically generated COM interface:

Public Property Money() As <MarshalAs(UnmanagedType.Currency)> Decimal
    <Description("Currency Example.")> _
    Get
        Return mMoney
    End Get
    <Description("Currency Example.")> _
    Set(<MarshalAs(UnmanagedType.Currency)> ByVal value As Decimal)
        mMoney = value
    End Set
End Property


If you lookup the propget and propput methods for this property in OLE View, you will find the following definitions:

[id(0x00000003), propget, helpstring("Currency Example.")]
CURRENCY Money();
[id(0x00000003), propput, helpstring("Currency Example.")]
void Money([in] CURRENCY rhs);


As you can see the help string is successfully captured and the .NET Decimal datatype value is marshaled as a VB6 Currency datatype.

However, if you use the recommended approach and explicitly define your COM class interface, the marshaling will be incorrect for the propput method. The following example demonstrates this problem:

<ComVisible(True)> _
<Guid(SampleClass.InterfaceId)> _
<Description("This is the interface for the SampleClass class.")> _
Public Interface ISampleClass

    <Description("This will not appear")> _
    Property Money() As <MarshalAs(UnmanagedType.Currency)> Decimal

End Interface

<Guid(SampleClass.ClassId)> _
<ClassInterface(ClassInterfaceType.None)> _
<Description("This is a sample COM class.")> _
Public Class SampleClass
    Implements ISampleClass

    Public Property Money() As _
        <MarshalAs(UnmanagedType.Currency)> Decimal _
        Implements ISampleClass.Money
        <Description("Currency Example.")> _
        Get
            Return mMoney
        End Get
        <Description("Currency Example.")> _
        Set(<MarshalAs(UnmanagedType.Currency)> ByVal value As Decimal)
            mMoney = value
        End Set
    End Property
    Private mMoney As Decimal

End Class


If you lookup the propget and propput methods for the Money property of the ISampleClass interface in OLE View, you will find the following definitions:

[id(0x60020003), propget]
CURRENCY Money();
[id(0x60020003), propput]
void Money([in] wchar_t rhs);


The propget method is marshaled correctly, but the propput is marshaled as a wchar_t. In this situation not only is the helpstring missing, but the property will not work correctly. If you want to marshal a Decimal property as a Currency, it is impossible to implement your solution using VB.NET using the “recommended” approach.

C#, however, does not have this problem as demonstrated in the code sample below:

[ComVisible(true)]
[Description("This is the interface for the SampleClass class.")]
[Guid(SampleClass.InterfaceId)]
public interface ISampleClass
{
    decimal Money
    {
        [Description("Money property description.")]
        [return: MarshalAs(UnmanagedType.Currency)]
        get;
        [Description("Money property description.")]
        [param: MarshalAs(UnmanagedType.Currency)]
        set;
    }
}


If you lookup the propget and propput methods for the Money property of the ISampleClass interface in OLE View, you will find the following definitions:

[id(0x60020003), propget, helpstring("Money property description.")]
CURRENCY Money();
[id(0x60020003), propput, helpstring("Money property description.")]
void Money([in] CURRENCY rhs);


In the C# version, not only is the helpstring present, but the property is correctly marshaled.

It is issues like these that give VB.NET a bad rap.

Tuesday, October 10, 2006

Improving String Handling Performance in ASP Applications

A must read for anyone who does work in Visual Basic 6 or VB Script.

Improving String Handling Performance in ASP Applications
By James Musson
Developer Services, Microsoft UK
http://msdn2.microsoft.com/en-us/library/aa302329.aspx
https://web.archive.org/web/20080410000235/http://msdn2.microsoft.com:80/en-us/library/aa302329.aspx

This article specifically discusses ASP applications, but it is also true for Visual Basic 6 applications. The problem appears to be magnified if the ASP application is hosted in a VMWare environment.