A COM-visible type has only parameterized constructors
ID |
vbnet.portability.com_with_param_constructors |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Interop |
Language |
VB.NET |
Description
Reports a class whose declaration carries <ComVisible(True)> or <ComVisibleAttribute(True)>, which
declares a public constructor taking at least one parameter and no public parameterless constructor. A
type with both forms is not reported, and neither is a type that declares no constructor at all, since
the compiler then supplies the public parameterless one. A Private or Protected parameterless
constructor does not satisfy the requirement and the type is still reported. Types marked
<ComVisible(False)> and types with no attribute are not examined, so a type made COM-visible only
through an assembly-level <Assembly: ComVisible(True)> is outside this rule’s reach.
Rationale
COM creates objects through IClassFactory::CreateInstance, which takes no arguments. Every COM
client - CreateObject("Acme.SessionBridge") from VBScript or VB6, New against a referenced type
library, CoCreateInstance from C++ - goes through that one path, so a COM-visible class is creatable
only if it has a public parameterless constructor. Declaring any constructor in VB suppresses the
implicit one, so a class that declares only New(endpoint As String) has no parameterless constructor
at all and cannot be instantiated from COM by any means. Nothing detects this before a client tries:
the class compiles, regasm registers it, and the type library is generated. The failure lands on the
client as a creation error - CreateObject returns 0x80040110 (CLASS_E_NOAGGREGATION) or a plain
"cannot create ActiveX component" - which names the class but says nothing about the missing
constructor, and it is raised in a script or a legacy application rather than in the code that has the
defect. Because the class is usable from .NET and its own tests, the defect survives every check on the
producing side and is discovered only by whoever integrates it.
The following code illustrates the pattern detected by this rule:
Namespace Acme.Interop
' FLAGGED: A COM-visible type has only parameterized constructors
<ComVisible(True)>
<Guid("8c2b7d40-6f19-4a5e-9d33-1c0e5b7a2f88")>
Public Class SessionBridge
Public Sub New(endpoint As String)
Me.Endpoint = endpoint
End Sub
Remediation
Add a public parameterless constructor and let the client set the state afterwards through properties -
that is the COM idiom, and the parameterized constructors can stay for .NET callers. Where the object
genuinely cannot exist without its arguments, keep it uncreatable and export a COM-visible factory
class that does have a parameterless constructor and whose instance methods return configured
instances; the client creates the factory and asks it for objects. Where the type does not need to be
creatable from COM at all, say so by marking it <ComVisible(False)>, so the intent is recorded rather
than left to be discovered at integration time. Validate in the property setters or in an explicit
initialisation method, so a COM-created object still cannot be used in a half-configured state.
' Before: CreateObject("Acme.SessionBridge") cannot succeed
<ComVisible(True)>
Public Class SessionBridge
Public Sub New(endpoint As String)
Me.Endpoint = endpoint
End Sub
Public Property Endpoint As String
End Class
' After: creatable from COM, still convenient from .NET
<ComVisible(True)>
Public Class SessionBridge
Public Sub New()
End Sub
Public Sub New(endpoint As String)
Me.Endpoint = endpoint
End Sub
Public Property Endpoint As String
End Class