VisiBroker for .NET Developer’s Guide : Using VisiBroker for .NET with COM

Using VisiBroker for .NET with COM
This chapter presents a set of development techniques for using VisiBroker for .NET to enable COM-based client applications to access server-side components developed with RMI, EJB or CORBA.
Although in theory any object developed for the Common Language Runtime (CLR) can be exposed to COM-based clients, in practice certain development and deployment techniques will make such access simple, flexible, and trouble-free. We discuss these various technique in this chapter, and show you how to use VisiBroker for .NET to make COM access work.
The first problem encountered when exposing managed objects to COM clients is determining which objects to expose. In general, it is desirable to expose the types which provide access to business logic, while hiding the types which simply provide the “middleware infrastructure.” Following this guideline, your component interfaces should be visible to COM, while your marshaling stubs should be invisible.
When run with the -COM flag, the VisiBroker for .NET compiler adds the ComVisible attribute to all public type declarations:
[System.Runtime.InteropServices.ComVisible(value)]
where value is true for types that are exposed to COM clients, and false for all other types.
The following generated types are visible to COM clients:
The following generated types are invisible to COM clients:
The Helper class
The ValueFactory and ValueData class
The Operations interface
The MarshalingStub and LocalStub classes
The RemotingProxy class
The POA and POATie classes
Overriding COM Visibility
Although the default COM visibility provided by the compiler is adequate for most applications, there are cases where it may be desirable to fine-tune to COM visibility of certain types. All ComVisible declarations include the fully-scoped type name immediately prior to the visibility value of true or false. It should be straightforward to write regular expressions to modify the visibility of individual types. VisiBroker for .NET compilers do not generate COM visibility attribute for type data members and you may need to fine tune this to control visibility of class data members. Also note that static and const members of a type are not COM visible.
ClassInterface attributes
By default, the following types will be annotated as requiring ClassInterfaceType.AutoDual interface classes:
In COM, an AutoDual interface class provides early binding COM clients with type access to all the public methods and properties provided by a given class. Unfortunately, AutoDual interface classes must be used with caution, as they can lead to fragile COM clients. As such, if a class that is AutoDual is modified, all the early binding COM clients that use that class must be recompiled (or redefined, in the case of using interpreted languages like Visual Basic). As such, it is strongly recommended that the AutoDual class interface only be used in situations where the underlying type is immutable. That is, newer versions of the types will not break existing COM clients.
However, the immutability requirement for using AutoDual in COM is analogous to the immutability requirement for the above listed types defined in IDL or Java. That is, if the user makes changes to the IDL definition of a struct, union or valuetype (or if a user makes changes to the serializable data fields of a class defined Java) it will lead to IIOP marshaling errors.
In short, IDL and Java types must be immutable with respect to field layout (which determines their marshaled format) in the same way that AutoDual types must be immutable with respect to their method and field layout. As such, the default behavior of the VisiBroker for .NET compilers is to annotate the above-mentioned types as AutoDual.
As with COM visibility, the AutoDual annotation of a given type (or all types) can be fine-tuned if required.
Note
This behavior may change in the future. VisiBroker for .NET compilers may choose to add ClassInterface.None and ComInterfaceType.InterfaceIsDual for generated interfaces.
Defining custom interfaces
Microsoft recommends using user-defined interfaces as a more robust alternative to using ClassInterfaceType.AutoDual on implementation classes, where the implementation class is likely to change over time. It is also suggested to mark the implementation classes with ClassInterfaceType.None ClassInterface attribute to avoid generation of the _<impl class> interface (which becomes the [default] interface otherwise.) The user-defined interfaces can be inspected and appropriately marked with ComInterfaceType.InterfaceIsDual InterfaceType attribute if necessary to generate dual interface COM servers.
At this time VisiBroker for .NET compilers do not explicitly mark classes with ClassInterfaceType.None. Neither are interfaces marked with InterfaceIsDual. VisiBroker for .NET compilers generate AutoDual flags for public implementation classes that are generated or Java based.
There are a number of different techniques that can be used, which we will illustrate using the following Java class definition:
public class Quote implements java.io.Serializable {
private String symbol;
private float price;
public Quote(String symbol, float price) {
this.symbol = symbol;
this.price = price;
}
public String getSymbol() {
return symbol;
}
public float getPrice() {
return price;
}
}
This Java class represents a stock quote, and contains data fields corresponding to the stock symbol and price. Note that this class allows users to access the quote's symbol or price, but not to modify these values. Ideally, we would like our C# type to be likewise read-only with respect to the symbol and price fields.
By default, if we run the VisiBroker for .NET java2cs compiler over this class (with the -COM flag enabled), we will produce the following C# class:
[Serializable]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Quote {
public Quote() {
}
public Quote(float Price, string Symbol) {
this._Price = Price;
this._Symbol = Symbol;
}
private float _Price;
public virtual float Price {
get { return this._Price; }
set { this._Price = value; }
}
private string _Symbol;
public virtual string Symbol {
get { return this._Symbol; }
set { this._Symbol = value; }
}
}
This class has the following drawbacks:
1
2
The Symbol and Price properties have public getters and setters. This is at odds with our design guidelines, which indicate that Symbol and Price should be read-only.
So, instead of using this generated class, we would like to introduce a user-defined interface. Here is the user-defined C# interface representing a Quote:
 
