Problem returning object from VB.NET COM Assembly into VBA (Access) - vb.net

I have an assembly in VB .NET 2.0 that I am trying to use to call a webservice.
This will be COM visible, and return the results to Access in VBA.
The .NET Assembly passes all tests and executes perfectly.
I was experiencing "Object does not support this property or method" errors when calling the methods from VBA.
I broke it down to a certain object that was being returned and added some test methods to the .NET DLL.
There is a "Patient" object I want to return.
It looks like this (made it very very simple to test it):
Option Strict On
Option Explicit On
<ComClass(Patient.ClassId, Patient.InterfaceId, Patient.EventsId)> _
Public Class Patient
#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 = "672dfbd9-8f3a-4ba2-a33d-89fef868f2b9"
Public Const InterfaceId As String = "74a9c54c-4427-4d31-8220-3258ecda345d"
Public Const EventsId As String = "dc25515e-1bb7-4a66-97d5-270c00d792a9"
#End Region
Public Sub New()
MyBase.New()
End Sub
Public Property StorePatientID() As Integer
Get
Return m_StorePatientID
End Get
Set(ByVal value As Integer)
m_StorePatientID = value
End Set
End Property
Private m_StorePatientID As Integer
End Class
So about as simple as an object can be really.
I have a method that just returns a dummy record, just to test it:
Public Function GetPatientTest() As Patient
Dim patient As New Patient
patient.StorePatientID = 99
Return patient
End Function
This fails with the afformentioned error.
HOWEVER,
This method succeeds!
Public Function GetPatientArrayTest() As Patient()
Dim strings As New List(Of Patient)
Dim patient As New Patient
patient.StorePatientID = 99
strings.Add(patient)
Return strings.ToArray
End Function
The DLL is made com visible through "Properties" page.
Builds to project/bin/debug, always do a rebuild.
Always seems to be updated with new methods etc when I look at it in VBA so don't think it's looking at an old version.
Obviously no funny dependencies with these methods.
Really really struggling with this.
EDIT:
Update 16/03/2011 - Added VBA script
Public Function FindPatientsTest(ByVal surname As String, ByVal surnameBeginsWith As Boolean, ByVal forename As String, ByVal forenameBeginsWith As Boolean, ByVal dateOfBirth As String)
Dim token As String
token = Login()
Dim patient As SCIStoreWS60.patient
Set patient = New SCIStoreWS60.patient
'// This doesn't work.
'// When adding a "Watch" to the function, I can see it returns an "Object/Patient" and is the correct results
'// When adding a "Watch" to the variable "patient" I can see it is a "Patient/Patient"
patient = sciStore.GetPatientTest()
'// This works fine
Dim something As Variant
something = sciStore.GetPatientArrayTest()
End Function
Update 16/03/2011 5 minutes later - Chastising myself
Sorry, I just worked it out.
I need to "Set" the patient variable.
Set patient = sciStore.GetPatientTest()
Why didn't I need to do this for the "something" variant?

So, yes, you need to Set object references, but not arrays.

Related

'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'

