分享
 
 
 

将击键发送到其它应用程序,如何从 MFC 应用程序中调用.NET 框架?

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

C++ Q&A 专栏...

将击键发送到其它应用程序,如何从 MFC 应用程序中调用 .NET 框架?...

原著:Paul DiLascia

翻译:Northtibet

原代码下载:CQA0501.exe (231KB)

原文出处:MSDN Magazine January 2005 (C++ Q&A)

如何发送击键到其它应用程序?

关于如何通过编程来发送 Ctrl+Alt+Del 击键?

如何从 MFC 应用程序中调用 .NET 框架?

我想编写一个应用程序,它能通过击键将信息写到另外一个应用程序的窗体中。我是不是应该发送 WM_KEYDOWN 和 WM_KEYUP

消息?有没有更好的办法?

Many readers

发送 WM_KEYDOWN 和 WM_KEYUP 消息也许能行得通,但

SendInput 是专门被设计用于此目的的 API 函数。它通过 INPUT 结构数组参数来合成包括击键和鼠标事件在内的输入,每个 INPUT

结构数组元素对应一个输入事件——击键或鼠标动作。INPUT 结构含有一个联合类型,其成员为 MOUSEINPUT,KEYBDINPUT (或

HARDWAREINPUT,仿真面包烤箱)。对于击键来说其 KEYBDINPUT 结构如下:

struct KEYBDINPUT {

WORD wVk; // virt key code

WORD wScan; // hw scan code

DWORD dwFlags; // flags—see doc

DWORD time; // time stamp, 0 = dflt

ULONG_PTR dwExtraInfo; // app-defined

};

所以向另外一个应用程序发送击键其实就是建立一个 INPUT 数组,每一个数组元素对应一次击键(弹起和按下),然后调用 SendInput

函数。为了示范其实际使用方法,我编写了一个叫 Typematic

的小程序,你只要按下一个热键,便可以快速将姓名、地址、电话号码或其它信息敲入窗体中。这对于网上购物者们来说是件很理想的事情。当你第一次运行

Typematic 时,显示的对话框如 Figure 1 所示:

Figure 1 Typematic 的初始对话框

按 “OK”按钮后进入隐藏状态。其后你可以按 <WinKey+T 来重新激活

Typematic,显示如 Figure 2

所示的对话框:

Figure 2 重新被激活得 Typematic

此时可以看到对话框里显示出了一列缩写信息。敲入“n”代表姓名,“a”代表地址,Typematic

发送相应的字符串到当前窗体或应用程序。这些缩写信息定义在一个静态表中,你可以将它们改为自己的信息:

struct ABBREV {

TCHAR key;

LPCTSTR text;

} MYABBREVS[] = {

{ _T(''n''),_T("Elmer Fudd") },

{ _T(''a''),_T("1 Bunny Way") },

...

{ 0,NULL}

};

当然,在实际开发过程中,你不必硬编码这些信息,你可以提供一个用户界面来定制它,并将其保存在用户配置文件中,以便这一台机器的每一个用户都有不同的设置。

Typematic 示范了一些其它的技巧:如何注册热键来激活你的应用程序(参见 2000

第十二期的专栏)以及如何让静态文本控件接受键盘输入(你必须处理 WM_GETDLGCODE 并返回DLGC_WANTCHARS )。

Typematic 定义了一个专门的静态文本控件 CStaticAbbrev,它既可以显示缩写信息也可以读取加速键。代码如

Figure 3

所示。当用户按下热键。 Typematic 便被唤醒并将焦点定位到该 CStaticAbbrev 控件,等待字符输入。当

CStaticAbbrev::OnChar 获得一个与表中缩写之一匹配的键时,它便隐藏对话框,然后调用辅助函数 SendString 发送文本:

// in CStaticAbbrev::OnChar

if (/* find char in ABBREV table */) {

GetParent()-ShowWindow(SW_HIDE); // hide dialog

SendString(abbrev.text); // send text

}

当 Typematic 将自己隐藏后,Windows

自动将焦点恢复到之前拥有焦点的窗口,这样输入便定下向到用户按热键之前焦点所在的窗口。非常聪明,不是吗?如果你需要将输入定向到一个特定的应用程序或窗口,调用 SendInput 之前一定要确保它是活动的,为此可以调用 SetForegroundWindow 函数。

