windows剪贴板

Posted 刘收获

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了windows剪贴板相关的知识,希望对你有一定的参考价值。

0x01  Windows剪贴板
  

  Windows剪贴板是一种比较简单同时也是开销比较小的IPC(InterProcess Communication,进程间通讯)机制。Windows系统支持剪贴板IPC的基本机制是由系统预留的一块全局共享内存,用来暂存在各进程间进行交换的数据:提供数据的进程创建一个全局内存块,并将要传送的数据移到或复制到该内存块;接受数据的进程(也可以是提供数据的进程本身)获取此内存块的句柄,并完成对该内存块数据的读取。

  为使剪贴板的这种IPC机制更加完善和便于使用,需要解决好如下三个问题:提供数据的进程在结束时Windows系统将删除其创建的全局内存块,而接受数据的进程则希望在其退出后剪贴板中的数据仍然存在,可以继续为其他进程所获取;能方便地管理和传送剪贴板数据句柄;能方便设置和确定剪贴板数据格式。为完善上述功能,Windows提供了存在于USER32.dll中的一组API函数、消息和预定义数据格式等,并通过对这些函数、消息的使用来管理在进程间进行的剪贴板数据交换。

  Windows系统为剪贴板提供了一组API函数和多种消息,基本可以满足编程的需要。而且Windows还为剪贴板预定义了多种数据格式。通过这些预定义的格式,可以使接收方正确再现数据提供方放置于剪贴板中的数据内容。

 

 

 

0x02  多数据项和延迟提交技术

  要把数据放入剪贴板,在打开剪贴板后一定要调用EmptyClipboard()函数清除当前剪贴板中的内容,而不可以在原有数据项基础上追加新的数据项。但是,可以在EmptyClipboard()和CloseClipboard()调用之间多次调用SetClipboardData()函数来放置多个不同格式的数据项。例如:

OpenClipboard(hWnd);
EmptyClipboardData();
SetClipboardData(CF_TEXT, hGMemText);
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();


  这时如果用CF_TEXT或CF_BITMAP等格式标记去调用IsClipboardFormatAvailable()都将返回TRUE,表明这几种格式的数据同时存在于剪贴板中。以不同的格式标记去调用GetClipboardData()函数可以得到相应的数据句柄。

  对于多数据项的剪贴板数据,还可以用CountClipboardFormats()和EnumClipboardFormats()函数得到当前剪贴板中存在的数据格式数目和具体的数据格式。EnumClipboardFormats()的函数原型为:

UINT EnumClipboardFormats(UINT format);


  参数format指定了剪贴板的数据格式。如果成功执行将返回format指定的格式的下一个数据格式值,如果format为最后的数据格式值,那么将返回0。由此不难写出处理剪贴板中所有格式数据项的程序段代码:

