介绍Windows的窗口、消息、子类化和超类化

win32消息映射13-子类化和超类化 12 子类化和超类化 回顾SDK窗口创建的过程。首先注册一个类,这里重要的是类名和窗口的回调函数,然后调用CreateWindowEx创建窗口,创建时必须指定类名,实际上也是指定了窗口的回调函数。 windows操作系统内置了一些标准控件,供程序员使用。这些标准控件控件,不需要注册类名,系统已经帮你注册好,直接拿过来用就是了。比如按钮的类名是“BUTTON”,编辑框的类名是“EDIT”……,C... 阅读详情

介绍Windows的窗口、消息、子类化和超类化

这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词。为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程。子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法。不要把“子类化、超类化”与面向对象语言中的派生类、基类混淆起来。“子类化、超类化”中的“类”是指Windows的窗口类。

0 运行程序

希望读者在阅读本节前先看看"谈谈Windows程序中的字符编码"开头的第0节和附录0。第0节介绍了Windows系统的几个重要模块。附录0概述了Windows的启动过程,从上电到启动Explorer.exe。本节介绍的是运行程序时发生的事情。

0.1 程序的启动

当我们通过Explorer.exe运行一个程序时,Explorer.exe会调用CreateProcess函数请求系统为这个程序创建进程。当然,其它程序也可以调用CreateProcess函数创建进程。

系统在为进程分配内部资源,建立独立的地址空间后,会为进程创建一个主线程。我们可以把进程看作单位,把线程看作员工。进程拥有资源,但真正在CPU上运行和调度的是线程。系统以挂起状态创建主线程,即主线程创建好,不会立即运行,而是等待系统调度。系统向Win32子系统的管理员csrss.exe登记新创建的进程和线程。登记结束后,系统通知挂起的主线程可以运行,新程序才开始运行。

这时,在创建进程中CreateProcess函数返回;在被创建进程中,主线程在完成最后的初始化后进入程序的入口函数(Entry-point)。创建进程与被创建进程在各自的地址空间独立运行。这时,即使我们结束创建进程,也不会影响被创建进程。

0.2 程序的执行

可执行文件(PE文件)的文件头结构包含入口函数的地址。入口函数一般是Windows在运行时库中提供的,我们在编译时可以根据程序类型设定。在VC中编译、运行程序的小知识点讨论了Entry-point,读者可以参考。

入口函数前的过程可以被看作程序的装载过程。在装载时,系统已经做过全局和静态变量(在编译时可以确定地址)的初始化,有初值的全局变量拥有了它们的初值,没有初值的变量被设为0,我们可以在入口函数处设置断点确认这一点。

进入入口函数后,程序继续运行环境的建立,例如调用所有全局对象的构造函数。在一切就绪后,程序调用我们提供的主函数。主函数名是入口函数决定的,例如main或WinMain。如果我们没有提供入口函数要求的主函数,编译时就会产生链接错误。

0.3 进程和线程

我们通常把存储介质(例如硬盘)上的可执行文件称作程序。程序被装载、运行后就成为进程。系统会为每个进程创建一个主线程,主线程通过入口函数进入我们提供的主函数。我们可以在程序中创建其它线程。

线程可以创建一个或多个窗口,也可以不创建窗口。系统会为有窗口的线程建立消息队列。有消息队列的线程就可以接收消息,例如我们可以用PostThreadMessage函数向线程发送消息。

没有窗口的线程只要调用了PeekMessage或GetMessage,系统也会为它创建消息队列。

1 窗口和消息

1.1 线程的消息队列

每个运行的程序就是一个进程。每个进程有一个或多个线程。有的线程没有窗口,有的线程有一个或多个窗口。

我们可以向线程发送消息,但大多数消息都是发给窗口的。发给窗口的消息同样放在线程的消息队列中。我们可以把线程的消息队列看作信箱,把窗口看作收信人。我们在向指定窗口发送消息时,系统会找到该窗口所属的线程,然后把消息放到该线程的消息队列中。

