How can I downcast an object using only late-binding in VBA? - excel

I am writing a VBA application, and for a specific function, I am using only late-binding, as most of the users of the application won't have the reference installed (and won't use this specific function).
The object I am using behaves like:
class PISDK{
PIServer GetServer(string hostName)
}
The GetServer method returns a PIServer object, but a more specific interface exists, implementing PIServer:
interface IGetPoints2 : PIServer{}
I would like to downcast the PIServer object to a IGetPoints2 object.
Without doing anything, I get a PIServer object:
Dim PiSdk As Object
Dim PiServer As Object
Set PiSdk = CreateObject("PISDK.PISDK")
Set PiServer = PiSdk.GetServer("foo")
Looking at PiServer in the debugger confirms that.
Using a strongly typed variable should work, but I do not want to reference any of the types used here.
How can I downcast this object using late-binding only?

Please read this:
As you are not adding a reference to the PI SDK Type Library, I
believe you cannot use "rtInterpolated" as the second parameter of
the ArcValue method; instead, you can use the corresponding number
(which is 3 for "rtInterpolated" in the RetrievalTypeConstants
enumeration).
VBA with late binding is tricky with optional
parameters, as we cannot omit them when calling a method. Instead,
you need to use either Nothing (in case the optional parameter is
an object) or "" (in case the optional parameter is a string) as
"parameter placeholders" (by the way, the same happens in
VBScript, a scripting language for scripts contained in files with
.vbs extension that run independently from any application).
able to put some functional sample code together, which is more
complete and will hopefully help you.

Related

Confusion in creating excel application object using VB.net: CreateObject vs New

