已經有將近6年時間沒寫過MFC了,想想以前我也是寫VC++入門程序開發的,那時候寫協議棧、搞語音編碼、做視頻壓縮和實時數據傳輸,相比現在更多偏業務的開發,那時候搞得都是非常技術的東西。眨眼間,MFC已經退出舞台,就連微軟也在主推C#.net,曾經風光無限的MFC開發現如今已經幾乎消失;ActiveX有java的applet來替代,MFC桌面應用程序也由更簡單友好的C#取代,而軟件開發的趨勢早已經從傳統的C/S過渡到B/S,雲等等。技術的日新月異逼迫IT人不得不馬不停蹄的學習,否則,一不留神就會被這個時代所拋棄,被后來者所碾壓。時刻保持一顆不斷學習,謙遜,對技術敬畏的心,才能在這條路上才能走的更遠走得穩。
因為一些緣故,剛畢業時候帶我的老領導找我緊急支援一點東西,具體需求就是用MFC寫一個抓屏程序,把當前屏幕抓取下來,實時提交給服務器,然后再由服務中轉推送給客戶端去顯示,可以簡單理解為一個桌面監控程序。讓我幫忙處理抓屏和POST提交文件的功能。我聽了之后本來想拒絕,盡管老領導對我的印象還是VC++coder,但畢竟我已經好多年不寫MFC,着實沒有信心搞定。可是又不想辜負老領導對我期望,畢竟是他是我IT路上的引路人,所以最后還答應了。然后這個周末就一直在網上搜相關的解決方案,所幸,這些東西並不復雜,關於截屏的代碼非常多,大多都能滿足需求。但是最后關於POST提交文件這一塊遇到了困難,網上能搜索到很多關於MFC使用post上傳下載文件的代碼,但是幾乎沒有哪一個能真正用的。
我這里記錄並分享一個我最后搜索並優化的方案,先看代碼
CString CScreenPushDlg::MakeRequestHeaders(CString& strBoundary)
{
CString strFormat;
CString strData;
strFormat = _T("Content-Type: multipart/form-data; boundary=%s\r\n");
strData.Format(strFormat, strBoundary);
return strData;
}
CString CScreenPushDlg::MakePreFileData(CString& strBoundary, CString& strFileName, int iRecordID)
{
CString strFormat;
CString strData;
strFormat += _T("--%s");
strFormat += _T("\r\n");
strFormat += _T("Content-Disposition: form-data; name=\"job_number\"");
strFormat += _T("\r\n\r\n");
strFormat += _T("%d");
strFormat += _T("\r\n");
strFormat += _T("--%s");
strFormat += _T("\r\n");
strFormat += _T("Content-Disposition: form-data; name=\"image\"; filename=\"%s\"");
strFormat += _T("\r\n");
//strFormat += _T("Content-Type: audio/wav");
strFormat += _T("Content-Type: image/png");
strFormat += _T("\r\n");
strFormat += _T("Content-Transfer-Encoding: binary");
strFormat += _T("\r\n\r\n");
strData.Format(strFormat, strBoundary, iRecordID, strBoundary, strFileName);
return strData;
}
CString CScreenPushDlg::MakePostFileData(CString& strBoundary)
{
CString strFormat;
CString strData;
strFormat = _T("\r\n");
strFormat += _T("--%s");
strFormat += _T("\r\n");
strFormat += _T("Content-Disposition: form-data; name=\"act\"");
strFormat += _T("\r\n\r\n");
strFormat += _T("upload_img");
strFormat += _T("\r\n");
strFormat += _T("--%s--");
strFormat += _T("\r\n");
strData.Format(strFormat, strBoundary, strBoundary);
return strData;
}
//上傳文件數據至HTTP服務器
int CScreenPushDlg::UploadFile(const CString &szServerURL, const CString &szUploadFileName)
{
if (szServerURL.IsEmpty() || szUploadFileName.IsEmpty())
{
return -1;
}
BOOL bRet = FALSE;
DWORD dwServiceType = 0;
CString strServer = _T("");
CString strObject = _T("");
INTERNET_PORT nPort = 0;
bRet = AfxParseURL(szServerURL, dwServiceType, strServer, strObject, nPort);
if(!bRet)
{
return -2;
}
int nRet = 0;
CInternetSession Session;
CHttpConnection * pHttpConnection = NULL;
CFile fTrack;
CHttpFile* pHTTPFile = NULL;
CString strHTTPBoundary = _T("");
CString strPreFileData = _T("");
CString strPostFileData = _T("");
DWORD dwTotalRequestLength;
DWORD dwChunkLength = 0;
DWORD dwReadLength = 0;
DWORD dwResponseLength = 0;
TCHAR szError[MAX_PATH] = {0};
void* pBuffer = NULL;
LPSTR szResponse = NULL;
CString strResponse = _T("");
BOOL bSuccess = TRUE;
CString strDebugMessage = _T("");
if (FALSE == fTrack.Open(szUploadFileName, CFile::modeRead | CFile::shareDenyWrite))
{
//AfxMessageBox(_T("Unable to open the file."));
return -3;
}
int nPos = szUploadFileName.ReverseFind('\\');
CString strFileName = szUploadFileName.Mid(nPos+1);
int iRecordID = _ttoi(m_Num);
strHTTPBoundary = _T("----istroop----");
strPreFileData = MakePreFileData(strHTTPBoundary, strFileName, iRecordID);
strPostFileData = MakePostFileData(strHTTPBoundary);
dwTotalRequestLength = strPreFileData.GetLength() + strPostFileData.GetLength() + fTrack.GetLength();
dwChunkLength = 64 * 1024;
pBuffer = malloc(dwChunkLength);
if (NULL == pBuffer)
{
fTrack.Close();
return -4;
}
try
{
pHttpConnection = Session.GetHttpConnection(strServer,nPort);
pHTTPFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject);
pHTTPFile->AddRequestHeaders(MakeRequestHeaders(strHTTPBoundary));
pHTTPFile->SendRequestEx(dwTotalRequestLength, HSR_SYNC | HSR_INITIATE);
#ifdef _UNICODE
USES_CONVERSION;
pHTTPFile->Write(W2A(strPreFileData), strPreFileData.GetLength());
#else
pHTTPFile->Write((LPSTR)(LPCSTR)strPreFileData, strPreFileData.GetLength());
#endif
dwReadLength = -1;
DWORD m_dwUploadSize = 0;
while (0 != dwReadLength)
{
// strDebugMessage.Format(_T("%u / %un"), fTrack.GetPosition(), fTrack.GetLength());
// TRACE(strDebugMessage);
dwReadLength = fTrack.Read(pBuffer, dwChunkLength);
if (0 != dwReadLength)
{
m_dwUploadSize += dwReadLength;
pHTTPFile->Write(pBuffer, dwReadLength);
}
}
#ifdef _UNICODE
pHTTPFile->Write(W2A(strPostFileData), strPostFileData.GetLength());
#else
pHTTPFile->Write((LPSTR)(LPCSTR)strPostFileData, strPostFileData.GetLength());
#endif
pHTTPFile->EndRequest(HSR_SYNC);
dwResponseLength = pHTTPFile->GetLength();
while (0 != dwResponseLength)
{
szResponse = (LPSTR)malloc(dwResponseLength + 1);
szResponse[dwResponseLength] = '\0';
pHTTPFile->Read(szResponse, dwResponseLength);
strResponse += szResponse;
free(szResponse);
szResponse = NULL;
dwResponseLength = pHTTPFile->GetLength();
}
//strResponse;
}
catch (CException* e)
{
// e->GetErrorMessage(szError, MAX_PATH);
// e->Delete();
nRet = -5;
}
if (NULL != pHTTPFile)
{
pHTTPFile->Close();
delete pHTTPFile;
pHTTPFile = NULL;
}
fTrack.Close();
if (NULL != pBuffer)
{
free(pBuffer);
pBuffer = NULL;
}
return nRet;
}
這段代碼相比網上能搜到的大多數方案,最大的區別在於寫了一個MakeRequestHeaders和MakePreFileData方法。
因為這些年主要寫java和前端js開發,java中有httpclient可以直接發起post/get請求,非常方便,js中有ajax、axios等都非常方便的發起的請求,類似設置header、Content-type、入參data等都有很簡單明了的方法,但是VC++中使用CHttpFile類進行post請求卻相對復雜些,主要在於api封裝的顆粒度。我們先來看以下下面這兩張截圖:
(截圖一)
(截圖二)
這是一個ajax發起的上傳文件並攜帶參數的POST請求,我以前都是只關注截圖一,從來沒考慮也沒看過截圖二中的內容。其實對於MFC中的POST請求,就需要我們自己封裝原始的數據,即自己封裝好截圖二中的數據格式,類似Content-Disposition、Content-Type、通過name指定參數名,通過filename指定具體文件,還要設置FormBoundar等。這些東西在java、js中時絕對不會接觸到的。而MakePreFileData正是起到這個作用。
大概記錄到這里吧,開發語言這東西,真的是不寫就會生疏,以前寫VC++都能盲打api,現在看數據類型的聲明類都覺得生疏,真的是感慨萬千。畢業的這幾年收獲最大的大概是不像以前那么玻璃心,見不得別人和自己相左的意見;但是相對的,我覺得那時候的自己更自負,而現在我卻有點過分自知,甚至不那么自信。
認清自己不難,難的是知道自己的不足和缺點后怎么鞭策自己去改變。
---------------------