Wednesday, January 30, 2008

Fun with COM Part 1 - Interfaces

Early in our development process we encountered a problem with our COM objects; namely, they are not serializable and therefore cannot be (safely) cached. Fortunately, all of the objects used within VB6 implement a VB6 interface...as a result, the decision was made to create .Net classes can implement these VB6 interfaces, so they can (among other things) be made cacheable. It was not an easy task, however, thanks to some oversights in the VB6 code and a generally stupid decision on the part of Microsoft: Visual Basic 6 defaults all parameters to being passed by reference if not explicitly specified. As a result, we have many, many places in our VB6 interface which look like this:
Private Property Get IField_ControlIdx() As Integer
IField_ControlIdx = m_intControlIdx
End Property

Private Property Let IField_ControlIdx(p_intControlIdx As Integer)
m_intControlIdx = p_intControlIdx
End Property


When implemented in VB.Net, that (should) look like this:
Public Property ControlIdx() As Short Implements ...
Get
returns m_intControlIdx
End Get
Set(ByRef value As Short)
m_controlIdx = value
End Set
End Property


Unfortunately, that doesn't compile, instead giving the following error: 'Set' parameter cannot be declared 'ByRef'. Changing 'ByRef' to 'ByVal' makes the property valid, but not the class, which gives the following compile error: [PropertyName] cannot implement [Propertyname] because there is no matching property on interface [Interface]. Stuck.


Microsoft has the following article on this issue: http://support.microsoft.com/kb/316581

Their solution? "To resolve this bug, rebuild the COM library so that it passes arguments to property procedures by value instead of by reference." Well sure, of course THAT would work, but that comes with its own issues...in our case, the fact that it would mean roughly 50 VB6 classes that would require updating and testing. Our interface is shared with our windows application, which is well past the point of a core change, so this wasn't an option.



Fortunately, there is another solution: C#.

Because the parameters are passed by reference, the C# interpreter sees the set properties as methods:
public void set_ControlIdx(ref short value)


Alternatively, you can implement the interface explicitly:
void [InterfaceName].set_ControlIdx(ref short value)


This is how we handled it. By implementing the interface explicitly, we were able to create our own, valid public properties, while hiding the ugly ByRef versions. To keep things consistent, we had the explicit methods reference the cleaner, public methods, keeping the logic in the same place.
public short ControlIdx
{
get { return m_controlIdx; }
set { m_controlIdx = value; }
}

short [Interface].get_controlIdx()
{
return this.ControlIdx;
}

void [Interface].set_controlIdx(ref short value)
{
this.ControlIdx = value;
}



Hope that helps!




Additional note for VB developers:

We actually took an additional step in our C# classes. In order to keep the bulk of our logic in Visual Basic (I personally prefer C#, but this is a VB shop), we actually made our C# classes abstract, and our public properties are abstract as well.

Then, we created matching VB.Net classes which inherit from our C# classes and put the logic there. The result is an extra file, and an external class library, but we were able to implement our VB6 interface without changing it, and while still keeping the actual logic in VB.Net.

What's this for, anyway?

The purpose of this page is simple: to provide a singular place to find solutions to problems I've encountered in programming projects. Hopefully, I can save someone else some trouble :)



For anyone who may be interested in the background details, I'm currently working on a large projected which involves providing access to existing business logic via a .Net web service. Initially the service will utilized by an outside company developing their own web site front end, but we are also developing our own presentation layer for testing (and eventually our own public release).

The project utilizes the following technologies:

  • Visual Basic 6 - all existing business logic is via VB6, logic that is much too large to port to .Net at this time.

  • Visual Basic.Net - the web service and test presentation layer

  • C# - to resolve a compatibility issue, which I plan to touch on in my first actual post.

  • XML

  • Ajax