Quick question - I've just been testing overwriting methods of a class by changing entries in its VTable using low level copy memory api.
Background
I've had some success, and can swap 2 entries in the VTable of a class if they have the same signature. So a class definition like this:
Option Explicit
Public Sub Meow()
Debug.Print "Meow"
End Sub
Public Sub Woof()
Debug.Print "Woof"
End Sub
... generates a VTable like this:
... and I can swap the entries at positions 7 and 8 to make cls.Meow
print Woof
and vice-versa. I can also swap the entry from the VTable of one class with the VTable of an entirely different one (provided I don't try to dereference the implicit this
pointer by calling Me.anything
)
So I can make another class
Option Explicit
Public Sub Tweet()
Debug.Print "Tweet"
End Sub
and swap the behaviour of Woof
from one with Tweet
from the other. Not too complicated, I can share the code if people need it.
What I can't do...
... however, is figure out how to swap a class method with a method from a standard module?
Based on this article, it appears that the COM machinery which VBA is built on requires 2 things of class methods which VBA hides:
- They have an implicit
this
pointer - They return an HRESULT (
typedef long
)
So I thought
Public Sub Meow()
in a class module Class1
is equivalent to
Public Function Meow(ByVal this As LongPtr) As Long
I've also tried
Public Function Meow(ByRef meObj As Class1) As Long
Public Function Meow(ByRef meObj As Class1) As LongPtr 'but HResult is 32 bit int
Public Sub Meow(ByVal this As LongPtr)
etc. But VBA always crashes when I try to invoke the method from the VTable. So I'm at a bit of a loss. I wonder if things are different on a 64 bit computer, or if the standard module functions do something weird to the calling stack. The thing is I've seen examples of code where the entire VTable is assembled from standard module functions, so I know it's possible but just not sure how to convert the signatures correctly
How can I overwrite a VTable entry with a method defined in a standard module?
I was only partially corect in my comment on your question. I still believe that the
Me
keyword plays a role in preventing the 'redirection' of a class method to a method inside a standard .bas module. But that is only applicable to early binding.IDispatch::Invoke can actually call a method inside a .bas module with no problem. Your initial method signature was correct:
Class1
code:Code in a standard .bas module:
I've used LibMemory for the memory manipulation.
If you change the
Meow
class method to aFunction
instead of aSub
then you need to have an extraByRef
paramater at the end of the parameters list within theMeow
method in the .bas module.EDIT #1
I thought of the issue discussed in the comments below and the only reason I could come up with was that the IDispatch only works with a pointer to the IUnknown interface.
This means that:
will crash the Application
But, this works:
because passing
ByVal
forces a QueryInterface and an AddRef on the IUnknown (with Release when exiting scope)This also works:
EDIT #2
Apologies for making another edit.
The Invoke method is not working with a pointer to IUnknown. It is working with a pointer to IDispatch. This can be checked with:
which will print the ptr to the IDispatch interface. So, why does
ByRef this As Class1
fail? And why doByVal this As Class1
andByRef this As IUnknown
work?ByRef this As Class1
I believe the VarPtr(this) address is not accessible to VB, hence we are reading memory that we should not. It's not like there is an extra AddRef or Release on the IUnknown interface because the method never gets called using this declaration. The Application simply crashes when Invoke is trying to call the method.
ByVal this As Class1
The method simply creates a VB variable (on VB memory space) and calls AddRef
ByRef this As IUnknown
As this is not a dual interface, a call to QueryInterface and an AddRef is done. The memory address of 'this' is on local memory space, same as in the second example.