I am facing difficulty in understanding the following concepts. I had posted a question some time back - read through the answers but some things are still not clear. I state my confusion below:
My first question refers to the following code piece
Option Strict On
Imports Microsoft.Office.Interop
Dim oxl As Excel.Application
oxl = CreateObject("Excel.Application")
In the above code piece, the statement oxl = CreateObject("Excel.Application") throws an error stating, Option Strict On disallows implicit conversions from Object to Application. My question is I read from many sources that it is always better to keep Option Strict ON but in this case when we need to create a new excel application, the Option Strict ON is preventing us from doing so. So what is the best practice that should be followed for such a conflict?
Next I tried replacing the statement oxl = CreateObject("Excel.Application") with oxl = New Excel.Application. It was observed that even with Option Strict ON, we can create a new excel application object with the NEW keyword. It was also checked with GetType that in both cases that is, using CreateObject and NEW, the type of object being created was: System._ComObject.So my question is if the type of object being created remains remains the same, why is that Option Strict disallows CreateObject but allows the creation of the excel application object using NEW?
To study it further, I extended the above code to the following:
Option Strict On
Imports System
Imports Microsoft.Office.Interop
Module Program
Dim oxl As Excel.Application
Dim owb As Excel.Workbook
Dim osheet As Excel.Worksheet
Sub Main()
oxl = New Excel.Application
'oxl = CreateObject("Excel.Application")
Console.WriteLine(oxl.GetType)
oxl.Visible = True
owb = oxl.Workbooks.Add()
osheet = owb.Worksheets("Sheet1") ‘Error: Option Strict ON disallows implicit conversions from ‘Object’ to ‘Worksheet’
osheet.Range("A1").Value = 53
Console.WriteLine("Hello World!")
Console.ReadLine()
End Sub
End Module
When we run the code we see that the error Option Strict ON disallows implicit conversions from ‘Object’ to ‘Worksheet’ comes at the line: osheet = owb.Worksheets("Sheet1")
Question:
Why is the error coming? I mean if, owb = oxl.Workbooks.Add()can work (that it returns a workbook which is referred to by owb) then why is osheet = owb.Worksheets("Sheet1") not working because the right hand side returns the “Sheet1” of the workbook which osheet should be able to point to (given that it is of the type Excel.Worksheet)?
Sometimes it just doesn't have the information that something is more specific than an Object. If you don't use the default property and use Item instead, as in owb.Worksheets.Item("Sheet1"), you can hover over the Worksheets part to see that represents the .Sheets, but hovering over the Item part reveals it has no details of the items therein - it says it returns an Object.
You know what it should be, so if you had
Imports XL = Microsoft.Office.Interop.Excel
then you could do
osheet = DirectCast(owb.Worksheets("Sheet1"), XL.Worksheet)
and the types would all work out.
This is what VB statements about COM objects actually do.
Information for Visual Basic Programmers
Visual Basic provides full support for Automation. The following table
lists how Visual Basic statements translate into OLE APIs.
Visual Basic statement OLE APIs
CreateObject (“ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface to get IDispatch interface.
GetObject (“filename”, “ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface for IPersistFile interface.
Load on IPersistFile interface.
QueryInterface to get IDispatch interface.
GetObject (“filename”)
CreateBindCtx creates the bind context for the subsequent functions.
MkParseDisplayName returns a moniker handle for BindMoniker.
BindMoniker returns a pointer to the IDispatch interface.
Release on moniker handle.
Release on context.
GetObject (“ProgID”)
CLSIDFromProgID
GetActiveObject on class ID.
QueryInterface to get IDispatch interface.
Dim x As New interface
Find CLSID for interface.
CoCreateInstance
QueryInterface
A standard COM VTable the first three entries are IUnknown
AddRef, Release (decreases the ref count), and QueryInterface to find what interfaces this object support.
The next four entries are IDispatch
GetIDsOfNames, Invoke , GetTypeInfoCount, GetTypeInfo.
The entries after that are your methods and properties, and all are a indirect function call.
To get the code in memory you use the COM API calls, such as CoCreateInstance
You need to decide to early or late bind. Early binding requires the program to be installed so its type library can be read so it types are compiled into the program. Late binding doesn't care about compile time. There is a conversation Hello object, do you have a function called x. Object replies Yes, it is function 7, Can you please do function 7 object. Early binding function 7 is hard coded. You can only late bind to generic objects. –
So a COM object is 4 x 32 bit. One is the reference count, one is the address if the Virtual Function Table (VTable), 2 are unused. In early binding to call a function 7 the compiler does Address_Of_Vtable + (4 x 7) (being 4 bytes for an address). See IDispatch. N.B. Microsoft.Office.Interop is not used at all in late binding. –
Only the generic object can be used in late binding and cannot be used in early binding. Early binding requires you to tell it the specific object. You are mixing and matching. The compiler is confused, just like you. –
Option Strict Restricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an Object type. learn.microsoft.com/en-us/dotnet/visual-basic/… –

GetObject Fails while CreateObject Works [duplicate]

This question already has answers here:
How to use GetObject in VBScript
(2 answers)
Closed 3 years ago.
I recently got a small CG software package from my friend his father wrote about 30 years ago. It has a window where one can create and render some solid primitives. Interestingly, it has a VBA interface and I can in Excel (let’s call it TheSoftware) use Set app = CreateObject(“TheSoftware.Application”) to create such application (after setting app.Visible to True one can get the window on the screen)! However, the GetObject(, “TheSoftware.Application”) returns the “ActiveX component can’t create object” error. More surprisingly to me, the GetObject(“”, “TheSoftware.Application”) successfully creates the application! I have its source code in C++ about 900 KB that I can share, but just to be quick, has anyone had this issue before, or have a clue what may be the issue? I am no expert in C++, and the author passed away 4 years ago. I have seen the “opposite” problem of failing to create an object but OK when getting it, but not this one. Any idea is welcome, thanks!
GetObject(FName) opens a file or connects to a file if it already opened (and if class is specified use that class to open it) OR GetObject(,"Appname") connects to a running instance of an application. You are creating a blank file.
Visual Basic Scripting Edition
GetObject Function
See Also CreateObject Function
Requirements
Version 5
Returns a reference to an Automation object from a file.
GetObject([pathname] [, class])
Arguments
pathname
Optional; String. Full path and name of the file containing the object to retrieve.
If pathname is omitted, class is required.
class
Optional; String. Class of the object.
The class argument uses the syntax appname.objectype and has these
parts:
Use the GetObject function to access an Automation object from a file
and assign the object to an object variable. Use the Set statement to
assign the object returned by GetObject to the object variable. For
example:
Dim CADObject
Set CADObject = GetObject("C:\CAD\SCHEMA.CAD")
When this code is executed, the application associated with the
specified pathname is started and the object in the specified file is
activated. If pathname is a zero-length string (""), GetObject
returns a new object instance of the specified type. If the pathname
argument is omitted, GetObject returns a currently active object of
the specified type. If no object of the specified type exists, an
error occurs.
Some applications allow you to activate part of a file. Add an
exclamation point (!) to the end of the file name and follow it with a
string that identifies the part of the file you want to activate. For
information on how to create this string, see the documentation for
the application that created the object.
For example, in a drawing application you might have multiple layers
to a drawing stored in a file. You could use the following code to
activate a layer within a drawing called SCHEMA.CAD:
Set LayerObject = GetObject("C:\CAD\SCHEMA.CAD!Layer3")
If you don't specify the object's class, Automation determines the application to start and the object to activate, based on the file
name you provide. Some files, however, may support more than one class
of object. For example, a drawing might support three different types
of objects: an Application object, a Drawing object, and a Toolbar
object, all of which are part of the same file. To specify which
object in a file you want to activate, use the optional class
argument. For example:
Dim MyObject
Set MyObject = GetObject("C:\DRAWINGS\SAMPLE.DRW", "FIGMENT.DRAWING")
In the preceding example, FIGMENT is the name of a drawing application
and DRAWING is one of the object types it supports. Once an object is
activated, you reference it in code using the object variable you
defined. In the preceding example, you access properties and methods
of the new object using the object variable MyObject. For example:
MyObject.Line 9, 90
MyObject.InsertText 9, 100, "Hello, world."
MyObject.SaveAs "C:\DRAWINGS\SAMPLE.DRW"
Note Use the GetObject function when there is a current instance
of the object or if you want to create the object with a file already
loaded. If there is no current instance, and you don't want the
object started with a file loaded, use the CreateObject function.
If an object has registered itself as a single-instance object, only
one instance of the object is created, no matter how many times
CreateObject is executed. With a single-instance object, GetObject
always returns the same instance when called with the zero-length
string ("") syntax, and it causes an error if the pathname argument is
omitted.
Requirements Version 5
See Also CreateObject Function
© 2001 Microsoft Corporation. All rights reserved.
Build: Topic Version 5.6.9309.1546
Also these are the COM API calls that each form makes. From the Automation part of the COM docs.
CreateObject (“ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface to get IDispatch interface.
GetObject (“filename”, “ProgID”)
CLSIDFromProgID
CoCreateInstance
QueryInterface for IPersistFile interface.
Load on IPersistFile interface.
QueryInterface to get IDispatch interface.
GetObject (“filename”)
CreateBindCtx creates the bind context for the subsequent functions.
MkParseDisplayName returns a moniker handle for BindMoniker.
BindMoniker returns a pointer to the IDispatch interface.
Release on moniker handle.
Release on context.
GetObject (“ProgID”)
CLSIDFromProgID
GetActiveObject on class ID.
QueryInterface to get IDispatch interface.
Dim x As New interface
Find CLSID for interface.
CoCreateInstance
QueryInterface

VBA subclass reference

In this post there is the description of "subclass" usage in VBA. I'm looking for the next step of it: when I have first subitem added I want to use it and don't know how.
When I write baseItem(1).itemName it doesn't work.
I assume it's just because that baseItem is not a collection or an array, but I don't know any other way.
Welcome to SO!
In vba like many other languages it is a Property. You call them directly and not numerically, but you need to write GET/LET methods unless the class is public (that is not really encapsulation to simply access directly).
baseItem.itemName would be your call.
But ... As I said before, better that you write accessor(s) to your class as methods.
Here is a guy that sets the tone for OOP in VBA (for me):
https://stackoverflow.com/a/45570268/8716187
He is a driver of the Rubberduck project.
I would ask you to ask yourself if you really need a class, I will often use 4-10 dictionaries of keys holding arrays. I could wrap them in a class but why bother? What I need is a searchable and editable ("array"-the dictionary of arrays).
I have very few class modules written, it seems that one can operate without it many times.
-WWC
I assume it's just because that baseItem is not a collection or an
array...
The baseItem itself is not a collection nor an array. It is just an object of type BaseClass. But this object baseItem wrapps a collection so we maybe could say it is almost a collection.
The problem with this object though is, that as it is defined now in the answer you mentioned, it provides no way how the clients can get to this collection. The class BaseClass needs to be modified so it provides access to this inner collection for the client of this class. With access e.g. some public function is meant which will return something from this collection.
Add something like this to the BaseClass:
Public Function getSubItem(index As Variant) As SubClass
Set getSubItem = subClassCollection.Item(index)
End Function
Now objects which will be at runtime created based on this class definition will provide access to the inner collection via this function getSubItem. The code which will use this function will look like this. So it is now almost that what you are trying to achieve.
Dim name As String
name = baseItem.getSubItem(1).itemName
Debug.Print name ' Prints "Something" in output window
But it could be made even exactly to what you are trying to achieve. When exporting the file of BaseClass.cls and adding Attribute Value.VB_UserMemId = 0 to the very beginning of function getSubItem and importing it again to project.
Public Function getSubItem(index As Variant) As SubClass
Attribute Value.VB_UserMemId = 0
Set getSubItem = subClassCollection.Item(index)
End Function
Now you can really write your code exactly that way you wanted. HTH
Dim name As String
name = baseItem(1).itemName
Debug.Print name ' Prints "Something" in output window
For more information about Creating A Default Member In VBA have a look e.g. here.

Getting 'Invalid use of property' when using Property Set in VBA

I know there are tons of threads and questions about this and it's pretty obvious, generally, where the error is. Most folks aren't using the SET keyword when moving objects around. I am.
Here's what's happening:
This is in an excel sheet so I've made a little set of functions to keep track of the columns and make an index so that each time the app runs it will reindex the columns so I can do things like .Cells(row_num, pCust_index("custID")) in case of column changes.
I have a form called custContagions. It's just a little modal window that allows users to add/remove/edit customer's contagious status. It contains a property:
Private pCust_index as dictionary
It also contains this property setter:
Public Property Set set_cust_index(ByRef custIndex As Dictionary)
Set pCust_index = New Dictionary
Set pcust_index = custIndex
End Property
Pretty straight forward right? Takes a dictionary object, resets my index and points to the existing passed object.
Now, on the calling form I have the other side:
Private Sub newCustContagious_LBL_Click()
Dim contForm as New custContagions
Call contForm.set_cust_index(pCust_index) 'note pCust_index is a private here too
Call contForm.Show
...
I'm getting the Invalid Use of Property compiler error on the set_cust_index call.
What did I miss?
Most folks aren't using the SET keyword when moving objects around
Then they are not moving objects around. The Set keyword is the way to move object around.
There is also CopyMemory to directly copy the ObjPtr, but I do not believe most folks do that.
Pretty straight forward right?
Not quite. You create a dictionary, immediately discard it and replace with another dictionary passed as a parameter. You should remove the first of the two lines, and make the param ByVal:
Public Property Set set_cust_index(ByVal custIndex As Dictionary)
Set pcust_index = custIndex
End Property
I'm getting the Invalid Use of Property compiler error
You declared a property and then use it as a sub. With a property, you should have done:
Set contForm.set_cust_index = pCust_index
At which point the set_cust_index name does not look great. It would make a sensible name for a sub (Public Sub set_cust_index(ByVal custIndex As Dictionary)), but for a property you would be better off with Public Property Set cust_index(ByVal custIndex As Dictionary).

Object reference exactly

Demo demo=new Demo();
Here demo is a reference variable. Are reference variables equal to object references... ?
If no then please explain the concept of object reference.
Demo represents the type of the object.
demo represents the object reference, you will be able to refer to new Demo() object and calling its methods (for example).
new Demo() represents the object itself which is situated in heap memory.
In case of java language,
When you write the statement
Demo obj = new Demo();
It means that You are declaring a variable named obj and it is of type Demo.
by writing
obj = new Demo();
You are creating a new object in the heap memory and the reference variable "obj" will refer to it so that when you want to access the object created just now, you can access it through reference variable "obj".
so when you want to call some method on Demo object, you can call it using
obj.someMethod();
A reference to an object is a way to denote that object. The address of an object, for example, is one kind of reference (probably the simplest kind). Other kinds of references can exist, too, and they are written and read using some more complicated logic. It could theoretically be a double pointer, a pseudo-address, or something else - as long as it contains enough information that (if interpreted in a specified way) can be used to denote a specific object, .
A reference variable is a variable whose value is a reference to an object. So, for example, a reference variable could be a variable whose value is the address of an object, or (as I described above) something different, but equivalent.
For comparison, the other common type of variable (called primitive type in Java, value type in C# and other names in other contexts) is the kind variable whose value is an actual object (instead of a reference).

Resources