C++ 下载文件 WinInet - 0kb 写入文件

Posted

技术标签:

【中文标题】C++ 下载文件 WinInet - 0kb 写入文件【英文标题】:C++ Download File WinInet - 0kb written to file 【发布时间】:2015-09-10 18:56:13 【问题描述】:

谁能告诉我我的代码有什么问题?

我正在尝试使用 WinInet 从 Internet 下载文件。该函数可以很好地连接到目标站点,我不明白为什么这段代码不起作用。谁能帮帮我?

这是我的代码:

HANDLE hFile = CreateFileW(FilePath, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, NULL, NULL);
if (hFile != INVALID_HANDLE_VALUE || GetLastError() == ERROR_ALREADY_EXISTS)

  CHAR Buffer[2048];
  DWORD BytesRead=0, BytesToRead=0;
  DWORD BytesWritten=0, BytesToWrite=0;
  SetFilePointer(hFile, 0, 0, FILE_BEGIN);
  do
  
    if (BytesRead)
    
      WriteFile(hFile, Buffer, BytesWritten, &BytesToWrite, FALSE);
    
  
  while
  (InternetReadFile(hRequest, (LPVOID)Buffer, BytesToRead, &BytesRead) != FALSE);

CloseHandle(hFile);

hRequest 被传递给函数,它是来自 HttpOpenRequestA 的 HINTERNET 句柄。

【问题讨论】:

WinINet trouble downloading file to client? 的可能重复项 您永远不会将 BytesWritten 设置为 0 以外的任何值(顺便说一句,您在 WriteFile 调用中交换了 BytesWrittenBytesToWrite 的名称)。 但除此之外,其余代码看起来不错? 如果你修复它应该至少写入数据:) 您的第一个 if-clause 没有意义。如果最后一个错误代码设置为ERROR_ALREADY_EXISTS,您可能会最终写入由句柄INVALID_HANDLE_VALUE 标识的文件,即您没有写入任何内容。 【参考方案1】:

你的代码有一些逻辑问题。

    您在调用CreateFileW() 时误用了GetLastError()。不管文件是否已经存在,CreateFileW() 如果成功创建/打开文件,都不会返回INVALID_HANDLE。这就是您需要检查的全部内容(仅当CreateFileW() 失败并且您想找出原因时才调用GetLastError())。此外,根本不需要调用SetFilePointer(),因为CREATE_ALWAYS 确保打开的文件是空的,如果文件已经存在并且其中有数据,则截断文件。

    您的 do..while 循环应该是 while 循环,以便首先调用 InternetReadFile()。在第一次循环迭代中跳过 WriteFile() 是没有意义的。如果您使用do..while 循环,则不应将InternetReadFile() 用作循环条件。

    更重要的是,如果InternetReadFile() 因错误而失败,只有你才打破循环。您期望它在响应结束时失败,但它实际上返回 TRUE 并将 BytesRead 设置为 0。这是记录在案的行为,您根本没有处理:

    InternetReadFile function

    InternetReadFile 的操作与基本的 ReadFile 函数非常相似,但有一些例外。通常,InternetReadFile 从 HINTERNET 句柄中检索数据作为顺序字节流。每次调用 InternetReadFile 要读取的数据量由 dwNumberOfBytesToRead 参数指定,数据在 lpBuffer 参数中返回。正常读取为每次调用 InternetReadFile 检索指定的 dwNumberOfBytesToRead,直到到达文件末尾。 为确保检索到所有数据,应用程序必须继续调用 InternetReadFile 函数,直到该函数返回 TRUE 并且 lpdwNumberOfBytesRead 参数等于零。如果将请求的数据写入缓存,这一点尤其重要,因为否则缓存将无法正确更新,下载的文件也不会提交到缓存。请注意,除非打开数据流的原始请求设置了 INTERNET_FLAG_NO_CACHE_WRITE 标志,否则缓存会自动发生。

    ReadFile function

    当同步读取操作到达文件末尾时,ReadFile 返回 TRUE 并将 *lpNumberOfBytesRead 设置为零

    在调用WriteFile() 时,您将BytesWritten 传递给nNumberOfBytesToWrite 参数,但BytesWritten 永远不会设置为0 以外的任何值,因此不会将任何内容写入文件。您需要改为传递BytesRead

话虽如此,请使用更像这样的东西:

HANDLE hFile = CreateFileW(FilePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile == INVALID_HANDLE_VALUE)

    // handle error as needed...

else

    BYTE Buffer[2048];
    DWORD BytesRead, BytesWritten;

    do
    
        if (!InternetReadFile(hRequest, Buffer, sizeof(Buffer), &BytesRead))
        
            // handle error as needed...
            break;
        

        if (!BytesRead)
            break;

        if (!WriteFile(hFile, Buffer, BytesRead, &BytesWritten, FALSE))
        
            // handle error as needed...
            break;
        
    
    while (true);

    CloseHandle(hFile);

MSDN 甚至有一个如何使用InternetReadFile() 的完整示例:

HOWTO: Using InternetReadFile To Get File

BOOL GetFile (HINTERNET IN hOpen, // Handle from InternetOpen()
              CHAR *szUrl,        // Full URL
              CHAR *szFileName)   // Local file name

   DWORD dwSize;
   CHAR   szHead[] = "Accept: */*\r\n\r\n";
   VOID * szTemp[25];
   HINTERNET  hConnect;
   FILE * pFile;

   if ( !(hConnect = InternetOpenUrl ( hOpen, szUrl, szHead,
         lstrlen (szHead), INTERNET_FLAG_DONT_CACHE, 0)))
   
      cerr << "Error !" << endl;
      return 0;
   

   if  ( !(pFile = fopen (szFileName, "wb" ) ) )
   
      cerr << "Error !" << endl;
      return FALSE;
   
   do
   
      // Keep coping in 25 bytes chunks, while file has any data left.
      // Note: bigger buffer will greatly improve performance.
      if (!InternetReadFile (hConnect, szTemp, 50,  &dwSize) )
      
         fclose (pFile);
         cerr << "Error !" << endl;
         return FALSE;
      
      if (!dwSize)
         break;  // Condition of dwSize=0 indicate EOF. Stop.
      else
         fwrite(szTemp, sizeof (char), dwSize , pFile);
      // do
   while (TRUE);
   fflush (pFile);
   fclose (pFile);
   return TRUE;

【讨论】:

你的空闲时间显然太多了 ;) 我没有得到 MSDN 的例子。 void* szTemp[25] 是什么?指向字节数组的指针?它没有被分配然后它在这个调用中读取 50 个字节InternetReadFile (hConnect, szTemp, 50, &amp;dwSize) 他们似乎是故意这样做的,他们甚至对此发表评论。 @BarmakShemirani:很好。在 MSDN 的第一个示例中,VOID* szTemp[25]; 需要改为 BYTE szTemp]50];,而在他们的第二个示例中,TCHAR sz[1024]; 需要改为BYTE sz[1024];。我已将这些更改提交给 Microsoft。

以上是关于C++ 下载文件 WinInet - 0kb 写入文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 Wininet 下载二进制文件

恢复简单下载管理器的能力(C++ - WinInet)

如何在 WinInet C++ 中获取 FTP 下载的进度

如何在 C++ 中使用 wininet 创建 POST 请求

C++ Wininet 自定义 http 标头

在 C++ 中使用 Wininet 发送 POST 请求时的编码问题