Subclassing Without The Crashes

VB滚动条的应用实例,VB6.0源代码编写. 立即下载
 Prior to VB5, it was impossible to subclass a control without relying on a proprietary custom control. This greatly restricted what you could do if you wanted to play around with some of the neater customisation features - creating a new control was completely out, finding out in detail what was going on with menus was impossible and so on.

The introduction of the AddressOf operator to VB5 now allows you to subclass, although it is not nearly as simple as it ought to be.

The SSubTmr component is a more stable and consistent way of working with subclassing, without some of the attendent difficulties. You can either use it as an external DLL to provide, or by including one module and one interface class, you can compile it directly into your application once you are happy the subclassing is working. SSubTmr is the basis for most of the VB controls on this site.

The component itself mostly uses the SubTimer.DLL component code from Bruce McKinney's excellent "Hardcore Visual Basic" book, however it has a couple of very useful enhancements described in this article.

The Problem With Subclassing

If you develop ActiveX controls and intend to subclass or hook a window, you'll very quickly discover that doing it is not at all straightforward. A typical problem is that a control which works fine when there is a single instance on a form suddenly stops working or starts GPFing when you place two instances on the form. Why is this happening?

The AddressOf operator requires you to place the callback routine in a module (and most annoying this is too!). This module is shared between all instances of your control and therefore the variables and subroutines contained by that module are also shared.

To visualise the situation, consider a control which wants to know if its parent has changed size. To do this, you create a callback routine in a module to handle the WndProc callback. Then, you want to add this callback routine to the parent's window handle so you will be routed all the parent's messages. To add the callback, you change the parent's GWL_WNDPROC Window Long to the address of your WndProc function. This function returns a pointer to the previously installed WndProc function, which you must be sure to forward all messages to that you aren't fully handling yourself (otherwise you quickly find your control will do nothing or, more likely, prevent its container from doing anything!). When you remove an instance of your control, you need to put the old WndProc back again.

The following diagrams show how easy it is to end up with one control not functioning in this circumstance, because of the sharing of variables in the module:

With One Instance of Your Control

With Two Instances

You can correct this problem by storing a separate WndProc for each control instance that gets created. However, there is now a much more difficult problem to solve - how to make sure your control terminates correctly.

The WndProc function in the module receives all the messages, but you need to pass this information on to your UserControl for the object to do anything about the messages. Therefore the module needs to know about the instance of the control it wants to inform about the message. Since the module is shared between all instances of the UserControl in question, it will need to know which hWnd is associated with which UserControl instance, and also needs to be sure it only installs the WindowProc function for the same hWnd once.

You might think you can get around this by storing an array of references to UserControls in your WindowProc handler message, and when you receive a message, you then enumerate through your array and inform each item in turn. I tried this and there is a big downside. If your module holds a reference to any UserControl instance, it will not allow the UserControl object to be released until VB figures out that the reference has gone out of scope or if you set the object to nothing. However, unless you can arrange for the user of the control to call a method before the UserControl is destroyed, it is impossible for the reference to go out of scope!

Often the user of the control will forget to call this method, or will press the Stop button in VB IDE, or call End, the consequences being:

  • The UserControl_Terminate event never fires (this only fires when there are no references to the UserControl instance)
  • .. Hence the UserControl never stops running
  • .... And so you never remove the WindowProc, which suddenly stops being valid because the container no longer exists....

This is a very quick way to get a big and nasty GPF. Worse, this sort of thing sometimes works in design mode, but then when you build the OCX version all of a sudden you are getting 'Control.OCX referenced memory at 2x4500E000. The memory could not be "read"'...

Fixing It - Slimy Hacks and Neat Tips

Two things you need to fix these problems are:

  • Don't Store Direct References to Objects - Use a Slimy Hack Instead!
  • Use Window's built in database to store information against hWnds

These are now described in turn.

Don't Store Direct References to Objects - Use a Slimy Hack Instead!