UINT format = 0; // 从第一种格式值开始枚举
OpenClipboard(hWnd);
while(format = EnumClipboardFormats(format))
{
…… // 对相关格式数据的处理
}
CloseClipboard();


  在数据提供进程创建了剪贴板数据后,一直到有其他进程获取剪贴板数据前,这些数据都要占据内存空间。如在剪贴板放置的数据量过大,就会浪费内存空间,降低对资源的利用率。为避免这种浪费,可以采取延迟提交(Delayed rendering)技术,即由数据提供进程先创建一个指定数据格式的空(NULL)剪贴板数据块,直到有其他进程需要数据或自身进程要终止运行时才真正提交数据。

  延迟提交的实现并不复杂,只需剪贴板拥有者进程在调用SetClipboardData()将数据句柄参数设置为NULL即可。延迟提交的拥有者进程需要做的主要工作是对WM_RENDERFORMAT、WM_DESTORYCLIPBOARD和WM_RENDERALLFORMATS等剪贴板延迟提交消息的处理。

  1.当另一个进程调用GetClipboardData()函数时,系统将会向延迟提交数据的剪贴板拥有者进程发送WM_RENDERFORMAT消息。剪贴板拥有者进程在此消息的响应函数中应使用相应的格式和实际的数据句柄来调用SetClipboardData()函数,但不必再调用OpenClipboard()和EmptyClipboard()去打开和清空剪贴板了。在设置完数据有也无须调用CloseClipboard()关闭剪贴板。

  2.如果其他进程打开了剪贴板并且调用EmptyClipboard()函数去清空剪贴板的内容,接管剪贴板的拥有权时,系统将向延迟提交的剪贴板拥有者进程发送WM_DESTROYCLIPBOARD消息,以通知该进程对剪贴板拥有权的丧失。而失去剪贴板拥有权的进程在收到该消息后则不会再向剪贴板提交数据。另外,在延迟提交进程在提交完所有要提交的数据后也会收到此消息

  3.如果延迟提交剪贴板拥有者进程将要终止,系统将会为其发送一条WM_RENDERALLFORMATS消息,通知其打开并清除剪贴板内容,再调用SetClipboardData()设置各数据句柄后关闭剪贴板。

  下面这段代码将完成对数据的延迟提交,WM_RENDERFORMAT消息响应函数OnRenderFormat()并不会立即执行,当有进程调用GetClipboardData()函数从剪贴板读取数据时才会发出该消息。在消息处理函数中完成对数据的提交:

  进行延迟提交:

HWND hWnd = GetSafeHwnd(); // 获取安全窗口句柄
::OpenClipboard(hWnd); // 打开剪贴板
::EmptyClipboard(); // 清空剪贴板
::SetClipboardData(CF_TEXT, NULL); // 进行剪贴板数据的延迟提交
::CloseClipboard(); // 关闭剪贴板


  在WM_RENDERFORMAT消息的响应函数中:

 

 

DWORD dwLength = 100; // 要复制的字串长度
HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配内存块
LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 锁定内存块
for (int i = 0; i < dwLength; i++) // 将"*"复制到全局内存块
*lpGlobalMemory++ = \'*\';
GlobalUnlock(hGlobalMemory); // 锁定内存块解锁
::SetClipboardData(CF_TEXT, hGlobalMemory); // 将内存中的数据放置到剪贴板

 

0x03  API

 

  剪贴板的打开 – OpenClipboard

  要想把数据放置到剪贴板中,则必须先打开剪贴板,而这是通过 OpenClipboard 成员函数实现:

  BOOL  OpenClipboard(

    HWND  hWndNewOwner );  //指向一个与之关联的窗口句柄,即代表是这个窗口打开剪贴,如果这个参数设置为 NULL 的话,则以当前的任务或者说是进程来打开剪贴板。

  如果打开剪贴板成功,则该函数返回非 0 值,如果其他程序已经打开了剪贴板,那么当前这个程序就无法再打开剪贴板了,所以会致使打开剪贴板失败,从而该函数返回 0 值。

                            

  剪贴板的清空 - EmptyClipboard

  这个函数将清空剪贴板,并释放剪贴板中数据的句柄,然后将剪贴板的所有权分配给当前打开剪贴板的窗口,这样做之后当前打开这个剪贴板的程序就拥有了剪贴板的所有权,因此这个程序就可以往剪贴板上放置数据了。

  BOOL EmptyClipboard(void);

        

                      

  剪贴板的关闭 - CloseClipboard

  如果某个进程打开了剪贴板,则在这个进程没有调用 CloseClipboard 函数关闭剪贴板句柄之前,其他进程都是无法打开剪贴板的,所以我们每次使用完剪贴板之后都应该关闭剪贴板。注意,这里的关闭剪贴板并不代表当前打开剪贴板的这个程序失去了对剪贴板的所有权,只有在别的程序调用了 EmptyClipboard 函数之后,当前的这个程序才会失去对剪贴板的所有权,

  而那个调用 EmptyClipboard 函数的程序才能拥有剪贴板。

  BOOL CloseClipboard(void);

                           

  数据发送到剪贴板 - SetClipboardData

  可以通过 SetClipboardData 函数来实现往剪贴板中放置数据,这个函数以指定的剪贴板格式向剪贴板中放置数据。

  HANDLE  SetClipboardData(
      UINT uFormat, //要放到剪贴板上的数据的格式,常见的有 CF_BITMAP ,CF_TEXT ,CF_DIB 等等(其他格式可以参考 MSDN)。
     HANDLE hMem ); //指定格式的数据的句柄,该参数可以是 NULL ,如果该参数为 NULL 则表明直到有程序对剪贴板中的数据进行请求时,该程序(也就是拥有剪贴板所有权的进程)才会将数据复制到剪贴板中,也就是提供指定剪贴板格式的数据,

 

              

  剪贴板中数据格式判断 – IsClipboardFormatAvaliable

 BOOL  IsClipboardFormatAvailable( UINT format ); 

  该函数用来判断剪贴板上的数据格式是否为 format 指定的格式。

            

 

  剪贴板中数据接收 - GetClipboardData

 HANDLE  GetClipboardData( UINT uFormat ); 

  该函数根据 uFormat 指定的格式,返回一个以指定格式存在于剪贴板中的剪贴板对象的句柄。

 

 

