Windows核心编程_剪辑版操作
前言:
Windows提供了一组对剪辑版读写操作的API,不过在此之前需要了解剪辑版的特性:
剪辑版是Windows下的一块内存区域,这块内存区域可以用来临时存储数据,但是这个内存块是共享的,它是独一的,并且当某个进程对此剪辑版进行操作时会,那么这个剪辑版会处于占用状态,也就相当于线程中的加锁同步机制一样,被占用时其它进程无法对此空间读写,所以当进程对剪辑版处理完之后应当立马关闭对剪辑版的占用权!
- 对剪辑版写:
首先介绍一组API:
OpenClipboard(),EmptyClipboard(),SetClipboardData(),CloseClipboard();
1.1 API介绍:
OpenClipboard函数原型:
BOOL OpenClipboard(
__in HWND hWndNewOwner
);
参数介绍:
hWndNewOwne Long类型,与打开剪辑版相关联的窗口句柄。如果这个参数为NULL,打开剪贴板与当前任务相关联。
返回值
如果函数执行成功,返回非零值.
如果函数执行失败,返回零,为了获得更多的错误信息,调用GetLastError.
注:
如果另一个窗口已经打开的剪贴板,OpenClipboard函数会失败.每次成功调用OpenClipboard后都应有一次CloseClipboard调用.
由hWndNewOwner参数确定的窗口不会成为剪贴板所有者,除非函数被EmptyClipboard调用。
函数介绍:
用于打开剪辑版
2.EmptyClipboard函数原型:
BOOL EmptyClipboard(
VOID
);
返回值:
如果函数成功,返回非零值,否则,返回零值。
函数介绍:
该函数清空剪辑版并释放剪切板内数据的句柄。函数在之后会将剪辑版的所有权指派给当前打开剪切板的窗口。
3.SetClipboardData函数原型:
HANDLE SetClipboardData(UINT uFormat,HANDLE hMem);
参数介绍:
uFormat
用来指定要放到剪切板中的数据的格式.
uFormat 可以定义的格式如下:
CF_DIB ——DIB图片,它包含一个BITMAPINFO结构,然后是位图位。
CF_DIF ——软件领域的数据交换格式。
CF_PALETTE ——调色板。每当应用程序放置数据在剪贴板依赖于或承担的调色板,它应该在剪贴板上放置的调色板。
如果剪贴板中包含在CF_PALETTE(逻辑调色板)格式的数据,应用应该使用SelectPalette和RealizePalette的函数来实现(比较)针对该逻辑调色板剪贴板中的任何其他数据。
剪贴板显示剪贴板中的数据时,总是使用当前调色板的剪贴板是在CF_PALETTE格式的任何对象。
CF_PENDATA ——笔扩展的Microsoft圀椀渀搀漀眀猀笔计算的数据。
CF_RIFF ——表示更复杂的音频数据可以被表示为一个CF_WAVE的标准波形格式。
CF_SYLK ——微软符号链接(SYLK)格式。
CF_TEXT ——ANSI文本格式。回车/换行(CR-LF)组合表示换行。必须用NULL结束字符串。使用此格式为ANSI文本。
CF_WAVE ——表示在的标准电波格式之一,例如11 kHz或22kHz的脉冲编码调制(PCM)的音频数据。
CF_TIFF ——TIFF标记图像文件格式。
CF_UNICODETEXT ——Unicode文本格式。回车/换行(CR-LF)组合表示换行。必须用NULL结束字符串。这是Unicode格式的字符串。
hMem
指定具有指定格式的数据的句柄,该参数可以是空.可以使用此参数达到延迟效果后面说明
函数介绍:用于设置剪辑版内容
4.CloseClipboard函数原型:
BOOL CloseClipboard(VOID);
返回值:
如果函数执行成功,返回非零值.
返回0时可以使用GetLastError函数获取错误信息
函数介绍完毕了,那么写一个示列将一串字符写入到剪辑版中:
OpenClipboard(NULL); //控制权归当前调用者所拥有
EmptyClipboard(); //清空剪辑版内容
SetClipboardData(CF_TEXT,"DD"); //写入数据
CloseClipboard(); //关闭剪辑版
运行时会发现出现中断错误:
原因是SetClipboardData使用CF_TEXT置文字时,必须是全局内存,因为剪辑内存也是一块内存,在windows下两个不同的内存区域是无法相互交互数据的,剪辑内存所以我们要使用GlobalAlloc来分配一块全局堆内存,来做操作!
完整代码:
OpenClipboard(NULL); //控制权归当前调用者所拥有
EmptyClipboard(); //清空剪辑版内容
TCHAR A[256] = "HEI\n";
HGLOBAL hemn = GlobalAlloc(GMEM_MOVEABLE, ((strlen(A) + 1))); //分配内存
if (hemn == NULL){
return -1;
}
LPSTR lpstr = (LPSTR)GlobalLock(hemn); //锁住内存
memcpy(lpstr, A, ((strlen(A)))); //copy
lpstr[strlen(A)] = (TCHAR)0; //结束符
GlobalUnlock(hemn); //释放锁
SetClipboardData(CF_TEXT,hemn); //写入数据
CloseClipboard(); //关闭剪辑版
这里有几行代码需要解释一下:
HGLOBAL hemn = GlobalAlloc(GMEM_MOVEABLE, ((strlen(A) + 1)));
和
lpstr[strlen(A)] = (TCHAR)0; //结束符
上面对SetClipboardData函数的CF_TEXT属性有说过,CF_TEXT结尾必须是0
而我们分配的内存大小是(strlen(A) + 1),而操作时却是lpstr[strlen(A)]
原因非常简单,数组下标是以0开始计算的(以堆栈偏移量+开始0代表首地址),我上面多分配了一个字节的内存空间:((strlen(A) + 1))
只是为了在操作数组时以(strlen(A) 为单位,其实也可以这样:
HGLOBAL hemn = GlobalAlloc(GMEM_MOVEABLE, ((strlen(A))));
lpstr[strlen(A)-1] = (TCHAR)0; //结束符 ,0开始计算下标
GlobalAlloc分配内存不是以0为单位而是1
假如GlobalAlloc分配了10个字节,并返回了首地址:0x000;
如果以下标1为单位,那么首地址就是偏移量+数据单位
假如数据单位size是4字节,那么以数组下标1为单位首地址就是004,显然丢了一个子分页表(内存块),这样做的方法显然不对!
但是,倘若分配的内存大小是(strlen(A) ,而操作的时候是lpstr[strlen(A)],显然越界了,但是vs下c++编译器对这种越界并不会报错,运行时我们越的是我们当前进程下的内存空间,操作系统也不会查杀掉,但是我们更改了其它内存块上的内存!
但是SetClipboardData这行就会报错
原因:
末尾必须是0,但是我们的0复制到其它内存空间里去了,所以很明显不对!
运行测试可以在运行后随便在一个可输入控件里copy一下看看,文字有没有输入进去!
更改一下列子,将位图写入到剪辑板中:
HBITMAP BMP = (HBITMAP)LoadImage(NULL, "C:\\Users\\ZZH\\Desktop\\1.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);//加载位图资源到内存,并得到位图资源句柄
OpenClipboard(NULL); //控制权归当前调用者所拥有
EmptyClipboard(); //清空剪辑版内容
SetClipboardData(CF_BITMAP, BMP); //写入数据
CloseClipboard(); //关闭剪辑版
这里就无需使用GlobalAlloc函数了,因为LoadImage加载的资源位图所分配的内存块是共享的!
读取剪辑板内容:
需要API:GetClipboardData
函数原型:
WINUSERAPI
HANDLE
WINAPI
GetClipboardData(
__in UINT uFormat);
uFormat是剪贴板的格式。
返回值:
内存首地址
获取字符串
// 从剪切板获取数据
OpenClipboard(NULL);
HGLOBAL hMem = GetClipboardData(CF_TEXT);
if (hMem)
{
// 获取字符串
LPSTR lpStr = (LPSTR)GlobalLock(hMem);
if (lpStr)
{
printf(lpStr);
// 释放内存锁
GlobalUnlock(hMem);
}
}
// 关闭剪切板
CloseClipboard();
获取位图:
// 从剪切板获取数据
OpenClipboard(NULL);
HGLOBAL hMem = GetClipboardData(CF_BITMAP);
if (hMem)
{
// 获取位图句柄
HBITMAP BMP = (HBITMAP)GlobalLock(hMem);
if (BMP)
{
// 释放内存锁
GlobalUnlock(hMem);
}
}
// 关闭剪切板
CloseClipboard();
剪辑板内存是在共享堆中的,GetClipboardData函数会返回剪辑板的共享内存首地址,所以我们一定要加锁,不然其它程序使用OpenClipboard,EmptyClipboard函数将其删除掉的话,即便BMP句柄有效读取出来的数据也是0,因为BMP句柄指向GlobalLock返回的内存首地址
还有其次不能这样写:
if (::OpenClipboard(NULL))
{
HBITMAP h = (HBITMAP)GetClipboardData(CF_BITMAP); //这一句返回NULL
int e=GetLastError(); //GetLastError()返回0
}
GetClipboardData返回的是剪辑板共享内存句柄,我们需要通过GlobalLock等函数才能对其进行操作,句柄并不是实际的首地址,只是一个映射关系,映射地址不能直接拿来用,需要间接转换!
多种数据处理:
剪辑板内部有一个数据结构维护的,具有不同类型的数据信息都会在此结构体下被维护起来,也就是说我们写入图片,在写入文字,之前的图片不会被替换掉而是被维护起来了,但是最后写入的文字会被视为活动窗口,默认调用copy快捷键会默认读取活动窗口里的数据!
写入
OpenClipboard(hwnd);
EmptyClipboard();
//下面设置多种数据项,但这3种数据项必须不同,且在Empty和CloseClipboard间设置。
//将文本字符串写到位图或图元文件中,这样字符串即可被文读文本的程序访问。也可
//被读位图的程序访问,但这些程序没办法轻易判断位图中其实是字符串。
SetClipboardData(CF_TEXT,hGlobalText); //每次,每个数据项只能是一种格式。
SetClipboardData(CF_BITMAP,hBitmap); //不能给字符串
CloseClipboard();
读取:
hGlobalText= GetClipboardData(CF_TEXT);
hBitmap = GetClipboardData(CF_BITMAP);
延迟提交数据:
这里延迟提交需要有必要的详细解释一下:
第一当我们使用OpenClipboard打开剪辑板时,假如第一个参数是hwnd,那么剪辑板归窗口所有,然后我们调用了:CloseClipboard函数,并不是关禁闭对剪辑板的拥有,而是关闭占用了,此时我们依然是剪辑板的拥有者,那么此时其它窗口调用OpenClipboard函数时,拥有权依旧是我们,但是Windows会保存OpenClipboard的hwnd但不会立即生效,除非我们调用了:EmptyClipboard函数,那么Windows将剪贴板拥有者设为hwnd,并向旧拥有者发送一条WM_DESTROYCLIPBOARD消息以告知旧拥有者,剪贴板的内容己被我清空,而且我也正式接管理剪贴板的拥有权,而你失去拥有权了!
如何达到延迟提交呢?
延迟提交就是假如A程序是剪辑板的拥有者,在写入数据时SetClipboardData第二个参数给定了NULL:
SetClipboardData(CF_TEXT,NULL);
那么维护剪辑板的对应CF_TEXT的链表里用于指向共享内存数据的句柄就是NULL
然后当我们在B程序下调用GetClipboardData来读取数据,而GetClipboardData会检查读取的数据类型,也就是SetClipboardData第一个参数,在剪辑板数据链表找到对应的维护的子链表并把数据值的共享内存块句柄返回,在此之前会检查共享内存块句柄是否为NULL如果是NULL,则向此剪辑板的拥有者发送一个消息:WM_RENDERFORMAT消息,并且wParam参数是数据格式!
那么此时拥有者窗口消息里必须处理此消息:
case WM_RENDERFORMAT: //wParam:要请求传送的数据格式
[根据wParam的数据格式];
SetClipboardData((UINT)wParam,hGlobal/*提交数据*/);
return 0;
并返回,GetClipboardData一方会阻塞等着拥有者处理返回,当拥有者返回时,GetClipboardData会将数据的共享内存句柄返回,并且还会检查是否为NULL!
注意上面千万不能在调用OpenClipboard函数打开,因为在WM_RENDERFORMAT消息里,B进程已经打开了,此时被占用了,二次调用只会失败!
并且B进程不能调用EmptyClipboard函数,因为WM_RENDERFORMAT消息只会发送给拥有者!
如果当拥有者将要被销毁的时候,并且SetClipboardData第二个参数是NULL,那么会收到WM_RENDERALLFORMATS消息,所以要将剪辑板内容全部清空!
内部可以调用CloseClipboard将剪辑板关闭!
示列:
拥有者:
OpenClipboard(hwnd); //Windows先把新的窗口句柄(hwnd)保存到剪辑板维护链表中下来,但并不立即变
//更剪贴者拥有者,要调用EmptyClipboard后才变更。
EmptyClipboard(); //调用该函数时,Windows将剪贴板拥有者设为hwnd,并向旧拥有者发送一条WM_DESTROYCLIPBOARD消息
//以告知旧拥有者,剪贴板的内容己被我清空,而且我也正式接管理剪贴板的拥有权,而你失去拥有权了
SetClipboardData(CF_TEXT,NULL); //设为NULL,以延迟提交数据句柄。会一直等到有程序对剪贴板中的数据进行请求时,该程序(也就是剪贴板的拥有者)才会按指定数据格式将数据复制到剪贴板中,这就是“延迟提交
//技术”。
CloseClipboard(); //关闭剪辑板的占用
处理消息:
case WM_RENDERFORMAT: //wParam:要请求传送的数据格式
[根据wParam的数据格式];
SetClipboardData((UINT)wParam,hGlobal/*提交数据*/);
break;
case WM_RENDERALLFORMATS:
OpenClipboard (hwnd) ;
EmptyClipboard () ;
CloseClipboard () ;
break;
B进程:
OpenClipboard(NULL);
// 从剪切板获取数据
HGLOBAL hMem = GetClipboardData(CF_TEXT);
if (hMem)
{
// 获取字符串
LPSTR lpStr = (LPSTR)GlobalLock(hMem);
if (lpStr)
{
printf(lpStr);
// 释放内存锁
GlobalUnlock(hMem);
}
}
// 关闭剪切板
CloseClipboard();
读取剪辑版图像数据:
// 从剪切板获取数据
HGLOBAL hMem = GetClipboardData(CF_BITMAP);
if (hMem)
{
// 获取字符串
LPSTR lpStr = (LPSTR)GlobalLock(hMem);
if (lpStr)
{
printf(lpStr);
// 释放内存锁
GlobalUnlock(hMem);
}
}
// 关闭剪切板
CloseClipboard();
OpenClipboard会将拥有者设置成当前窗口或任务,并且还会打开剪辑板,在其它进程可以调用CloseClipboard关闭剪辑板,但是拥有者如果调用了EmptyClipboard,那么它就是剪辑板的拥有者,即便剪辑板的状态被关闭了它也还是拥有者!
延迟提交的两个消息:
WM_RENDERFORMAT:其它进程想要读取剪辑板内容,和SetClipboardData有关
比如SetClipboardData第一个值是CF_TEXT、第二值是NULL,那么当读取CF_TEXT类型剪辑板数据时才会触发此消息!
WM_RENDERALLFORMATS:如果当拥有者将要被销毁的时候,并且SetClipboardData第二个参数是NULL,那么会收到WM_RENDERALLFORMATS消息,所以要将剪辑板内容全部清空!
OpenClipboard (hwnd) ;
EmptyClipboard () ;
CloseClipboard () ;
WM_DESTROYCLIPBOARD:剪辑板拥有者发生变更时也就是调用了EmptyClipboard函数,向旧的剪辑板发送的消息,一般无需处理!