-
Jan 14th, 2018, 10:08 AM
#1
[RESOLVED] Unique Scenario: Passing IUnknown as optional parameter
I code a lot with VTable-only interfaces vs TLB usage. This question is more for personal knowledge than fixing some existing code.
I have a scenario where I'd like to know if a user passed an optional parameter and if so, return an exact Interface reference to the user via that parameter. Just not quite getting what I want...
1. If I declare the parameter: Optional oUnknown As IUnknown. Result is that I can't really tell if the user passed anything, since not passing it returns the same ObjPtr, & VarType as if it were passed. So, can't really test whether the parameter was skipped or provided. Was hoping some magic, kinda like testing a true null string with StrPtr() vs. comparing a string to vbNullString.
These calls result in oUnknown testing identically within the TestRoutine where oUnknown is used:
Call TestRoutine()
Call TestRoutine(Nothing)
Dim tmpUnk As IUnknown: Call TestRoutine(tmpUnk)
2. If I declare the parameter: Optional oUnknown As Variant. This is much easier to test whether the parameter was passed a variable, passed Nothing, or skipped. However, the returned interface is coherced to its identity IUnknown, which is what I was hoping to avoid.
For example, let's say I passed an IShellItem interface back to oUnknown (parameter declared as variant). When the routine returns to the caller, then the interface they get is not IShellItem rather its IUnknown identity. Testing ObjPtr() before the object is passed thru the variant and after, returns two different values. One would need to QueryInterface the returned IUnknown for the IShellItem in order to make calls to the IShellItem.
3. In summary. The 1st example obviously returns the same interface reference but doesn't reliably allow testing whether or not the parameter was passed. The 2nd example makes it easy to test whether the parameter was passed, but doesn't result in the same interface reference.
If you want to play. Here's sample code that can be used. Change the test file to any valid file, if needed.
Code:
Private Declare Function SHILCreateFromPath Lib "shell32.dll" (ByVal pszPath As Long, ByRef pidl As Long, ByVal pAttrs As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Function SHCreateShellItemArrayFromIDLists Lib "shell32" (ByVal cidl As Long, ByVal rgpidl As Long, ppsiItemArray As IUnknown) As Long
Private Sub TestMe(oReturn As Variant)
Dim oUnk As IUnknown
Dim pidl As Long
SHILCreateFromPath StrPtr("C:\Windows\winhlp32.exe"), pidl, 0&
Debug.Assert pidl <> 0
SHCreateShellItemArrayFromIDLists 1, VarPtr(pidl), oUnk
If pidl Then CoTaskMemFree pidl
Debug.Assert Not oUnk Is Nothing
Debug.Print "oUnk: "; ObjPtr(oUnk)
Set oReturn = oUnk
End Sub
Private Sub Form_Load()
Dim oReturn As IUnknown
TestMe oReturn
Debug.Print "oReturn: "; ObjPtr(oReturn)
Unload Me
End Sub
P.S. I know a TLB should solve this. But as stated, no TLBs used.
Edited: FYI. This IUnknown identity issue applies if assigning it to Variant or any variant based property/object, i.e., the Collection object. For example, adding it to a collection and returning it from the collection results in the identity IUnknown reference.
Last edited by LaVolpe; Jan 14th, 2018 at 11:09 AM.
-
Jan 14th, 2018, 10:29 AM
#2
Re: Unique Scenario: Passing IUnknown as optional parameter
Well, an ugly fix would be to just pass a second optional argument that indicates what you passed.
However, I agree; it'd be cool to know if an optional argument was passed or not. I'm traveling today, but I'll definitely be poking at it to see if there's a way to figure this out.
Best Regards,
Elroy
Last edited by Elroy; Jan 14th, 2018 at 10:37 AM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Jan 14th, 2018, 10:32 AM
#3
Re: Unique Scenario: Passing IUnknown as optional parameter
@Elroy. Yep thought about that but not an option simply due to its ugliness
P.S. The reason for the optional parameter is that if it is passed, then another routine needs to be called to build the return value. Don't want to waste time/cpu cycles performing something that isn't wanted.
Edited: And just FYI, don't want the caller to call yet another routine that passes that IUknown, performs the QueryInterface, and returns the proper reference. In fact, this should always be done when passing an interface reference, because one really has no way of knowing which reference was passed. It could be the identity IUnknown, another interface that implements the desired interface, etc. VB does this automatically for us when we use keyword SET for objects (IDispatch implemented) and when a TLB is used to define an interface and that interface is declared via the TLB. As stated in 1st post, this thread's goal is really just a learning opportunity for me.
Last edited by LaVolpe; Jan 14th, 2018 at 10:49 AM.
-
Jan 14th, 2018, 10:37 AM
#4
Re: Unique Scenario: Passing IUnknown as optional parameter
Also, just thinking this through a bit ... the called procedure must keep track of whether or not an optional argument was passed. Here's why I say this. It's always been my assumption that, if an argument is passed, it's just used. However, if it's not passed, the called procedure implicitly declares (Dim) a variable of the type specified in the procedure declaration. Now, if the called procedure declared the variable, then it's responsible for destroying it when the procedure terminates. If it was passed in, it's not destroyed. Therefore, there's somehow a difference. Whether it can be detected is another matter.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Jan 14th, 2018, 10:41 AM
#5
Re: Unique Scenario: Passing IUnknown as optional parameter
Maybe I've misunderstood, but doesn't IsMissing do what you need?
Code:
Private Declare Function SHILCreateFromPath Lib "shell32.dll" (ByVal pszPath As Long, ByRef pidl As Long, ByVal pAttrs As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Function SHCreateShellItemArrayFromIDLists Lib "shell32" (ByVal cidl As Long, ByVal rgpidl As Long, ppsiItemArray As IUnknown) As Long
Private Sub TestMe(Optional oReturn As Variant)
Dim oUnk As IUnknown
Dim pidl As Long
If IsMissing(oReturn) Then Exit Sub
SHILCreateFromPath StrPtr("C:\Windows\winhlp32.exe"), pidl, 0&
Debug.Assert pidl <> 0
SHCreateShellItemArrayFromIDLists 1, VarPtr(pidl), oUnk
If pidl Then CoTaskMemFree pidl
Debug.Assert Not oUnk Is Nothing
Debug.Print "oUnk: "; ObjPtr(oUnk)
Set oReturn = oUnk
End Sub
Sub Test()
Dim oReturn As IUnknown
TestMe 'oReturn
Debug.Print "oReturn: "; ObjPtr(oReturn)
End Sub
For me that shortcircuits TestMe when no optional parameter is passed,.
-
Jan 14th, 2018, 10:42 AM
#6
Re: Unique Scenario: Passing IUnknown as optional parameter
@jpbro. Yes, but if the interface was requested by the caller, then they wouldn't get the actual interface passed through that optional parameter; they would get a reference to its identity IUnknown instead.
-
Jan 14th, 2018, 10:47 AM
#7
Re: Unique Scenario: Passing IUnknown as optional parameter
Ahh that was point #2, I think I understand a bit better now..Probably out of my league, but I'll play with it and see if I can come up with anything.
-
Jan 14th, 2018, 10:53 AM
#8
Re: Unique Scenario: Passing IUnknown as optional parameter
Originally Posted by jpbro
... but I'll play with it and see if I can come up with anything.
Don't spend too much time messing with it. If dilettante or The Trick pops in, they may know the answer right off (if there's an answer).
Any routines I write for VTable-only interfaces always perform a QueryInterface on the IUnknown passed to my routines to ensure it implements the interface that routine was designed for. In this case, I was more 'concerned' about the caller not realizing this and expecting something else. Would rather give them what they want vs an indirect reference to what they want. Also, since these interfaces are likely not to be directly accessed by the user, rather passed to APIs or other custom routines written by me, this 'problem' really isn't a problem. But would be nice to know if there is a solution to this specific scenario that gets me exactly what I want.
-
Jan 14th, 2018, 11:07 AM
#9
Re: Unique Scenario: Passing IUnknown as optional parameter
Here's another idea, but it's also a bit kludgy. You could possibly user VarPtr and compare the proximity of two variables. Something along these lines:
Code:
Option Explicit
Sub Test(Optional ByRef Maybe As Long, Optional ByRef NeverPassThis As Long)
Debug.Print VarPtr(Maybe), VarPtr(NeverPassThis)
End Sub
If they're not precisely 4 apart, then "Maybe" should have been passed in.
EDIT1: I'm also wondering if the same idea would work with a variable just declared at the top of the function, rather than a second Optional variable. Running out of time for now though. Good Luck.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
-
Jan 14th, 2018, 12:39 PM
#10
Re: Unique Scenario: Passing IUnknown as optional parameter
Well, here's some experimenting with Variant (as the optional parameter type)
In this line: Set oReturn = oUnk
I tested the objptr placed into the variant using CopyMemory to retrieve the far pointer referenced by the Variant. It's same as oUnk, not the identity IUnknown. However, once that reference is extracted from the Variant, that's when we get the identity IUnknown. I think what is happening is that VB is performing a QueryInterface on that reference, looking for IUnknown because: Dim oReturn As IUnknown
So, looks like I'm not gonna get exactly what I'd like. No biggie, just a little disappointing.
-
Jan 14th, 2018, 12:53 PM
#11
Re: [RESOLVED] Unique Scenario: Passing IUnknown as optional parameter
Oh and for Elroy and anyone else that may be curious.
I think I'll go this route: pass the optional parameter as a pointer. The modified code is shown below. Also note that I modified one of the API parameters from IUnknown to Long.
Code:
Private Declare Function SHILCreateFromPath Lib "shell32.dll" (ByVal pszPath As Long, ByRef pidl As Long, ByVal pAttrs As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Function SHCreateShellItemArrayFromIDLists Lib "shell32" (ByVal cidl As Long, ByVal rgpidl As Long, ByVal ppsiItemArray As Long) As Long
Private Sub TestMe(Optional ByVal oReturnPtr As Long)
Debug.Assert oReturnPtr <> 0
Dim pidl As Long
SHILCreateFromPath StrPtr("C:\Windows\winhlp32.exe"), pidl, 0&
Debug.Assert pidl <> 0
SHCreateShellItemArrayFromIDLists 1, VarPtr(pidl), oReturnPtr
CoTaskMemFree pidl
End Sub
Private Sub Form_Load()
Dim oReturn As IUnknown
TestMe VarPtr(oReturn)
Debug.Print "oReturn: "; ObjPtr(oReturn)
Unload Me
End Sub
-
Jan 14th, 2018, 01:18 PM
#12
Re: [RESOLVED] Unique Scenario: Passing IUnknown as optional parameter
Maybe pass a pointer to object variable?
Code:
Private Declare Function GetMem4 Lib "msvbvm60" (src As Any, dst As Any) As Long
Private Sub TestMe(Optional ByVal ppObj As Long)
Dim pObj As IUnknown
If ppObj = 0 Then
Debug.Print "Not passed"
Else
GetMem4 ByVal ppObj, pObj
If pObj Is Nothing Then
Debug.Print "Passed Nothing or uninitialized variable"
Else
Debug.Print "Passed initialized variable"
Set pObj = Nothing
GetMem4 0&, ByVal ppObj
End If
Dim pidl As Long
SHILCreateFromPath StrPtr("C:\Windows\winhlp32.exe"), pidl, 0&
Debug.Assert pidl <> 0
SHCreateShellItemArrayFromIDLists 1, VarPtr(pidl), pObj
If pidl Then CoTaskMemFree pidl
Debug.Assert Not pObj Is Nothing
Debug.Print "pUnk: "; ObjPtr(pObj)
GetMem4 pObj, ByVal ppObj
GetMem4 0&, pObj
End If
End Sub
Private Sub Form_Load()
Dim oReturn As IUnknown
Set oReturn = New Collection
Debug.Print ObjPtr(oReturn)
TestMe VarPtr(oReturn)
Debug.Print ObjPtr(oReturn)
TestMe
TestMe VarPtr(Nothing)
End Sub
-
Jan 16th, 2018, 04:35 PM
#13
Re: [RESOLVED] Unique Scenario: Passing IUnknown as optional parameter
this is how i do it. As you can see, I'm not too worried about whether or not the optional oReturn is passed. VB will clean up the references for us.
code was equivalent to your option #1... interesting.
yeah I don't see a way other than passing a pointer...
Code:
Private Declare Function SHILCreateFromPath Lib "shell32.dll" (ByVal pszPath As Long, ByRef pidl As Long, ByVal pAttrs As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Function SHCreateShellItemArrayFromIDLists Lib "shell32" (ByVal cidl As Long, ByVal rgpidl As Long, ppsiItemArray As IUnknown) As Long
Private Declare Function vbaObjSetAddref Lib "msvbvm60" Alias "__vbaObjSetAddref" (ByVal ObjPtr As Long, ByVal NewPtr As Long) As Long
Private Sub TestMe(Optional pReturn As Long)
Dim oUnk As IUnknown
Dim pidl As Long
SHILCreateFromPath StrPtr("C:\Windows\winhlp32.exe"), pidl, 0&
Debug.Assert pidl <> 0
SHCreateShellItemArrayFromIDLists 1, VarPtr(pidl), oUnk
If pidl Then CoTaskMemFree pidl
Debug.Assert Not oUnk Is Nothing
Debug.Print "oUnk: "; ObjPtr(oUnk)
If pReturn Then vbaObjSetAddref pReturn, ObjPtr(oUnk)
End Sub
Private Sub Form_Load()
Dim oReturn As IUnknown
TestMe VarPtr(oReturn)
Debug.Print "oReturn: "; ObjPtr(oReturn)
Unload Me
End Sub
I think vbaObjSetAddref already checks if you're passing a populated object pointer, and frees it automatically - but I'd have to double check.
Last edited by DEXWERX; Jan 17th, 2018 at 08:17 AM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|