0x04  源代码  

          自己用MFC实现的一个剪贴板程序,如图:

    

    

 

 

源代码:

  1 // ClipBoardDlg.cpp : 实现文件
  2 //
  3 
  4 #include "stdafx.h"
  5 #include "ClipBoard.h"
  6 #include "ClipBoardDlg.h"
  7 #include "afxdialogex.h"
  8 
  9 #ifdef _DEBUG
 10 #define new DEBUG_NEW
 11 #endif
 12 
 13 
 14 // 用于应用程序“关于”菜单项的 CAboutDlg 对话框
 15 VOID ShowErrorMessage(DWORD ErrorCode);
 16 class CAboutDlg : public CDialogEx
 17 {
 18 public:
 19     CAboutDlg();
 20 
 21 // 对话框数据
 22 #ifdef AFX_DESIGN_TIME
 23     enum { IDD = IDD_ABOUTBOX };
 24 #endif
 25 
 26     protected:
 27     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
 28 
 29 // 实现
 30 protected:
 31     DECLARE_MESSAGE_MAP()
 32 };
 33 
 34 CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
 35 {
 36 }
 37 
 38 void CAboutDlg::DoDataExchange(CDataExchange* pDX)
 39 {
 40     CDialogEx::DoDataExchange(pDX);
 41 }
 42 
 43 BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
 44 END_MESSAGE_MAP()
 45 
 46 
 47 // CClipBoardDlg 对话框
 48 
 49 
 50 
 51 CClipBoardDlg::CClipBoardDlg(CWnd* pParent /*=NULL*/)
 52     : CDialogEx(IDD_CLIPBOARD_DIALOG, pParent)
 53 {
 54     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 55 }
 56 
 57 void CClipBoardDlg::DoDataExchange(CDataExchange* pDX)
 58 {
 59     CDialogEx::DoDataExchange(pDX);
 60     DDX_Control(pDX, IDC_MESSAGE_LIST, m_CListBox_Message);
 61 }
 62 
 63 BEGIN_MESSAGE_MAP(CClipBoardDlg, CDialogEx)
 64     ON_WM_SYSCOMMAND()
 65     ON_WM_PAINT()
 66     ON_WM_QUERYDRAGICON()
 67     ON_BN_CLICKED(IDC_TEXT, &CClipBoardDlg::OnBnClickedText)
 68     ON_WM_DESTROYCLIPBOARD()
 69     ON_BN_CLICKED(IDC_BITMAP, &CClipBoardDlg::OnBnClickedBitmap)
 70     ON_BN_CLICKED(IDC_SELF_DEFINE, &CClipBoardDlg::OnBnClickedSelfDefine)
 71     ON_BN_CLICKED(IDC_BUTTON4, &CClipBoardDlg::OnBnClickedButton4)
 72     ON_WM_DESTROYCLIPBOARD()
 73 END_MESSAGE_MAP()
 74 
 75 
 76 // CClipBoardDlg 消息处理程序
 77 
 78 BOOL CClipBoardDlg::OnInitDialog()
 79 {
 80     CDialogEx::OnInitDialog();
 81 
 82     // 将“关于...”菜单项添加到系统菜单中。
 83 
 84     // IDM_ABOUTBOX 必须在系统命令范围内。
 85     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 86     ASSERT(IDM_ABOUTBOX < 0xF000);
 87 
 88     CMenu* pSysMenu = GetSystemMenu(FALSE);
 89     if (pSysMenu != NULL)
 90     {
 91         BOOL bNameValid;
 92         CString strAboutMenu;
 93         bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
 94         ASSERT(bNameValid);
 95         if (!strAboutMenu.IsEmpty())
 96         {
 97             pSysMenu->AppendMenu(MF_SEPARATOR);
 98             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
 99         }
100     }
101 
102     // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
103     //  执行此操作
104     SetIcon(m_hIcon, TRUE);            // 设置大图标
105     SetIcon(m_hIcon, FALSE);        // 设置小图标
106 
107     // TODO: 在此添加额外的初始化代码
108     this->m_DeskopPixelX = ::GetSystemMetrics(SM_CXSCREEN);  //以像素为单位计算的屏幕尺寸
109     this->m_DeskopPixelY = ::GetSystemMetrics(SM_CYSCREEN);
110 
111     this->m_NewFormat = RegisterClipboardFormat(L"TEST_FORMAT"); //注册一个新的剪贴板格式,如果注册格式已经存在,将返回已经存在格式的值而不是注册新的格式。这样做的好处是可以在多个应用程序中使用同一种格式来复制和粘贴数据。
112     return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
113 }
114 
115 void CClipBoardDlg::OnSysCommand(UINT nID, LPARAM lParam)
116 {
117     if ((nID & 0xFFF0) == IDM_ABOUTBOX)
118     {
119         CAboutDlg dlgAbout;
120         dlgAbout.DoModal();
121     }
122     else
123     {
124         CDialogEx::OnSysCommand(nID, lParam);
125     }
126 }
127 
128 // 如果向对话框添加最小化按钮,则需要下面的代码
129 //  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
130 //  这将由框架自动完成。
131 
132 void CClipBoardDlg::OnPaint()
133 {
134     if (IsIconic())
135     {
136         CPaintDC dc(this); // 用于绘制的设备上下文
137 
138         SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
139 
140         // 使图标在工作区矩形中居中
141         int cxIcon = GetSystemMetrics(SM_CXICON);
142         int cyIcon = GetSystemMetrics(SM_CYICON);
143         CRect rect;
144         GetClientRect(&rect);
145         int x = (rect.Width() - cxIcon + 1) / 2;
146         int y = (rect.Height() - cyIcon + 1) / 2;
147 
148         // 绘制图标
149         dc.DrawIcon(x, y, m_hIcon);
150     }
151     else
152     {
153         CDialogEx::OnPaint();
154     }
155 }
156 
157 //当用户拖动最小化窗口时系统调用此函数取得光标
158 //显示。
159 HCURSOR CClipBoardDlg::OnQueryDragIcon()
160 {
161     return static_cast<HCURSOR>(m_hIcon);
162 }
163 
164 
165 
166 void CClipBoardDlg::OnBnClickedText()
167 {
168     // TODO: 在此添加控件通知处理程序代码
169     // 打开剪贴板
170     ::OpenClipboard(this->GetSafeHwnd());
171     //OpenClipboard();
172     // 首先清空剪贴板
173     ::EmptyClipboard();
174     // 设置剪贴板文本数据
175     this->WriteTextIntoClipboard();
176     // 关闭剪贴板
177     ::CloseClipboard();
178 }
179 
180 VOID CClipBoardDlg::WriteTextIntoClipboard()
181 {
182     // 初始化文本信息
183     const char BufferData[] = "刘大大你可别是妖怪吧!";
184     DWORD BufferLength = strlen(BufferData);
185     HANDLE MemoryHandle = GlobalAlloc(GHND, BufferLength + 1);
186     if (MemoryHandle)
187     {
188         LPBYTE VirtualAddress = (LPBYTE)GlobalLock(MemoryHandle);
189         memcpy(VirtualAddress, BufferData, BufferLength);
190         //VirtualAddress[BufferLength] = 0;   //调试看内存发现编译器已经做了优化了,这步没必要
191         GlobalUnlock(MemoryHandle);
192         // 设置剪贴板文本数据
193         if (::SetClipboardData(CF_TEXT, MemoryHandle) == NULL)
194         {
195             ShowErrorMessage(GetLastError());
196         }
197     }
198     else
199         this->MessageBox(L"WriteTextIntoClipboard() Error");
200 }
201 
202 
203 void CClipBoardDlg::OnBnClickedBitmap()
204 {
205     // TODO: 在此添加控件通知处理程序代码
206     // 打开剪贴板
207     ::OpenClipboard(this->GetSafeHwnd());
208     // 首先清空剪贴板
209     ::EmptyClipboard();
210     // 设置剪贴板位图数据
211     this->WriteBitmapIntoClipboard();
212     // 关闭剪贴板
213     ::CloseClipboard();
214 }
215 
216 // 写入位图数据
217 VOID CClipBoardDlg::WriteBitmapIntoClipboard()
218 {
219     // 取得当前屏幕位图
220     CDC            DeviceContext, MemoryDeviceContext;
221     CBitmap        ScreenBitmap;
222     DeviceContext.CreateDC(L"DISPLAY", NULL, NULL, NULL);
223     MemoryDeviceContext.CreateCompatibleDC(&DeviceContext);
224     ScreenBitmap.CreateCompatibleBitmap(&DeviceContext, m_DeskopPixelX, m_DeskopPixelY);
225     MemoryDeviceContext.SelectObject(&ScreenBitmap);
226     MemoryDeviceContext.BitBlt(0, 0, m_DeskopPixelX, m_DeskopPixelY, &DeviceContext, 0, 0, SRCCOPY);
227     // 设置剪贴板文本数据
228     if (::SetClipboardData(CF_BITMAP, ScreenBitmap.GetSafeHandle()) == NULL)
229     {
230         ShowErrorMessage(GetLastError());
231     }
232     ScreenBitmap.DeleteObject();
233     MemoryDeviceContext.DeleteDC();
234 }
235 
236 
237 void CClipBoardDlg::OnBnClickedSelfDefine()
238 {
239     // TODO: 在此添加控件通知处理程序代码
240     // TODO: 在此添加控件通知处理程序代码
241     ::OpenClipboard(this->GetSafeHwnd());
242     // 首先清空剪贴板
243     ::EmptyClipboard();
244     // 设置剪贴板位图数据
245     this->WriteSelfDefineDataIntoClipboard();
246     // 关闭剪贴板
247     ::CloseClipboard();
248 }
249 
250 // 写入自定义数据
251 VOID CClipBoardDlg::WriteSelfDefineDataIntoClipboard()
252 {
253     PERSON_INFORMATION PersonInfo = { L"张飞", 23 };
254 
255     // 初始化自定义格式
256     DWORD PersonInfoLength = sizeof(PERSON_INFORMATION);
257     HANDLE MemoryHandle = GlobalAlloc(GHND, PersonInfoLength + 1);
258     if (MemoryHandle)
259     {
260         LPBYTE VirtualAddress = (LPBYTE)GlobalLock(MemoryHandle);
261         memcpy(VirtualAddress, &PersonInfo, PersonInfoLength);
262         VirtualAddress[PersonInfoLength] = 0;
263         GlobalUnlock(MemoryHandle);
264         ::SetClipboardData(this->m_NewFormat, MemoryHandle);
265     }
266 }
267 
268 void CClipBoardDlg::OnBnClickedButton4()
269 {
270     // TODO: 在此添加控件通知处理程序代码
271     ::OpenClipboard(this->GetSafeHwnd());
272     //OpenClipboard();
273     // 首先清空剪贴板
274     ::EmptyClipboard();
275     // 写入3种数据
276     this->WriteTextIntoClipboard();
277     this->WriteBitmapIntoClipboard();
278     this->WriteSelfDefineDataIntoClipboard();
279     // 关闭剪贴板
280     ::CloseClipboard();
281 }
282 
283 
284 VOID ShowErrorMessage(DWORD ErrorCode)
285 {
286     CHAR Message[128] = { 0 };
287     sprintf(Message, "(ErrorCode=%p)  (ErrorCode=%d)", ErrorCode, ErrorCode);
288     MessageBoxA(0, Message, 0, 0);
289 }
290 
291 void CClipBoardDlg::OnDestroyClipboard()
292 {
293     CDialogEx::OnDestroyClipboard();
294 
295     // TODO: 在此处添加消息处理程序代码
296     this->m_CListBox_Message.AddString(L"WM_DESTORYCLIPBOARD");
297     this->m_CListBox_Message.SetCurSel(this->m_CListBox_Message.GetCount() - 1);
298 }
View Code
  1 // ClipBoardClientDlg.cpp : 实现文件
  2 //
  3 
  4 #include "stdafx.h"
  5 #include "ClipBoardClient.h"
  6 #include "ClipBoardClientDlg.h"
  7 #include "afxdialogex.h"
  8 
  9 #ifdef _DEBUG
 10 #define new DEBUG_NEW
 11 #endif
 12 
 13 
 14 // 用于应用程序“关于”菜单项的 CAboutDlg 对话框
 15 
 16 class CAboutDlg : public CDialogEx
 17 {
 18 public:
 19     CAboutDlg();
 20 
 21 // 对话框数据
 22 #ifdef AFX_DESIGN_TIME
 23     enum { IDD = IDD_ABOUTBOX };
 24 #endif
 25 
 26     protected:
 27     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
 28 
 29 // 实现
 30 protected:
 31     DECLARE_MESSAGE_MAP()
 32 };
 33 
 34 CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
 35 {
 36 }
 37 
 38 void CAboutDlg::DoDataExchange(CDataExchange* pDX)
 39 {
 40     CDialogEx::DoDataExchange(pDX);
 41 }
 42 
 43 BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
 44 END_MESSAGE_MAP()
 45 
 46 
 47 // CClipBoardClientDlg 对话框
 48 
 49 
 50 
 51 CClipBoardClientDlg::CClipBoardClientDlg(CWnd* pParent /*=NULL*/)
 52     : CDialogEx(IDD_CLIPBOARDCLIENT_DIALOG, pParent)
 53 {
 54     m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 55 }
 56 
 57 void CClipBoardClientDlg::DoDataExchange(CDataExchange* pDX)
 58 {
 59     CDialogEx::DoDataExchange(pDX);
 60     DDX_Control(pDX, IDC_LIST1, m_CListBox_Message);
 61 }
 62 
 63 BEGIN_MESSAGE_MAP(CClipBoardClientDlg, CDialogEx)
 64     O

以上是关于windows剪贴板的主要内容,如果未能解决你的问题,请参考以下文章

Windows 上 Python 3.4 中的 Tkinter 不会在退出时将内部剪贴板数据发布到 Windows 剪贴板

Ditto - Windows剪贴板增强小工具,方便复制粘贴多条记录

在 Windows XP 中将剪贴板传递给批处理

谷歌浏览器 怎么用js复制东西到剪贴板

windows剪贴板

在Windows中,将当前窗口复制到剪贴板的快捷键是啥?