进程通信(VC_Win32)

目录

邮槽
匿名管道
命名管道
剪贴板

(本章节中例子都是用 VS2005 编译调试的)

进程还可以通过套接字进行通信


邮槽

通信流程:

  • 服务器

  • 客户端

注意:

  • 邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输
  • 邮槽可以实现一对多的单向通信,我们可以利用这个特点编写一个网络会议通知系统,而且实现这一的系统所需要编写的代码非常少.如果读者是项目经理,就可以给你手下每一位员工的机器上安装上这个系统中的邮槽服务器端程序,在你自己的机器上安装油槽的客户端程序,这样,当你想通知员工开会,就可以通过自己安装的邮槽客户端程序.将开会这个消息发送出去,因为机器上都安装了邮槽服务器端的程序,所以他们都能同时收到你发出的会议通知.采用邮槽实现这一的程序非常简单的,如果采用Sockets来实现这一的通信,代码会比较复杂
  • 邮槽是一种单向通信机制,创建邮槽的服务器进程只能读取数据,打开邮槽的客户机进程只能写入数据
  • 为保证邮槽在各种Windows平台下都能够正常工作,我们传输消息的时候,应将消息的长度限制在424字节以下

CreateMailslot函数详解

函数原型:

HANDLE CreateMailslot(
  LPCTSTR lpName, // mailslot name
  DWORD nMaxMessageSize, // maximum message size
  DWORD lReadTimeout, // read time-out interval
  LPSECURITY_ATTRIBUTES lpSecurityAttributes // inheritance option
);

参数说明:

  • lpName
    指向一个空终止字符串的指针,该字符串指定了油槽的名称,该名称的格式必须是:"\\.\mailslot\[path]name ",其中前两个反斜杠之后的字符表示服务器所在机器的名称,圆点表示是主机;接着是硬编码的字符串:"mailslot",这个字符串不能改变,但大小写无所谓;最后是油槽的名称([path]name)由程序员起名
  • nMaxMessageSize
    用来指定可以被写入到油槽的单一消息的最大尺寸,为了可以发送任意大小的消息,卡伊将该参数设置为0
  • lReadTimeout
    指定读写操作的超时间间隔,以ms为单位,读取操作在超时之前可以等待一个消息被写入到这个油槽之中.
    • 如果这个值设置为0,那么若没有消息可用,该函数立即返回;
    • 如果这个值设置为MAILSOT_WAIT_FOREVER,则函数一直等待,直到有消息可用
  • lpSecurityAttributes
    指向一个SECURITY_ATTRIBUTES结构的指针,可以简单地给这个参数传递NULL值,让系统为所创建的油槽赋予默认的安全描述符

代码样例: 

服务器端源码:

View Code
#include<windows.h>
#include<cstdlib>
#include<iostream>
using namespace std;

void main()
{
    HANDLE hMailslot;
    char buf[100];
    DWORD dwRead;

    //创建邮槽
    hMailslot=CreateMailslot("\\\\.\\mailslot\\Communication",0,
        MAILSLOT_WAIT_FOREVER,NULL);
    if(INVALID_HANDLE_VALUE==hMailslot)
    {
        cout<<"创建邮槽失败!"<<endl;
        system("pause");
        return;
    }
    
    //等待用户写入数据然后读取出数据
    if(!ReadFile(hMailslot,buf,100,&dwRead,NULL))
    {
        cout<<"读取数据失败!"<<endl;
        CloseHandle(hMailslot);
        system("pause");
        return;
    }
    cout<<buf<<endl;
    
    //关闭邮槽
    CloseHandle(hMailslot);
    
    system("pause");
}

客户端源码:

View Code
#include<windows.h>
#include<cstdlib>
#include<iostream>
using namespace std;

void main()
{
    HANDLE hMailslot;
    char buf[]="this is message";

    //打开邮槽
    hMailslot=CreateFile("\\\\.\\mailslot\\Communication",GENERIC_WRITE,
        FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hMailslot)
    {
        cout<<"打开邮槽失败!"<<endl;
        system("pause");
        return;
    }

    //向邮槽写数据
    DWORD dwWrite;
    if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))
    {
        cout<<"写入数据失败!"<<endl;
        CloseHandle(hMailslot);
        system("pause");
        return;
    }

    //关闭邮槽
    CloseHandle(hMailslot);

    system("pause");
}

