Using VB.net Collection class via COM returns error: "Class doesn't support automation" - vb.net

I have an existing VB.net class library which has a public property with a type of VB's Collection class. I'm exposing the class library as a COM-object to be able to use it in Progress.
When I access the Collection-property with an integer index (e.g. comObj.OutputCol.Item(1)) it works fine, but when I try to use the string indexer (e.g. comObj.OutputCol.Item("FirstCol")) I get the following error (from a VBScript I use for testing):
Error message: Class doesn't support automation
Error code: 800A01AE
Is it possible to use the string indexer in any way via COM?
Sample code, COM-object i VB.net:
<ComClass(TestClass.ClassId, TestClass.InterfaceId, TestClass.EventsId)>
Public Class TestClass
Public Const ClassId As String = "063CA388-9926-44EC-B3A6-856D5299C210"
Public Const InterfaceId As String = "094ECC57-4E84-423A-B20E-BD109AEDBC20"
Public Const EventsId As String = "038B18BD-54B4-42D3-B868-71F4C52345B0"
Private _sOutputCol As Collection = Nothing
Private Property sOutputCol() As Collection
Get
If _sOutputCol Is Nothing Then
_sOutputCol = New Collection()
End If
Return _sOutputCol
End Get
Set(ByVal Value As Collection)
_sOutputCol = Value
End Set
End Property
Public ReadOnly Property OutputCol() As Collection
Get
Return sOutputCol
End Get
End Property
Public Sub New()
sOutputCol.Add("First object", "FirstCol")
sOutputCol.Add(2, "SecondCol")
End Sub
End Class
Sample test-code in VBScript:
Set comObj = WScript.CreateObject("VbComTest.TestClass")
wscript.echo comObj.OutputCol.Item(1) ' Works
wscript.echo comObj.OutputCol.Item(CStr("FirstCol")) ' Gives the error
I have registred the dll with: >regasm "...path...\VbComTest.dll" /codebase

OK, the problem was that the indexer is overloaded and you shouldn't use that in COM-visible interfaces: https://msdn.microsoft.com/en-us/library/ms182197.aspx
Extract from the page about what happens to overloaded methods:
When overloaded methods are exposed to COM clients, only the first
method overload retains its name. Subsequent overloads are uniquely
renamed by appending to the name an underscore character '_' and an
integer that corresponds to the order of declaration of the overload.
For example, consider the following methods.
void SomeMethod(int valueOne); void SomeMethod(int valueOne, int
valueTwo, int valueThree); void SomeMethod(int valueOne, int
valueTwo);
These methods are exposed to COM clients as the following.
void SomeMethod(int valueOne); void SomeMethod_2(int valueOne,
int valueTwo, int valueThree); void SomeMethod_3(int valueOne, int
valueTwo);
Visual Basic 6 COM clients cannot implement interface methods by using
an underscore in the name.
So to use the string indexer I have to write:
wscript.echo comObj.OutputCol.Item_3("FirstCol")
(Item_2 takes an Object as parameter and will also work, if the documentation is correct).

Related

From A Method With In The Class Return An Instance Of The Class As An Interface Type That The Class Implements

