Delphi:如何以相反的顺序删除子类? [英] Delphi: How to remove subclasses in reverse order?
问题描述
Mike Lischke的 TThemeServices
子类 Application.Handle
,以便它可以从Windows接收广播通知(即 WM_THEMECHANGED
)当主题更改。
它分类应用程序
对象的窗口:
FWindowHandle:= Application.Handle;
如果FWindowHandle<> 0然后
begin
//如果给出了一个窗口句柄,则子类化窗口以获得关于主题更改的通知。
{$ ifdef COMPILER_6_UP}
FObjectInstance:= Classes.MakeObjectInstance(WindowProc);
{$ else}
FObjectInstance:= MakeObjectInstance(WindowProc);
{$ endif COMPILER_6_UP}
FDefWindowProc:=指针(GetWindowLong(FWindowHandle,GWL_WNDPROC));
SetWindowLong(FWindowHandle,GWL_WNDPROC,Integer(FObjectInstance));
结束
然后,子类窗口procdure会按照它应该是 WM_DESTROY
消息,删除它的子类,然后通过
WM_DESTROY
消息:
程序TThemeServices.WindowProc(var Message:TMessage);
begin
case Message.Msg
WM_THEMECHANGED:
begin
[... snip ...]
end;
WM_DESTROY:
begin
//如果我们连接到一个窗口,那么我们必须听其破坏。
SetWindowLong(FWindowHandle,GWL_WNDPROC,Integer(FDefWindowProc));
{$ ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$ else}
FreeObjectInstance(FObjectInstance);
{$ endif COMPILER_6_UP}
FObjectInstance:= nil;
结束
结束
with Message do
结果:= CallWindowProc(FDefWindowProc,FWindowHandle,Msg,WParam,LParam);
结束
TThemeServices
对象是单例,销毁在单元定稿期间:
初始化
finalization
InternalThemeServices.Free;
结束。
这一切都很好 - 只要TThemeServices是唯一一个子类化应用程序句柄的人。
我有一个类似的单身图书馆,也想钩住 Application.Handle
所以我可以收到广播: / p>
程序TDesktopWindowManager.WindowProc(var Message:TMessage);
开始
案例Message.Msg
WM_DWMCOLORIZATIONCOLORCHANGED:...
WM_DWMCOMPOSITIONCHANGED:...
WM_DWMNCRENDERINGCHANGED:...
WM_DESTROY:
begin
//如果我们连接到一个窗口,那么我们必须听其破坏。
SetWindowLong(FWindowHandle,GWL_WNDPROC,Integer(FDefWindowProc));
{$ ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$ else}
FreeObjectInstance(FObjectInstance);
{$ endif COMPILER_6_UP}
FObjectInstance:= nil;
结束
结束
with Message do
结果:= CallWindowProc(FDefWindowProc,FWindowHandle,Msg,WParam,LParam);
和我的单例在单元完成时也被删除:
初始化
...
finalization
InternalDwmServices.Free;
结束。
现在我们来问题。我不能保证有人可能选择访问 ThemeServices
或 DWM
的顺序,每个都应用其子类。我也不知道Delphi将最终确定单位的顺序。
子类以错误的顺序被删除,应用程序关闭时出现崩溃。 >
如何解决?我如何确保我保持我的子类化方法足够长在我完成之后,直到其他的人完成 (我毕竟不想泄漏内存)
另请参见
更新:我看到Delphi 7通过重写 TApplication
来解决错误。 ><
程序TApplication.WndProc(var Message:TMessage);
...
开始
...
与消息do
case Msg
...
WM_THEMECHANGED:
如果ThemeServices.ThemesEnabled然后
ThemeServices.ApplyThemeChange;
...
end;
...
end;
Grrrr
<换句话说:尝试子类化TApplication是一个错误,Borland在采用Mike的
TThemeManager
时修复。 很好可能意味着没有办法以相反的顺序删除 TApplication
上的子类。有人以答案的形式提出,我会接受的。
更改代码以调用 SetWindowSubclass
,作为您链接的文章建议。但是,只有每个人都使用相同的API,所以补丁主题管理器使用相同的技术。该API是在Windows XP中引入的,所以没有任何危险,它不需要在需要的系统上。
补丁主题管理器应该没有问题。它旨在支持Microsoft不再支持的Windows XP,并支持Borland不再支持的Delphi 4到6。由于所有相关产品的开发已经停止,因此您可以安全地拨出主题管理器项目,而不会因未来更新而落后。
您不是真的引入依赖。相反,您正在修复仅在同时使用两个窗口外观库时才出现的错误。您的库的用户不需要为您的主题管理器工作,但如果他们希望使用这两者,他们需要使用主题管理器应用您的修补程序。应该没有什么反对,因为他们已经有基础版本,所以不像他们将要安装一个全新的图书馆。他们只是应用补丁并重新编译。
Mike Lischke's TThemeServices
subclasses Application.Handle
, so that it can receive broadcast notifications from Windows (i.e. WM_THEMECHANGED
) when theming changes.
It subclasses the Application
object's window:
FWindowHandle := Application.Handle;
if FWindowHandle <> 0 then
begin
// If a window handle is given then subclass the window to get notified about theme changes.
{$ifdef COMPILER_6_UP}
FObjectInstance := Classes.MakeObjectInstance(WindowProc);
{$else}
FObjectInstance := MakeObjectInstance(WindowProc);
{$endif COMPILER_6_UP}
FDefWindowProc := Pointer(GetWindowLong(FWindowHandle, GWL_WNDPROC));
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FObjectInstance));
end;
The subclassed window procdure then does, as it's supposed to, WM_DESTROY
message, remove it's subclass, and then pass the WM_DESTROY
message on:
procedure TThemeServices.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_THEMECHANGED:
begin
[...snip...]
end;
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
end;
The TThemeServices
object is a singleton, destroyed during unit finalization:
initialization
finalization
InternalThemeServices.Free;
end.
And that all works well - as long as TThemeServices is the only guy who ever subclasses the Application's handle.
i have a similar singleton library, that also wants to hook Application.Handle
so i can receive broadcasts:
procedure TDesktopWindowManager.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_DWMCOLORIZATIONCOLORCHANGED: ...
WM_DWMCOMPOSITIONCHANGED: ...
WM_DWMNCRENDERINGCHANGED: ...
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
And my singleton is similarly removed when the unit finalizes:
initialization
...
finalization
InternalDwmServices.Free;
end.
Now we come to the problem. i can't guarantee the order in which someone might choose to access ThemeServices
or DWM
, each of which apply their subclass. Nor can i know the order in which Delphi will finalize units.
The subclasses are being removed in the wrong order, and there is a crash on application close.
How to fix? How can i ensure that i keep my subclassing method around long enough until the other guy is done after me is done? (i don't want to leak memory, after all)
See also
- Raymond Chen: Safer Subclassing
- MSDN: Using Window Procedures
- Raymond Chen: When the normal window destruction messages are thrown for a loop
Update: i see Delphi 7 solves the bug by rewriting TApplication
. ><
procedure TApplication.WndProc(var Message: TMessage);
...
begin
...
with Message do
case Msg of
...
WM_THEMECHANGED:
if ThemeServices.ThemesEnabled then
ThemeServices.ApplyThemeChange;
...
end;
...
end;
Grrrr
In other words: trying to subclass TApplication was a bug, that Borland fixed when they adopted Mike's TThemeManager
.
That very well may mean that there is no way to remove subclasses on TApplication
in reverse order. Someone put that in the form of an answer, and i'll accept it.
Change your code to call SetWindowSubclass
, as the article you linked to advises. But that only works if everyone uses the same API, so patch Theme Manager to use the same technique. The API was introduced in Windows XP, so there's no danger that it's not available on the systems where it would be needed.
There should be no problem with patching Theme Manager. It's designed to support Windows XP, which Microsoft doesn't support anymore, and to support Delphi 4 through 6, which Borland doesn't support anymore. Since development has ceased on all relevant products, it is safe for you to fork the Theme Manager project without risk of falling behind due to future updates.
You're not really introducing a dependency. Rather, you're fixing a bug that's only present when both window-appearance libraries are in use at the same time. Users of your library don't need to have Theme Manager for yours to work, but if they wish to use both, they need to use Theme Manager with your patches applied. There should be little objection to that since they already have the base version, so it's not like they'd be installing an entirely new library. They'd just be applying a patch and recompiling.
这篇关于Delphi:如何以相反的顺序删除子类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!