线程消息队列是系统内部的数据结构,我们在程序中看不到这个结构。但我们可以通过Windows的API向消息队列发送、投递消息;从消息队列接收消息;转换和分派接收到的消息。

1.2 最小的Windows程序

Windows的程序员大概都看过这么一个最小的Windows程序:

// 例程1
#include "windows.h"
static const char m_szName[] = "窗口";
////////////////////////////////////////////////////////////////////////////////////////////////////
// 主窗口回调函数 如果直接用 DefWindowProc, 关闭窗口时不会结束消息循环
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);	// 关闭窗口时发送WM_QUIT消息结束消息循环
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// 主函数
int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.style = CS_VREDRAW|CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.lpszClassName = m_szName;
RegisterClass(&wc);		// 登记窗口类
HWND hWnd;
hWnd = CreateWindow(m_szName,m_szName,WS_OVERLAPPEDWINDOW,100,100,320,240,
NULL,NULL,hInstance,NULL);	// 创建窗口
ShowWindow(hWnd, nCmdShow);		// 显示窗口
MSG sMsg;
while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循环
if (ret != -1) {
TranslateMessage(&sMsg);
DispatchMessage(&sMsg);
}
}
return 0;
}

这个程序虽然只显示一个窗口,但经常被用来说明Windows程序的基本结构。在MFC框架内部我们同样可以找到类似的程序结构。这个程序包含以下基本概念:

  • 窗口类、窗口和窗口过程
  • 消息循环

下面分别介绍。

1.3 窗口类、窗口和窗口过程

创建窗口时要提供窗口类的名字。窗口类相当于窗口的模板,我们可以基于同一个窗口类创建多个窗口。我们可以使用Windows预先登记好的窗口类。但在更多的情况下,我们要登记自己的窗口类。在登记窗口类时,我们要登记名称、风格、图标、光标、菜单等项,其中最重要的就是窗口过程的地址。

窗口过程是一个函数。窗口收到的所有消息都会被送到这个函数处理。那么,发到线程消息队列的消息是怎么被送到窗口的呢?

1.4 消息循环

熟悉嵌入式多任务程序的程序员,都知道任务(相当于Windows的线程)的结构基本上都是:

	while (1) {
等待信号;
处理信号;
}

任务收到信号就处理,否则就挂起,让其它任务运行。这就是消息驱动程序的基本结构。Windows程序通常也是这样:

	while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循环
if (ret != -1) {
TranslateMessage(&sMsg);
DispatchMessage(&sMsg);
}
}

GetMessage从消息队列接收消息;TranslateMessage根据按键产生WM_CHAR消息,放入消息队列;DispatchMessage根据消息中的窗口句柄将消息分发到窗口,即调用窗口过程函数处理消息。

1.5 通过消息通信

创建窗口的函数会返回一个窗口句柄。窗口句柄在系统范围内(不是进程范围)标识一个唯一的窗口实例。通过向窗口发送消息,我们可以实现进程内和进程间的通信。

我们可以用SendMessage或PostMessage向窗口发送或投递消息。SendMessage必须等到目标窗口处理过消息才会返回。我试过:如果向一个没有消息循环的窗口SendMessage,SendMessage函数永远不会返回。PostMessage在把消息放入线程的消息队列后立即返回。

其实只有投递的消息才是通过DispatchMessage分派到窗口过程的。通过SendMessage发送的消息,在线程GetMessage时,就已经被分派到窗口过程了,不经过DispatchMessage。

1.5.1 窗口程序与控制台程序的通信实例

大家是不是觉得“例程1”没什么意思,让我们用它来做个小游戏:让“例程1”和一个控制台程序做一次亲密接触。我们首先将“例程1”的窗口过程修改为:

static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static DWORD tid = 0;
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);	// 关闭窗口时发送WM_QUIT消息结束消息循环
break;
case WM_USER:
tid = wParam;	// 保存控制台程序的线程ID
SetWindowText(hWnd, "收到");
break;
case WM_CHAR:
if (tid) {
switch(wParam) {
case '1':
PostThreadMessage(tid, WM_USER+1, 0, 0);	// 向控制台程序发送消息1
break;
case '2':
PostThreadMessage(tid, WM_USER+2, 0, 0);	// 向控制台程序发送消息2
break;
}
}
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}