what I'm trying to archive with the code below is to have the GetInstance generic function take in an interface type that SystemVars implements (say IAuthentication) then create an instance of SystemVars and return it as interface type T.
The problem I an having is that no matter what casting method I try I can't find a way to return the new instance of SystemVars as T. The line in the GetInstance method Return <CastingFunction>(New SystemVars,T) always fails to compile with the error message saying Value of type SystemVars cannot be converted to 'T'.
How do I return the instance of the class as the interface type that was passed into T?
Imports System.Drawing
Public Class SystemVars
Implements IAuthentication,
IAuthorization,
IApplicationStarting
Private Sub New()
End Sub
Public Shared Function GetInstance(Of T)() As T
Return DirectCast(New SystemVars, T)
End Function
Public ReadOnly Property Username As String _
Implements IAuthentication.Username,
IAuthorization.Username
Get
Return _userName
End Get
End Property
Public ReadOnly Property Rolls As List(Of String) _
Implements IAuthorization.Rolls
Get
Return _rolls
End Get
End Property
Public ReadOnly Property InstallationId As Guid _
Implements IAuthentication.InstallationId,
IApplicationStarting.InstallationId
Get
Return _installationId
End Get
End Property
Public ReadOnly Property MainWindowStartUpPlacement As Rectangle _
Implements IApplicationStarting.MainWindowStartUpPlacement
Get
Return _mainWindowStartUpPlacement
End Get
End Property
'........
Private Shared _userName As String
Private Shared _rolls As List(Of String)
Private Shared _installationId As Guid
Private Shared _mainWindowStartUpPlacement As Rectangle
End Class
You can make an otherwise illegal cast work by passing through Object.
Public Shared Function GetInstance(Of T)() As T
Return DirectCast(CObj(New SystemVars), T)
End Function
You will get a runtime error if the cast isn't possible; as noted in the comments, this strategy is chucking type safety out the window and basically telling the compiler, "Don't bother me, I know what I'm doing." The runtime will throw an InvalidCastException on failure if you don't test and throw yourself. You can test using Type.IsAssignableFrom if you want to create a more developer-friendly error message; there isn't much context available in the debugger at the point of failure, though it may be pretty obvious if you look up the call stack.
For just three interfaces, it might be better to do three separate specific functions rather than a generic version, especially considering that the functions are necessarily Shared (and thus can't themselves be part of an interface).
You might also consider a design that includes a Dependency Injection container. In this kind of design, there would be a configuration step that would associate the interfaces with SystemVars as the implementation, then the client would ask the container for an instance of the interface and receive a SystemVars object.
The rough way that the three options (the third being to cast the SystemVars object to the requested interface) would look in code is:
'Casting a received object to a requested interface
Dim asInterface = DirectCast(SystemVars.GetInstance(), IAuthorization)
'Using a custom casting function on SystemVars
Dim asInterface = SystemVars.GetInstance(Of IAuthorization)
'Using a DI container
'Behavior if the interface isn't supported depends on the container
Dim asInterface = container.GetInstance(Of IAuthorization)
Note that TryCast could be used instead of DirectCast, in which case the result would be Nothing if the interface isn't supported.

Method 'set_Description' in type 'myAssembly.NetProduct' from assembly 'myAssembly' does not have an implementation

I have a DLL file created in VB6. It contains a class named Product and that contains the following simple code:
Option Explicit
Private sDescription As String
Public Property Get Description() As String
Description = sDescription
End Property
Public Property Let Description(Value As String)
sDescription = Value
End Property
I want to use this DLL in VB.NET, which is nothing more than registering the DLL on my system and including the DLL file in the references. Visual Studio automatically generates an interop DLL to consume the COM DLL. This interop DLL generates interfaces for all classes. In VB.NET I want to create a new class that implements the Product interface from the interop DLL. So I code:
Imports myAssembly
Public Class NetProduct
Implements myAssembly.Product
Public Property Description As String Implements _Product.Description
Get
Throw New NotImplementedException()
End Get
Set(value As String)
Throw New NotImplementedException()
End Set
End Property
End Class
The property is auto-generated because I implemented the Product interface. But here comes the problem because when I start using the NetProduct class I get an error telling me this:
Method 'set_Description' in type 'myProject.NetProduct' from
assembly 'myProject, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null' does not have an implementation.
The problem is that there is no method set_Description in the interface. When I view the definition of the Product interface it shows me the following:
Imports System.Runtime.InteropServices
Namespace myAssembly
<CoClass(GetType(ProductClass))> <Guid("49CE2F98-931C-441B-B322-9F39B6D6F212")>
Public Interface Product
Implements _Product
End Interface
End Namespace
The definition of the _Product interface is:
Imports System.Runtime.InteropServices
Namespace myAssembly
<Guid("49CE2F98-931C-441B-B322-9F39B6D6F212")> <TypeLibTypeAttribute(4304)>
Public Interface _Product <DispId(1745027072)>
Property Description As String
End Interface
End Namespace
When I use the interface myAssembly.Product directly to create a new object then everything works as you would expect. The property does not pose a problem there. But when I implement the interface in a .NET class the problem arises.
How do I solve this?
[update 1] After creating a method Set_Description I see the following error appear:
property 'Description' implicitly defines 'set_Description', which
conflicts with a member of the same name in class 'NetProduct'.
This must have something to do with my problem, although I don't know what it is. I already tried completing the property to make sure the Throw New NotImplementedException() wouldn't be in the way but that didn't make the error go away. My code builds just fine by the way. The error I gave earlier is a runtime error. Not a build error.
Private myDescription As String
Public Property Description As String Implements Product.Description
Get
Return myDescription
End Get
Set(value As String)
myDescription = value
End Set
End Property
[update 2] I have used JetBrains DotPeek to disassemble the interop.dll that Visual Studio generates. Disassembly is coded in C#. It contains 2 interfaces and 1 class for the single Product class from VB6. Here are all details.
I'll start with the Product class itself.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace myAssembly
{
[ClassInterface(0)]
[Guid("C54B96A8-1499-4B76-8508-0B732E551326")]
[TypeLibType(2)]
[ComImport]
public class ProductClass : _Product, Product
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
public extern ProductClass();
[DispId(1745027072)]
public virtual extern string Description { [DispId(1745027072), MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.BStr)] get; [DispId(1745027072), MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [param: MarshalAs(UnmanagedType.BStr), In, Out] set; }
}
}
The ProductClass uses 2 interfaces. I don't understand why because one of those is just an implementation of the other. This is the Product interface.
using System.Runtime.InteropServices;
namespace myAssembly
{
[CoClass(typeof (ProductClass))]
[Guid("49CE2F98-931C-441B-B322-9F39B6D6F212")]
[ComImport]
public interface Product : _Product
{
}
}
And then we have the _Product interface. They even share the same Guid. It might have something to do with backwards compatibility.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace myAssembly
{
[Guid("49CE2F98-931C-441B-B322-9F39B6D6F212")]
[TypeLibType(4304)]
[ComImport]
public interface _Product
{
[DispId(1745027072)]
string Description { [DispId(1745027072), MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.BStr)] get; [DispId(1745027072), MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [param: MarshalAs(UnmanagedType.BStr), In, Out] set; }
}
}
This is all I could find. Still no clue where the error for Set_Description comes from.
[Update 3] Example code
The code for the VB6 class is on top of this question. Nothing fancy there. The code for testing implementation in .NET is like this:
Imports myAssembly
Public Class NetProduct
Implements myAssembly.Product
Private myDescription As String
Public Property Description As String Implements Product.Description
Get
Return myDescription
End Get
Set(value As String)
myDescription = value
End Set
End Property
End Class
To test the NetProduct class I dropped a Button on a Form and create an instance of the class when the button is being clicked.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '<- Error happens here, so on loading the datatype!
Dim Product As New NetProduct 'Error does NOT happen here.
End Sub
The whole project compiles without errors. The project even runs without errors UNTIL you click the button. Probably because the NetProduct type is first loaded on that point.
I used a console app to do my test. Other than that, my VB.NET code is basically identical to yours in update 3. The VB.NET properties were auto-generated by VS with the stub Throw New NotImplementedException() after using the Implements statement :
Imports OurCOMDll
Class TestClass
Implements OurCOMDll.ClassInCOMDll
Dim msStringProperty As String = String.Empty
Public Property StringProperty As String Implements _ClassInCOMDll.StringProperty
Get
StringProperty= msStringProperty
End Get
Set(value As String)
msStringProperty = value
End Set
End Property
End Class
Module Module1
Sub Main()
Dim o As New OurCOMDll.ClassInCOMDll
o.StringProperty = "Hello World!"
Console.WriteLine(o.StringProperty) ' Outputs 'Hello World!' as expected
Console.ReadLine()
End Sub
End Module
Same is true for the VB6 code. The string property is implemented like yours.
Distinguishing factors so far:
VS 2019 vs. VS 2017
(Consuming) GUI vs. Console application
Different property names

