#c #winapi #download #executable #winhttp
#c #winapi #Скачать #исполняемый файл #winhttp
Вопрос:
Я пытаюсь загрузить исполняемый файл с веб-сервера HTTP на C с использованием WinHTTP. Приведенный ниже код отлично работает с HTML-файлами, но когда я пытаюсь загрузить исполняемый файл (.exe), он загружает только часть файла (и количество загружаемых байтов отличается при каждом запуске программы).
Код (вдохновленный этим примером):
...
LPSTR retVal = (LPSTR) GlobalAlloc(GMEM_FIXED, 20000);
retVal[0] = 0;
LPSTR tempVal = 0;
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPCWSTR accept[2] = { L"application/octet-stream", NULL };
HINTERNET hSession = WinHttpOpen(userAgent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
hConnect = WinHttpConnect(hSession, domain, INTERNET_DEFAULT_HTTP_PORT, 0);
hRequest = WinHttpOpenRequest(hConnect, L "GET", pathL, NULL, WINHTTP_NO_REFERER, accept, NULL);
BOOL work = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
work = WinHttpReceiveResponse(hRequest, NULL);
if (work) {
do {
dwSize = 0;
WinHttpQueryDataAvailable(hRequest, amp; dwSize);
tempVal = (LPSTR) GlobalAlloc(GMEM_FIXED, dwSize 1);
ZeroMemory(tempVal, dwSize 1);
WinHttpReadData(hRequest, (LPVOID) tempVal, dwSize, amp; dwDownloaded);
StringCchCatA(retVal, 20000, tempVal);
GlobalFree(tempVal);
} while (dwSize > 0);
}
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return retVal;
В чем может быть причина этого и как я могу попытаться это исправить? Я ценю каждый комментарий!
Комментарии:
1. Знаете ли вы, как работают строки в C?
2. Я хочу знать, как много вы знаете, чтобы ответ имел смысл для вас. Итак, вы знаете, как работают строки в C?
3. Мое основное внимание сосредоточено на языках высокого уровня, таких как Java или PHP, поэтому мои знания ограничены. Насколько я понимаю, строки в C — это массивы символов, заканчивающиеся нулевым значением.
4. Правильно. HTML-файлы обычно не содержат нулевых значений. Но EXE-файлы делают.
5. Ну, я предполагаю, что StringCchCatA будет объединяться только до тех пор, пока не будет достигнуто первое нулевое значение? Как уже указывал другой пользователь, мне придется использовать memcpy вместо StringCchCatA, что для меня имеет смысл.
Ответ №1:
Не используйте StringCchCatA с двоичными строками, такими как исполняемые файлы. Вы захотите использовать memcpy или что-то подобное, с указанием назначения на текущий конец буфера, который вы создаете на каждой итерации.
Строка C использует нулевое значение », двоичный 0, для обозначения конца строки. Исполняемые файлы загружаются с ними примерно в случайных местах. Таким образом, ваш исполняемый файл будет объединен до первого 0 байта в буфере, а затем со следующим буфером 0 байт будет перезаписан, и так далее.
Итак, вам нужно выполнить собственную арифметику указателей, чтобы выяснить, куда в вашем целевом буфере должна помещаться каждая часть буфера, и использовать копию памяти, а не строковую копию, чтобы поместить ее туда.
Ответ №2:
StringCchCatA()
работает со строками, заканчивающимися нулем, но EXE-файл не является текстовыми данными, это двоичные данные, и в нем будут 0x00
байты, что приведет StringCchCatA
к усечению данных.
Если загружаемый файл превышает 20000 байт, вам придется расширить буфер после его заполнения до максимальной емкости. Если вы не загружаете небольшие файлы, вам обычно следует использовать буфер фиксированного размера (в WinHttpReadData()
документации предлагается 8 КБ) и просто повторно использовать и добавлять этот буфер во временный файл на каждой итерации цикла. WinHttpReadData()
указывает, сколько байтов находится в буфере после каждого чтения.
Вы также не проверяете возвращаемые значения WinHttpQueryDataAvailable()
или WinHttpReadData()
на предмет невозможности прервать цикл загрузки, если это необходимо.
Вместо этого попробуйте что-то более похожее:
...
DWORD dwFileCap = 20000;
DWORD dwFileSize = 0;
DWORD dwReadSize = 8192;
DWORD dwAvailableSize = 0;
DWORD dwDownloaded = 0;
// TODO: create a file instead...
LPSTR fileData = (LPSTR) GlobalAlloc(GMEM_FIXED, dwFileCap);
if (!fileData)
{
// error handling ...
return NULL;
}
LPSTR readBuffer = (LPSTR) GlobalAlloc(GMEM_FIXED, dwReadSize);
if (!readBuffer)
{
// error handling and cleanup ...
return NULL;
}
LPCWSTR accept[2] = { L"application/octet-stream", NULL };
HINTERNET hSession = WinHttpOpen(userAgent, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession)
{
// error handling and cleanup ...
return NULL;
}
hConnect = WinHttpConnect(hSession, domain, INTERNET_DEFAULT_HTTP_PORT, 0);
if (!hConnect)
{
// error handling and cleanup ...
return NULL;
}
hRequest = WinHttpOpenRequest(hConnect, L"GET", pathL, NULL, WINHTTP_NO_REFERER, accept, NULL);
if (!hRequest)
{
// error handling and cleanup ...
return NULL;
}
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
{
// error handling and cleanup ...
return NULL;
}
if (!WinHttpReceiveResponse(hRequest, NULL))
{
// error handling and cleanup ...
return NULL;
}
bool done = false;
do
{
dwAvailableSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, amp;dwAvailableSize))
{
// error handling and cleanup ...
return NULL;
}
do
{
if (!WinHttpReadData(hRequest, readBuffer, min(dwAvailableSize, dwReadSize), amp;dwDownloaded))
{
// error handling and cleanup...
return NULL;
}
if (dwDownloaded == 0)
{
done = true;
break;
}
// TODO: if using a file instead, ignore this part ...
if ((dwFileSize dwDownloaded) > dwFileCap)
{
DWORD newCapacity = double(dwFileCap) * 1.5;
LPSTR newFileData = (LPSTR) GlobalReAlloc((HGLOBAL)fileData, newCapacity, 0);
if (!newFileData)
{
// error handling and cleanup ...
return NULL;
}
fileData = newFileData;
dwFileCap = newCapacity;
}
//
// TODO: if using a file instead, write the bytes to the file here...
memcpy(fileData dwFileSize, readBuffer, dwDownloaded);
dwFileSize = dwDownloaded;
dwAvailableSize -= dwDownloaded;
}
while (dwAvailableSize > 0);
}
while (!done);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
GlobalFree((HGLOBAL)readBuffer);
// TODO: if using a file instead, close the file here...
// use file data up to dwFileSize bytes as needed ...
// TODO: if using a file instead, ignore this part ...
GlobalFree((HGLOBAL)fileData);
Комментарии:
1. Большое вам спасибо! Я изучу ваши изменения позже сегодня. Однако у меня есть один вопрос: что вы подразумеваете под «локальным файлом»? Запись загруженных байтов в новый файл в той же функции?
2. @smokeSH да. Использование
CreateFile()
WriteFile()
илиfopen()
fwrite()
вместоGlobal(Re)Alloc()
memcpy()
3. Еще раз спасибо. Я внедрил ваши изменения в свою программу, и она работает нормально. Я только думаю
break
, что он должен быть в другом месте, потому что в противномwhile(true)
случае цикл будет продолжаться вечно.4. @smokeSH вы правы, хороший улов. Я это исправил.