If this is your first visit, be sure to
check out the FAQ by clicking the
link above. You may have to register
before you can post: click the register link above to proceed. To start viewing messages,
select the forum that you want to visit from the selection below.

'Object doesn't support this property or method' - But it's there!

Firstly, it should be noted that I am using VB5 SP0, and it is not possible
at all to upgrade to SP3 or any higher version of VB.

As part of a requirement from 'on high', I've been told to dump all ActiveX
controls that we are currently using and to replace them with UserControls.
Fair enough, except that each ActiveX control has its own code modules, so
I've tried to consolidate these modules into a single code module. The
UserControls are subclassed, so I have a common WndProc in the code module
that forwards to each UserControl's friend WndProc function.

However, VB chokes while compiling or running the program on the indicated
line, and if I attempt to debug and resume execution the IDE GPFs.

Public Function WndProc (ByVal hWnd As Long, ByVal uMsg As Long, ByVal
wParam As Long, ByVal lParam As Long) As Long
Dim pObj As Object, lpPrevWndProc As Long
Set pObj = PtrToCtl(GetProp(hWnd, "ControlPtr"))
lpPrevWndProc = GetProp(hWnd, "OldWndProc")
If Not (pObj Is Nothing) Then
'// VB throws the error on the next line.
WndProc = pObj.WndProc(hWnd, uMsg, wParam, lParam)
Else
If lpPrevWndProc <> 0 Then
WndProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, lParam)
Else
WndProc = DefWindowProc(hWnd, uMsg, wParam, lParam)
End If
End If
End Function

In the UserControl's source code, however, there *is* a WndProc function,
and it doesn't matter what (if anything) is inside the code. Making it a
Public function results in an error message similar to the following:

Re: 'Object doesn't support this property or method' - But it's there!