运行结果(先运行服务器端程序,然后在运行客户端程序):


匿名管道

说明:

匿名管道是一个未命名的,单向管道,通常用来在一个父进程和一个子进程之间传输数据,匿名管道只能实现在本机上的两个进程通信,而不能实现跨网络的通信

通信过程

  • 父进程读写数据:

  • 子进程读写数据:

相关函数

CreatePipe 管道创建

函数原型 

BOOL CreatePipe(
PHANDLE hReadPipe, // pointer to read handle
PHANDLE hWritePipe,     // pointer to write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes,  // pointer to security attributes
DWORD nSize // pipe size
);

参数说明:

  • hReadPipe    作为返回类型使用,返回管道读取句柄
  • hWritePipe    作为返回类型使用,返回管道写入句柄
  • lpPipeAttributes  指向SECURITY_ATTRIBUTES结构体的指针,检测返回句柄是否能被子进程继承,如果此参数为NULL,则句柄不能被继承
  • nSize      指定管道的缓冲区大小,改大小只是个建议值,系统将用这个值来计算一个适当的缓存区大小,如果此参数是0,系统会使用默认的缓冲区大小

返回值

若函数成功返回非零值
若函数失败返回0,详细消息可以调用GetLastError函数获得

代码样例:

工程目录结构:

匿名管道
|-- child
| `-- debug
| `-- child.exe
` -- parent
    `-- debug