然后,我们创建一个控制台程序,代码如下:

#include "windows.h"
#include "stdio.h"
static HWND m_hWnd = 0;
void process_msg(UINT msg, WPARAM wp, LPARAM lp)
{
char buf[100];
static int i = 1;
if (!m_hWnd) {
return;
}
switch (msg) {
case WM_USER+1:
SendMessage(m_hWnd, WM_GETTEXT, sizeof(buf), (LPARAM)buf);
printf("你现在叫:%s/n/n", buf);		// 读取、显示对方的名字
break;
case WM_USER+2:
sprintf(buf, "我是窗口%d", i++);
SendMessage(m_hWnd, WM_SETTEXT, sizeof(buf), (LPARAM)buf);	// 修改对方名字
printf("给你改名/n/n");
break;
}
}
int main()
{
MSG sMsg;
printf("Start with thread id %d/n", GetCurrentThreadId());
m_hWnd = FindWindow(NULL,"窗口");
if (m_hWnd) {
printf("找到窗口%x/n/n", m_hWnd);
SendMessage(m_hWnd, WM_USER, GetCurrentThreadId(), 0);
}
else {
printf("没有找到窗口/n/n");
}
while (int ret=GetMessage(&sMsg, NULL, 0, 0)) {		// 消息循环
if (ret != -1) {
process_msg(sMsg.message, sMsg.wParam, sMsg.lParam);
}
}
return 0;
}

大家能看懂这游戏怎么玩吗?首先运行“例程1”wnd,然后运行控制台程序msg。msg会找到wnd的窗口,并将自己的主线程ID发给wnd。wnd收到msg的消息后,会显示收到。这时,wnd和msg已经建立了通信的渠道:wnd可以向msg的主线程发消息,msg可以向wnd的窗口发消息。

我们如果在wnd窗口按下键'1',wnd会向msg发送消息1,msg收到后会通过WM_GETTEXT消息获得wnd的窗口名称并显示。我们如果在wnd窗口按下键'2',wnd会向msg发送消息2,msg收到后会通过WM_SETTEXT消息修改wnd的窗口名称。

这个小例子演示了控制台程序的消息循环,向线程发消息,以及进程间的消息通信。

1.5.2 地址空间的问题

不同的进程拥有独立的地址空间,如果我们在消息参数中包含一个进程A的地址,然后发送到进程B。进程B如果在自己的地址空间里操作这个地址,就会发生错误。那么,为什么上例中的WM_GETTEXT和WM_SETEXT可以正常工作?

这是因为WM_GETTEXT和WM_SETEXT都是Windows自己定义的消息,Windows知道参数的含义,并作了特殊的处理,即在进程B的空间分配一块内存作为中转,并在进程A和进程B的缓冲区之间复制数据。例如:在1.5.1节的例子中,如果我们设置断点观察,就会发现msg发送的WM_SETTEXT消息中的lParam不等于wnd接收到的WM_SETTEXT消息中的lParam。

如果我们在自己定义的消息中传递内存地址,系统不会做任何特殊处理,所以必然发生错误。

Windows提供了WM_COPYDATA消息用来向窗口传递数据,Windows同样会为这个消息作特殊处理。

在进程间发送这些需要额外分配内存的消息时,我们应该用SendMessage,而不是PostMessage。因为SendMessage会等待接收方处理完后再返回,这样系统才有机会额外释放分配的内存。在这种场合使用PostMessage,系统会忽略要求投递的消息,读者可以在msg程序中试验一下。

2 子类化和超类化

窗口类是窗口的模板,窗口是窗口类的实例。窗口类和每个窗口实例都有自己的内部数据结构。Windows虽然没有公开这些数据结构,但提供了读写这些数据的API。

例如:用GetClassLong和SetClassLong函数可以读写窗口类的数据;用GetWindowLong和SetWindowLong可以读写指定窗口实例的数据。使用这些接口,可以在运行时读取或修改窗口类或窗口实例的窗口过程地址。这些接口是子类化的实现基础。