Comments inline. Also, I'd strongly recommend that you use
SubClass.bas/PushParamThunk.bas/DbgWProc.dll as described in my book
(http://www.PowerVB.com), or at least DbgWProc.dll as described in Feb 97
VBPJ or earlier. You'll save yourself all of the GetProp/SetProp/CopyMemory
work, work correctly with Friend functions, and support debugging
automatically. Subclassing with this technique requires a class variable + 2
lines of code, your window proc, and a 3 line (including Function/End
Function) redirection function in a standard module. Trust me, it is much
easier than what you're trying here, and has a fraction of the per-message
overhead.

-Matt

"Damit Senanayake" <damit@mvps.org> wrote in message
news:3b1d0acf@news.devx.com...
> Firstly, it should be noted that I am using VB5 SP0, and it is not
possible
> at all to upgrade to SP3 or any higher version of VB.

Not a problem. Anything I mention below works equally well in all VB5/VB6
versions.
> As part of a requirement from 'on high', I've been told to dump all
ActiveX
> controls that we are currently using and to replace them with
UserControls.
> Fair enough, except that each ActiveX control has its own code modules, so
> I've tried to consolidate these modules into a single code module. The
> UserControls are subclassed, so I have a common WndProc in the code module
> that forwards to each UserControl's friend WndProc function.

You can't call a Friend function on multiple objects from a single function.
A Friend function is not virtual, which means several things. First, it must
be bound to a specific class type, not a specific interface type. Public
functions are bound through an interface (read: vtable), not to a specific
class type. This also means that a Friend function cannot be called than an
'As Object' variable, as this implies a late-bound call, which will only
work against Public functions. If you change your WndProc callbacks to
Public, this would probably work. If you want to leave them as Friend, then
you need to create a different .bas function for each type of UserControl
you use. I would also recommend using a different PtrToCtl function for each
UserControl type. Trying to use this code generically (with 'As Object'
instead of 'As MyCtl' can lead to a lot of problems if you're not careful.
> However, VB chokes while compiling or running the program on the indicated
> line, and if I attempt to debug and resume execution the IDE GPFs.
>
> Public Function WndProc (ByVal hWnd As Long, ByVal uMsg As Long, ByVal
> wParam As Long, ByVal lParam As Long) As Long
> Dim pObj As Object, lpPrevWndProc As Long
> Set pObj = PtrToCtl(GetProp(hWnd, "ControlPtr"))
> lpPrevWndProc = GetProp(hWnd, "OldWndProc")

Running GetProp against an Atom is about two orders of magnitude faster than
using a string everytime. Also, if "ControlPtr" is actually set, then
"OldWndProc" is redundant. The previous window procedure can be stored in
the UserControl, which should be fully responsible for the forwarding calls.
This code might seem more robust, but if the primary mechanism fails
(because the property is not set), then so will the backup mechanism.

I use the following code to get a shared Atom value I can use. You'll want
to modify the string and create another class instance if you need more than
one string.
'In Atomizer.cls, Atomizer.
Private m_Atom As Integer

'Value should be set as the default property
Public Property Get Value() As Long 'As Long for Get/SetProp calls
Value = m_Atom And &HFFFF&
End Property

> If Not (pObj Is Nothing) Then
> '// VB throws the error on the next line.
> WndProc = pObj.WndProc(hWnd, uMsg, wParam, lParam)

This error is expected if WndProc is Friend. The function isn't there. Note
that VB throws out of a .bas function if an error is not caught, and the
windows is definitely not expecting a throw here. You should never let an
error out of this procedure, which means not letting one out of your
UserControl WndProc once you manage to call the function.
> Else
> If lpPrevWndProc <> 0 Then
> WndProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, lParam)

Once again, this is all overkill if "ControlPtr" actually works.
> Else
> WndProc = DefWindowProc(hWnd, uMsg, wParam, lParam)
> End If
> End If
> End Function
>
> Public Function PtrToCtl (ByVal ptr As Long) As Object
> If ptr = 0 Then
> Set PtrToCtl = Nothing
> Exit Function
> End If
> Dim oCtl As Object
> CopyMemory oCtl, ptr, 4
> Set PtrToCtl = oCtl
> CopyMemory oCtl, 0&, 4
> End Function
>
> In the UserControl's source code, however, there *is* a WndProc function,
> and it doesn't matter what (if anything) is inside the code. Making it a
> Public function results in an error message similar to the following:
>
> "Method WndProc of _TreeView failed."
>
> Friend Function WndProc (ByVal hWnd As Long, ByVal uMsg As Long, ByVal
> wParam As Long, ByVal lParam As Long) As Long
> '// ...
> End Function
>
> My intention is actually to write a generic WndProc forwarder for each
> UserControl. I have tried getting the UserControls to implement a callback
> interface, but that leads to the same issue.

The callback interface is possible, but is limited if you need more than one
subclass per control (ie, subclass a textbox and the UserControl itself).
You need to branch on the hWnd parameter for every message coming in. It is
much easier to code window procedures if you have distinct procs for each
subclassed item. If you did this, you should adjust your PtrToCtl function
to return the interface directly (be sure the local variable and the return
type have the same type), and make sure you use a Set statement to get the
correct interface before the initial SetProp call (Dim pSubClass As
ISubClass: Set pSubClass = Me: SetProp hWnd, Atomizer, ObjPtr(pSubClass)).
> Additionally, I am using some vtable replacement code for the
> IOleInPlaceActiveObject interface, but the error and subsequent crash
still
> occurs even if that code is commented out.

Wrapping this interface and plugging it in with
IOleInPlaceFrame.SetActiveObject is one thing (this can be done in response
to WM_SETFOCUS as outlined in Chapter 16 in my book or the June 97 VBPJ),
but doing vtable replacement on this interface is a very bad idea. You
should only use vtable replacement on your primary or explicitly implemented
vtables, and this should be a one-off replacement, not a toggle back and
forth between the functions. The primary/implements vtables are shared with
every instance of the class, but the VB-provided vtables (from IUnknown to
IOleInPlaceActiveObject) are shared by every instance of every
Class/Form/UserControl that happens to use them. You can't touch
IOleInPlaceActiveObject without touching every user control in your Dll/Ocx
and other Dll/Ocx/Exe instances in the same process. You get even more
problems if you swap the vtable pointer back and forth to forward the call.
I've been known to do some pretty hairy things in VB (including introducing
vtable replacement, March 97 VBPJ), but I won't touch this application of
the technique with a ten foot pole. What are you trying to accomplish with
the vtable replacement?
> Any help on this matter will be greatly appreciated.
>
> Thanks,
> Damit
>
>

Re: 'Object doesn't support this property or method' - But it's there!