I am trying to find a particular user from a mongodb collection that matches the given id. Folliwng is my VB.Net code. However, I keep getting the error 'Public member 'Find' on type 'MongoCollectionImpl(Of BsonDocument)' not found.'
Public Function GetCollectionByName(ByVal collectionName As String)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.Find(filter).ToList()`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ERROR here
Return list
End Function
Firstly, you must have Option Strict Off for that code to even compile. That's bad. You should immediately turn Option Strict On in the project properties and address all the issues it raises. One of those will be the fact that your GetCollectionByName has no return type declared. That means that here:
Dim list = collection.Find(filter).ToList()
that collection variable is implicitly type Object and you are relying on late binding when calling that Find method because the Object class has no such method. As a result, you get no help from Intellisense and Intellisense would have told you what members were and were not available if you were doing this properly.
Regardless, you still could have made it work if you had actually read the documentation for the types you're using to see what members they have. Here is the documentation for the interface you're using in that GetCollectionByName method and, I don't know about you but I don't see any Find method listed there. There is a FindSync method, so maybe that's what you actually want. If you had Option Strict On and used proper types every where, Intellisense would have shown you that.
You should also turn Option Strict On in the IDE options, so that it is On for all future projects.
I had a look at some documentation for the MongoCollectionImpl for Java and there appears to be a find method there but that doesn't necessarily mean that the same method is available in .NET and you aren't working directly with that class anyway. You are working with the IMongoCollection so you should only be working with members of that interface. Basically, your code would need to look more like the below with Option Strict On:
Public Function GetCollectionByName(ByVal collectionName As String) As IMongoCollection(Of BsonDocument)
Dim db As IMongoDatabase = DBcontext()
Dim collection As IMongoCollection(Of BsonDocument)
collection = db.GetCollection(Of BsonDocument)(collectionName)
Return collection
End Function
Public Function GetUser(ByVal id As String) As List(Of BsonDocument)
Dim filter = Builders(Of BsonDocument).Filter.Eq(Of String)("ID", id)
Dim collection = GetCollectionByName("Users")
Dim list = collection.FindSync(filter).ToList()
Return list
End Function
You may want to declare the GetUser method as type IList(Of BsonDocument) if you want to work with interfaces. You probably ought to rename that method or change the implementation too. If a method is returning a list then the name should not indicate that it returns a single item.

VB6 how to consume a VB.NET array of Objects?

I have a VB.NET Dll, registered as COM interoperability, that exposes something like this:
Class Society
with a:
Property ListPersons As Person()
This is the VB.NET Code:
Public Class Society
...
<System.Xml.Serialization.XmlArrayItemAttribute("Person", Form:=System.Xml.Schema.XmlSchemaForm.Unqualified)> _
Public Property ListPersons() As Person()
Get
Return Me.ListPersonsField
End Get
Set
Me.ListPersonsField = value
Me.RaisePropertyChanged("ListPersons")
End Set
End Property
I have to fill that list with VB6 but I cannot find the way
I have struggled with this issue a lot in the past and to be honest I could not find a solution to pass an array of object.
One of the solutions I used in the past was to pass the data of the single object as parameters and then create the object in the .net DLL and add it to your list.
Example
<ServiceContract()>
Public Interface IPersonAdd
<OperationContract()>
Function AddPerson(ByVal id As Integer, ByVal value As Integer) As Boolean
End Interface
Public Function AddPerson(ByVal id As Integer, ByVal value As Integer)
Dim p as new Person(id, value)
ListPersons.Add(p)
End Function

Compile error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions

I'm struggling with a little bit of VBa and Excel. I need to create a structure in VBa, which is a Type. The problem I have is, I get an error message when I try to execute the code! I feel I need to explain how I have arrived where I am in case I've made an error.
I have read that to create a type, it needs to be made public. As such I created a new Class (under Class Modules). In Class1, I wrote
Public Type SpiderKeyPair
IsComplete As Boolean
Key As String
End Type
And within ThisWorkbook I have the following
Public Sub Test()
Dim skp As SpiderKeyPair
skp.IsComplete = True
skp.Key = "abc"
End Sub
There is no other code. The issue I have is I get the error message
Cannot define a public user-defined type within an object module
If I make the type private I don't get that error, but of course I can't access any of the type's properties (to use .NET terminology).
If I move the code from Class1 into Module1 it works, but, I need to store this into a collection and this is where it's gone wrong and where I am stuck.
I've updated my Test to
Private m_spiderKeys As Collection
Public Sub Test()
Dim sKey As SpiderKeyPair
sKey.IsComplete = False
sKey.Key = "abc"
m_spiderKeys.Add (sKey) 'FAILS HERE
End Sub
Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
I have looked into this but I don't understand what it is I need to do... How do I add the SpiderKeyPair to my collection?
Had the exact same problem and wasted a lot of time because the error information is misleading. I miss having List<>.
In Visual Basic you can't really treat everything as an object. You have Structures and Classes which have a difference at memory allocation: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-classes
A Type is a structure (so are Arrays), so you if you want a "List" of them you better use an Array and all that comes with it.
If you want to use a Collection to store a "List", you need to create a Class for the object to be handled.
Not amazing... but it is what the language has available.
You seem to be missing basics of OOP or mistaking VBA and VB.NET. Or I do not understand what are you trying to do. Anyhow, try the following:
In a module write this:
Option Explicit
Public Sub Test()
Dim skpObj As SpiderKeyPair
Dim m_spiderKeys As New Collection
Dim lngCounter As Long
For lngCounter = 1 To 4
Set skpObj = New SpiderKeyPair
skpObj.Key = "test" & lngCounter
skpObj.IsComplete = CBool(lngCounter Mod 2 = 0)
m_spiderKeys.Add skpObj
Next lngCounter
For Each skpObj In m_spiderKeys
Debug.Print "-----------------"
Debug.Print skpObj.IsComplete
Debug.Print skpObj.Key
Debug.Print "-----------------"
Next skpObj
End Sub
In a class, named SpiderKeyPair write this:
Option Explicit
Private m_bIsComplete As Boolean
Private m_sKey As String
Public Property Get IsComplete() As Boolean
IsComplete = m_bIsComplete
End Property
Public Property Get Key() As String
Key = m_sKey
End Property
Public Property Let Key(ByVal sNewValue As String)
m_sKey = sNewValue
End Property
Public Property Let IsComplete(ByVal bNewValue As Boolean)
m_bIsComplete = bNewValue
End Property
When you run the Test Sub in the module you get this:
Falsch
test1
-----------------
-----------------
Wahr
test2
Pay attention to how you initialize new objects. It happens with the word New. Collections are objects and should be initialized as well with New.

Type-safe delegate to property getter

Suppose I have this simple class:
Public Class Person
Public Property Name() As String
Public Property Married() As Boolean
End Class
I want to create a delegate to its property getters. After a bit of search, including this, I've written this code in VB.NET:
Dim p As New Person()
p.Name = "Joe"
p.Married = True
Dim propGetter1 As Func(Of Boolean) = Function() p.Married
Dim propGetter2 As Func(Of String) = Function() p.Name
And retrieve the value of the properties as follows:
Dim married as Boolean = propGetter1.Invoke
Dim name as String = propGetter2.Invoke
It works fine, but contrary to the example in C# from the link above, it does not check at compile-time the type returned by the Function, so I could just write this code:
Dim propGetter1 As Func(Of Boolean) = Function() p.Name
Getting an error at runtime.
I've also tried this code:
Dim propGetter3 As Func(Of String) = Function(p As Person) p.Name
But I get the following error:
Nested function does not have a signature that is compatible with delegate 'System.Func(Of Boolean)'
So, how can I write this delegates with type-checking at compile-time, just like in C#??
EDIT:
As I've been asked to do so, I explain what I'm trying to do with this approach. I have an application that reads the state of a system (probe mesures, flows, preassures...) each few seconds, and stores this information in some properties of the objects that model the system.
I want to offer the user a list of all the objects defined which have properties that get their values updated this way. Therefore, the user can set alarm rules, so when a property reaches a certain value, an alarm is fired.
In orden to do so, I need to save delegates to the chosen properties, so I can check its values each few seconds, and compare them to the expected ones.
Does it make sense?
This is VB.NET's in/famous dynamic typing at work:
Option Strict Off
Module Module1
Sub Main()
Dim p As New Person With {.Name = "True"}
Dim propGetter1 As Func(Of Boolean) = Function() p.Name
Dim result = propGetter1() '' Fine :)
End Sub
End Module
If you don't like surprises like this then just use Option Strict On and the compiler will tell you that you got it wrong at build time. Use Tools + Options, Projects and Solutions, VB Defaults to change the default setting. Do beware that you might have some trouble with older projects ;)

How can I copy an object of an unknown type in VB.net?

Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class