⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 17.1.1 数据发送.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 TXT
字号:
17.1.1 数据发送
在把数据放置到剪贴板之前,首先需要打开剪贴板,这可以利用 CWnd类的 OpenClipBoard成员函数
实现,该函数的原型声明如下所示。 
BOOL OpenClipboard( ); 
OpenClipboard函数的返回值是 BOOL类型。如果打开剪贴板操作成功,则该函数返回非 0值:如果其
他程序,或者当前窗口己经打开了剪贴板,则该函数返回 0值。如果某个程序己经打开了剪贴板,
则其他应用程序将不能修改剪贴板,直到前者调用了 CloseClipboard函数。并且只有调用了 
EmptyClipboard函数之后,打开剪贴板的当前窗口才拥有剪贴板。 EmptyClipboard函数将清空剪贴
板,并释放剪贴板中数据的句柄,然后将剪贴板的所有权分配给当前打开剪贴板的窗口。因为剪贴
板是所有进程都可以访问的,所以在我们编写的这个 Clipboard进程使用剪贴板之前,可能己经有
其他进程把数据放置到剪贴板上了,那么在该进程打开剪贴板之后,需要调用 EmptyClipboard函数,
清空剪贴板,释放剪贴板上数据的句柄,并将剪贴板的所有权分配给当前打开剪贴板的窗口,之后
就可以向剪贴板中放置数据了。向剪贴板中放置数据,可以通过调用 SetClipboardD ata函数实现。
这个函数是以指定的剪贴板格式向剪贴板上放置数据,该函数的原型声明如下所示: 
HANDLE SetClipboardData(UINT uFormat , HANDLE hMem ); 
需要注意的是,当前调用 SetClipboardData函数的窗口必须是剪贴板的拥有者,而且
在这之前,该程序必须已经调用了 OpenClipboard函数打开剪贴板。在随后响应 WM—_RENDEFORMAT
和WM二.RENDERALLFORMATS消息时,当前剪贴板的拥有者在调用SetClipboardData函数之前就不必再
调用句enClipboard函数了。 SetClipboardData函数有两个参数,其含义分别如下所述。 
. uFolIuat 
指定剪贴板格式,这个格式可以是已注册的格式,或者是任一种标准的剪贴板格式(读者可自行查看
MSDN中提供的帮助信息)。本程序中只是利用剪贴板作为进程间通信的一种方式,因此选择标准的剪
贴板格式,并且因为将传输文本数据,所以选择CF_TEXT格式,该格式表示文本格式,在这种格式下,
每行数据以 "0x0A0x0D"(回车换行)这一组合字符终止,并以空字符作为数据的结尾。 
.hMem 
具有指定格式的数据的句柄。该参数可以是NULL,指示调用窗口直到有对剪贴板数. 据的请求时,
才提供指定剪贴板格式的数据。如果窗口采用延迟提交技术,则该窗口必须处理WM_RENDERFORMAT
和WM_RENDERALLFORMATS消息。
当一个提供数据的进程创建了剪贴板数据之后,直到其他进程获取剪贴板数据之前,
这些数据都要占据内存空间。如果在剪贴板上放置的数据过大,就会浪费内存空间,降低对资源的
利用率。为了避免这种浪费,就可以采取延迟提交技术,也就是由数据提供进程先提供一个指定格
式的空剪贴板数据块,即把 SetClipboardD咽U函数的 hMem参数设置为 NULL。当需要获取数据的进
程想要从剪贴板上得到数据时,操作系统会向数据提供进程发送WM_RENDEFORMAT消息,而数据提
供进程可以响应这个消息,并在此消息的响应函数中,再一次调用 SetClipboardData函数,将实际
的数据放到剪贴板上。当再次调用 SetClipboardData函数时,就不再需要调用OpenClipboard函数,
也不再需要调用 EmptyClipboard函数。
也就是说,为了提高资源利用率,避免浪费内存空间,可以采取延迟提交技术。第一次调用 SetClipbo
arData函数时,将其hMem参数设置为NULL,在剪贴板上以指定的剪贴板格式放置一个空剪贴板数据
块。然后直到有其他进程需要数据或者自身进程需要终止运行时再次调用 SetClipboardData函数,
这时才真正提交数据。
应用程序在调用SetClipboardData函数之后,系统就拥有了hMem参数所标识的数据对象。该应用程
序可以读取这个数据对象,但是在应用程序调用CloseClipboard函数之前,它不能释放该对象的句
柄,或者锁定这个句辆。如果hMem参数标识了一个内存对象,那么这个对象必须是利用GMEM_MOVEABLE
标志调用GlobalAlloc函数为其分配内存的。 
GlobalAlloc函数是从堆上分配指定数目的字节, Win32内存管理没有提供一个单独的本地堆和全局
堆。也就是说,在 Win32平台下,已经没有本地堆和全局堆了,在以前的 Win16平台下有本地堆和
全局堆。因为与其他内存管理函数相比,全局内存函数的运行速度要稍稍慢些,而且它们没有提供
更多的特性,所以新的应用程序应该使用堆函数。然而全局函数仍然与动态数据交换,以及剪贴板
函数一起使用。本程序是利用剪贴板在进程间进行通信,因此还是需要使用GlobalAlloc这个函数。
该函数的原型声明如下所示= 
HGLOBAL GlobalAlloc( UINT uFlags , SIZE_T dwBytes); 
GlobalAlloc函数有两个参数,其中 dwBytes指定分配的字节数, uFlags是一个标记,用来指定分
配内存的方式,该参数可以取表17.1中列出的一个或多个值,但是应注意,这些值中的有些值是不
能一起使用的。如果 uFlags参数值是 0,则该标记就是默认的 GMEM FIXED。
表17.1 uFlags参数取值