2.1 子类化

子类化的目的是在不修改现有代码的前提下,扩展现有窗口的功能。它的思路很简单,就是将窗口过程地址修改为一个新函数地址,新的窗口过程函数处理自己感兴趣的消息,将其它消息传递给原窗口过程。通过子类化,我们不需要现有窗口的源代码,就可以定制窗口功能。

子类化可以分为实例子类化和全局子类化。实例子类化就是修改窗口实例的窗口过程地址,全局子类化就是修改窗口类的窗口过程地址。实例子类化只影响被修改的窗口。全局子类化会影响在修改之后,按照该窗口类创建的所有窗口。显然,全局子类化不会影响修改前已经创建的窗口。

子类化方法虽然是二十年前的概念,却很好地实践了面向对象技术的开闭原则(OCP:The Open-Closed Principle):对扩展开放,对修改关闭。

2.2 超类化

超类化的概念更简单,就是读取现有窗口类的数据,保存窗口过程函数地址。对窗口类数据作必要的修改,设置新窗口过程,再换一个名称后登记一个新窗口类。新窗口类的窗口过程函数还是仅处理自己感兴趣的消息,而将其它消息传递给原窗口过程函数处理。使用GetClassInfo函数可以读取现有窗口类的数据。

3 MFC中的消息循环和子类化

MFC将子类化方法应用得淋漓尽致,是一个不错的例子。候捷先生的《深入浅出MFC》已经将MFC的主要框架分析得很透彻了,本节只是看看MFC的消息循环,简单分析MFC对子类化的应用。

3.1 消息循环

随便建立一个MFC单文档程序,在视图类中添加WM_RBUTTONDOWN的处理函数,并在该处理函数中设置断点。运行,断下后,查看调用堆栈:

CHelloView::OnRButtonDown(unsigned int, CPoint)
CWnd::OnWndMsg(unsigned int, unsigned int, long, long *)
CWnd::WindowProc(unsigned int, unsigned int, long)
AfxCallWndProc(CWnd *, HWND__ *, unsigned int, unsigned int, long)
AfxWndProc(HWND__ *, unsigned int, unsigned int, long)
AfxWndProcBase(HWND__ *, unsigned int, unsigned int, long)
USER32! 7e418734()
USER32! 7e418816()
USER32! 7e4189cd()
USER32! 7e4196c7()
CWinThread::PumpMessage()
CWinThread::Run()
CWinApp::Run()
AfxWinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
WinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
WinMainCRTStartup()
KERNEL32! 7c816fd7()

WinMainCRTStartup是这个程序的入口函数。候捷先生已经详细介绍过AfxWinMain。我们就看看CWinThread::PumpMessage中的消息循环:

BOOL CWinThread::PumpMessage()
{
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) {
return FALSE;
}
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}

这就是MFC程序主线程中的消息循环,它把发送到线程消息队列的消息分派到线程的窗口。

3.2 子类化

CWnd::CreateEx在创建窗口前调用SetWindowsHookEx函数安装了一个钩子函数_AfxCbtFilterHook。窗口刚创建好,钩子函数_AfxCbtFilterHook就被调用。_AfxCbtFilterHook调用SetWindowLong将窗口过程替换为AfxWndProcBase,并将SetWindowLong返回的原窗口地址保存到成员变量oldWndProc。上节调用堆栈中的AfxWndProcBase就是由此而来。

可见,通过CWnd::CreateEx创建的所有窗口都会被子类化,即它们的窗口过程都会被替换为AfxWndProcBase。MFC为什么要这样做?

让我们再看看调用堆栈中的CWnd::WindowProc函数:

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}

按照侯捷先生的介绍,CWnd::OnWndMsg就是“MFC消息泵”的入口,消息通过这个入口流入MFC消息映射中的消息处理函数。消息泵只会处理我们定制过的消息,我们没有添加过处理的消息会原封不动地流过"消息泵",进入DefWindowProc函数。在DefWindowProc函数中,消息会传给子类化时保存的原窗口地址oldWndProc。