C# to VB6 COM events (“object or class does not support the set of events”), but different

I am aware of a 10 year old question with the same title as this one but I have double checked and I am not mistakenly using the delegate name. This is a different issue.
Here at work we have an old VB6 application I need to teach new(er) tricks. The first thing I had to do was have it call methods from a .Net COM-visible DLL written in C#. I have that working. Now I need to have it handle incoming progress notification events from that same DLL. I asked a similar question yesterday wherein the VB6 IDE was not even seeing that the DLL had events to offer. That issue was solved by decorating the C# interfaces and classes correctly.
First, the C# codez:
namespace NewTricksDLL
{
[ComVisible(true)]
[Guid("16fb3de9-3ffd-4efa-ab9b-0f4117259c75")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITransfer
{
[DispId(2)]
string SendAnEvent();
}
[ComVisible(true)]
[Guid("16fb3de9-3ffd-4efa-ab9b-0f4117259c74")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IManagedEventsToCOM
{
[DispId(2)]
void NotificationEvent();
}
[ComVisible(true)]
[Guid("dcf177ab-24a7-4145-b7cf-fa06e892ef21")]
[ComSourceInterfaces(typeof(IManagedEventsToCOM))]
[ProgId("ADUTransferCS.NewTricks")]
public class NewTricks : ITransfer
{
public delegate void NotificationEventHandler();
public event NotificationEventHandler NotifificationEvent;
public string SendAnEvent()
{
if (NotifificationEvent != null)
NotifificationEvent();
}
}
}
An now my attempt to use it in VB6. Please note that the _tricky_NotificationEvent event handler was generated by the IDE by picking _tricky from the left-hand dropdown and NotificationEvent from the right-hand dropdown so I know this event is visible to the VB6 IDE.
Option Explicit
Public WithEvents _tricky As NewTricksDLL.NewTricks
Private Sub Command1_Click()
' The next line fails with 'Object or class does not support the set of events'
Set _tricky = CreateObject("NewTricksDLL.NewTricks")
' Execution never makes to the next line
_tricky.SendAnEvent()
End Sub
Private Sub _tricky_NotificationEvent()
' This handler was auto generated by the IDE
End Sub
Hopefully this can help - here's a minimal bare bones VB.net implementation of your requirements: COM interface consumable by VB6, one event, one method.
Option Explicit On
Option Strict On
<ComClass(newTricks.ClassId, newTricks.InterfaceId, newTricks.EventsId)>
Public Class newTricks
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "386d540d-f8b8-46e1-939d-7b69dd5eff0a"
Public Const InterfaceId As String = "78b4036e-86a0-4671-997d-da5a33bf026f"
Public Const EventsId As String = "7b0fa5b5-b45e-4db2-9282-c06e09852161"
#End Region
' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
Public Sub New()
MyBase.New()
End Sub
Public Event NotificationEvent()
Public Sub SendAnEvent()
RaiseEvent NotificationEvent()
End Sub
End Class
This was created by starting a new project (Windows class library), delete from the project the Class1.vb that's automatically created, add a COM Class item renaming it to NewTricks. Then added the event declaration and the sub declaration and code; also added the Option statements. Built the project.
This was successfully used from this VB6 code. Clicking the button resulted in "Event fired" being written to the immediate window. This was successful with using both New and CreateObject methods of creating the reference to newTricks.
Option Explicit
Private WithEvents oTricks As NewTricksDll.newTricks
Private Sub Command1_Click()
Set oTricks = New NewTricksDll.newTricks
'Set oTricks = CreateObject("NewTricksDll.newTricks")
oTricks.SendAnEvent
End Sub
Private Sub oTricks_NotificationEvent()
Debug.Print "Event fired"
End Sub
Here is the corresponding C# code for the VB.net code, as converted straight-up by https://converter.telerik.com/
using Microsoft.VisualBasic;
[ComClass(newTricks.ClassId, newTricks.InterfaceId, newTricks.EventsId)]
public class newTricks
{
// These GUIDs provide the COM identity for this class
// and its COM interfaces. If you change them, existing
// clients will no longer be able to access the class.
public const string ClassId = "386d540d-f8b8-46e1-939d-7b69dd5eff0a";
public const string InterfaceId = "78b4036e-86a0-4671-997d-da5a33bf026f";
public const string EventsId = "7b0fa5b5-b45e-4db2-9282-c06e09852161";
// A creatable COM class must have a Public Sub New()
// with no parameters, otherwise, the class will not be
// registered in the COM registry and cannot be created
// via CreateObject.
public newTricks() : base()
{
}
public event NotificationEventEventHandler NotificationEvent;
public delegate void NotificationEventEventHandler();
public void SendAnEvent()
{
NotificationEvent?.Invoke();
}
}
I have not tried this C# code.
If you use CreateObject, it creates an object of type Object I believe. You should create the object simply by using New:
Set _tricky = New NewTricksDLL.NewTricks
Since you declared the variable as WithEvents, you can't just declare it using As New NewTricksDLL.NewTricks which I imagine you probably tried.

Using Reflection to Get All Static Properties in a Class As Objects VB.NET

I would like to start that I don't want a to hear about how expensive and terrible reflection is. That won't help—I have a very good reason to use reflection and that's not my question.
Specifically, I have a class within a class that contains several static properties of the same type.
Public Class Foo
Public Class Bar
Public Shared Property prop1 As New CustomClass()
Public Shared Property prop2 As New CustomClass()
Public Shared Property prop3 As New CustomClass()
End Class
End Class
Public Class CustomClass
Public Sub DoStuff()
End Sub
End Class
I'm looking to create a method in Foo that calls DoStuff on each of the properties contained within it. How can I do this? Here's the general idea of what I want to include in Foo, but I obviously can't convert PropertyInfo to CustomClass:
Private Sub Example()
For Each prop As PropertyInfo In GetType(Foo.Bar).GetProperties()
DirectCast(prop, CustomClass).DoStuff()
Next
End Sub
How can I get the static properties and cast them to CustomClass objects?
PropertyInfo represents the type's property get/set method pair. To evaluate the getter you simply call GetValue, like so:
(in C# because I'm a language snob)
foreach( PropertyInfo pi in typeof(Foo.Bar).GetProperties() ) {
// Use null as arguments because it's a static property without an indexer.
Object got = pi.GetValue( null, null );
CustomClass got2 = got as CustomClass;
if( got2 != null ) {
Console.WriteLine( got2.ToString() );
}
}
And to convert Dai's answer to VB because I'm not a language snob:
For Each pi As System.Reflection.PropertyInfo in Foo.Bar.GetType.GetProperties()
' Use nothing as arguments because it's a shared property without an indexer.
Dim got = pi.GetValue(Nothing, Nothing)
Dim got2 as CustomClass = DirectCast(got, CustomClass)
If Not IsNothing(got2) Then Console.WriteLine(got2.toString())
Next
huzzah for less lines and more keystrokes...

singleton pattern in vb

I am normally a c# programmer but am now working in VB for this one project when I use to set up a singleton class I would follow the Jon Skeet model
public sealed class Singleton
{
static Singleton instance = null;
static readonly object padlock = new object();
Singleton()
{
}
public static Singleton Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
//Added to illustrate the point
public static void a()
{
}
public void b()
{
}
}
or one of the variations now if I write the statement in c#
Singleton.Instance What procedures is all of the members that are not static, b but not a.
Now when I do the same in VB
Private Shared _instance As StackTracker
Private Shared ReadOnly _lock As Object = New Object()
Private Sub New()
_WorkingStack = New Stack(Of MethodObject)
_HistoryStack = New Queue(Of MethodObject)
End Sub
Public Shared ReadOnly Property Instance() As StackTracker
Get
SyncLock _lock
If (_instance Is Nothing) Then
_instance = New StackTracker()
End If
End SyncLock
Return _instance
End Get
End Property
I get StackTracker.Instance.Instance and it keeps going, while it is not the end of the world it looks bad.
Question is there a way in VB to hide the second instance so the user can not recursively call Instance?
Here's the full code:
Public NotInheritable Class MySingleton
Private Shared ReadOnly _instance As New Lazy(Of MySingleton)(Function() New
MySingleton(), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication)
Private Sub New()
End Sub
Public Shared ReadOnly Property Instance() As MySingleton
Get
Return _instance.Value
End Get
End Property
End Class
Then to use this class, get the instance using:
Dim theSingleton As MySingleton = MySingleton.Instance
The original question was not about how to implement the singleton pattern, but referring to the fact that in C# it's a compiler error to try to access a static member via an instance. In the current VB it's a warning.
Solution:
You can change the project compiler settings to "Treat all warnings as errors", but I don't know any way to explicitly treat just warning 42025 as an error.
That being said, there is also a much simpler way to implement singletons in VB:
public class Singleton
private sub new()
end sub
public shared readonly property Instance as Singleton
get
static INST as Singleton = new Singleton
return INST
end get
end property
end class
This relies on VB thread-safe single initialization of static variables which is a feature not found in C#. The line of code beginning with the word "static" is only evaluated once even if the Instance property is accessed many times from many threads.
This is actually not the proposal put forth by Jon. You implemented the third version referenced in the article on the matter, which he points out doesn't work according to the EMCA spec due to lack of memory barriers.
Rather, you should work with the fifth version, which uses a nested class and performs the assignment of the instance in the declaration of the static field on the nested class.
If you are working in .NET 4.0, then you don't have to do any of this. You can create a static readonly field of type Lazy<T>, passing LazyThreadSafetyMode.ExecutionAndPublication to the constructor (along with your Func<T> to indicate how to create the instance) to guarantee that the value will only be created once.
Then, you expose a property which simply calls the Lazy<T>.Value property to return the lazy-loaded singleton value.
Maybe I'm missing something but I just do some variation on this, depending on what else is going on in the class:
Class MySingleton
'The instance initializes once and persists (provided it's not intentionally destroyed)
Private Shared oInstance As MySingleton = New MySingleton
'A property initialized via the Create method
Public Shared Property SomeProperty() As Object = Nothing
'Constructor cannot be called directly so prevents external instantiation
Private Sub New()
'Nothing to do
End Sub
'The property returns the single instance
Public Shared ReadOnly Property Instance As MySingleton
Get
Return oInstance
End Get
End Property
'The method returns the single instance while also initializing SomeProperty
Public Shared Function Create(
ByVal SomeParam As Object) As MySingleton
_SomeProperty = SomeParam
Return oInstance
End Function
End Class
Obviously, you would usually only provide either the Instance property or the Create method, not both (though you could if you wanted to for some reason).
In its simplest form it's:
Class MySingleton
Private Shared oInstance As MySingleton = New MySingleton
Private Sub New()
End Sub
Public Shared ReadOnly Property Instance As MySingleton
Get
Return oInstance
End Get
End Property
End Class