值 说明  
GHND  GMEM_MOVEABLE和GMEM_ZERO的IT的组合  
GMEM FIXED  分配一块固定内存,返回值是一个指针 
GMEM MOVEABLE  分配一块可移动的内存,在 Win32平台下,内存块在物理内存中从来不被移动,但
可在一个默认堆中被移动。创建一个进程时,系统为应用程序分配一块默认堆。返回值是一块内存
对 象句柄,如果想将这个句柄转换为一个指针,可以使用 GlobalLock函数。这个标志不能与 GMEM_F
反ED标志一起使用。  
GME如LZEROINIT 初始化内存的内容为0  
GPTR  G!'.伍M_FDαD和G!'.伍M_ZEROINIT的组合 

如表 17.1所示中提到的GlobalLock函数的作用是对全局内存对象加锁,然后返回该对象内存块第一
个字节的指针。该函数的原型声明如下所示: 
LPVOID GlobalLock( HGLOBAL hMern) ; 
GlobalLock函数的参数是一个全局内存对象句柄 (HGLOBAL类型),返回值是一个指针。每个内存对
象的内部数据结构中都包含了一个初始值为O的锁计数,对于可移动的内存对象来说, GlobalLock
函数将其锁计数加1.而GlobalUnlock函数将该锁计数减10对于一个进程来说,每一次调用 
GlobalLock函数后,最后一定要记住调用 GlobalUnlock函数。被锁定的内存对象的内存块将保持锁
定,直到它的锁计数为 0,这时,该内存块才能被移动,或者被废弃。另外,已被加锁的内存不能
被移动,或者被废弃,除非调用了 GlobalReal1oc函数重新分配了该内存对象。
使用GMEM FIXED标志分配的内存对象其锁计数总是0。对于这些对象, GlobalLock 函数返回的指针
值等于指定的句柄值。 GMEM_FIXED与GMEM_MOVEABLE这两个标志的区别是:如果指定的是前者,那么 
GlobalAlloc函数返回的句柄值就是分配的内存地址:如果指定的是后者,那么 GlobalAlloc函数返
回的不是实际内存的地址,而是指向该进程中句柄表条目的指针,在该条目中包含有实际分配的内
存指针。
很多函数都使用HGLOBAL类型作为返回值或参数来代替内存地址,如果这样的一个函数返回了一个 
HGLOBAL类型的值,那么我们就应该假定它的内存是采用 GMEM MOVEABLE标志来分配的,这也就意味
着必须调用 GlobalLock函数对该全局内存对象加锁,并且返回该内存的地址。如果一个函数采用
HGLOBAL类型的参数,为了保证安全,我们就应该用GMEM MOVEABLE标志调用GlobalAl1oc函数来生成
这个参数值。在这个剪贴板程序中,需要采用GMEM_MOVEABLE标志来分配内存。
下面就编写向剪贴板发送数据的代码。双击 Clipboard程序中的主界面对话框资源上
的【发送】按钮,VC++开发环境将为我们自动创建该按钮的单击命令响应函数:OnBtnSend, 然后在
此函数中添加代码以实现向剪贴板发送数据的功能,结果如例17-1所示。例17-1 
void CClipboardDlg::OnBtnSend{) 
// TODO: Add your control notification handler code here 
if(OpenClipboard () ) / /打开剪贴板 
{ 

CString str; //保存发送编辑框控件上的数据 
HANDLE hClip; //保存调用 GlobalAlloc函数后分配的内存对象的句柄 
char *pBuf; //保存调用 GlobalLock函数后返回的内存地址 
EmptyClipboard(); //清空剪贴板上的数据 
GetDlgItemText(IDC_EDIT_SEND, str); 
hClip=GlobalAlloc (GMEM_MOVEABLE , str.GetLength() +1) ; 
pBuf=(char*)GlobalLock(hClip); 
strcpy(pBuf , str); 
GlobalUnlock(hClip); 
SetClipboardData(CF_TEXT, hClip); 
CloseClipboard ( ) ; / /关闭剪贴板

在上述例17-1所示OnBtnSend函数中,首先调用OpenClipboard打开剪贴板,如果成功打开,则调用
EmptyClipboard函数清空剪贴板,释放剪贴板上数据的句柄,并将剪贴板的所有权分配给当前窗口。
接着,调用GetDlgItemText函数获得发送编辑框中的数据,并保存到str变量中。
这时,就可以采用GMEM_MOVEABLE标志调用GlobalAlloc函数来分配内存对象了。该函数的第二个参
数用来指定分配的字节数,可以利用 CString类提供的 GetLength成员方法得到将要发送的数据的
长度。因为如果设定的是文本数据,那么在剪贴板中,该数据是以空字符作为结尾的。这样的话,
如果在分配时按照数据实际大小分配内存空间,那么当把该数据放置到剪贴板上以后,剪贴板会在
该数据的最后一个字节中放置一个空字符,这样就会丢失一个数据,因此这里在分配内存时要多分
配一个字节。
接下来,需要把GlobalAlloc函数返回的句柄转换为指针,这可以通过调用GlobalLock函数,对内存
对象加锁,并返回它的内存地址。因为GlobalLock函数返回的类型是LPVOID,而这里需要的是char*
类型,所以需要进行强制转换。
之后,可以调用strcpy函数将由对象中的数据复制到pBuf指向的内存中,然后可以调用GlobalUnlock
函数对该内存块解锁。解锁完成之后,就可以调用SetClipboardData函数以指定的剪贴板格式向剪
贴板上放置数据了,该函数第一个参数指定使用文本格式 CF_TEXT),第二个参数就是包含了将要
放置的数据的内存的句柄 ChClip)。
最后,读者一定要记住,在把数据放置到剪贴板之后,一定要记得调用。CloseClipboard
,否则其他进程将无法打开剪贴板。 

Build并运行Clipboard程序,在左边发送编辑框中任意输入一些数据,例如:"Hello", 
之后单击【发送】按钮。然后,打开记事本程序,选择【编辑\粘贴】菜单命令,即可以看到记事本
程序接收到了 "Hello"这串字符。结果如图 17.2所示。这就说明我们编写的 Clipboard程序与系统
提供的记事本程序之间通过剪贴板完成了数据的传输。
图 17.2 Clipboard程序与记事本程序之间实现的数据传输 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -