|
Nah, creating thread is pretty straightforward, there are lots of examples here on code project. A quick search should find several like this CThread - a Worker Thread wrapper class[^]
Or you can use CWinThread (if you're derived from CWinApp). Or use _beginthreadex()[^] native Windows API.
Threads is a good thing to learn early. In this case, you can create the simplest of thread routines and not worry about complicated events and synchronization later. Just get it to do your download and send a completion message to your main windows process.
|
|
|
|
|
Well since I used the simplest way to download, the simplest thread sounds like a good idea. I'll start that tomorrow.
Right now I'm experimenting with creating a cancel button in the download class, and seeing if I can capture the message when the button is clicked. I got the button to appear and position itself with the right font in the Callback under BINDSTATUS_CONNECTING
I wrapped the original call to the class in a do loop, to keep it from progressing. Got the idea from the socket code I wrote.
I'm just messing around right now, experimenting till morning.
do {
bResult = caDownload._download_SQLServerExpress_GUI(cWnd, pb_SQL_Server_Download_Status, lbl_SQL_Server_Download_Status, bAreWeX64 );
} while (bResult == FALSE);
|
|
|
|
|
Which one was the easy one?, they both look intimidating.
|
|
|
|
|
They're both a lot easier having done them than before. I had no idea about threads or sockets 2 1/2 weeks ago, but have now learnt enough to get by and have cobbled together a rss reader - one which can pre-load all subscribed feeds in parallel, each in it's own thread.
Notifying the main window of a thread's completion is the easier of the two. Without MFC, it's as (1) defining a user message, and deciding on the format (if any) of the passed information (2) sending that message to the main window upon completion (3) handling that message in the main window.
E.g
Part 1 - Main window completion notification mechanism
#define WM_GALLERY_HTML_AVAILABLE WM_USER+1
#define WM_FILE_DOWNLOAD_COMPLETE WM_USER+2
...
...
...
__stdcall void galleryHtmlDownloadedCallbackFunc(PVOID data)
{
downloadHeader_t *hdr;
hdr = (downloadHeader_t*)data;
galHtmlStr = hdr->result;
galUrlStr = hdr->szUrl;
free(hdr->result);
hdr->result = NULL;
hdr->contentLen = 0;
PostMessage(mainDlgWnd, WM_GALLERY_HTML_AVAILABLE, 0, 0);
}
...
...
...
...
case WM_INITDIALOG:
mainDlgWnd = hwndDlg;
editWnd = GetDlgItem(hwndDlg, IDC_RICHEDIT1);
SetFocus(GetDlgItem(hwndDlg, IDC_RICHEDIT1));
return FALSE;
case WM_FILE_DOWNLOAD_COMPLETE:
return true;
case WM_GALLERY_HTML_AVAILABLE:
findLinks((char*)galHtmlStr.c_str(), links, validExts);
strLinks.clear();
printf("Number of links: %d\n", links.size());
for (i=0; i<links.size(); i++)
strLinks += links[i] + "\n";
SetDlgItemText(mainDlgWnd, IDC_RICHEDIT1, strLinks.c_str());
return true;
case WM_CLOSE:
EndDialog(hwndDlg, 0);
return TRUE;
Part 2 - Threading mechanism
static downloadHeader_t curDownload;
GetWindowText(urlHwnd, szUrl, urlLen+1);
savePath = getFolderName(mainDlgWnd, "C:\\001");
if (strlen(szUrl) && strlen(savePath))
{
curDownload.szUrl = szUrl;
curDownload.callback = (voidFuncPtr)galleryHtmlDownloadedCallbackFunc;
curDownload.saveFilePath = savePath;
_beginthread(DownloadThread, NULL, (void*)&(curDownload));
}
delete savePath;
delete szUrl;
typedef void (*voidFuncPtr)(void*);
struct downloadHeader_t
{
string szUrl; voidFuncPtr callback; string saveFilePath; PVOID lParam;
SOCKET conn;
char *readBuffer, *sendBuffer, *tmpBuffer, *result;
string server, filepath, filename;
long thisReadSize, headerLen, contentLen;
};
void mParseUrl(char *mUrl, string &serverName, string &filepath, string &filename)
{
string::size_type n;
string url = mUrl;
if (url.substr(0,7) == "http://")
url.erase(0,7);
n = url.find('/');
if (n != string::npos)
{
serverName = url.substr(0,n);
filepath = url.substr(n);
n = filepath.rfind('/');
filename = filepath.substr(n+1);
}
else
{
serverName = url;
filepath = "/";
filename = "";
}
}
SOCKET connectToServer(char *szServerName, WORD portNum)
{
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
SOCKET conn;
conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (conn == INVALID_SOCKET)
return NULL;
if(inet_addr(szServerName)==INADDR_NONE)
{
hp=gethostbyname(szServerName);
}
else
{
addr=inet_addr(szServerName);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL)
{
closesocket(conn);
return NULL;
}
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(portNum);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return NULL;
}
return conn;
}
int getHeaderLength(char *content)
{
const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
char *findPos;
int ofset = -1;
findPos = strstr(content, srchStr1);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr1);
}
else
{
findPos = strstr(content, srchStr2);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr2);
}
}
return ofset;
}
void __stdcall DownloadThread(void *dat)
{
downloadHeader_t *hdr;
hdr = (downloadHeader_t *)dat;
const int bufSize = 512;
printf("download thread - \n");
hdr->result = NULL;
hdr->readBuffer = (char*)malloc(bufSize);
hdr->sendBuffer = (char*)malloc(bufSize);
hdr->tmpBuffer = (char*)malloc(bufSize);
printf("download thread - \n");
mParseUrl((char*)hdr->szUrl.c_str(), hdr->server, hdr->filepath, hdr->filename);
printf("download thread - \n");
hdr->conn = connectToServer((char*)hdr->server.c_str(), 80);
if (hdr->conn)
printf("download thread - connected?\n");
sprintf(hdr->tmpBuffer, "GET %s HTTP/1.0", hdr->filepath.c_str());
strcpy(hdr->sendBuffer, hdr->tmpBuffer);
strcat(hdr->sendBuffer, "\r\n");
sprintf(hdr->tmpBuffer, "Host: %s", hdr->server.c_str());
strcat(hdr->sendBuffer, hdr->tmpBuffer);
strcat(hdr->sendBuffer, "\r\n");
strcat(hdr->sendBuffer, "\r\n");
send(hdr->conn, hdr->sendBuffer, strlen(hdr->sendBuffer), 0);
hdr->contentLen = 0;
while(1)
{
memset(hdr->readBuffer, 0, bufSize);
hdr->thisReadSize = recv (hdr->conn, hdr->readBuffer, bufSize, 0);
if ( hdr->thisReadSize <= 0 )
break;
hdr->result = (char*)realloc(hdr->result, hdr->thisReadSize+hdr->contentLen);
memcpy(hdr->result+hdr->contentLen, hdr->readBuffer, hdr->thisReadSize);
hdr->contentLen += hdr->thisReadSize;
}
hdr->headerLen = getHeaderLength(hdr->result);
hdr->contentLen -= hdr->headerLen;
memmove(hdr->result, hdr->result+hdr->headerLen, hdr->contentLen);
realloc(hdr->result, hdr->contentLen+1);
hdr->result[hdr->contentLen] = 0x0;
closesocket(hdr->conn);
free(hdr->readBuffer);
free(hdr->sendBuffer);
free(hdr->tmpBuffer);
if (hdr->callback)
hdr->callback(hdr);
}
|
|
|
|
|
Nice job on the code modules. I can see that creating a thread to run the program in is not that difficult. I have an idea of the components needed now after examining your sample.
Let's see what I can some up with today. I'll be back.
|
|
|
|
|
Not a problem.
I just noticed that I declared the DownloadThread function as being __stdcall. It's a mistake, and should read __cdecl. Errors with calling conventions are, I suspect, begin the majority of my program crashes.
FWIW, it's _beginthreadex that wants a thread function called using __stdcall.
|
|
|
|
|
I didn't run your sample. I just examined it for reverse engineering to suit my needs.
I spend half a day yesterday cleaning up my code, but was not able to successfully test my thread. I got confused with how the process worked. In other words, I show the cancel button, then I run a function, and the cancel button won't send a message back to wndproc until it completes. So I messsed around with the way I made the original class, and fixed my mistakes.
Today I will try to get my thread working.
|
|
|
|
|
He, he. I'm familiar with that feeling. It took me quite a while to wrap my head around when and where was the appropriate place to do things. I've since re-done the function such that it can send progress callbacks too.
Haven't bothered with a cancel functionality yet - though I'd imagine it to be a simple matter of setting a flag from the main program thread, checking this flag each time through the receive loop in the download thread. (may have to use a mutex to access the flag in a safe manner)
|
|
|
|
|
I have the create thread working now. just went simple on it.
I already had the progress bar and status text working, now the new thread launches the download, and the cancel button responds to my click. I know how to stop the Download Callback with E_ABORT, I just need to figure out how to send that message to the progress loop.
After that, I need to figure out how to destroy the thread on cancel, or when the download is complete, _endthread,
but I'm not sure if I need to close the handle, and the WaitForObject.
Getting there. Still experimenting with it, so it's not finalized.
hThread = (HANDLE)_beginthreadex(NULL, 0, SQL_SoftwareDownload, NULL, 0, (unsigned *)&thread_id);
return resultCode;
}
unsigned int __stdcall SQL_SoftwareDownload(void *) {
int iReturn = 0;
HRESULT hr = E_FAIL;
callback = new SQL_Servers_BindCallback;
callback->m_MDIChild = hSQL_Servers_Download;
callback->m_Progress_Text = lbl_SQL_Server_Download_Status;
callback->m_Progress_Bar = pb_SQL_Server_Download_Status;
callback->b_Downloading = TRUE;
hr = URLDownloadToFile(NULL, szUrl, szFileName, 0, callback);
if (hr == S_OK)
iReturn = 1;
return iReturn;
}
|
|
|
|
|
Congrats.
The HANDLE that you get back from _beginthreadex() should eventually be cleaned up with CloseHandle(). There is a resource that is backed by the HANDLE and you should clean that up when you are done with it.
The HANDLE remains a "waitable object" even after the thread exits. That way, you can do a WaitForSingleObject() on the HANDLE to either wait for the thread to finish or to test for completion (by adding a timeout value to the wait). Once the thread exits, the HANDLE becomes "signaled" and that will satisfy any wait, even one that comes along after the thread completes. That's why you have to eventually close it.
|
|
|
|
|
That sounds tricky.
I'm not sure what to do yet, but I'm moving forward. Here is my process
Button Click Next ->
_download_SQLServer_Begin - GUI stuff - Call _download_SQLServer_Package
_download_SQLServer_Package - Download Prep - Start Thread
SQL_SoftwareDownload - load callback, call urldownload function
_download_SQLServer_End - End Thread stuff, GUI unwind
cancel button click!
_cancel_SQLServer_Package - stop download process- send message to bind callback to E_ABORT
call _download_SQLServerEnd for thread and GUI unwind
I had to split the gui stuff in half - sorry to bombard you with code, I'm sure theirs much to delete
BOOL _download_SQLServer_Begin( HWND cWnd )
{
BOOL bResult = FALSE;
BOOL bPreFlightCheck = FALSE;
BOOL bAreWeX64 = FALSE;
CA_Windows caWin;
ShowWindow(lbl_SQL_Server_Download_Instructions, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Next, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Exit, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Cancel, SW_SHOW);
ShowWindow(lbl_SQL_Server_Download_Label, SW_HIDE);
ShowWindow(lbl_SQL_Server_Download_Status, SW_SHOW);
bAreWeX64 = caWin._getProcessorArchitecture();
if (bAreWeX64 == TRUE) {
SetWindowText(lbl_SQL_Server_Download_Status, L"Requesting X64 Version from microsoft.com");
}
else {
SetWindowText(lbl_SQL_Server_Download_Status, L"Requesting X86 Version from microsoft.com");
}
SendMessage(pb_SQL_Server_Download_Status, PBM_SETPOS, (WPARAM)0, LPARAM(100));
SendMessage(pb_SQL_Server_Download_Status, PBM_SETSTEP, (WPARAM)1, NULL);
ShowWindow(pb_SQL_Server_Download_Status, SW_SHOW);
UpdateWindow(pb_SQL_Server_Download_Status);
UpdateWindow(cWnd);
bResult = _download_SQLServer_Package( bAreWeX64 );
return bResult;
}
BOOL _download_SQLServer_End( HWND cWnd ) {
BOOL bResult = FALSE;
ShowWindow(bt_SQL_Server_Download_Cancel, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Exit, SW_SHOW);
if ( bResult == TRUE ) {
SetWindowText(lbl_SQL_Server_Download_Status, L"Download has completed successfully");
ShowWindow(lbl_SQL_Server_Download_Label, SW_SHOW);
BOOL preFlightCheck = FALSE;
Sleep(2000);
int msgboxID = MessageBoxEx(hSQL_Servers_Download,
L"Would you like to install SQL Server now?",
L"SQL Server has been downloaded successfully",
MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONINFORMATION,
LANG_NEUTRAL
);
switch ( msgboxID )
{
case IDOK:
preFlightCheck = TRUE;
break;
case IDCANCEL:
preFlightCheck = FALSE;
break;
}
}
return bResult;
}
BOOL _cancel_SQLServer_Package( HWND cWnd )
{
BOOL bResult = FALSE;
int msgboxID;
msgboxID = MessageBoxEx(hSQL_Servers_Download,
L"Canceling the download will prevent you from installing SQL Server?",
L"SQL Server is downloading now",
MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONINFORMATION,
LANG_NEUTRAL
);
switch ( msgboxID )
{
case IDOK:
_download_SQLServer_End( cWnd );
bResult = FALSE;
break;
case IDCANCEL:
bResult = FALSE;
break;
}
return bResult;
}
BOOL _download_SQLServer_Package( BOOL X64 )
{
BOOL resultCode = FALSE;
BOOL bThread = FALSE;
HRESULT hr = S_OK;
char *fs_X86 = "762761216";
char *fs_X64 = "834555904";
char *ckSum_X86 = "10ef23fe7bd9b6a64dcaf1927dd19df7";
char *ckSum_X64 = "5122f358a63e12a3095aaabba8203292";
WCHAR *szUrl_X86 = L"http://download.microsoft.com/download/9/7/6/9761DF25-CBD8-4D48-9415-065F6BD4E63D/SQLEXPRADV_x86_ENU.exe";
WCHAR *szUrl_X64 = L"http://download.microsoft.com/download/9/7/6/9761DF25-CBD8-4D48-9415-065F6BD4E63D/SQLEXPRADV_x64_ENU.exe";
WCHAR *szFileName_X86 = L"SQLEXPRADV_x86_ENU.EXE";
WCHAR *szFileName_X64 = L"SQLEXPRADV_x64_ENU.EXE";
WCHAR *szTargetFolder = NULL;
WCHAR *szSystemDrive = NULL;
WCHAR *szFileName_X = NULL;
DWORD dwReserved = 0;
LPCWSTR szError = NULL;
if (X64 == FALSE) {
int iSize = wcslen(szUrl_X86)*sizeof(szUrl_X86);
szUrl = szUrl_X86;
}
else {
int iSize = wcslen(szUrl_X64)*sizeof(szUrl_X64);
szUrl = szUrl_X64;
}
CA_Windows *caWin = new CA_Windows;
szSystemDrive = caWin->_getWindowsFolder();
caWin = NULL;
wcsncpy_s(szFileName, szSystemDrive, wcslen(szSystemDrive) );
wcsncat_s(szFileName, L"\\Temp\\", wcslen(L"\\Temp\\") );
if (X64 == FALSE) {
wcsncat_s(szFileName, szFileName_X86, wcslen(szFileName_X86) );
}
else {
wcsncat_s(szFileName, szFileName_X64, wcslen(szFileName_X64) );
}
hThread = (HANDLE)_beginthreadex(NULL, 0, SQL_SoftwareDownload, NULL, 0, (unsigned *)&thread_id);
return resultCode;
}
unsigned int __stdcall SQL_SoftwareDownload(void *) {
int iReturn = 0;
HRESULT hr = E_FAIL;
SQL_Servers_BindCallback *callback;
callback = new SQL_Servers_BindCallback;
callback->m_MDIChild = hSQL_Servers_Download;
callback->m_Progress_Text = lbl_SQL_Server_Download_Status;
callback->m_Progress_Bar = pb_SQL_Server_Download_Status;
callback->Release();
hr = URLDownloadToFile(NULL, szUrl, szFileName, 0, callback);
if (hr == S_OK)
iReturn = 1;
return iReturn;
}
|
|
|
|
|
From a look over your code, I think that you're seeing the way to cancel the download as being to kill the thread. Given that you're using URLDownloadToFile (a blocking function), there's no apparent way to ask it to stop. With this in mind the clear choice would be to kill the thread - PROVIDING it's safe to nuke a thread in the middle of this function.
Providing it's safe, I guess you could just kill the thread. From what I can tell, you can only terminate a thread started with _beginthreadex from within it, using _endthreadex (you can't pass a thread ID to the function). With this in mind - it seems that _beginthreadex and _endthreadex are insufficient for your needs. If you were looping in the download function (like I was with recv in my download function), you could check a flag to see if the download should be discontinued - if so, simply exit the receive loop gracefully, cleaning up any/all memory resources.
Since you're not running a loop that you can check for the flag, it seems that your only option is to cancel the download be exiting/terminating the thread. In this case, it may perhaps be that you need to use the OpenThread, CloseThread, SuspendThread, ResumeThread, TerminateThread family of functions, since they allow you to pass a thread identifier to Suspend, Resume and Terminate any given thread.
There's some history associated with beginthread, _beginthreadex and OpenThread. I forget which supersedes which and for quite what reason, though I recall that there were inherent problems that caused the 're'-implementation. I'm inclide to think it had something to do with memory leaks or issues with handles.
|
|
|
|
|
I tried killing the thread, but it destroyed my window. but that was before I split the functions.
Here's the other half of the code - Callback Statu. I think somehow, there is a way for the callback to receive messages or values during the OnProcess loop, or it's just optimism on my behalf. I should be able to get the loop to ask the window if cancel has been invoked.
There is a code sample on CP that uses a dialog box, in which a handle is created back to the dialog box, I'm not sure if the handle is bi-directional.
FYI:
I'm not not looking for code, just ideas or pointers.
This is the callback, the h file is seperate. I'm fuzzy about this line in the constructor
: m_Progress_Text(NULL), m_Progress_Bar(NULL) {
in which I' not really sure what it means, or what it does.
#include "stdafx.h"
#include <stdlib.h>
#include "atlstr.h"
#include <string>
#include <tchar.h>
#include <math.h>
#include "resource.h"
#include "stdio.h"
#include <commctrl.h>
#include <Windows.h>
#include <Winuser.h>
#include <Winbase.h>
#include "SQL_Servers_BindCallback.h"
#pragma comment(lib, "urlmon.lib")
#pragma comment(lib,"wininet.lib")
#define RETURN_ON_FAILURE(hr) if (FAILED(hr)) return (hr);
SQL_Servers_BindCallback::SQL_Servers_BindCallback ( ) : m_Progress_Text(NULL), m_Progress_Bar(NULL) {
ShowWindow(m_Progress_Bar, SW_SHOW);
UpdateWindow(m_Progress_Bar);
UpdateWindow(m_MDIChild);
SendMessage(m_Progress_Bar, PBM_SETRANGE, 0, MAKELPARAM(0, 5000) );
SendMessage(m_Progress_Bar, PBM_SETPOS, (WPARAM)10, 0);
}
SQL_Servers_BindCallback::~SQL_Servers_BindCallback( ) {
}
HRESULT SQL_Servers_BindCallback::OnProgress(
ULONG ulProgress,
ULONG ulProgressMax,
ULONG ulStatusCode,
LPCWSTR wszStatusText)
{
static int iMsgLen;
static double dBytes;
static double dTotal;
static double dPercent;
static double dPB;
static TCHAR szStatusMessage[80];
wcsncpy_s(szStatusMessage, L"please wait...", wcslen(L"please wait...") );
switch (ulStatusCode) {
case BINDSTATUS_CONNECTING:
wcsncpy_s(szStatusMessage, L"Connecting to download.microsoft.com", wcslen(L"Connecting to download.microsoft.com") );
break;
case BINDSTATUS_DOWNLOADINGDATA:
dBytes = ((float)ulProgress / (1024*1024));
dTotal = ((float)ulProgressMax / (1024*1024));
dPercent = dBytes/dTotal*100;
if ((dBytes > 0.01) && (dTotal > 0.01)) {
swprintf_s(szStatusMessage,
80,
L"Downloading %.2f MB of %.2f MB [%.1f%%]",
dBytes, dTotal, dPercent
);
}
break;
case BINDSTATUS_ENDDOWNLOADDATA:
wcsncpy_s(szStatusMessage, L"Disconnecting from download.microsoft.com", wcslen(L"Disconnecting from download.microsoft.com") );
Sleep(1000);
return TRUE;
break;
}
SetWindowText(m_Progress_Text, szStatusMessage);
dPB = (float)ulProgress/ulProgressMax*5000.0;
SendMessage(m_Progress_Bar, PBM_SETPOS, (WPARAM)dPB, 0);
SendMessage(m_Progress_Bar, PBM_SETRANGE, 0, MAKELPARAM(0, 5000) );
iLoopCount++;
return S_OK;
}
HRESULT SQL_Servers_BindCallback::OnStartBinding(
DWORD dwReserved,
IBinding __RPC_FAR *pib)
{ return S_OK; }
HRESULT SQL_Servers_BindCallback::GetPriority(
LONG __RPC_FAR *pnPriority)
{ return E_NOTIMPL; }
HRESULT SQL_Servers_BindCallback::OnLowResource(
DWORD reserved)
{ return E_NOTIMPL; }
HRESULT SQL_Servers_BindCallback::OnStopBinding(
HRESULT hresult,
LPCWSTR szError)
{ return S_OK; }
HRESULT SQL_Servers_BindCallback::GetBindInfo(
DWORD __RPC_FAR *grfBINDF,
BINDINFO __RPC_FAR *pbindinfo)
{ return E_NOTIMPL; }
HRESULT SQL_Servers_BindCallback::OnDataAvailable(
DWORD grfBSCF,
DWORD dwSize,
FORMATETC __RPC_FAR *pformatetc,
STGMEDIUM __RPC_FAR *pstgmed)
{ return S_OK; }
HRESULT SQL_Servers_BindCallback::OnObjectAvailable(
REFIID riid,
IUnknown __RPC_FAR *punk)
{ return E_NOTIMPL; }
ULONG SQL_Servers_BindCallback::AddRef()
{ return 0; }
ULONG SQL_Servers_BindCallback::Release()
{ return 0; }
HRESULT SQL_Servers_BindCallback::QueryInterface(
REFIID riid,
void __RPC_FAR *__RPC_FAR *ppvObject)
{ return E_NOTIMPL; }
|
|
|
|
|
jkirkerx wrote: I should be able to get the loop to ask the window if cancel has been invoked.
I'm not too sure about this hope. My understanding had been that the communication should be the other way around - i.e The main window will tell the thread to cancel the download, rather than the thread asking the window if it should continue/cancel
jkirkerx wrote: FYI:
I'm not not looking for code, just ideas or pointers.
This is the callback, the h file is seperate. I'm fuzzy about this line in the constructor
: m_Progress_Text(NULL), m_Progress_Bar(NULL) {
in which I' not really sure what it means, or what it does.
Sure thing, sorry if I've innundated you with useless code, confusing the matter.
I think you'l find that the above snippet will call the constructors for the progress text and the progress bar. I reckon you'd find that if you changed the NULLs to "myProgTest" and 50, respectively that you'd have a progress bar at 50% with the text myProgTest - why not give it a try?
After thinking over this some more last night, I decided to have a go at implementing a cancel button for my downloads. In this case, I added an extra field to the struct that I pass to the thread function - a simple boolean flag, bCancelled. Cancelling the download is a simple matter of setting this flag in the struct from the main thread in response to the cancel button.
In the download function, during the receive loop I simply check the status of this flag. If true, I just clean-up the memory I used and exit. It seems to work flawlessly. Also, since it's a graceful exit, I can be certain of the number of bytes downloaded before it was cancelled. This has a two-fold benefit -
(1) I can close the handle on the file I'm saving it too, avoiding total loss of the data and
(2) I know where the download is up to, so I can resume it at a later time of my choosing
Initially, my aim had been to get the size of a resource if given a url. Since wininet didn't seem to provide the functionality, I had to get access to the raw HTTP headers. From what I could determine, this required low-level use of sockets. However, the rewards have been, well worth-while.
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_BTN_CANCEL:
singleDownload.bCancelled = true;
return true;
hdr->contentLen = 0;
while(1)
{
if (hdr->bCancelled)
break;
memset(hdr->readBuffer, 0, bufSize);
hdr->thisReadSize = recv (hdr->conn, hdr->readBuffer, bufSize, 0);
if ( hdr->thisReadSize <= 0 )
break;
|
|
|
|
|
I just didn't want to sound like I wanted someone to fix my code because I posted it. No insult intended.
I got it to work this afternoon. I sort of did the same thing, but not as elaborate. I examined the code sample in CP, and put the if statement back in, and made it a long this time instead of a boolean.
I just needed to get the mechanics working, so I didn't go around in circles trying complex things. I cleaned up this program alot, and got rid of some the erratic side effects of experimenting. It's not perfect in the purest sense, but it's a start. Now I can clean it up some more based on feedback, and write a check to see if the file is already downloaded, and integrate the installation program I already wrote.
Edit:
After reading your post several times, I realize that all of the benefits you listed are well worth it. And you right about pushing the cancel to the thread. But having the byte count, and being able to resume is pretty sweet, when you need to pause to do something else, and pick up again. I wanted to write a socket based program, but I've only been doing this since oct 5, so I had to keep it simple for now. Are you checksuming your download?
I put the variable in the h file for the callback
SQL_Servers_BindCallback::SQL_Servers_BindCallback ( ) : m_Progress_Text(NULL), m_Progress_Bar(NULL) {
}
SQL_Servers_BindCallback::~SQL_Servers_BindCallback( ) {
}
HRESULT SQL_Servers_BindCallback::OnProgress(
ULONG ulProgress,
ULONG ulProgressMax,
ULONG ulStatusCode,
LPCWSTR wszStatusText)
{
static int iMsgLen;
static double dBytes;
static double dTotal;
static double dPercent;
static double dPB;
static TCHAR szStatusMessage[80];
if ( g_fAbortDownload == 0 ) <- put in h file of this cpp file
return E_ABORT;
Then I made the callback global to the window page, and changed the way I close the thread.
BOOL _download_SQLServer_Begin( BOOL bCancel ) {
int errorNumber;
hThread = (HANDLE)_beginthreadex(NULL, 0, SQL_SoftwareDownload, NULL, 0, (unsigned *)&thread_id);
if (hThread == 0) {
errorNumber = errno;
}
Sleep(2000);
int rv = WaitForSingleObjectEx(hThread, 3000, FALSE);
if (rv == WAIT_OBJECT_0)
resultCode = 1;
return resultCode;
}
unsigned int __stdcall SQL_SoftwareDownload(void *) {
int iReturn = 0;
HRESULT hr = E_FAIL;
callback = new SQL_Servers_BindCallback;
callback->m_MDIChild = hSQL_Servers_Download;
callback->m_Progress_Text = lbl_SQL_Server_Download_Status;
callback->m_Progress_Bar = pb_SQL_Server_Download_Status;
hr = URLDownloadToFile(NULL, szUrl, szFileName, 0, callback);
if (hr == S_OK)
iReturn = 1;
return iReturn;
}
BOOL _download_SQLServer_End( BOOL bCancel ) {
BOOL bResult = FALSE;
BOOL bHandle = FALSE;
unsigned iThread;
callback->g_fAbortDownload = 0; < - made the call here, callback picked it up and aborted
callback->Release();
SetWindowText(lbl_SQL_Server_Download_Status, L"Disconnecting from download.microsoft.com");
UpdateWindow(hSQL_Servers_Download);
iThread = WaitForSingleObjectEx(hThread, 3000, FALSE); <- I thought this was needed
bHandle = CloseHandle(hThread); < - I know _endthread is called automatically
if (bHandle == 0) <- Not sure, but it shuts down real smooth like.
Sleep(2000);
ShowWindow(pb_SQL_Server_Download_Status, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Cancel, SW_HIDE);
ShowWindow(bt_SQL_Server_Download_Exit, SW_SHOW);
|
|
|
|
|
jkirkerx wrote: I just didn't want to sound like I wanted someone to fix my code because I posted it. No insult intended.
Oh, no no no no no. None taken - I'd feared having been an irritation.
Fantastic! Glad to hear you've got it working. 'twas quite the source of satisfaction for me when I had a running stable program, rather than just crash after crash[after crash, after crash]
Indeed, I used URLDownloadToFile or something similar in a VBA crud app in the last job I was at, though found it to be a pain for all sorts of reasons - not the least of which being that it's a blocking call - not so special in MS office apps when the whole UI freezes.. :grin:
I also hate the idea of having to continually allocate/reallocate memory to receive the download, hence the idea of being able to retrieve the size of content from the HTTP headers. Delving into the headers, I discovered the "Byte-Range:" or something similarly named, as a tag in the headers. This allows you to specify which _part_ of a particular resource you want. This is the thing that gives you the download resume functionality, it's also a way to (a) download large files with a number of parallel connections or (b)download only part of a file - e.g first part of an image file to determine dimensions (c) thinking about one of your questions, it would also mean you could checksum smaller portions of the file, minimizing the number of bytes that need to be re-downloaded in the event of a check-sum failure - think along the lines of torrent programs, where the download is split up into pieces, each of which may be check-summed and repeated in the event of failure. A 700mb file may be split up into 700 pieces of 1 meg, for instance. Much nicer to re-download 1mb rather than the whole 700
Sony PlayStation network, I hate you for this oversight. I am ever so tired of using 190mb of my monthly 4000mb plan to download a system-update, necessary to continue to play online - only to find that it fails 3, 4, 5 times in a row. Brilliant! 20% of my internet gone because nobody thought to checksum pieces rather than the whole payload
Oh, and in answer to your question, nope - not check-summing the download at this stage, thanks for causing me to start thing about it.
Given that it would appear that you'd only ever have one instance of the above class at any given time, looks like the global flag in the .h file works okay. Of course, if it were a member of the class itself, you could have multiple instances of the class co-existing - though I can see no reason for that for this particular implementation. A more generic threaded file download class whether it be inherited or not, would be the place. In my implementation I've tested with about 15 simultaneous threads all downloading rss feeds, also with a test-bed app that grabs 10 files simultaneously and updates the download progress of each in a listview as each progress callback is received. - I can cancel any of them by choice. Next comes resume!
|
|
|
|
|
Looks like the checksuming is a lot of work at this point.
I testing my code now, 742 meg download, and starting and stopping (Cancel). Strange, it picks up where it left off. I added a filesize check, to see if I have a partial or complete download before starting. If complete just install the file, that what the checksum was for, make sure it's legit.
I put that global in the callback h file, and cleaned up the h and cpp files for a class, so I suspect I can call it several times for concurrent use. I made the callback class global in the window code, so I call reference it from several different functions, and then release it at the end. All I need are more threads. I think I'll keep the downloads one at a time to get all the programs needed.
So far 554 megs, no stalls or freezes, progress bar looks accurate, percentage is correct.
No you were not a pain, I just had to much written already, and in theory, my code should work whether it was a dialog box or mdi child window, I didn't see the difference. I understood the last sample, just had to cherry pick what I needed.
What's your program for?. Commercial use?
I make web applications, and I've been waiting 8 years for a program that can install web applications with ease. Kind of like buying a bar-b-Que already build, having it delivered - with a bag of coals and matches. I tried very good instructions as a pdf, but the 1 click window app seems to be the way to go. I have a 99% failure rate on installation right now, so the goal is a single app packaged in installshield, in which it installs the window program, and sets up everything you need, and launches the browser window with the web code ready to go. Then add data import capability, like yahoo store, and an interface that sets your company info and preferences.
Download almost over, I need to stop and save a copy of a partial download so I don't have to keep downloading it.
Window Code:
SQL_Servers_BindCallback *callback;
HANDLE hThread;
long thread_id;
Function in Window Code:
callback = new SQL_Servers_BindCallback;
callback->m_MDIChild = hSQL_Servers_Download;
callback->m_Progress_Text = lbl_SQL_Server_Download_Status;
callback->m_Progress_Bar = pb_SQL_Server_Download_Status;
Class H:
#if !defined SQL_Servers_BindCallback_H
#define SQL_Servers_BindCallback_H
class SQL_Servers_BindCallback : public IBindStatusCallback
{
public:
SQL_Servers_BindCallback();
~SQL_Servers_BindCallback();
// Pointer to the download progress dialog.
LONG g_fAbortDownload;
HWND m_MDIChild;
HWND m_Progress_Text;
HWND m_Progress_Bar;
int iLoopCount;
// The time when the download should timeout.
BOOL m_bUseTimeout;
CTime m_timeToStop;
STDMETHOD(OnProgress)(
/* [in] */ ULONG ulProgress,
/* [in] */ ULONG ulProgressMax,
/* [in] */ ULONG ulStatusCode,
/* [in] */ LPCWSTR wszStatusText);
|
|
|
|
|
Hi everyone, I am trying to successfully run the Recipe Preview Sample from MSDN and cannot get it to work. I have downloaded the sample and the SDK, tried to change target platforms, changed the AppID, and followed all the instructions. I can get the handler to register successfully, but the .recipe file they provide as an example does not show in the preview pane.
Here is the link to the MSDN site. The code builds fine....
http://msdn.microsoft.com/en-us/library/windows/desktop/dd940374(v=vs.85).aspx
Any help would be appreciated, the source code they provide for download is very small if someone gets a chance to try it.
Thanks for the help,
AG
|
|
|
|
|
Sorry for non VC / MFC post.
Here is a base of my code to open COM port. Works as advertized.
The problem is that the DTR/RTS lines are used to key up a ham radio transmitter and are enabled when the file is first created.
Even this code still have considerable "flash" on these lines. I am just monitoring the lines with test LED's – I did not measure the flash lenght.
I could use a hardware delay circuit to stop this but would prefer to do this in software.
Any suggestions ?
Thanks for your time,
Vaclav
DCB dcb;
HANDLE hFile = ::CreateFile( strCOM ,
0, // querry only
0,
0,
OPEN_EXISTING, // communication file
0,
0);
dcb.fDtrControl = 0;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
if(!::SetCommState(hFile,&dcb))
{
TRACE("\nObtain the error code");
}
|
|
|
|
|
Vaclav_Sal wrote: Any suggestions ?
It would help if you formatted your code within <pre> tags so it is easier to read. You have also not really explained what your problem is, e.g. what is meant by the expression "flash" on these lines?
Unrequited desire is character building. OriginalGriff
I'm sitting here giving you a standing ovation - Len Goodman
|
|
|
|
|
"The problem is that the DTR/RTS lines are used to key up a ham radio transmitter and <b>are enabled when the file is first created</b>."
Disabling the DTR/RTS - setting to 0 - in DCB clears these lines but it takes a time to do that ( my test LED "flashes") , thus generating unwanted "key up " of the transmitter.
|
|
|
|
|
I would suggest this is a hardware, rather than a C/C++ issue. Maybe you should try an alternate forum.
Unrequited desire is character building. OriginalGriff
I'm sitting here giving you a standing ovation - Len Goodman
|
|
|
|
|
I'd like to speak about pointers, I am trying to stay neutral and thoughtful, please don't transform this thread into a language or belief war.
Pointers have several problems.
1 - they can be freed and they then point to garbage
2 - they can be freed twice
3 - sometime the programmer forget to free some memory
4 - probably more...
The most common solution these days seem to be using garbage collectors.
Garbage collector fix the issues 1,2, 3 above
Unfortunately garbage collectors has come with some new problems:
- you can't really release memory precisely when you want to
- it has to freeze the computer for some time occasionally to count all its blocks
- more...
There has been a lot of middle way attempts.
I know there are many flavour of smart pointers around, I am not claiming I know any of them and this is why I am writing here.
Lets imagine we have a language between C++ and C#
In this language :
- every allocation requires an owner.
The root owner would be the application itself.
- free is available and work synchronously
- when an object get freed, any child object is freed.
- when an object is freed any other pointer to it get nullified
This would solve our 3 original problems:
1 - they can be freed and they then point to garbage
They would point to null
2 - they can be freed twice
They would be freed once and then the pointer being null they would not be freed again
3 - sometime the programmer forget to free some memory
When the owner is freed, the object is freed
I was thinking of implementing those pointers and trying them on a free project like Apache or something like that.
But first, I thought I would submit it to all, there is a good chance that it already exists ...
Or perhaps the constraint of having an owner is too constraining.
It is hard to say until you try on a real project.
Having an owner per object could have other advantages in a multi-threading environment.
I haven't finished on this but first, does anyone have come across anything like it?
|
|
|
|
|
Smart pointers are OK, for lazy/bad programmers, but not needed otherwise. Which means they are needed a lot.
==============================
Nothing to say.
|
|
|
|
|
LOL.
There are good reasons to use them on occasion.
I've used them when I'm keeping multiple tables of objects in memory, that can be indexed and accessed from different points. I've had this come up in multi-threaded program where multi-threads are processing data at different entry points.
In this case there is no clear owner of the object, so there's no clear way to determine when an object is out of scope and can be safely deleted.
|
|
|
|
|