Comments inline. Also, I'd strongly recommend that you use
SubClass.bas/PushParamThunk.bas/DbgWProc.dll as described in my book
(http://www.PowerVB.com), or at least DbgWProc.dll as described in Feb 97
VBPJ or earlier. You'll save yourself all of the GetProp/SetProp/CopyMemory
work, work correctly with Friend functions, and support debugging
automatically. Subclassing with this technique requires a class variable + 2
lines of code, your window proc, and a 3 line (including Function/End
Function) redirection function in a standard module. Trust me, it is much
easier than what you're trying here, and has a fraction of the per-message
overhead.

-Matt

"Damit Senanayake" <damit@mvps.org> wrote in message
news:3b1d0acf@news.devx.com...
> Firstly, it should be noted that I am using VB5 SP0, and it is not
possible
> at all to upgrade to SP3 or any higher version of VB.

Not a problem. Anything I mention below works equally well in all VB5/VB6
versions.
> As part of a requirement from 'on high', I've been told to dump all
ActiveX
> controls that we are currently using and to replace them with
UserControls.
> Fair enough, except that each ActiveX control has its own code modules, so
> I've tried to consolidate these modules into a single code module. The
> UserControls are subclassed, so I have a common WndProc in the code module
> that forwards to each UserControl's friend WndProc function.

You can't call a Friend function on multiple objects from a single function.
A Friend function is not virtual, which means several things. First, it must
be bound to a specific class type, not a specific interface type. Public
functions are bound through an interface (read: vtable), not to a specific
class type. This also means that a Friend function cannot be called than an
'As Object' variable, as this implies a late-bound call, which will only
work against Public functions. If you change your WndProc callbacks to
Public, this would probably work. If you want to leave them as Friend, then
you need to create a different .bas function for each type of UserControl
you use. I would also recommend using a different PtrToCtl function for each
UserControl type. Trying to use this code generically (with 'As Object'
instead of 'As MyCtl' can lead to a lot of problems if you're not careful.
> However, VB chokes while compiling or running the program on the indicated
> line, and if I attempt to debug and resume execution the IDE GPFs.
>
> Public Function WndProc (ByVal hWnd As Long, ByVal uMsg As Long, ByVal
> wParam As Long, ByVal lParam As Long) As Long
> Dim pObj As Object, lpPrevWndProc As Long
> Set pObj = PtrToCtl(GetProp(hWnd, "ControlPtr"))
> lpPrevWndProc = GetProp(hWnd, "OldWndProc")

Running GetProp against an Atom is about two orders of magnitude faster than
using a string everytime. Also, if "ControlPtr" is actually set, then
"OldWndProc" is redundant. The previous window procedure can be stored in
the UserControl, which should be fully responsible for the forwarding calls.
This code might seem more robust, but if the primary mechanism fails
(because the property is not set), then so will the backup mechanism.

I use the following code to get a shared Atom value I can use. You'll want
to modify the string and create another class instance if you need more than
one string.
'In Atomizer.cls, Atomizer.
Private m_Atom As Integer

'Value should be set as the default property
Public Property Get Value() As Long 'As Long for Get/SetProp calls
Value = m_Atom And &HFFFF&
End Property

> If Not (pObj Is Nothing) Then
> '// VB throws the error on the next line.
> WndProc = pObj.WndProc(hWnd, uMsg, wParam, lParam)

This error is expected if WndProc is Friend. The function isn't there. Note
that VB throws out of a .bas function if an error is not caught, and the
windows is definitely not expecting a throw here. You should never let an
error out of this procedure, which means not letting one out of your
UserControl WndProc once you manage to call the function.
> Else
> If lpPrevWndProc <> 0 Then
> WndProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, lParam)

Once again, this is all overkill if "ControlPtr" actually works.
> Else
> WndProc = DefWindowProc(hWnd, uMsg, wParam, lParam)
> End If
> End If
> End Function
>
> Public Function PtrToCtl (ByVal ptr As Long) As Object
> If ptr = 0 Then
> Set PtrToCtl = Nothing
> Exit Function
> End If
> Dim oCtl As Object
> CopyMemory oCtl, ptr, 4
> Set PtrToCtl = oCtl
> CopyMemory oCtl, 0&, 4
> End Function
>
> In the UserControl's source code, however, there *is* a WndProc function,
> and it doesn't matter what (if anything) is inside the code. Making it a
> Public function results in an error message similar to the following:
>
> "Method WndProc of _TreeView failed."
>
> Friend Function WndProc (ByVal hWnd As Long, ByVal uMsg As Long, ByVal
> wParam As Long, ByVal lParam As Long) As Long
> '// ...
> End Function
>
> My intention is actually to write a generic WndProc forwarder for each
> UserControl. I have tried getting the UserControls to implement a callback
> interface, but that leads to the same issue.

The callback interface is possible, but is limited if you need more than one
subclass per control (ie, subclass a textbox and the UserControl itself).
You need to branch on the hWnd parameter for every message coming in. It is
much easier to code window procedures if you have distinct procs for each
subclassed item. If you did this, you should adjust your PtrToCtl function
to return the interface directly (be sure the local variable and the return
type have the same type), and make sure you use a Set statement to get the
correct interface before the initial SetProp call (Dim pSubClass As
ISubClass: Set pSubClass = Me: SetProp hWnd, Atomizer, ObjPtr(pSubClass)).
> Additionally, I am using some vtable replacement code for the
> IOleInPlaceActiveObject interface, but the error and subsequent crash
still
> occurs even if that code is commented out.

Wrapping this interface and plugging it in with
IOleInPlaceFrame.SetActiveObject is one thing (this can be done in response
to WM_SETFOCUS as outlined in Chapter 16 in my book or the June 97 VBPJ),
but doing vtable replacement on this interface is a very bad idea. You
should only use vtable replacement on your primary or explicitly implemented
vtables, and this should be a one-off replacement, not a toggle back and
forth between the functions. The primary/implements vtables are shared with
every instance of the class, but the VB-provided vtables (from IUnknown to
IOleInPlaceActiveObject) are shared by every instance of every
Class/Form/UserControl that happens to use them. You can't touch
IOleInPlaceActiveObject without touching every user control in your Dll/Ocx
and other Dll/Ocx/Exe instances in the same process. You get even more
problems if you swap the vtable pointer back and forth to forward the call.
I've been known to do some pretty hairy things in VB (including introducing
vtable replacement, March 97 VBPJ), but I won't touch this application of
the technique with a ten foot pole. What are you trying to accomplish with
the vtable replacement?
> Any help on this matter will be greatly appreciated.
>
> Thanks,
> Damit
>
>