CWnd::CreateEx里的钩子会子类化所有窗口吗?其实不尽然。的确,MFC所有窗口相关的类都是从CWnd派生的,这些类的实例在创建窗口时都会调用CWnd::CreateEx,都会被子类化。但是,通过对话框模板创建的窗口是通过CreateDlgIndirect创建的,不经过CWnd::CreateEx函数。

但这点其实也不是问题,因为如果我们想通过MFC定制一个控件的消息映射,就必须先子类化这个控件,MFC还是有机会将窗口过程替换成自己的AfxWndProcBase。下一节将介绍对话框控件的子类化。

4 子类化和超类化的例子

我写了一个很简单的对话框程序,用来演示子类化和超类化。这个对话框程序有两个编辑框,我将编辑框的右键菜单换成了一个消息框。两个编辑框的定制分别采用了子类化和超类化技术:

 

4.1 子类化的例子

首先从CEdit派生出CMyEdit1,定制WM_RBUTTONDOWN的处理。很多文章都建议我们在对话框的OnInitDialog中用SubclassDlgItem实现子类化:

	m_edit1.SubclassDlgItem(IDC_EDIT1, this);

这样做当然可以。其实如果我们已经为IDC_EDIT1添加过CMyEdit1对象:

void CSubclassingDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSubclassingDlg)
DDX_Control(pDX, IDC_EDIT1, m_edit1);
//}}AFX_DATA_MAP
}

DDX_Control会自动帮我们完成子类化,没有必要手工调用SubclassDlgItem。大家可以通过在PreSubclassWindow中设置断点看看。

通过DDX_Control或者SubclassDlgItem子类化控件的效果是一样的,MFC都是把窗口过程替换成AfxWndProcBase。用户添加过处理函数的消息通过MFC消息泵流入用户的处理函数。

4.2 必经之路:PreSubclassWindow

PreSubclassWindow是一个很好的定制控件的位置。如果我们通过重载CWnd::PreCreateWindow定制控件,而用户在对话框中使用控件。由于对话框中的控件窗口是通过CreateDlgIndirect创建,不经过CWnd::CreateEx函数,PreCreateWindow函数不会被调用。

其实,用户要在对话框中使用定制控件,必须用DDX或者SubclassDlgItem函数子类化控件,这时PreSubclassWindow一定会被调用。

如果用户直接创建定制控件窗口,CWnd::CreateEx函数就一定会被调用,控件窗口一定会被子类化以安装MFC消息泵。所以在MFC中,PreSubclassWindow是创建窗口的必经之路。

4.3 超类化的例子

我很少看到超类化的例子(除了罗云彬的Win32汇编),在大多数应用中,子类化技术已经足够了。但我还是写了一个例子:CMyEdit2从CEdit派生。CMyEdit2::RegisterMe获取窗口类Edit的信息,保存原窗口过程,设置新窗口过程MyWndProc和新名称MyEdit,登记一个新窗口类。新窗口过程MyWndProc定制自己需要处理的消息,将其它消息送回原窗口过程。

我在对话框的OnInitDialog中先调用CMyEdit2::RegisterMe登记新窗口类,然后创建窗口。这样创建窗口必须经过CWnd::CreateEx,所以MFC还是会把窗口过程换成AfxWndProcBase。没有被MFC消息映射拦截的消息才会流入MyWndProc。

C++ 监视用户输入的数据(键盘钩子)代码及详解 #include<Windows.h> #include<cstdio> SYSTEMTIME systime; LRESULT CALLBACK Proc(int code,WPARAM w,LPARAM l){ KBDLLHOOKSTRUCT *T=(KBDLLHOOKSTRUCT*)l; DWORD x=T->vkCode; if(w==WM_KEYDOWN){ GetLocalTime(&systime); printf("Tip->... 阅读详情

相关推荐

走出MFC子类化的迷宫子类化,SUBCLASSWINDOW ,MFC消息机制

走出MFC子类化的迷宫子类化,SUBCLASSWINDOW ,MFC消息机制