Whenever you store a reference to an object in VB, VB increments it's COM reference count for you. This is great in the sense that it allows you not to worry about objects being invalid when you call them. However, its a massive pain if you have two objects which reference each other, because the COM reference count can never go down to zero unless you can figure a way to manually inform one or the other objects that they should terminate. Luckily there is a great hack to work around this.

Instead you store a long pointer to an object. This allows you to get an object reference, but prevents the object's reference count being incremented. Obviously this will lead you into difficulties if the object goes out of scope before you make a call to the object being pointed to (GPF...) but when used carefully this is a great technique. Here is how you do it:

  ' All credit for this code to Bruce McKinney:
  Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

  Private Property Get ObjectFromPtr(ByVal lPtr As Long) As Object
  Dim oThis As Object

    ' Turn the pointer into an illegal, uncounted interface
    CopyMemory oThis, lPtr, 4
    ' Do NOT hit the End button here! You will crash!
    ' Assign to legal reference
    Set ObjectFromPtr = oThis
    ' Still do NOT hit the End button here! You will still crash!
    ' Destroy the illegal reference
    CopyMemory oThis, 0&, 4
    ' OK, hit the End button if you must--you'll probably still crash,
    ' but this will be your code rather than the uncounted reference!

  End Property

  Private Property Get PtrFromObject(ByRef oThis As Object) As Long

    ' Return the pointer to this object:
    PtrFromObject = ObjPtr(oThis)

  End Property

That gives the general picture of how to do it. You can improve matters by typing the variables of type Object to suit your particular application.

Use Window's built in database to store information against a hWnd