`-- parent.exe

父进程源码:

View Code
#include<windows.h>
#include<iostream>
#include<cstdlib>
using namespace std;

void main()
{
    SECURITY_ATTRIBUTES sa;
    STARTUPINFO sui;
    PROCESS_INFORMATION pi;
    HANDLE hRead,hWrite;
    char rebuf[100];
    DWORD dwRead;

    //创建匿名管道
    sa.bInheritHandle=TRUE;
    sa.lpSecurityDescriptor=NULL;
    sa.nLength=sizeof(SECURITY_ATTRIBUTES);
    if(!CreatePipe(&hRead,&hWrite,&sa,0))
    {
        cout<<"创建匿名管道失败!"<<endl;
        system("pause");
        return;
    }

    //创建子进程并对相关子进程相关数据进行初始化  (用匿名管道的读取写入句柄赋予子进程的输入输出句柄)
    ZeroMemory(&sui,sizeof(STARTUPINFO));
    sui.cb=sizeof(STARTUPINFO);
    sui.dwFlags=STARTF_USESTDHANDLES;
    sui.hStdInput=hRead;
    sui.hStdOutput=hWrite;
    sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);

    if(!CreateProcess("..\\..\\child\\debug\\child.exe",NULL,NULL,NULL,true,CREATE_NEW_CONSOLE,NULL,NULL,&sui,&pi))
    {
        cout<<"创建子进程失败!"<<endl;
        system("pause");
        return;
    }
    else
    {
        //关闭子进程相关句柄(进行句柄,进程主线程句柄)
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        Sleep(2000);

        //读取数据
        if(!ReadFile(hRead,rebuf,100,&dwRead,NULL))
        {
            cout<<"读取数据失败!"<<endl;
            system("pause");
            return;
        }
        cout<<rebuf<<endl;
        
    }
    system("pause");
}

子进程源码:

View Code
#include<windows.h>
#include<iostream>
#include<cstdlib>
using namespace std;

void main()
{
    HANDLE hRead,hWrite;

    //获得匿名管道输入输出句柄
    hRead=GetStdHandle(STD_INPUT_HANDLE);
    hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
    char sebuf[]=" 子进程写入管道成功";
    char rebuf[100];
    DWORD dwWrite;

    //写入数据
    if(!WriteFile(hWrite,sebuf,strlen(sebuf)+1,&dwWrite,NULL))
    {
        cout<<"写入数据失败!"<<endl;
        system("pause");
        return;
    }
    Sleep(500);

    system("pause");
}

命名管道

[相关函数][代码示例]

作用

命名管道不仅可以实现在本机上两个进程间的通信,还可以跨网络实现两个进程间的通信

两种通信模式

  • 字节模式:   在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动
  • 消息模式:   在消息模式下,客户机和服务器则通过一系列不连续的数据单位,进行数据收发,每次在管道上发一条消息后,它必须作为一条完整的消息读入

通信流程

  • 服务器

  • 客户端

相关函数

显示相关函数

[CreateNamePine 创建命名管道][ConnectNamePipe 创建连接命名管道][WaitNamedPipe 进行命名管道连接]

CreateNamePipe 创建命名管道

函数原型:

HANDLE CreateNamedPipe(
  LPCTSTR lpName, // pipe name
  DWORD dwOpenMode, // pipe open mode
  DWORD dwPipeMode, // pipe-specific modes
  DWORD nMaxInstances, // maximum number of instances
  DWORD nOutBufferSize, // output buffer size
  DWORD nInBufferSize, // input buffer size
  DWORD nDefaultTimeOut, // time-out interval
  LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD
);

参数说明:

  • lpName

    一个指向空终止的字符串,该字符串的格式必须是:"\\.\pine\pinename"其中该字符串开始是两个连续的反斜杠,其后的原点表示是本地机器,如果想要与远程的服务器建立连接连接,那么在原点这个位置应该指定这个远程服务器的名称.接下来是"pine"这个固定的字符串,也就是说这个字符串的内容不能修改,但其大小写是无所谓的,最后是所创建的命名管道的名称

  • dwOpenMode

    指定管道的访问方式,重叠方式.写直通方式,还有管道句柄的安全访问方式()
    用来指定管道的访问方式的标志取值如下(下面这三个值只能够取其中一个),并且管道的每一个实例都必须具有同样的访问方式

      • PIPE_ACCESS_INBOUND 管道只能用作接收数据(服务器只能读数据,客户端只能写数据),相当于在CreateFile中指定了GENERIC_READ
      • PIPE_ACCESS_OUTBOUND 管道只能用作发送数据(服务器只能写数据,客户端只能读数据),相当于在CreateFile中指定了GENERIC_WRITE
      • PIPE_ACCESS_DUPLEX 管道既可以发送也可以接收数据,相当于在CreateFile中指定了GENERIC_READ | GENERIC_WRITE

    用来指定写直通方式和重叠方式的标志,取值可以是一下一个或多个组合

      • FILE_FLAG_WRITE_THROUGH 管道用于同步发送和接收数据,只有在数据被发送到目标地址时发送函数才会返回,如果不设置这个参数那么在系统内部对于命名管道的处理上可能会因为减少网络附和而在数据积累到一定量时才发送,并且对于发送函数的调用会马上返回
      • FILE_FLAG_OVERLAPPED 管道可以用于异步输入和输出,异步读写的有关方法和文件异步读写是相同的

    用来指定管道安全访问方式的标志,取值可以是一下一个或多个组合

      • WRITE_DAC 调用者对命名管道的任意范围控制列表(ACL)都可以进行写入访问
      • WRITE_OWNER 调用者对命名管道的所有者可以进行写入访问
      • ACCESS_SYSTEM_SECURITY 调用者对命名管道打安全范围控制列表(SACL)可以进行写入访问
  • dwPipeMode

    指定管道类型,,读取和等待方式可以是下面值的组合(0为字节写字节读阻塞方式)
    用于指定管道句柄的写入的标志

      • PIPE_TYPE_BYTE 数据在通过管道发送时作为字节流发送,不能与PIPE_READMODE_MESSAGE共用
      • PIPE_TYPE_MESSAGE 数据在通过管道发送时作为消息发送,不能与PIPE_READMODE_BYTE共用

    用于指定管道句柄的读取方式

      • PIPE_READMODE_BYTE 在接收数据时接收字节流该方式在PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE类型均可以使用
      • PIPE_READMODE_MESSAGE 在接收数据时接收消息该方式只用在PIPE_TYPE_MESSAGE类型下才可以使用

    用于指定管道句柄的等待方式(同一管道的不同实例可以采取不同的等待方式)

      • PIPE_WAIT 使用等待模式(阻塞方式),在读,写和建立连接时都需要管道的另一方完成相应动作后才会返回
      • PIPE_NOWAIT 使用非等待模式(非阻塞方式),在读,写和建立连接时不需要管道的另一方完成相应动作后就会立即返回
  • nMaxInstances

    为管道的的最大数量,在第一次建立服务器方管道时这个参数表明该管道可以同时存在的数量。PIPE_UNLIMITED_INSTANCES表明不对数量进行限制

  • nOutBufferSize

    表示输出缓冲区的大小

  • nInBufferSize

    表示输入缓冲区的大小

  • nDefaultTimeOut

    表示在等待连接时最长的等待时间(以毫秒为单位),如果在创建时设置为NMPWAIT_USE_DEFAULT_WAIT表明无限制的等待,而以后服务器方的其他管道实例也需要设置相同的值

  • lpSecurityAttributes

    为安全属性,一般设置为NULL。如果创建或打开失败则返回INVALID_HANDLE_VALUE。可以通过GetLastError得到错误

返回值

  • 若函数成功,返回值是一个命名通道实例的句柄,如果命名通道已经存在则返回一个以存在的命名通道的句柄,并调用GetLastError函数的返回值为 ERROR_ALREADY_EXISTS
  • 若函数失败,返回值为INVALID_HANDLE_VALUE若想获得更多信息调用GetLastError函数获得

ConnectNamePipe 创建命名管道连接

函数原型

BOOL ConnectNamedPipe(
  HANDLE hNamedPipe, // handle to named pipe
  LPOVERLAPPED lpOverlapped // overlapped structure
);

参数说明:

  • lpNamedPipe :  指向一个命名管道实例的服务的句柄,该句柄由CreateNamedPipe函数返回
  • lpOverlapped:  指向一个OVERLAPPED结构的指针,如果hNamedPipe参数所标识的管道是用FILE_FLAG_OVERLAPPED标志打开的,则这个参数不能是NULL,必须是一个有效的指向一个OVERLAPPED的结构指针;否则函数则会错误的执行.如果hNampdPipe参数标志的管道用FILE_FLAG_OVERLAPPED标志打开的,并且这个参数不是NULL,则这个OVERLAPPED结构体必须包含人工重置对象句柄.

返回值

如果函数成功返回非零值如果失败返回0详细消息可以调用GetLastError函数获得

WaitNamedPipe 进行命名管道连接

函数原型

BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
DWORD nTimeOut // time-out interval
);

参数说明:

  • lpNamedPipeName

    用来指定管道的名称,这个名称必须包括创建该命名管道的服务器进程所在的机器的名称,该名称的格式必须是"\\.\pine\pinename".如果在同一台机器上编写的命名管道的服务器端程序和客户端程序,则应该指定这个名称时,在开始的两个反斜杆后可以设置一个圆点,表示服务器进程在本地机器上运行;如果是跨网络通信,则在这个圆点位置处应该指定服务器端程序所在的主机名

  • nTimeOut

    指定超时间隔.

      • NMPWAIT_USE_DEFAULT_WAIT 超时间隔就是服务器端创建该命名管道时指定的超时值
      • NWPWAIT_WAIT_FOREVER 一直等待,直到出现了一个可用的命名管道的实例

    也就是说,如果这个参数的值是NMPWAIT_USE_DEFAULT_WAIT,并且在服务器端调用CreateNamedPipe函数创建命名管道时,设置的超时间隔为1000ms,那么一个命名管道的所有实例来说,它们必须使用同样的超时间隔

返回值

如果函数成功返回非零值如果失败返回0详细消息可以调用GetLastError函数获得

代码样例:

服务器:

View Code
#include<windows.h>
#include<cstdlib>
#include<iostream>
using namespace std;

void main()
{
    HANDLE hPipe,hEvent;;
    DWORD dwRead,dwWrite;
    OVERLAPPED ovlap;
    char sebuf[]="this is sever!";
    char rebuf[100];

    /*创建命名连接*****************************************************/
    hPipe=CreateNamedPipe("\\\\.\\pipe\\Communication",
        PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
        0,1,1024,1024,0,NULL);
    if(INVALID_HANDLE_VALUE==hPipe)
    {
        cout<<"创建命名管道失败!"<<endl;
        hPipe=NULL;
        system("pause");
        return;
    }
    
    /*创建命名管道连接*************************************************/
    hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
    if(!hEvent)
    {
        cout<<"创建事件对象失败!"<<endl;
        CloseHandle(hPipe);
        hPipe=NULL;
        system("pause");
        return;
    }
    
    ZeroMemory(&ovlap,sizeof(OVERLAPPED));
    ovlap.hEvent=hEvent;
    
    //创建管道连接
    if(!ConnectNamedPipe(hPipe,&ovlap))
    {
        if(ERROR_IO_PENDING!=GetLastError())
        {
            cout<<"等待客户端连接失败!"<<endl;
            CloseHandle(hPipe);
            CloseHandle(hEvent);
            hPipe=NULL;
            system("pause");
            return;
        }
    }
    //等待客户端连接
    if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE))
    {
        cout<<"等待对象失败!"<<endl;
        CloseHandle(hPipe);
        CloseHandle(hEvent);
        hPipe=NULL;
        system("pause");
        return;
    }
    CloseHandle(hEvent);
    
    /*读写管道数据*****************************************************/
    //写入数据
    if(!ReadFile(hPipe,rebuf,100,&dwRead,NULL))
    {
        cout<<"读取数据失败!"<<endl;
        system("pause");
        return;
    }
    cout<<rebuf<<endl;
    
    //写入数据
    if(!WriteFile(hPipe,sebuf,strlen(sebuf)+1,&dwWrite,NULL))
    {
        cout<<"写入数据失败!"<<endl;
        system("pause");
        return;
    }
    
    system("pause");
}

客户端:

View Code
#include<windows.h>
#include<cstdlib>
#include<iostream>
using namespace std;

void main()
{
    HANDLE hPipe;
    DWORD dwRead,dwWrite;
    char sebuf[]="this is client!";
    char rebuf[100];

    /*连接管道连接*****************************************************/
    if(!WaitNamedPipe("\\\\.\\pipe\\Communication",NMPWAIT_WAIT_FOREVER))
    {
        cout<<"当前没有可利用的命名管道实例!"<<endl;
        system("pause");
        return;
    }

    /*打开管道连接*****************************************************/
    hPipe=CreateFile("\\\\.\\pipe\\Communication",GENERIC_READ | GENERIC_WRITE,
        0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(INVALID_HANDLE_VALUE==hPipe)
    {
        cout<<"打开命名管道失败!"<<endl;
        hPipe=NULL;
        system("pause");
        return;
    }

    /*读写管道数据*****************************************************/
    //写入数据
    if(!WriteFile(hPipe,sebuf,strlen(sebuf)+1,&dwWrite,NULL))
    {
        cout<<"写入数据失败!"<<endl;
        system("pause");
        return;
    }

    //读取数据
    if(!ReadFile(hPipe,rebuf,100,&dwRead,NULL))
    {
        cout<<"读取数据失败!"<<endl;
        system("pause");
        return;
    }
    cout<<rebuf<<endl;
    
    system("pause");
}


剪贴板

[相关函数][代码样例]

通信流程

  • 接收数据

  • 发送数据

相关函数

显示相关函数

[打开/关闭剪贴板][清空剪贴板][向剪贴板写入数据][从剪贴板读取数据][判断剪贴板数据格式]

打开/关闭剪贴板

函数原型

//打开剪贴板
BOOL OpenClipboard();
//关闭剪贴板
BOOL CloseClipboard(); 

EmptyClipboard 清空剪贴板

函数原型

BOOL EmptyClipboard();

说明

只有调用了EmptyClipboard函数后,打开剪贴板的当前窗口才拥有剪贴板.EmptyClipboard函数将清空剪贴板,并释放剪贴板中的句柄,然后剪贴板的所有权分配给当前窗口.

SetClipboardData 向剪贴板写入数据

函数原型

HANDLE SetClipboardData(UINT uFormat,HANDLE hMem);

参数说明

  • uFormat   指定剪贴板的格式,这个格式可以是以注册的格式,或者是任一种标准的形式的剪贴板详细参见MSDN
  • hMem    具有指定格式的句柄.该参数可以为NULL,指示调用窗口直到有对剪贴板数据的请求时候才提供指定的剪贴板格式的数据.如果窗口采用延时提交技术,则该窗口必须处理WM_RENDERFORMAT和WM_RENDERALLFORMATS消息

返回值

如果函数成功返回的是数据句柄
如果函数失败返回的是NULL,详细消息可以调用GetLastError函数获得
说明 当前调用的SetClipboardData函数的窗口必须是剪贴板的拥有着,而且在这个之前,该程序已经调用了OpenClipboard函数打开剪贴板
当一个提供的进程创建了剪贴板数据之后,知道其他进程获取剪贴板数据前,这些数据都是要占据内存空间的,如果在剪贴板上放置的数据过大,就会浪费内存空间,降低资源利用率.为了避免这种浪费,就可以采用延迟提交技术,也就是有数据提供进程先提供一个指定格式的空剪贴板数据块,即把SetClipboardData函数的hMem参数设置为NULL.当需要获取数据的进程想要从剪贴板上得到数据时,操作系统会向数据提供进程发送WM_RENDERFORMAT消息,而数据提供进程可以响应这个消息,并在此消息的响应函数中,再一次调用SetClipboardData函数,将实际的数据放到剪贴板上,当再次调用SetClipboardData函数时就不需要调用OpenClipboard函数,也不需要调用EmptyClipboard函数.也就是说为了提高资源利用率,避免浪费内存空间,可以采用延迟提交技术.第一次调用SetClipboard函数时,将其hMem参数设置为NULL,在剪贴板上以指定的剪贴板放置一个空剪贴板数据块

GetClipboardData从剪贴板读取数据

函数原型

HANDLE GetClipboardData( UINT uFormat );

参数说明

  • uFormat  指定返回数据的句柄格式这个格式可以是以注册的格式,或者是任一种标准的形式的剪贴板详细参见MSDN

返回值

若函数成功返回的是剪贴板数据内容的指定格式的句柄
若函数失败返回值是NULL,详细消息可以调用GetLastError函数获得

IsClipboardFormatAvailable 判断剪贴板的数据格式

函数原型

BOOL IsClipboardFormatAvailable(UINT format);

参数说明

  • uFormat  判断剪贴板里的数据的句柄格式这个格式可以是以注册的格式,或者是任一种标准的形式的剪贴板详细参见MSDN

返回值

若剪贴板中的数据格式句柄为uFormat格式返回非零值
若剪贴板中的数据格式句柄不为uFormat格式返回零

程序样例: 

View Code
#include<windows.h>
#include<cstdlib>
#include<iostream>
#include<string>
using namespace std;

void main()
{
    HANDLE hClip;
    char *pBuf;
    string str="this is message";

    /*向剪贴板写入数据************************************************************************/
    //打开剪贴板
    if(OpenClipboard(NULL))
    {
        //清空剪贴板
        EmptyClipboard();

        //想剪贴板写入数据
        hClip=GlobalAlloc(GMEM_MOVEABLE,str.size()+1);
        pBuf=(char*)GlobalLock(hClip);
        strcpy(pBuf,str.c_str());
        GlobalUnlock(hClip);
        SetClipboardData(CF_TEXT,hClip);
        
        //释放剪贴板
        CloseClipboard();
    }

    /*从剪贴板读取数据************************************************************************/
    //打开剪贴板
    if(OpenClipboard(NULL))
    {
        //检查剪贴板中的数据格式
        if(IsClipboardFormatAvailable(CF_TEXT))
        {
            //接收数据
            hClip=GetClipboardData(CF_TEXT);
            pBuf=(char*)GlobalLock(hClip);
            GlobalUnlock(hClip);
            cout<<pBuf<<endl;

            //释放剪贴板
            CloseClipboard();
        }
    }
    system("pause");
}

运行结果:

原文地址:https://www.cnblogs.com/kzang/p/2753367.html