PreSubclassWindow函数作用

CWnd中PreCreateWindow、PreSubclassWindow、SubclassWin MFC(VC6.0)的CWnd及其子类中,有如下三个函数: class CWnd : public CCmdTarget<br />{<br />    <br />public:<br />    <br />         virtual BOOL PreCreateWindow(CREATESTRUCT& cs);<br />     virtual void PreSubclassWindow()

jellocomeon的专栏 1062

易语言子类化挂接模块

窗口消息子类化,可以实现多消息的拦截 .版本 2 .子程序 子类化挂接窗口, 逻辑型, 公开 .参数 参数窗口句柄, 整数型 .参数 参数回调, 子程序指针, , 四个参数hwnd, msg, wparam, lparam 原窗口指针 = GetWindowLongA (参数窗口句柄, -4) 程序外部回调地址 = 到整数 (参数回调) .如果真 (设置窗口信息_ (参数窗口句柄, -4, 取类方法函数_内部 (4, 4)) > 0) 原句柄 = 参数窗口句柄 返回 (真) .如果真结束 返回 (假)

眼见为实(2):介绍Windows的窗口、消息、子类化和超类化

眼见为实(2):介绍Windows的窗口、消息、子类化和超类化 这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词。为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程。子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法。不要把“子类化、超类化”与面向对象语言中的派生类、基类混淆起来。“...

技术代码资料库 406

「 网络安全常用术语解读 」软件成分分析SCA详解:从发展背景到技术原理再到业界常用检测工具推荐

软件成分分析(Software Composition Analysis,SCA)是一种用于识别和分析软件内部组件及其关系的技术,旨在帮助开发人员更好地了解和管理其软件的构建过程,同时可帮助安全人员揭秘软件内部结构的神秘面纱。SCA技术的发展与软件行业的快速发展密不可分,下面将介绍SCA的发展背景、技术原理以及当前主流SCA工具。

所有成功的背后,都是痛苦的坚持,所有的痛苦,都是傻瓜般的不放弃! 5628

SAP 资产主数据屏幕增强:对比字段追加与子屏幕/页签 2 种方案优劣

本文深入对比了SAP资产主数据(AS01/AS03)屏幕增强的两种方案:字段追加与子屏幕/页签。从技术实现、业务适配、系统维护等维度分析各自优劣,提供方案选型决策树和混合方案实践建议,帮助企业在SAP实施中做出最优选择。

weixin_28559803的博客 283

解说Win32的窗口子类化

也许你需要一个特殊的Edit来限制浮点数的输入,但是现有的Edit却并不能完成这项工作——因为它只能够单纯的限制大小写或者纯数字。当你在论坛上求救的时候,某个网友告诉你:“用子类化。”你也许会在看到一线曙光的同时多出了一连串的问题:何为子类化?子类化的原理是什么?如何实现子类化?下面就让我从一个简单的C++程序开始,一步步解开你的疑团吧。  首先,我为你列出以下这个C++程序:#include

清清湖水 1127

实现高精度卷积混响--MATLAB仿真篇

一、前言 之前在STM32F407上现实了直播声卡,实现的经典Schroeder混响模型和Moorer混响模型,得到不错效果。后来得知卷积混响最为真实地表现空间的特性,所以也就尝试在MCU上实现一个高精度的卷积混响(卷积点数为32768),目前MCU出来的混响效果算得上一流水准,本人也在计划在ADI的21489或者SC584上更高精度的卷积混响。 二、卷积方式 1、conv1是使用MATLAB自带卷积函数conv 2、conv2和conv3是改进后卷积过程,具有减少运算量特点,方便嵌入式移植 clear;

Six44的博客 5074

子类化和超类化区别(介绍Windows的窗口、消息、子类化和超类化

这篇文章本来只是想介绍一下子类化和超类化这两个比较“生僻”的名词。为了叙述的完整性而讨论了Windows的窗口和消息,也简要讨论了进程和线程。子类化(Subclassing)和超类化(Superclassing)是伴随Windows窗口机制而产生的两个复用代码的方法。不要把“子类化、超类化”与面向对象语言中的派生类、基类混淆起来。“子类化、超类化”中的“类”是指Windows的窗口类。 0 运行

小龙王2010 933

第1章 环境准备

本章是 Kubernetes 高可用集群部署的前置基础环境配置,所有节点必须严格按顺序执行。主要内容包括:节点规划(IP、角色、配置要求)、VMware 克隆节点环境清理(解决 MAC 地址和主机名冲突)、统一工作目录创建、主机名与 hosts 解析配置、系统基础升级、防火墙与 swap 关闭、内核模块加载(overlay、br_netfilter、ip_vs)、网络参数优化(IP 转发、桥接过滤)、时间同步配置等。Ubuntu 24.04 内核 6.8+ 原生支持 eBPF 和 IPVS,无需手动升级内核

幸福街--linux技术分享 210

WPF在特定领域的应用:打造一款专业的图像编辑工具

WPF在特定领域的应用:打造一款专业的图像编辑工具,在软件开发的广阔领域中,Windows Presentation Foundation(WPF)以其强大的图形渲染能力和丰富的用户界面控件,为开发者提供了构建高性能、美观应用程序的平台。图像编辑工具作为一种对图形处理要求极高的应用类型,WPF 的特性使其成为理想的选择。本文将深入探讨如何使用 WPF 打造一款专业的图像编辑工具,涵盖从基础概念到实际代码实现的各个方面。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 6万+

Surface重装系统:固件信任链与官方恢复工具深度解析

Surface设备并非标准Windows PC,其本质是微软深度定制的硬件平台,依赖UEFI Secure Boot、TPM 2.0和分层签名验证链(Chain of Trust)构建硬件级安全启动体系。理解这一机制是掌握Surface系统重装的前提——它不是简单的ISO写入与安装,而是固件、驱动与操作系统三者协同再认证的过程。技术价值体现在对Secure Boot验证失败、人脸识别驱动异常、触控笔压感失准等典型问题的根因定位与解决;应用场景覆盖二手设备清空、企业批量部署及Windows 11 25H2新特

weixin_30861459的博客 312

YOLOv8改进,YOLOv8主干网络替换为ShuffleNetV2

ShuffleNet V2的设计原则:1.减少内存访问成本:通过优化数据传输路径,减少内存访问延迟,提高整体运算速度。2.考虑平台特性:针对不同硬件平台的特性进行优化设计,确保在目标平台上的高效运行。3.结构简洁高效:采用简洁而高效的模块设计,降低计算复杂度,同时保持或提升模型的预测准确性。下图,以四种不同计算复杂度水平下的准确性(在ImageNet验证集上的分类),速度和FLOPs的测量结果。(a, c) 为GPU结果,批量大小为8。(b, d) 为ARM结果,批量大小为1。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 1564

窗口子类化和超类化技术的应用

窗口子类化和超类化技术的应用

woyaowenzi的专栏 1015

11、硬件工程师笔试面试——IGBT(2025版本)

IGBT的应用展望与创新趋势表明,随着电子技术的不断发展,IGBT在各个领域都有广泛的应用,如高压电力系统、交通工具、电锅炉、太阳能和风能发电系统等。FS型IGBT(场终止型绝缘栅双极晶体管)在电力传输领域的应用中,通过其独特的设计和特性,有助于确保系统的稳定性和安全性。每种器件都有其独特的优势,能够满足电力传输系统中不同的技术要求。FS型IGBT通常具有正温度系数,这意味着随着温度的升高,导通压降的主要影响因素是载流子迁移率的降低以及集电极和发射极的接触电阻的增加,有助于在高温下维持稳定的性能。

XU157303764的博客 2671

STM32(HAL库)驱动OLED

通过STM32F103C8T6单片机采用HAL库方式对0.96寸OLED(IIC通讯)屏幕进行驱动。

weixin_44597885的博客 8793
上一篇: 在VC中编译、运行程序的小知识点
下一篇: VC中缺省库冲突的解决
SWenJing
博客等级 码龄18年 8粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值