[System.Runtime.InteropServices.ComVisible(true)]
public interface Quote {
string GetSymbol();
float GetPrice();
}
This class is COM visible, and has getter methods for Symbol and Price. Mark this interface with the appropriate ComInterfaceType attribute to specify dual or dispatch interface or IUnknown as required.
The next step is to tell the VisiBroker for .NET compiler not to generate the Quote interface, since we are providing our own implementation. This is done by introducing a hint file, which contains the following hint:
 
<?xml version="1.0"?>
<hints>
<hint>
<java-class>Quote</java-class>
<cs-sig-type>Quote</cs-sig-type>
<cs-impl-type>QuoteImpl</cs-impl-type>
<mode>automatic</mode>
</hint>
</hints>
This hint indicates that the Java type Quote maps to a pair of C# types: a signature type Quote, and an implementation type QuoteImpl. We also specify that we will be using the automatic code-generation mode. (In fact, the
<mode/> element can be omitted, as automatic is the default code-generation mode.)
The XML element <cs-sig-type/> indicates the type name that will be used when clients interact with a Quote. The XML element <cs-impl-type/> indicates the type that will be used to implement the Quote (for example, QuoteImpl).
The user must then provide implementations of both the public Quote type and the internal QuoteImpl type. The Quote interface was listed above. Below is the QuoteImpl:
 
internal class QuoteImpl : Quote {
internal string Symbol;
internal float Price;
public string GetSymbol() {
return Symbol;
}
public float GetPrice() {
return Price;
}
}
A few notes about this implementation class:
Since this class is marked internal, we do not have to indicate its COM visibility: only public types can be COM visible. This implementation class is invisible to COM clients (which was our intent).
The automatic code generation mode indicated in the hint file requires that this class have fields corresponding to the serializable fields in the Java class. The Java class has two serializable fields (symbol and price) and thus our C# implementation class also has two such fields (Symbol and Price). Obviously, we could have implemented these fields as properties instead, if desired.
The two serializable fields in QuoteImpl (Symbol and Price) must be marked as internal (or public), since these fields are read/written by the generated class QuoteValueFactory when marshaling a QuoteImpl. These fields cannot be private or protected.
An alternate technique is available for implementing the QuoteImpl class, if it is desirable to not have to “repeat” the serializable fields. In such cases, it is possible to implement the QuoteImpl by extending the generated class QuoteValueData:
 
internal class QuoteImpl : QuoteValueData, Quote {
public string GetSymbol() {
return Symbol;
}
public float GetPrice() {
return Price;
}
}
Note that this class does not declare the fields Symbol and Price, as these fields are “inherited” from the base class QuoteValueData.
Support for array-valued parameters and return values
There are known issues with respect to invoking methods from COM clients on types implemented in managed code, where one or more of the parameters or the return value of the method is an array type.
To address these issues, when the -COM flag is specified, the VisiBroker for .NET compilers generate an “overloaded” method for every such problematic method.
Let's consider, as an example, the following method:
 
int[] GetLengths(string[] strings);
This method takes an array of strings as a parameter, and returns an array of integers, where each element in the result indicates the length of the corresponding input string. So, if this method is called as follows:
 
string[] strings = { "VisiBroker", "Rocks" };
int[] lengths = o.GetLengths(strings);
The result would be an array containing the elements 10 and 5.
Unfortunately, if we export this C# signature to COM, some COM clients will not be able to invoke the method GetLengths. For example, if we run the following Visual Basic code within an Excel spreadsheet:
 
Dim strings(1) As String
strings(0) = "VisiBroker"
strings(1) = "Rocks"
lengths = o.GetLengths(strings)
We will receive the following error:
Compile error: Function or interface marked as restricted, or the function uses an Automation type not supported by Visual Basic
To handle this problem, the VisiBroker for .NET compiler will output an “overloaded” method with the following signature:
object GetLengthsForCom(object strings);
This method signature substitutes the type object for all array-valued parameters and/or return values. (Note that this method is technically not overloaded with respect to the original method GetLengths, since we append the suffix ForCom to the original method name. We cannot use true overloading because C# does not permit method signatures that are overloaded based on return type.)
We can now use this generated method in our Visual Basic client:
Dim strings(1) As String
strings(0) = "VisiBroker"
strings(1) = "Rocks"
lengths = o.GetLengthsForCom(strings)
We will obtain a lengths value which is an array of 32-bit integers, where the array elements contain the values 10 and 5, as expected.
Avoiding ProgId collisions
Microsoft's COM interoperation documentation indicates that problems may occur when trying to export types with very long type names to COM clients. In particular, if the C# type name exceeds 39 characters, COM client may not be able to access the type unambiguously. Microsoft recommends adding a ProgId annotation to long type names that would otherwise be ambiguous. A simple workaround is to use regular expression-based tools to modify the code generated by the VisiBroker for .NET compiler.