views:

87

answers:

3

I'm using WinInet to connect and retrieve information from one of our server. I'm using the following:

indexdata: array of byte[1..5000] of byte;
infoBuffer: array [0..5000] of char;
BufferSize: DWORD;
reserved:   DWORD;
text: string;

BufferSize := Length(infoBuffer);
res := HttpQueryInfo(hHttpRequest, HTTP_QUERY_RAW_HEADERS_CRLF, @infoBuffer, BufferSize, Reserved);

Reserved := 0;
InternetReadFile(hHttpRequest, @indexdata, sizeof(indexdata), Reserved);

SetLength(text, Reserved);
CopyMemory(@text[1], @indexdata[1], Reserved);

The two array of bytes were enough up until now. Things changed. The server can return now information that can be bigger or smaller than 5000; worst yet, in InternetReadFile can return a variable size in the infoBuffer.

So i tried declaring the indexdata and infobuffer as array of byte and then using SetLength to set its length, but 2 things happened.

1) I still don't know the size of indexdata that the server will return so I cannot properly set it to, say, 100000.

2) I cannot use (as it is now) CopyMemory passing Low(indexdata) to copy indexdata to a simple string variable so I can use the data.

How do I handle this in Delphi? I can do it in C but I can't seem to be able to do it properly in Delphi.

Code is appreciated

thanks!

+2  A: 

You do know that char is a Unicode character since Delphi 2009, but an ANSI character prior to Delphi 2009. In the same way, in Delphi 2009 a string is a UnicodeString, not an AnsiString.

So, when you write SetLength(text, Reserved) you do set the number of characters in text to Reserved. But the number of bytes will be 2*Reserved.

In other words, in Delphi 2009+, one char is not one byte, but two bytes.

You can get back the old behaviour by replacing all char with AnsiChar and all string with AnsiString.

Update

Since you did not post your entire code, I cannot really say what the problem is. Nevertheless, you might find it interesting to read my example usage of InternetReadFile in Delphi. See my answer to this question. It is a fully-working example of how to read a text file from the Internet using Delphi and InternetReadFile.

For your convinience, I paste my code below as well:

To read data from the Internet, use InternetReadFile function. I use the following code to read a small (one-line) text file from the Internet:

function WebGetData(const UserAgent: string; const Server: string; const Resource: string): string;
var
  hInet: HINTERNET;
  hURL: HINTERNET;
  Buffer: array[0..1023] of AnsiChar;
  i, BufferLen: cardinal;
begin
  result := '';
  hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  try
    hURL := InternetOpenUrl(hInet, PChar('http://' + Server + Resource), nil, 0, 0, 0);
    try
      repeat
        InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen);
        if BufferLen = SizeOf(Buffer) then
          result := result + AnsiString(Buffer)
        else if BufferLen > 0 then
          for i := 0 to BufferLen - 1 do
            result := result + Buffer[i];
      until BufferLen = 0;
    finally
      InternetCloseHandle(hURL);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
end;

Sample usage:

WebGetData('My UserAgent', 'www.rejbrand.se', '/MyDir/update/ver.txt')
Andreas Rejbrand
I'm using delphi 7, but thanks for the explanation
Jessica
@Jessica, you should implement Andreas's suggestion immediately. You can change your declaration to AnsiString and not break anything, and then if/when you move to a newer Delphi, it won't have to be re-worked. i.e. there's no downside to declaring as AnsiString/AnsiChar right now.
Chris Thornton
ok, thanks. still this doesn't answer my question.
Jessica
got it, thanks! No I can see where my code is failing: I was trying to use InternetReadFile in one go instead of reading chucks and saving them in a string variable.
Jessica
iirc in Delphi 2009 a string is a UnicodeString, not a WideString
mjustin
@mjustin: Yes, I always make this mistake.
Andreas Rejbrand
Well, isn't UnicodeString Kylix' widestring ? :-)
Marco van de Voort
+1  A: 

Instead of CopyMemory, try using the Move routine, like so:

Move(indexdata[1], text[1], reserved);

(Yes, without the @ symbols.) That should solve problem #2. As for problem #1, that's between you and the server. You need to have some way to know what the upper bound is on the size that it will return, and make your buffer at least that large. You should either be able to find this in the documentation, or call another API first that will give you the size of the incoming data.

Also put some check on there so that if it returns something larger than your buffer, it immediately raises an exception. If your buffer is overrun, you should assume that your program is under attack and react accordingly.

Mason Wheeler
the "move" fails with an exception: access violation of address xxxxx.... read of address yyyyy
Jessica
Did you call SetLength first? If you called SetLength, and you're not using @s, and you're not exceeding the size of `text`, then that shouldn't happen.
Mason Wheeler
@Jessica: Please notice that the lack of the "@"'s is not the only difference between `CopyMemory` and `move` -- the two first arguments comes in different order, (Destionation, Source) versus (Source, Destination)!
Andreas Rejbrand
thanks. I saw that, it was very clear on the comments Mason wrote.
Jessica
`Move` and `CopyMemory` should behave identically in every way since the latter is implemented with the former. If any problem was fixed by using one instead of the other, then the problem lay deeper than either one of them.
Rob Kennedy
@Rob Kennedy: I agree to some extent. (At first I even considered giving -1 to Mason!) The definition of `CopyMemory(Destination: Pointer; Source: Pointer; Length: DWORD)` is `Move(Source^, Destination^, Length);`. But perhaps you do lose some information about data types going the way through `CopyMemory`. Still, I am not 100 % sure about how Mason come to his conclusion that `Move` would solve the issue.
Andreas Rejbrand
No, @Andreas, you also lose data-type information by passing through the untyped parameters of `Move`; `CopyMemory` doesn't take away anything that `Move` won't take away anyway. Besides, there's nothing `Move` could do even if it *did* have type information.
Rob Kennedy
My mistake. I typed CopyMemory and hit F1 and got the Windows API version, and I didn't check to see that that's what it was really calling.
Mason Wheeler
+1  A: 

Windows functions that return a buffer of unknown size, usually return the require size of the buffer if a too small buffer is passed. This way you do not need to size in advance a buffer large enough to hold the largest data - pass a small buffer (even a zero length one), and the function will return how large the buffer should be. Then you can size your buffer and pass it again the the function to read data - check for example HttpQueryInfo() explanation in MSDN. To handle buffer of an unknown size in Delphi, you have two ways.

  • Use dynamic arrays and SetLength()
  • Use dynamically allocated buffers using GetMem() and FreeMem(), very much alike you would do in C.

For example:

var
  Buf: Pointer;
  BufSize: DWORD; 
  Rez: DWORD;
  ...
const
  InitialBufSize = 1024;

...
BufSize := InitialBufSize;
GetMem(Buf, BufSize);
try
  if not HttpQueryInfo(hRequest, dwInfoLevel, Buf, BufSize, lpdwIndex) then
  begin
    Rez := GetLastError;
    if Rez = ERROR_INSUFFICIENT_BUFFER then
    begin
      FreeMem(Buf, InitialBufSize);
      GetMem((Buf, BufSize);
      if not HttpQueryInfo(hRequest, dwInfoLevel, Buf, BufSize, lpdwIndex) then
        Rez := GetLastError
    end;
 ...
finally
  FreeMem(Buf, BufSize);
end;
ldsandon