How to use late binding to invoke method with ByRef parameters - vb.net

I have a COM component that I want to call using late-binding from VB.NET (using the painful Primary Interop Assembly - PIA method)
My IDL signature for the COM method looks like:
HRESULT Send([in]BSTR bstrRequestData,
[out]VARIANT *pvbstrResponseData,
[out]VARIANT *pvnExtCompCode,
[out,retval]int *pnCompletionCode);
So 2 'ByRef' parameters in VB.NET lingo, and a return value.
I attempt to invoke this method like so:
Dim parameters(2) As Object
parameters(0) = "data"
parameters(1) = New Object()
parameters(2) = New Object()
Dim p As New ParameterModifier(3)
p(1) = True
p(2) = True
Dim parameterMods() As ParameterModifier = {p}
objReturn = MyObject.GetType().InvokeMember("Send", _
BindingFlags.InvokeMethod, _
Nothing, _
MyObject, _
parameters, _
parameterMods, _
Nothing, _
Nothing)
This fails spectactularly with an exception: {"Invalid callee. (Exception from HRESULT: 0x80020010 (DISP_E_BADCALLEE))"}
I assume this means I'm doing something wrong in my parameterMods array. Because if I comment out setting any value of the ParameterMods array to 'True' - it works. It of course doesnt update the parameters that are [out] parameters and so it's not working as intended.
Is there something else to consider since the method also has a return value? The MSDN example pretty much does exactly what I am doing, with the exception that example did not have a return value. Any help is appreciated.

One issue is that your argument and ParameterModifier arrays have different sizes. I believe they must match up such so that the CLR/BCL can match every argument with a ParameterModifier.
If the PIA was generated with the preserve signature attributes, the method actually has 4 arguments instead of 3. You'll need to extend the arrays to hold 4 members and the return value of pnCompletionCode will be in the last index of the argument array after the call completes.
Also I'm curious why you're using this method of invocation. Since you're using VB.Net why not disable Option Explicit and use the VB late binder. It's much easier than writing out the reflection code yourself (and will typically be a bit more correct because it will deal with weird marshalling rules).
Option Explicit Off
...
Dim obj As Object = DirectCast(MyObject,Object)
obj.Send("data", new Object(), new Object())

Related

Implicit conversion from object to integer and other errors

