在windows VS下,用C++实现http服务器,目前据我所知,可以使用win32 http-server-api进行实现,也可以使用libevent进行实现,以下例子是用win32 http-server-api实现的http服务器,支持get和post请求,本文提供服务器的demo工程下载,该demo是在vs2008下,可以修改工程支持其他vs版本。
win32 http-server-api官方文档访问路径:
https://learn.microsoft.com/zh-cn/windows/win32/http/http-api-start-page
1.HttpInitialize 初始化WinHTTP函数库;
2.HttpCreateHttpHandle 创建一个HTTP队列句柄,用来接收HTTP请求;
3.HttpAddUrl 绑定要监听的URL,写为http://:80/表示所有网卡80端口的HTTP请求都处理,其中的号可以改为IP地址;
4.创建一个线程用来处理HTTP请求队列;
5.HttpReceiveHttpRequest 在线程中调用此函数,接收HTTP请求,在返回的PHTTP_REQUEST结构中有我们想要的各种数据;
6.HttpReceiveRequestEntityBody 这个函数用来接收HTTP请求的BODY部分,如果数据很长需要多次调用;
httpserver.h
/*
* Filename: httpserver.h
* Author: ybLin
* Description: httpserver
* *******************************************************/
#pragma once
#include "stdafx.h"
#include
class CHttpServer;
int myprint(const char* msg, ...);
int myprint(const wchar_t *msg, ...);
typedef enum REQUEST_TYPE_E
{
REQUEST_TYPE_GET = 0,
REQUEST_TYPE_POST
};
class IHttpServerListener
{
public:
virtual BOOL OnRecvRequest(CHttpServer *inst, PHTTP_REQUEST request, enum REQUEST_TYPE_E nRequestType)=0;
};
class CHttpServer
{
public:
CHttpServer();
BOOL Init(int port, IHttpServerListener *pListener);
BOOL DeInit();
//服务器发送响应
DWORD SendHttpGetResponse(PHTTP_REQUEST pRequest,USHORT StatusCode,PSTR pReason,PSTR pEntityString);
DWORD SendHttpPostResponse(PHTTP_REQUEST pRequest);
//处理接收请求
static UINT AFX_CDECL RecvRequestThread(LPVOID param);
private:
BOOL DoReceiveRequests();
void InitUri(INT nPort);
private:
wchar_t* m_wUrI[32];
int m_nUriCnt;
LPBYTE m_req_buffer;
HANDLE m_req_queue;
UINT m_req_buffer_size;
CWinThread *m_thread;
IHttpServerListener *m_listener;
};
httpserver.cpp
#include "StdAfx.h"
#include "httpserver.h"
#include
#include
#pragma comment(lib, "httpapi.lib")
#pragma comment(lib,"Iphlpapi.lib")
#define RECV_BUF_SIZE 4096
#define MAX_ULONG_STR ((ULONG) sizeof("4294967295"))
#define INITIALIZE_HTTP_RESPONSE( resp, status, reason ) \
do \
{ \
RtlZeroMemory( (resp), sizeof(*(resp)) ); \
(resp)->StatusCode = (status); \
(resp)->pReason = (reason); \
(resp)->ReasonLength = (USHORT) strlen(reason); \
} while (FALSE)
#define ADD_KNOWN_HEADER(Response, HeaderId, RawValue) \
do \
{ \
(Response).Headers.KnownHeaders[(HeaderId)].pRawValue = \
(RawValue);\
(Response).Headers.KnownHeaders[(HeaderId)].RawValueLength = \
(USHORT) strlen(RawValue); \
} while(FALSE)
#define ALLOC_MEM(cb) HeapAlloc(GetProcessHeap(), 0, (cb))
#define FREE_MEM(ptr) HeapFree(GetProcessHeap(), 0, (ptr))
// 打印函数
int myprint(const char* msg, ...)
{
char buf[1024] = {0};
char test[1024] = {0};
va_list va;
time_t now = time(NULL);
char szTime[32] = {0};
struct tm t;
localtime_s(&t, &now);
_snprintf_s(szTime, sizeof(szTime), sizeof(szTime)-1, "%4d-%02d-%02d %02d:%02d:%02d",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
va_start (va, msg);
_vsnprintf_s(buf, sizeof(buf) - 1, (char*)msg, va);
va_end(va);
_snprintf_s(test, sizeof(test), sizeof(test) - 1, "[Time:%s]===%s===\n", szTime, buf);
OutputDebugStringA(test);
return 0;
}
int myprint(const wchar_t *msg, ...)
{
va_list vlArgs = NULL;
va_start(vlArgs, msg);
size_t nLen = _vscwprintf(msg, vlArgs) + 1;
wchar_t *strBuffer = new wchar_t[nLen];
_vsnwprintf_s(strBuffer, nLen, nLen, msg, vlArgs);
va_end(vlArgs);
OutputDebugStringW(strBuffer);
delete [] strBuffer;
return 0;
}
//多网卡获取PCI网卡的IP
void getAdapterInfoWithWindows(std::string& LoacalIp)
{
//PIP_ADAPTER_INFO结构体存储本机网卡信息,包括本地网卡、无线网卡和虚拟网卡
PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
//调用GetAdaptersInfo函数,填充pAdapterInfo指针变量,其中ulOutBufLen参数既是输入也是输出
if(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_SUCCESS)
{
//如果分配失败,释放后重新分配
delete pAdapterInfo;
//pAdapterInfo = NULL;
pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
}
if(GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == NO_ERROR)
{
while (pAdapterInfo)
{
myprint("Adapter Name:%s", pAdapterInfo->AdapterName);
myprint("Adapter IP Address:%s", pAdapterInfo->IpAddressList.IpAddress.String);
//pAdapter->Description中包含"PCI"为本地网卡,pAdapter->Type是71为无线网卡
if((strstr(pAdapterInfo->Description,"PCI") > 0 || strstr(pAdapterInfo->Description,"Intel") > 0)
&& (pAdapterInfo->Type != 71))
{
//这里假设每个网卡只有一个IP
char * ipAddress = pAdapterInfo->IpAddressList.IpAddress.String;
char tempIpAddress[3]={'\0'};
memcpy(tempIpAddress,ipAddress,3);
myprint("Adapter Name:%s", pAdapterInfo->AdapterName);
myprint("Adapter IP Address:%s", pAdapterInfo->IpAddressList.IpAddress.String);
myprint("Adapter IP Address:%s", ipAddress);
LoacalIp = pAdapterInfo->IpAddressList.IpAddress.String;
}
pAdapterInfo = pAdapterInfo->Next;
}
}
else
{
myprint("Call to GetAdaptersInfo failed.\n");
}
if(pAdapterInfo)
{
GlobalFree(pAdapterInfo);
}
}
wchar_t * char2wchar(const char* cchar)
{
wchar_t *m_wchar;
int len = MultiByteToWideChar(CP_ACP ,0,cchar ,strlen( cchar), NULL,0);
m_wchar= new wchar_t[len+1];
MultiByteToWideChar(CP_ACP ,0,cchar,strlen( cchar),m_wchar,len);
m_wchar[len]= '\0' ;
return m_wchar;
}
/
CHttpServer::CHttpServer()
{
m_thread = NULL;
m_req_queue = NULL;
m_req_buffer = NULL;
m_req_buffer_size = RECV_BUF_SIZE;
}
void CHttpServer::InitUri(INT nPort)
{
char sUrl[256] = {0};
std::string sLocalIP;
getAdapterInfoWithWindows(sLocalIP);
const char* pIP = sLocalIP.data();
_snprintf(sUrl, sizeof(sUrl)-1, "http://%s:%d/config", pIP, nPort);
m_wUrI[0] = char2wchar(sUrl);
_snprintf(sUrl, sizeof(sUrl)-1, "http://%s:%d/network", pIP, nPort);
m_wUrI[1] = char2wchar(sUrl);
m_nUriCnt = 2;
}
BOOL CHttpServer::Init(INT nPort, IHttpServerListener *pListener)
{
ULONG retCode;
HTTPAPI_VERSION version = HTTP_VERSION_1_0;
retCode = HttpInitialize(version,HTTP_INITIALIZE_SERVER,NULL);
if(retCode != NO_ERROR)
{
myprint("HttpInitialize error(%u)!\r\n", retCode);
goto Fail;
}
retCode = HttpCreateHttpHandle(&m_req_queue, 0);
if(retCode != NO_ERROR)
{
myprint("HttpCreateHttpHandle error(%u)!\n", retCode);
HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
return FALSE;
}
InitUri(nPort);
for(int i = 0; i < m_nUriCnt; i++)
{
retCode = HttpAddUrl(m_req_queue, m_wUrI[i], NULL);
if(retCode != NO_ERROR)
{
myprint("HttpAddUrl error(%u)!\n", retCode);
goto Fail;
}
}
m_req_buffer = (LPBYTE)malloc(m_req_buffer_size); //缓冲区大小
if(NULL == m_req_buffer)
{
goto Fail;
}
m_listener = pListener;
m_thread = AfxBeginThread(RecvRequestThread, this);
myprint("Http Server Create success");
return TRUE;
Fail:
for(int i = 0; i < m_nUriCnt; i++)
{
HttpRemoveUrl(m_req_queue, m_wUrI[i]);
}
m_nUriCnt = 0;
if(m_req_queue)
{
CloseHandle(m_req_queue);
}
HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
return FALSE;
}
BOOL CHttpServer::DeInit()
{
for(int i = 0; i < m_nUriCnt; i++)
{
HttpRemoveUrl(m_req_queue, m_wUrI[i]);
}
m_nUriCnt = 0;
if(m_req_queue)
{
CloseHandle(m_req_queue);
HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
m_req_queue = NULL;
}
if(m_req_buffer)
{
free(m_req_buffer);
m_req_buffer = NULL;
}
if(m_thread)
{
m_thread->Delete();
m_thread = NULL;
}
return TRUE;
}
DWORD CHttpServer::SendHttpGetResponse(PHTTP_REQUEST pRequest,USHORT StatusCode,
PSTR pReason,PSTR pEntityString)
{
HTTP_RESPONSE response;
HTTP_DATA_CHUNK dataChunk;
DWORD result;
DWORD bytesSent;
// Initialize the HTTP response structure.
do
{
RtlZeroMemory(&response, sizeof(response));
(response).StatusCode = (StatusCode);
(response).pReason = (pReason);
(response).ReasonLength = (USHORT) strlen(pReason);
} while (FALSE);
// Add a known header.
LPCSTR type = "text/html";
do
{
response.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = type;
response.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = (USHORT) strlen(type);
} while(FALSE);
if(pEntityString)
{
// Add an entity chunk.
dataChunk.DataChunkType = HttpDataChunkFromMemory;
dataChunk.FromMemory.pBuffer = pEntityString;
dataChunk.FromMemory.BufferLength = (ULONG) strlen(pEntityString);
response.EntityChunkCount = 1;
response.pEntityChunks = &dataChunk;
}
// Because the entity body is sent in one call, it is not
// required to specify the Content-Length.
result = HttpSendHttpResponse(m_req_queue, // ReqQueueHandle
pRequest->RequestId, // Request ID
0, // Flags
&response, // HTTP response
NULL, // pReserved1
&bytesSent, // bytes sent (OPTIONAL)
NULL, // pReserved2 (must be NULL)
0, // Reserved3 (must be 0)
NULL, // LPOVERLAPPED(OPTIONAL)
NULL // pReserved4 (must be NULL)
);
if(result != NO_ERROR)
{
myprint("HttpSendHttpResponse failed with %lu \n", result);
}
return result;
}
DWORD CHttpServer::SendHttpPostResponse(PHTTP_REQUEST pRequest)
{
HTTP_RESPONSE response;
DWORD result;
DWORD bytesSent;
PUCHAR pEntityBuffer;
ULONG EntityBufferLength;
ULONG BytesRead;
ULONG TempFileBytesWritten;
HANDLE hTempFile;
TCHAR szTempName[MAX_PATH + 1];
CHAR szContentLength[MAX_ULONG_STR];
HTTP_DATA_CHUNK dataChunk;
ULONG TotalBytesRead = 0;
BytesRead = 0;
hTempFile = INVALID_HANDLE_VALUE;
// 为实体缓冲区分配空间。 缓冲区可按需增加。
EntityBufferLength = 2048;
pEntityBuffer = (PUCHAR) ALLOC_MEM( EntityBufferLength );
if (pEntityBuffer == NULL)
{
result = ERROR_NOT_ENOUGH_MEMORY;
wprintf(L"Insufficient resources \n");
goto Done;
}
// 初始化HTTP response结构体.
INITIALIZE_HTTP_RESPONSE(&response, 200, "OK");
// 对于POST,从客户端回显实体
// 注意: 如果HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY标识通过HttpReceiveHttpRequest()
// 传递,则entity将是HTTP_REQUEST的一部分(使用pEntityChunks字段).因为此标识
// 未被传递,则entity不在HTTP_REQUEST中.
if(pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
{
myprint("post response 111");
// 实体主体通过多个调用发送. 收集这些在一个文件并回发.创建一个临时文件
if(GetTempFileName(
".",
"New",
0,
szTempName
) == 0)
{
result = GetLastError();
myprint("GetTempFileName failed with %lu \n", result);
goto Done;
}
hTempFile = CreateFile(
szTempName,
GENERIC_READ | GENERIC_WRITE,
0, // Do not share.
NULL, // No security descriptor.
CREATE_ALWAYS, // Overrwrite existing.
FILE_ATTRIBUTE_NORMAL, // Normal file.
NULL
);
if(hTempFile == INVALID_HANDLE_VALUE)
{
result = GetLastError();
myprint("Cannot create temporary file. Error %lu \n",
result);
goto Done;
}
do
{
// 从请求中读取entity chunk.
BytesRead = 0;
result = HttpReceiveRequestEntityBody(
m_req_queue,
pRequest->RequestId,
0,
pEntityBuffer,
EntityBufferLength,
&BytesRead,
NULL
);
switch(result)
{
case NO_ERROR:
if(BytesRead != 0)
{
TotalBytesRead += BytesRead;
WriteFile(
hTempFile,
pEntityBuffer,
BytesRead,
&TempFileBytesWritten,
NULL
);
}
break;
case ERROR_HANDLE_EOF:
// The last request entity body has been read.
// Send back a response.
//
// To illustrate entity sends via
// HttpSendResponseEntityBody, the response will
// be sent over multiple calls. To do this,
// pass the HTTP_SEND_RESPONSE_FLAG_MORE_DATA
// flag.
if(BytesRead != 0)
{
TotalBytesRead += BytesRead;
WriteFile(
hTempFile,
pEntityBuffer,
BytesRead,
&TempFileBytesWritten,
NULL
);
}
// Because the response is sent over multiple
// API calls, add a content-length.
//
// Alternatively, the response could have been
// sent using chunked transfer encoding, by
// passimg "Transfer-Encoding: Chunked".
//
// NOTE: Because the TotalBytesread in a ULONG
// are accumulated, this will not work
// for entity bodies larger than 4 GB.
// For support of large entity bodies,
// use a ULONGLONG.
sprintf_s(szContentLength, MAX_ULONG_STR, "%lu", TotalBytesRead);
ADD_KNOWN_HEADER(
response,
HttpHeaderContentLength,
szContentLength
);
result =
HttpSendHttpResponse(
m_req_queue, // ReqQueueHandle
pRequest->RequestId, // Request ID
HTTP_SEND_RESPONSE_FLAG_MORE_DATA,
&response, // HTTP response
NULL, // pReserved1
&bytesSent, // bytes sent-optional
NULL, // pReserved2
0, // Reserved3
NULL, // LPOVERLAPPED
NULL // pReserved4
);
if(result != NO_ERROR)
{
myprint(
"HttpSendHttpResponse failed with %lu \n",
result
);
goto Done;
}
// Send entity body from a file handle.
dataChunk.DataChunkType =
HttpDataChunkFromFileHandle;
dataChunk.FromFileHandle.
ByteRange.StartingOffset.QuadPart = 0;
dataChunk.FromFileHandle.
ByteRange.Length.QuadPart =
HTTP_BYTE_RANGE_TO_EOF;
dataChunk.FromFileHandle.FileHandle = hTempFile;
result = HttpSendResponseEntityBody(
m_req_queue,
pRequest->RequestId,
0, // This is the last send.
1, // Entity Chunk Count.
&dataChunk,
NULL,
NULL,
0,
NULL,
NULL
);
if(result != NO_ERROR)
{
myprint(
"HttpSendResponseEntityBody failed %lu\n",
result
);
}
myprint("post response success.");
goto Done;
break;
default:
myprint(
"HttpReceiveRequestEntityBody failed with %lu \n",
result);
goto Done;
}
} while(TRUE);
}
else
{
myprint("post response 222");
// 此请求没有实体主体。
result = HttpSendHttpResponse(
m_req_queue, // ReqQueueHandle
pRequest->RequestId, // Request ID
0,
&response, // HTTP response
NULL, // pReserved1
&bytesSent, // bytes sent (optional)
NULL, // pReserved2
0, // Reserved3
NULL, // LPOVERLAPPED
NULL // pReserved4
);
if(result != NO_ERROR)
{
myprint("HttpSendHttpResponse failed with %lu \n",
result);
}
}
Done:
if(pEntityBuffer)
{
FREE_MEM(pEntityBuffer);
}
if(INVALID_HANDLE_VALUE != hTempFile)
{
CloseHandle(hTempFile);
DeleteFile(szTempName);
}
return result;
}
UINT CHttpServer::RecvRequestThread(LPVOID param)
{
CHttpServer *self;
self = (CHttpServer *)param;
self->DoReceiveRequests();
Sleep(INFINITE);
return 0;
}
BOOL CHttpServer::DoReceiveRequests(void)
{
ULONG ret;
DWORD bytes_read;
PHTTP_REQUEST request;
request = (PHTTP_REQUEST)m_req_buffer;
while(1)
{
RtlZeroMemory(request, m_req_buffer_size);
ret = HttpReceiveHttpRequest(m_req_queue, HTTP_NULL_ID, 0, request,
m_req_buffer_size, &bytes_read, NULL);
if(NO_ERROR == ret)
{
switch(request->Verb)
{
case HttpVerbGET:
m_listener->OnRecvRequest(this, request, REQUEST_TYPE_GET);
break;
case HttpVerbPOST:
m_listener->OnRecvRequest(this, request, REQUEST_TYPE_POST);
break;
default:
myprint("unknown request %ws \n", request->CookedUrl.pFullUrl);
SendHttpGetResponse(request, 503, "Not Implemented", "Not Implemented \r\n");
break;
}
continue;
}
if(ret == ERROR_OPERATION_ABORTED)
{
return FALSE;
}
if(ret == ERROR_INVALID_HANDLE)
{
return FALSE;
}
}
return TRUE;
}
使用:
demoDlg.h
// demoDlg.h : 头文件
//
#pragma once
#include "httpserver.h"
// CdemoDlg 对话框
class CdemoDlg : public CDialog, public IHttpServerListener
{
// 构造
public:
CdemoDlg(CWnd* pParent = NULL); // 标准构造函数
~CdemoDlg();
// 对话框数据
enum { IDD = IDD_DEMO_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
BOOL OnRecvRequest(CHttpServer *inst, PHTTP_REQUEST request, enum REQUEST_TYPE_E nRequestType);
CHttpServer m_server;
bool m_bWait;
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
};
demoDlg.cpp
// demoDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "demo.h"
#include "demoDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CdemoDlg 对话框
CdemoDlg::CdemoDlg(CWnd* pParent /*=NULL*/)
: CDialog(CdemoDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_server.Init(54321, this);
}
CdemoDlg::~CdemoDlg()
{
m_server.DeInit();
}
void CdemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BOOL CdemoDlg::OnRecvRequest(CHttpServer *inst, PHTTP_REQUEST request, enum REQUEST_TYPE_E nRequestType)
{
//此处做接口处理
if(inst && request)
{
myprint(L"recv:%ws", request->CookedUrl.pFullUrl);
myprint(L"recv:%ws", request->CookedUrl.pAbsPath);
myprint(L"recv:%ws", request->CookedUrl.pQueryString);
if(nRequestType == REQUEST_TYPE_POST)
{
inst->SendHttpPostResponse(request);
}
else
{
char paramBuf[512] = {0};
char sTemp[512] = {0};
std::string sId;
int nParam = 0;
bool bCtlOk = false;
sprintf(paramBuf,"%S",request->CookedUrl.pQueryString+1);
vector res_split;
const char split[] = "&";
char* res = strtok(paramBuf, split);
while (res != NULL)
{
res_split.push_back(res);
res = strtok(NULL, split);
}
for(int i = 0; i < res_split.size(); i++)
{
char* pos;
if(pos = strstr(res_split[i], "setId="))
{
sscanf(pos, "%*[^=]=%[^\n] ", sTemp);
sId = sTemp;
myprint("sId:%s", sTemp);
bCtlOk = true;
}
if(pos = strstr(res_split[i], "setParam="))
{
sscanf(pos, "%*[^=]=%[^\n] ", sTemp);
nParam = atoi(sTemp);
myprint("nParam:%s", sTemp);
bCtlOk = true;
}
}
if(bCtlOk)
{
//可以开启超时等待响应
/*
m_bWait = true; //等待
int nWaitTime = 50;//超时5s
while(m_bWait && nWaitTime--)
{
Sleep(100);
}*/
inst->SendHttpGetResponse(request, 200, "OK",
"OK Set Success. \r\n");
}
}
}
return true;
}
BEGIN_MESSAGE_MAP(CdemoDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
// CdemoDlg 消息处理程序
BOOL CdemoDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CdemoDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CdemoDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}
https://download.csdn.net/download/linyibin_123/87580091