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
thispointer - 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
Mekeyword 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:
Class1code:Code in a standard .bas module:
I've used LibMemory for the memory manipulation.
If you change the
Meowclass method to aFunctioninstead of aSubthen you need to have an extraByRefparamater at the end of the parameters list within theMeowmethod 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
ByValforces 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 Class1fail? And why doByVal this As Class1andByRef this As IUnknownwork?ByRef this As Class1I 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 Class1The method simply creates a VB variable (on VB memory space) and calls AddRef
ByRef this As IUnknownAs 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.