This is an excellent feature. Windows has functions which allow you to store as many long values as you want against a named field for a given hWnd. Here are the functions:

  Declare Function GetProp Lib "user32" Alias "GetPropA" _
    (ByVal hwnd As Long, ByVal lpString As String) As Long
  Declare Function SetProp Lib "user32" Alias "SetPropA" _
    (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
  Declare Function RemoveProp Lib "user32" Alias "RemovePropA" _
    (ByVal hwnd As Long, ByVal lpString As String) As Long

You use them like this:

  ' To set a property called 'NumberOfInstances' to 3 for a form:
  SetProp Me.hWnd, "NumberOfInstances", 3

  ' To get the 'NumberOfInstances' value:
  lNumber = GetProp(Me.hWnd, "NumberOfInstances")

  ' To delete the property when finished with it 
  ' (Windows does this automatically when the application is ended):
  RemoveProp Me.hWnd, "NumberOfInstances"

You can use these functions to store as much information as you want against a given hWnd, and this is a perfect place to store pointers to objects which your subclassed window proc is going to call to, not to mention the old window proc value.

SSubTmr Implementation

SSubTmr implements these techniques to produce a stable subclasser which you can use regardless of how many instances you have and how many controls want to attempt to subclass the same hWnd. The implementation is very similar to the SubTimer component from Hardcore Visual Basic, but with some improvements.

The subclassing consists of three parts:

  • A bas module (subclass.bas) which contains the logic to associate object pointers with window handles, and manage the adding and removing of the subclassed proc.
  • An Interface, ISubClass.bas, defining the properties and methods which an object must support in order to take part in subclassing with subclass.bas. To ensure all these interfaces are present, subclass.bas will not accept any object which doesn't implement this interface.
  • A global class (GSubClass.Cls) which exposes functions to initiate and control the subclassing procedure.

For reference, here are the window properties SSUBTMR uses to manage the subclassing process:

C[hWnd]

Stores the number of instances using the subclass. When the property is 0 and you add to the subclass, it installs the WindowProc. Subsequent additions just increment the counter and use the existing WindowProc. When items are removed, the count is decremented, until it gets to zero, when the WindowProc is removed again.

[hWnd]#[Message]C

The count of how many times the message [Message] is attached to the hWnd [hWnd]. This allows you to subclass the same message on the same hWnd more than once. For example, if a control wants to subclass it's parent, and you place two controls on the same parent, you need both controls to be able to receive that message.

[hWnd]#[Message]#[Number]

Stores a pointer to the object which wants to receive notification for the hWnd/Message combination.

Differences between SSUBTMR and the Hardcore Visual Basic version

There are five main differences between this component and the one provided with Hardcore Visual Basic:

  1. Ability to subclass the same message for the same Window handle more than once
    This allows you to produce controls which intercept the same message on the parent window handle. Note that there was a problem with this in the first release that is now fixed.
  2. Response to messages is configurable on a message by message basis
    In the original SubTimer component you could specify which type of message response you wanted, but if you wanted to Pre-process some messages but Post-Process others you couldn't. This occurs because when the message response property is queried you don't know what the message it's querying for. This is resolved by the addition of a new CurrentMessage property which you can query in the MsgResponse event.
  3. You can change the wParam and lParam values when you return
    In SubTimer, wParam and lParam are passed ByVal so you can't change them. Some window procedures ask you to modify the wParam and lParam (drawing a customised title bar is one of these) when you return. This is resolved by adding a new CallOldWindowProc method. Set the message response to emrConsume to eat the message, and then use this call to send your new wParam and lParam values to the calling WindowProc.
  4. Reduction in modules
    To aid compiling SSubTmr directly into an EXE rather than using the DLL, the code has been moved around so the declares are localised to the modules that need them. To incorporate subclassing support into a project, just copy Subclass.bas and ISubClass.cls into your module and remove the reference to the SSubTmr.DLL component. (Warning: Don't do this until you've finished debugging, otherwise debugging can get to be seriously hard.)
  5. Sorry, there isn't a fifth one.

Quick Start - How to Use SSUBTMR

To quickly get started with subclassing, here is a simple example which demonstrates how to prevent a form from being resized smaller than a certain width and height. This is also available in the demonstration project download.

Step 1

Create a new Standard EXE project.

Step 2

Add a reference to SSubTmr.DLL. This appears in the references list as 'Subclassing and Timer Assistant (with configurable message response, multi-control support + timer bug fix)'

Step 3

Implement the Subclassing interface:

In the declarations section, add the following code:

  Implements ISubClass

Make sure that the required interfaces are supported under the ISubClass section:

  Private Property Let ISubClass_MsgResponse(ByVal RHS As EMsgResponse) 
    ' This Property Let is not really needed! 
  End Property 

  Private Property Get ISubClass_MsgResponse() As EMsgResponse 
    ' This will tell you which message you are responding to: 
    Debug.Print CurrentMessage 
    ' Tell the subclasser what to do for this message (here we do all processing):
    ISubClass_MsgResponse = emrConsume 
  End Property 

  Private Function ISubClass_WindowProc( _
    ByVal hWnd As Long, ByVal iMsg As Long, ByVal wParam As Long, ByVal lParam As Long _ 
    ) As Long 
    Debug.Print "Got Message" 
  End Function

Step 4

Now you have a template for all subclassing projects, here is the code you need to prevent form resizing smaller than a certain size:

Declarations:

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _ 
    lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long) 
Private Type POINTAPI 
    x As Long 
    y As Long 
End Type 
Private Type MINMAXINFO 
    ptReserved As POINTAPI 
    ptMaxSize As POINTAPI 
    ptMaxPosition As POINTAPI 
    ptMinTrackSize As POINTAPI 
    ptMaxTrackSize As POINTAPI
End Type
Private Const WM_GETMINMAXINFO = &H24 

To start and stop subclassing:

Private Sub Form_Load() 
    AttachMessage Me, Me.hwnd, WM_GETMINMAXINFO 
End Sub 

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) 
    DetachMessage Me, Me.hwnd, WM_GETMINMAXINFO
End Sub 

The code to handle the WM_GETMINMAXINFO message:

Private Function ISubclass_WindowProc( _
   ByVal hwnd As Long, ByVal iMsg As Long, _
   ByVal wParam As Long, ByVal lParam As Long) As Long 
Dim mmiT As MINMAXINFO 

    ' Copy parameter to local variable for processing 
    CopyMemory mmiT, ByVal lParam, LenB(mmiT) 
    
    ' Minimium width and height for sizing 
    mmiT.ptMinTrackSize.x = 128 
    mmiT.ptMinTrackSize.y = 128 
        
    ' Copy modified results back to parameter 
    CopyMemory ByVal lParam, mmiT, LenB(mmiT)

End Function

Another sample included in the demonstration shows how to create Windows graduated title bar effects like those in the image below:

转自:http://vbaccelerator.com/home/VB/Code/Libraries/Subclassing/SSubTimer/article.asp

ssubtmr6.dll的vb源码 从http://vbaccelerator.com/home/VB/ 下载的一个动态库ssubtmr6.dll的vb源代码,此动态链接库被许多程序调用. 立即下载

相关推荐

Inside Microsoft Visual Studio .NET 2003

Inside Microsoft Visual Studio .NET 2003 Inside Microsoft Visual Studio .NET 2003 Inside Microsoft Visual Studio .NET 2003 Inside Microsoft Visual Studio .NET 2003

VB高级编程初探(子类技术SUBCLASS与消息捕获)

一、前言 很早以前就想写一篇关于子类技术(SubClass)的文章,不过由于时间有限没有深入研究。这段时间由于工作需要用子类实现了大量的Windows消息捕获,正好有朋友今天又问到鼠标滚动事件的捕获问题(这个问题我将在文末给出一个较简洁的方法),因此决定写这篇文章,希望对大家有些帮助。 我们都知道VB有其局限性,我们也常常在试探通过某种方法拓展其应用范围,正如我在以前讲到过的如何在VB中使用隐

SunCherryDream的专栏 2843

侧边栏分类菜单控件(leftmenu)

侧边栏分类菜单控件(leftmenu)源码,非常使用,在很多软件中都用到

XML 简单接口 (SAX2)用Visual Basic 实现的示例

Martin Naughton2000年6月 下载本文的示例代码 (351 KB)摘要:本文概述用 Microsoft Visual Basic 编制 SAX2 接口的方法。简介 May 2000 MSXML Technology Preview 的关键功能之一是实现了 SAX2 (Simple API for XML, version 2)。MSDN XML 开发人员中心提供的题为 XML

.NET博文收藏 633

一款不错的侧边栏分类菜单控件

一款不错的侧边栏分类菜单控件(LeftMenu),该控件改版后支持菜单按钮上下移动,支持代码动态插入菜单按钮及菜单,支持ImageList配合使用。定义菜单时仅在InitializeLeftMenu过程中加载分级菜单。

2025年春科研论文写作指导(如何写好科研论文)研究生网课期末考试

希望能帮助到大家

beijieweiyu的博客 5788

VB中利用CopyMemory使用指针

  一、指针是什么?   不需要去找什么标准的定义,它就是一个32位整数,在C语言和在VB里都可以用Long类型来表示。在32位Windows平台下它和普通的32位长整型数没有什么不同,只不过它的值是一个内存地址,正是因为这个整数象针一样指向一个内存地址,所以就有了指针的概念。   有统计表明,很大一部分程序缺陷和内存的错误访问有关。正是因为指针直接和内存打交道,所以指针一直以来被看成一个危险的东

wjb9921的专栏 2824

什么是无人机巡检?如何实现无人机自动巡检?