所有发送击键的工作都在 SendString 中进行,它建立 INPUT 数组并调用 SendInput (参见

Figure 3)。SendString 发送一系列 KEYDOWN/KEYUP 的 INPUT 结构对,字符串中的每一个字符对应一双这样的结构表示按下/弹起。它用 KEYEVENTF_UNICODE 标志将串作为 Unicode 字符发送。Unicode 比较容易处理,因为你不必用 Shift

键合成大写字符。如果不借助 KEYEVENTF_UNICODE 的话,你必须将大写的 E 发送成 <Shift 后跟 e,每个字符都有一次按下/弹起(down/up)事件,

共有四次击键。感谢微软的人添加了 KEYEVENTF_UNICODE。

如果你用托管 C++ 或 Microsoft .NET 中其它的语言编程,发送击键更容易。有一个框架类叫 SendKeys,其静态函数 Send

使得发送击键易如反掌。你甚至可以用花哨的语法发送专用键。例如用“{F1}{BACKSPACE}A”来发送

F1,Backspace,A。为此我还写了.NET 版本的 Typematic,起名为:Typematic.NET,它使用 SendKeys。这样

SendString 函数变成这样:

#pragma managed

void SendString(LPCTSTR str) {

SendKeys::SendWait(str);

}

还有什么比这更容易呢?当我刚开始做的时候,我很自然地用 SendKeys::Send 尝试,而不是 SendWait。为什么我要等待应用程序吃完这些键呢?唉,我尝试的时候

Typematic 惨烈地崩溃了,在 Windows.Forms.dll 的某个地方发生

System.InvalidOperationException 异常。当我启动公共语言运行时(CLR)调试器察看缘由时,Output

窗口显示出下列信息:“附加信息:由于该应用程序不处理 Windows 消息,SendKeys 无法在该应用程序中运行。要么让该应用程序处理消

息,要么使用 SendKeys.SendWait 方法。”

这就是我所称得友好的出错信息!就让它成为所有人的一个例子吧。但在使用 SendKeys 之前,要提醒你的一件事情是:它不像

SendInput 那样稳定。通过针对不同的应用如:IE、Notepad、MFC 窗体视图或其它熟悉的应用,对 Typematic 和

Typematic.NET 进行测试,你自己就能发现这个结果。处于某些原因,SendKeys

工作并不总是正常。我猜测它是焦点或定时问题——这些键被发送后马上就消失了,因为你认为具有焦点的窗口实际上没有焦点。所以虽然 SendKeys

很容易使用,同时也比 SendInput 更强大,但它在使用过程中的表现不佳,很可惜!也许微软的老大们在下一个版本中能摆平这个问题。

一个最后的警告:发送击键是一种名声狼藉的控制其它应用程序的古怪方法。你必须完全正确地获得所有键,一旦上下文或用户界面稍有改变,便可能导致问题。如果你想要控制其它应用程序,可以看看脚本系统、编程接口或宏语言。

我读了一些关于禁用系统键序列的文章,如:Ctrl+Alt+Del,包括你在

2002 年 MSDN 杂志九月刊上的专栏文章。但是我如何通过编程来发送 Ctrl+Alt+Del 呢?

William Burns

本文的第一个问题回答了如何发送击键到任何应用程序,但是你无法用

SendInput 发送 Ctrl+Alt+Del,因为这个键序列是在操作系统底层处理的。不管怎样,用合成击键的方法来“发送”Ctrl+Alt+Del

是不合适的。那用什么方法呢?如果你想启动任务管理器,用“taskmgr.exe”作为参数调用 ShellExecute。如果你想重启机器,可以用

EWX_REBOOT 标志调用 ExitWindowsEx。ExitWindowsEx 具备所有以不同方式关闭或重启机器的标志(参见

Figure 4)。此外,你的应用程序必须具备 SE_SHUTDOWN_NAME 特权才能重启机器。

有人知道 Ctrl+Alt+Del 的由来吗?如果你认为你知道,给我发个 e-mail 过来。我会在以后的专栏里公布答案?

我能从 MFC 应用程序中调用 .NET 框架吗?我想从我的非托管

MFC 代码中调用托管类,并且我想通过 #using <mscorlib.dll 来做,但我总遇到“/RTC1 incompatible with

/clr”。那么我如何才能从 MFC 应用程序中调用 .NET?

Julian Kinsey

你当然可以从你的 MFC 程序中调用 .NET!就像 Windows