I have two Layes Classes Business Layer And Data Layer And i have The Main class i called DatabaseManager contain all functions i need for stored procedures
I search on these errors I cannot find solutions
First Error in DatabaseManager class is :
implicit conversion from object to integer
Public Function ExecuteScalar(ByVal sql As String) As Object
Dim cmd As New SqlCommand(sql) With {
.CommandType = CommandType.Text,
.Connection = Connection
}
Dim retval As Integer = ExecuteScalar(cmd)
Return retval
End Function
In Data Layer Class i have this code :
Friend Function Get_Last_Visits_Type(ByRef cmd As SqlCommand)
Dim retval As Integer
cmd = New SqlCommand("Get_Last_Visits_Type")
retval = dm.ExecuteScalar(cmd)
Return retval
End Function
I got two errors here
function without an 'as' clause return type of object assumed
And
implicit conversion from object to integer
When Form Loaded i put this code on Load action :
TxtVisitTypeID.Text = Val(p.Get_Last_Visits_Type)
And i got this error :
implicit conversion from Double to String
Thanks...
Quite a few problems here as mentioned in comments:
Avoid naming a function anything that is a reserved word in the scope of your project at the very least: ExecuteScalar is a method of SqlCommand so use something like MyExecuteScalar instead.
Dim retval As Integer = ExecuteScalar(cmd) probably should be Dim retval As Integer = cmd.ExecuteScalar() unless you want a recursion (which I doubt). (Refer 1.)
Turn Option Strict on in your project settings. As mentioned, ALWAYS have this on. (And I prefer to have Option Explicit on and Option Infer off as well for similar reasons.)
With compile options set as in 3. you will have (valid) compilation errors pertaining to type conversion (at least), with a good chance of resulting in working code once you fix them. Eg Dim retval As Integer = Ctype(cmd.ExecuteScalar(), Integer) (if you're sure that the result of the query will be Integer, otherwise you will need to test and/or error trap).
Connection isn't defined anywhere: .Connection = Connection. You don't pass it nor declare it.
Since retval is declared as an Integer then the return type can also be tightened up to Integer as well, rather than Object.
Your second function has no return type.
What is dm? Not declared/defined.
Consider using Using blocks to close-and-dispose of SQL connection and command on exit.
CommandType.Text is the default so you only need to state it by way of explanation.
Here's what I'd do with your first function:
Public Function MyExecuteScalar(ByVal sql As String) As Integer
Try
Using con As New SqlConnection(sql)
Using cmd As New SqlCommand(sql, con)
Return CType(cmd.ExecuteScalar(), Integer)
End Using
End Using
Catch
Return -1 ' Or something recognizable as invalid, or simply Throw
End Try
End Function
Addressing the first function:
This code is too abstract to be useful. The name of the function is bad. It appears to be recursive but the value you pass back to the function is not a string but a command. If the line of code is Dim retval As Integer = cmd.ExecuteScalar(), then .ExecuteScalar() returns an Object. You cannot convert an Object to an Integer without a conversion method. If you are declaring retval as an Integer why would you have typed your function As Object? I won't even get into the connection problem here. I suggest you delete the function and start again.
Addressing the second function:
Why are you passing the command ByRef? This function has no connection at all! How do you expect it to execute anything? Same problem with retval As Integer and ExecuteScalar returning an Object.
Again, delete and start again.
Now to the code in Form.Load:
Val went out with VB6. I can give unanticipated results. Guess what, Val returns a Double. A .Text property expects a string. Also you appear to be calling the function you showed us above. That function asks for the calling code to provide an argument, namely an SqlCommand object.
My suggestions:
Forget about layers and try to learn the basics of data access with ADO.net. Turn on Option Strict now and forever. Ask new questions with a single problem. Tell us what line the error occurs on. You have been advised before that functions require a datatype but it doesn't seem to sink in.

'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.

VB.NET Nothing evaluating to False [duplicate]

Coming from Basic boolean logic in C#, I was wondering why:
Dim b As Boolean
Dim obj As Object = Nothing
'followig evaluates to False'
b = DirectCast(Nothing, Boolean)
'This throws an "Object reference not set to an instance of an object"-Exception'
b = DirectCast(obj, Boolean)
A CType(obj, Boolean) would evaluate to False(just as CBool(obj)). I think it is because the compiler uses a helper function, but that is not my theme.
Why does casting Nothing to Boolean evaluates to False, whereas casting an object that is Nothing to Boolean throws an Nullreference-Exception? Does that make sense?
[Option Strict ON]
Presumably, this is because Nothing in VB.NET is not exactly the same thing as null in C#.
In the case of value types, Nothing implies the default value of that type. In the case of a Boolean, the default value is False, so the cast succeeds.
One of the primary differences between value types such as Integer or structures and reference types such as Form or String is that reference types support a null value. That is to say, a reference type variable can contain the value Nothing, which means that the variable doesn't actually refer to a value. In contrast, a value type variable always contains a value. An Integer variable always contains a number, even if that number is zero. If you assign the value Nothing to a value type variable, the value type variable just gets assigned its default value (in the case of Integer, that default value is zero). There is no way in the current CLR to look at an Integer variable and determine whether it has never been assigned a value - the fact that it contains zero doesn't necessarily mean that it hasn't been assigned a value.
–The Truth about Nullable Types and VB...
EDIT: For further clarification, the reason the second example throws a NullReferenceException at run-time is because the CLR is attempting to unbox the Object (a reference type) to a Boolean. This fails, of course, because the object was initialized with a null reference (setting it equal to Nothing):
Dim obj As Object = Nothing
Remember that, as I explained above, the VB.NET keyword Nothing still works the same way as null in C# when it comes to reference types. That explains why you're getting a NullReferenceException because the object you're attempting to cast is literally a null reference. It does not contain a value at all, and therefore cannot be unboxed to a Boolean type.
You don't see the same behavior when you try to cast the keyword Nothing to a Boolean, i.e.:
Dim b As Boolean = DirectCast(Nothing, Boolean)
because the keyword Nothing (this time, in the case of value types) simply means "the default value of this type". In the case of a Boolean, that's False, so the cast is logical and straightforward.
There's a couple of things you have to realize here.
The first is what others have already pointed out: Nothing can be interpreted by the VB compiler as simply the Boolean value False given the proper context, such as Dim b As Boolean = Nothing.
This means that when the compiler sees this:
b = DirectCast(Nothing, Boolean)
It sees a literal (Nothing) and also sees that you want to use this literal as a Boolean. That makes it a no-brainer.
But now here's the second thing you have to realize. DirectCast on an Object is essentially an unboxing operation (for value types). So what needs to happen from the VB compiler's perspective is: there needs to be a Boolean in that box, or else the operation will fail. Since there is in fact nothing in the box—and this time we're really talking nothing, as in null—it throws an exception.
If I were to translate this code to C#, it would look like this:
bool b;
object obj = null;
b = (bool)default(bool);
b = (bool)obj;
Hopefully that makes things a bit clearer?
There's a difference between using the keyword (literal) Nothing, and using a reference variable whose value is Nothing.
In VB.NET, the literal (keyword) Nothing gets special treatment. The Nothing keyword can be automatically converted into a value type, using the default value of that type.
A reference variable whose value is Nothing is different. You don't get the special behaviour.
The documentation says DirectCast "requires an inheritance or implementation relationship between the data types of the two arguments".
Clearly Object does not inherit from or implement Boolean, unless you have put a boxed Boolean into an object variable.
So the code below fails at runtime with an exception.
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean)
To get the expected behavior, you need this code:
Dim b As Boolean?
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean?)
The character ? mean Nullable(of ).
I'm finding that comparing of the Boolean variable to a string of "True", "False" or Is nothing seems to ensure that I get the correct comparisons. I was using a function to return an html string of a div with an image of a checked or unchecked radio button and was having the issue of nothing coming back as false. Using the variable = "True" or "False" string and doing the last check with IS NOTHING helped to resolve that issue.
Dim b as boolean = nothing
response.write CheckValue(b = "True")
response.write (b = "False")
response.write (b is nothing)
Function CheckValue(inVal as boolean) as string
if inVal then
return ("<div><img src="checked.png" ></div>
else
return ("<div><img src="unchecked.png" ></div>
end if
end function
The system seems to do the conversion to string when implicitly compared to a string whereas using the .tostring method just creates an error while allowing the last comparison to actually compare to a value of nothing.
Hopefully that helps somewhat. It at least let me

How to call a vb.net function by name with by ref parameters

We need to call a set of functions (currently around 30 or so) programatically by name. They all have the same signature so I've gone down the "invoke" route. I initially tried using delegates as I recalled doing that previously but that didn't achieve what I was after. So I've now looked at reflection to do it.
Dim webapiType as type = type.gettype("fully.qualified.model.webapi", true, true)
dim webAPIConstructor as system.reflection.constructorinfo = webapitype.getconstructor(type.emptytypes)
dim webapiClassObject as object = webapiConstructor.invoke(new object(){})
dim webapiMethod as system.reflection.methodinfo = webapitype.getmethod("myFunctionName")
dim webapiValue as object = webapimethod.invoke(webapiclassobject, new object(){model, xmlREsponse, msg, exDAL})
That all works fine and calls my function "myFunctionName" but it doesn't set the xmlResponse object value, that stays as nothing. (the function I'm calling has byval model, byref xmlREsponse, byref msg and byref exDal)
If I call the method directly rather than invoking it then the xmlResponse parameter is returned.
So the issue is that the values of the byRef values aren't being returned.
Is there some other way of invoking the method so that the ByRef parameters are returned correctly or do I need to investigate some other concept ?
(I've looked at CalledName and the documentation says that it only passes "ByVal" so that's no use)

Delegates: How to make sense of them in VB.NET?

I am looking to try and understand delegates better. I've looked over the examples on MSDN and various other sites, but I just don't "get" them. I know that they are virtually similar to a pointer to a function in C. But for some reason, C's syntax is just SO much clearer on the use of such constructs.
So I've developed a scenario to try and make use of a delegate, or at least, where I think such a use is valid. Assume the below code is in a class of some kind and that MyObj has a Name property to it of type String that returns a lowercased name that is the same as the object (i.e., Obj1.Name = "obj1"):
Private Shared MyList As New List(Of MyObj)(Obj1, Obj2, Obj3, Obj4, Obj5, Obj6)
Private Shared Function FindObj(ByVal obj As MyObj, ByVal name As String) As Boolean
Return String.Equals(obj.Name, name, OrdinalIgnoreCase)
End Function
Friend Shared Sub RedOctober()
Dim obj4Pos As Int32 = -1
For i As Int32 = 0 to (MyList.Count - 1) Step 1
If FindObj(MyList(i), "obj4") Then
obj4Pos = i
Exit For
End If
Next i
If obj4Pos <> -1 Then
Debug.Print("Found obj4!")
Else
Debug.Print("Couldn't find obj4! :(")
End If
End Sub
This is your basic O(N) "Search a list for a matching thingamajig and return the index when found". I can extrapolate this into something a little "better" if I use FindIndex, however:
Private Shared MyList As New List(Of MyObj)(Obj1, Obj2, Obj3, Obj4, Obj5, Obj6)
Friend Shared Sub RedOctober()
Dim obj4Pos As Int32 = MyList.FindIndex(
Function(o) String.Equals(obj.Name, "obj4", OrdinalIgnoreCase))
If obj4Pos <> -1 Then
Debug.Print("Found obj4!")
Else
Debug.Print("Couldn't find obj4! :(")
End If
End Sub
Problem is, what if I want to search for more than just obj4? If I use FindIndex that way, I'll need a dedicated lambda expression/anonymous function for each object of MyObj that I want to find. This adds extra functions/subs to the resulting binary that each do roughly the same thing, so it's bloat.
This is where I know delegates can be of use if I keep my FindObj function and somehow reference it in a delegate, passing it a different string dependeing on what object I want to find in MyList. Problem is, FindIndex wants a System.Predicate(Of T) whereas my FindObj function takes two arguments: the object to check the Name property on and the string to check it against.
My questions are thus:
Is this an appropriate situation for a delegate?
Is it going to be any quicker/better/more efficient/cleaner/pickyourownadjective than using a straight-up For loop?
Is this doable instead via pure lambda expressions in such a way that I can pass my two FindObj arguments and have the correct object found w/o declaring multiple lambda's of similar nature (and thus, adding bloat).
FindIndex isn't a Linq thing, but is there an approach using Linq that accomplishes the same task that may be better (in terms of efficiency -- yes, I am an optimization nut, and no, I will not apologize for it)?
The game with VB.NET (well, .NET in general) is that there are usually multiple ways to accomplish a task. The hard part is finding the way that suites a particular situation, is not unnecessarily bloaty or slow, and is readable to someone else reviewing the code (or me after a 2-3 month hiatus).
This should be an easy one for folks I suspect. And if I made any errors in my examples, feel free to point and laugh :)
To extend your second example you can do this:
Private Shared MyList As New List(Of MyObj)(Obj1, Obj2, Obj3, Obj4, Obj5, Obj6)
Friend Shared Sub RedOctober(toFind as String)
Dim obj4Pos As Int32 = MyList.FindIndex(
Function(o) String.Equals(o.Name, toFind, OrdinalIgnoreCase))
If obj4Pos <> -1 Then
Debug.Print("Found " & toFind & "!")
Else
Debug.Print("Couldn't find " & toFind & "! :(")
End If
End Sub
The argument to FindIndex is a lambda which is able to capture variables that are in scope when it is declared. This enables you to "pass in" the string to search for without it being an argument to the anonymous function.
A Delegate is basically a reference to a method. That method can be a member method in a class, an anonymous function or a lambda. Predicate(of T) is just a predefined delegate type that will access accept a reference to a method or a lambda, whichever is better for the context.
To answer your questions explicitly:
Predicate(Of T) is just a predefined delegate type. Whatever you pass to FindIndex() must be able to be converted to this type. That can be a reference to a method or a lambda.
In this context, probably not.
See the code above.
FindIndex is defined on the List(Of T) which is what you're dealing with here. Theoretically it will be optimised for the List implementation which the Linq operators may not be. Linq code will end up looking pretty much the same, and should have similar performance, but if you know you're using a List then you're probably better off sticking to native methods as you have done here.
Update
I understand now that you want RedOctober to use your FindObj method. Try this:
Friend Shared Sub RedOctober()
Dim obj4Pos As Int32 = MyList.FindIndex(
Function(o) FindObj(o, "obj4"))
If obj4Pos <> -1 Then
Debug.Print("Found obj4!")
Else
Debug.Print("Couldn't find obj4! :(")
End If
End Sub