复亚智能的无人机自动巡检系统通过高度自动化的技术,实现了无人机的高效巡检作业。无人机自动机场支持无人机的自动起降和电池更换,自动飞行算法确保无人机在复杂环境中的精准飞行和拍摄,而云端识别诊断和AI感知算法平台则实现图像的实时传输和智能分析。复亚智能,作为无人机技术的创新者和推动者,致力于开发先进的无人机自动巡检解决方案,助力各行各业实现智能化、自动化的巡检工作,引领智能巡检的未来。利用人工智能和机器学习算法,无人机可以被训练来识别特定的模式和异常,如结构损坏、热泄漏等,从而提高巡检的准确性和效率。

foiadrone的博客 2842

Ruff ty 类型检查器规则精讲:ineffective-final 如何识别被当作普通函数调用的 `final()`

本文围绕 Ruff 仓库中类型检查器 ty 的一条内置 lint 规则 **`ineffective-final`** 展开,讲解它如何识别「把 `typing.final()` 当成普通函数直接调用、而不是用作装饰器」的无效写法。读完本文,你将掌握该规则的判定条件、默认告警级别与版本信息、底层类型推断实现路径、官方测试用例,以及如何用 `@final` 装饰器正确改写此类代码,并了解 ty 中与

gitblog_00862的博客 379

组态王|以一定的时间间隔查询某个时间段内的历史数据

使用该函数其他都可以满足,唯一问题是变量的选择,当切换站点后查询历史数据时,所选的变量依然是上次站点查询时所选择的变量,每当更换站点查询时都需要重新选择变量,这一点太繁琐,是用户和操作者无法忍受的。但是由于现场可能数据变化并非太过频繁,想要实现记录频率不变,还是以1分钟一次的频率进行存储,但是报表查询的时候想要只显示整点的数据。其中日历控件的设置与之前的历史数据查询是一样的,报表的抬头设置都是不用变的,此处不再赘述,有兴趣的可以。以上为组态王中以一定的时间间隔查询某个时间段内的历史数据的实现过程。

做有价值的事,积累有价值的笔记! 2544

Lora无线温湿度传感器方案

综上所述,Lora无线温湿度传感器以其长距离传输、低功耗、高稳定性、精准测量、无线传输以及广泛的应用场景等特点,在环境监测领域展现出了强大的竞争力和广泛的应用前景。Lora无线温湿度传感器广泛应用于各种需要远程监控和数据采集的场景,如仓储、农业、机房、楼宇、交通、工业、矿产、电力等领域。Lora无线温湿度传感器是一种采用Lora无线通信技术进行数据传输的温湿度监测设备。四、产品参数(以某款工业级Lora温湿度传感器为例)

weixin_46632654的博客 758

关于Runnable接口的JDK文档翻译

<br />The Runnable interface should be implemented by any<br />class whose instances are intended to be executed by a thread. <br />Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。其中whose 是定语从句,whose 是定语从句中一个常用的关系代词, 它是关系代词who的所有格,在从句中作定语,也就是说当先行词与从句中某个名词有所属关系,表达“……的”意

huxin1的专栏 1288

物料帐配置之 重估消耗

物料帐配置 ML material ledger

SAP刺客的博客 5910

大型CAx(CAD/CAE/CAPP/CAM)工业软件开发中的关键组件

通过对FreeCAD、SALOME等多款代码的分析研究,发现这些软件在架构设计、模块实现等方面,存在许多相似(同)的技术思想。因此,有必要对这些共性、主流的技术予以总结分析。

国产CAx(CAD/CAE/CAM)软件研发 1万+

ESP32-S3数字麦克风入门:手把手教你用Arduino IDE读取MSM261S4030H0R的I2S数据

本文详细介绍了如何使用Arduino IDE读取MSM261S4030H0R数字麦克风的I2S数据,涵盖硬件连接、代码编写和数据可视化全流程。重点解决I2S引脚配置、环境噪声过滤和声波观察等实际问题,适合ESP32-S3开发者快速上手数字麦克风应用。

weixin_26742753的博客 318

使用微信小程序开发制作一个简单的在线问答应用

