1

Note: This code is in Delphi XE2.

I am trying to download a file without using UrlMon.dll.

I would like to use only wininet. This is what I have come up with so far:

uses Windows, Wininet;

procedure DownloadFile(URL:String;Path:String);
Var
  InetHandle:Pointer;
  URLHandle:Pointer;
  FileHandle:Cardinal;
  ReadNext:Cardinal;
  DownloadBuffer:Pointer;
  BytesWritten:Cardinal;
begin
  InetHandle := InternetOpen(PWideChar(URL),0,0,0,0);
  URLHandle := InternetOpenUrl(InetHandle,PWideChar(URL),0,0,0,0);
  FileHandle := CreateFile(PWideChar(Path),GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,0);
  Repeat
    InternetReadFile(URLHandle,DownloadBuffer,1024,ReadNext);
    WriteFile(FileHandle,DownloadBuffer,ReadNext,BytesWritten,0);
  Until ReadNext = 0;
  CloseHandle(FileHandle);
  InternetCloseHandle(URLHandle);
  InternetCloseHandle(InetHandle);
end;

I think the issue is with my loop and "ReadNext". When this code is executed, it create's the file in the correct path, yet the code finishes and the file is 0 bytes.

Josh Line
  • 625
  • 3
  • 13
  • 27

2 Answers2

3

I improved a bit your routine and it does the work for me:

procedure DownloadFile(URL: string; Path: string);
const
  BLOCK_SIZE = 1024;
var
  InetHandle: Pointer;
  URLHandle: Pointer;
  FileHandle: Cardinal;
  BytesRead: Cardinal;
  DownloadBuffer: Pointer;
  Buffer: array [1 .. BLOCK_SIZE] of byte;
  BytesWritten: Cardinal;
begin
  InetHandle := InternetOpen(PWideChar(URL), 0, 0, 0, 0);
  if not Assigned(InetHandle) then RaiseLastOSError;
  try
    URLHandle := InternetOpenUrl(InetHandle, PWideChar(URL), 0, 0, 0, 0);
    if not Assigned(URLHandle) then RaiseLastOSError;
    try
      FileHandle := CreateFile(PWideChar(Path), GENERIC_WRITE, FILE_SHARE_WRITE, 0,
        CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
      if FileHandle = INVALID_HANDLE_VALUE then RaiseLastOSError;
      try
        DownloadBuffer := @Buffer;
        repeat
          if (not InternetReadFile(URLHandle, DownloadBuffer, BLOCK_SIZE, BytesRead) 
             or (not WriteFile(FileHandle, DownloadBuffer^, BytesRead, BytesWritten, 0)) then
            RaiseLastOSError;
        until BytesRead = 0;
      finally
        CloseHandle(FileHandle);
      end;
    finally
      InternetCloseHandle(URLHandle);
    end;
  finally
    InternetCloseHandle(InetHandle);
  end;
end;

For example a call:

  DownloadFile
    ('https://dl.dropbox.com/u/21226165/XE3StylesDemo/StylesDemoSrcXE2.7z',
    '.\StylesDemoXE2.7z');

Works like a charm.

The changes I made is:

  • Providing a buffer
  • Checking the result of the call to WriteFile and raising an exception if it is false or if the number of bytes written is different from the number of bytes read.
  • Changed the variable name.
  • Named constant
  • [after edited] Proper Function result checking
  • [after edited] Resource leak protection with try/finally blocks

Edit Thanks TLama for raising aware about the last two points.

jachguate
  • 16,976
  • 3
  • 57
  • 98
  • Could you please explain how to execute the same code in a delphi 7 application? In delphi 7 this code compiles, yet does not download the file correctly. It seems to add the same line "byte sequence" over and over again... – Josh Line Nov 30 '12 at 05:40
  • actually, I edited all "PWideChar" to "PChar", also tried "PAnsiChar" – Josh Line Nov 30 '12 at 05:41
  • Josh, you have to match the Windows API. In Delphi pre 2009, it is Ansi, so you have to use PChar or PAnsiChar. In Delphi 2009+ it is unicode, so you use PWideChar or PChar as they are equivalents. The safest way to do it is use always PChar to do the cast. – jachguate Nov 30 '12 at 08:33
  • If you could kindly check my latest question: http://stackoverflow.com/questions/13640092/delphi-xe2-code-to-delphi7-needed-using-wininet-to-download-a-file I have tried with both PChar and PAnsiChar. It fails with both. – Josh Line Nov 30 '12 at 10:08
  • @jachguate, you should use `try..finally` blocks to close opened handles. Always rather use proper variable types. There's no need to check of `BytesWritten <> BytesRead` at `WriteFile`, the function would fail if that differs. And, as it's stated in the reference, you should call `InternetReadFile` until it returns True and until `lpdwNumberOfBytesRead` is 0. Take a look [`here`](http://stackoverflow.com/a/13646681/960757) for review. – TLama Dec 01 '12 at 11:01
  • @TLama you're right, it's my fault to let the code that way. I't s improved now. – jachguate Dec 01 '12 at 19:17
-2

this is wrong first of all.

InetHandle := InternetOpen(PChar(URL), 0, 0, 0, 0);

needs to be

InetHandle := InternetOpen(PChar(USERANGENT), 0, 0, 0, 0);

And is missing a ) on here....

(not InternetReadFile(URLHandle, DownloadBuffer, BLOCK_SIZE, BytesRead) ")"
Vojtech Ruzicka
  • 16,384
  • 15
  • 63
  • 66
mel
  • 1