中的其它机制一样,一旦你知道了正确的方法,它是很容易的。当你着手创建 MFC 程序时,应用程序向导(App

Wizard)为你设置所有的编译选项。其中之一是项目配置属性中“C/C++|代码生成”项下的“基本运行时检查”。在创建 MFC

程序时,应用程序向导在调试版本中选择“两者/RTC1,等同于/RTCsu)”以实现运行时检查,如检查堆栈帧、未初始化变量或缓冲溢出及内存不足。这些检查与

/clr 不兼容,因为托管代码完全不同(它是 Microsoft 的中间语言,非本机语言),但是当你添加 /clr ,那么 IDE 不会自动移除

/REC1。所以你必须用手动方式来做。

对于项目中的每一个 .cpp

文件,“基本运行时检查”设置为“默认”。这样做十分不幸,因为对于本地/非托管函数来说,这项检查是很好的事情。它有助于你在交付前发现程序的Bug。但对于要调用.NET类库的程序来说,这样做就不是一件好事。那么我们该怎么办呢?

问题是使用托管扩展调用 .NET 框架,你必须将“使用托管扩展”设置为“是”,它将打开/clr开关,进行这个设置的唯一地方是在项目属性页中,它是全局有效的。“使用托管扩展”为项目中所有模块打开/clr

开关。在缺省情况下,所有函数都是托管的了。如果你想默认使用本地模式,可以在 stdafx.h 文件的末尾添加一行:#pragma unmanaged

因为每个模块都要包含 stdafx.h 文件,所以所有模块都是以本地模式进行编译的。当要调用 .NET 框架时,你可以像这样跳到托管模式:

#pragma managed

void DoSomethingWithDotNET(...) {

// call framework classes here

// safe from managed functions

}

直到现在,我都是用这个技巧(将 #pragma unmanaged 放在 stdafx.h 末尾)。但它解决不了运行时检查问题,因为 /clr

仍然与 /RTC 不兼容,即便你的大多数函数是本地的。在混合模式的应用程序中,编译器不会让你在本地函数中进行运行时检查的。

Figure 5 项目设置

你真正想做的是只用 /clr 编译调用 .NET

框架的模块。但如何做呢?当在项目范围内使用“使用托管扩展”时,这一点很容易做到:在模块生成属性的“命令行”项下添加专门的开关即可。Figure 5

和 Figure 6 显示了设置方法。在全局项目设置中,将“使用托管扩展”设置为“否”,然后针对每个调用 .NET 框架的模块添加 /clr。此时其它模块仍然使用

/RTC1 并执行运行时检查。当然,你必须将<mscorlib.dll以及所有来自 stdafx.h

的框架文件移到托管模块中。如果你愿意,可以将所有标准的 .NET 包含在一个 UsesDotNet.h 文件中,就像下面这样:#using <mscorlib.dll

#using <System.dll

#include <vcclr.h

using namespace System;

Figure 6 模块设置

然后在每个调用 .NET 框架的模块中包含 #include "UsesDotNet.h" 即可。你仍然可以在某个模块中用 #pragma

managed/unmanaged 来混合托管和本地代码。

还有一个设置要做,那就是当你添加 /clr

到一个模块文件时,除了关闭运行时检查外,你同时还必须选择“不使用与编译头”。与编译头信息仅在所有模块具有相同的编译选项时才起作用;如果一个模块有 /clr

选项,那么它不能使用与其它没有 /clr

选项的模块相同的预编译头。嘿,当今计算机是如此之快,谁还需要预编译头?如果你确实想与众不同,那么就用单独的头文件如 UsesDotNet.h

来编译你的托管模块吧。

在实际应用中,如果你使用我这里所描述的方法,请参考本文附带的例子代码:Typematic.NET,其中所有模块都是以常规的本地方式编译的,通过 stdafx.h

使用预编译头,并且不使用“托管扩展”选项。唯一调用 .NET 框架的函数位于一个单独的模块文件中:SendString.cpp,只有它是用 /clr

开关编译的,不进行运行时检查,也不使用预编译头。

祝编程愉快!

向 Paul 提问和评论请发到 cppqa@microsoft.com.

作者简介

Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in

C++》书(Addison-Wesley, 1992)的作者。通过 http://www.dilascia.com 可以获得更多了解。

本文出自 MSDN Magazine

January 2005 期刊,可通过当地报摊获得,或者最好是

订阅

本文由 VCKBASE MTT 翻译

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有