以上就是一个简单的在线问答小程序的开发过程。其中包括前端界面的布局和逻辑,后端接口的设计和实现,以及部署和测试的步骤。通过以上步骤,你可以自己尝试开发一个类似的在线问答应用。

2401_85258012的博客 795

构建可审计的RAG系统:检索噪声、上下文饱和与答案漂移的实战应对

RAG(检索增强生成)是一种将外部知识库与大语言模型结合的关键技术,其核心原理在于通过向量检索获取相关上下文,再交由LLM进行条件化生成。技术价值体现在避免幻觉、提升事实准确性与支持私有数据闭环,广泛应用于法律文档审查、临床试验分析、内部知识库和多语言技术文档搜索等高可靠性场景。然而真实落地中,三大典型问题持续制约效果:检索噪声导致无关信息干扰、上下文窗口饱和引发静默截断、答案漂移造成结果不一致。本文聚焦LlamaIndex+Gemini Flash+Mirascope技术栈,深入剖析这三类失效模式的根因与

chuange6363的博客 615

【QGIS入门实战精品教程】1.2:QGIS与ArcGIS的区别和联系

以下是GISGeography官方给出的两者之间的27点比较(译): 官方网址:https://gisgeography.com/qgis-arcgis-differences/ 1.QGIS容纳更多的数据格式 ArcGIS没有支持所有的数据格式,毫无疑问,QGIS在处理各种类型的数据时非常顺手,QGIS在数据格式方面是个多面手,在使用数据方面是它的长处。 QGIS使用GDAL/OGR库来读取和写入地理信息数据格式,支持超过70种矢量格式。QGIS一开始就是为了与PostGIS协作而诞生的,而支持的名单.

智慧空间实验室 2040

装饰者模式 Decorator Pattern

装饰者模式还是《设计模式解析》这本书的例子比较符合我们的认知。装饰者模式 : 添加新的功能 The Decorator Pattern is another way of wrapping classes. The docorator pattern provides a way of attaching additional responsibilities to an object dyn

haimian520的专栏 635

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

《ECMAScript 6 入门》参考链接导航:从官方规格到实战工具的系统学习路线图

本篇文章是开源教程《ECMAScript 6 入门》(阮一峰著)中"参考链接"一章的整理与扩充版。它以教程正文之外的延伸资源为骨架,按照"官方文件与提案流程、综合介绍、各语法专题、开发工具"四条主线,为读者梳理出一条从读懂规格书到掌握语法细节、再到配置转译工具的系统学习路径。读完本文,你将掌握如何借助 TC39 提案流程跟踪 ECMAScript 的最新进展,如何针对 `let`/`const`、

gitblog_00858的博客 238

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

NetworkX 2.2 版本解析:统一的随机数治理、GraphView 重构与新算法全览

本文基于仓库 [doc/release/release_2.2.rst](https://link.gitcode.com/i/75b3dfd8c9a55e955e4d0f942d258de4) 梳理 NetworkX 2.2 版本的技术要点,涵盖随机数生成器(RNG)统一治理、GraphView 函数化重构、新增的 `G.update(H)` 方法、算法家族扩充及 API 变更与弃用项。读完本文

gitblog_00084的博客 490

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Deep Agents 框架-基础篇

DeepAgents是LangChain生态中的高级API,构建于LangChain核心智能体模块和LangGraph运行时之上,旨在简化复杂智能体的开发。它提供任务规划、文件系统管理、子智能体生成等内置功能,适用于长期运行的多步骤任务。然而,DeepAgents并非独立框架,仍需依赖LangChain和LangGraph,学习成本较高。文档详细介绍了其核心能力、与LangChain/LangGraph的差异、配置方法及最佳实践,强调其在处理复杂任务时的优势,但也指出工程化选择需谨慎考虑其依赖关系和实际需求

yanghuashuiyue的专栏 442
上一篇: 窗口属性
下一篇: HOOK专题
explover
博客等级 码龄24年 3粉丝 7原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值