10

Please advise if there is a WinHTTP wrapper in Delphi XE

In order of preference:

  1. a Delphi out of the box unit
  2. a third party open source pas file with ported entry routines
  3. a xxx_TLB.pas wrapper

Solution:

Since comments do not allow formatted code I am pasting the solution in the questions:

const
  winhttpdll = 'winhttp.dll';

  WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0;
  WINHTTP_FLAG_REFRESH              = $00000100;
  WINHTTP_FLAG_SECURE               = $00800000;
  WINHTTP_ADDREQ_FLAG_COALESCE      = $40000000;
  WINHTTP_QUERY_FLAG_NUMBER         = $20000000;

function WinHttpOpen(pwszUserAgent: PWideChar; dwAccessType: DWORD;
  pwszProxyName, pwszProxyBypass: PWideChar; dwFlags: DWORD): HINTERNET; stdcall; external winhttpdll;
function WinHttpConnect(hSession: HINTERNET; pswzServerName: PWideChar;
  nServerPort: INTERNET_PORT; dwReserved: DWORD): HINTERNET; stdcall; external winhttpdll;
function WinHttpOpenRequest(hConnect: HINTERNET; pwszVerb: PWideChar;
  pwszObjectName: PWideChar; pwszVersion: PWideChar; pwszReferer: PWideChar;
  ppwszAcceptTypes: PLPWSTR; dwFlags: DWORD): HINTERNET; stdcall; external winhttpdll;
function WinHttpCloseHandle(hInternet: HINTERNET): BOOL; stdcall; external winhttpdll;
function WinHttpAddRequestHeaders(hRequest: HINTERNET; pwszHeaders: PWideChar; dwHeadersLength: DWORD;
  dwModifiers: DWORD): BOOL; stdcall; external winhttpdll;
function WinHttpSendRequest(hRequest: HINTERNET; pwszHeaders: PWideChar;
  dwHeadersLength: DWORD; lpOptional: Pointer; dwOptionalLength: DWORD; dwTotalLength: DWORD;
  dwContext: DWORD): BOOL; stdcall; external winhttpdll;
function WinHttpReceiveResponse(hRequest: HINTERNET;
  lpReserved: Pointer): BOOL; stdcall; external winhttpdll;
function WinHttpQueryHeaders(hRequest: HINTERNET; dwInfoLevel: DWORD; pwszName: PWideChar;
  lpBuffer: Pointer; var lpdwBufferLength, lpdwIndex: DWORD): BOOL; stdcall; external winhttpdll;
function WinHttpReadData(hRequest: HINTERNET; lpBuffer: Pointer;
  dwNumberOfBytesToRead: DWORD; var lpdwNumberOfBytesRead: DWORD): BOOL; stdcall; external winhttpdll;
function WinHttpQueryDataAvailable(hRequest: HINTERNET; var lpdwNumberOfBytesAvailable: DWORD): BOOL; 
  stdcall; external winhttpdll;
function WinHttpSetOption(hInternet: HINTERNET; dwOption: DWORD; lpBuffer: Pointer; dwBufferLength: DWORD): BOOL; 
  stdcall; external winhttpdll;
function WinHttpQueryOption(hInternet: HINTERNET; dwOption: DWORD; var lpBuffer: Pointer; var lpdwBufferLength: DWORD): BOOL; 
  stdcall; external winhttpdll;
function WinHttpWriteData(hRequest: HINTERNET; lpBuffer: Pointer; dwNumberOfBytesToWrite: DWORD; 
  var lpdwNumberOfBytesWritten: DWORD): BOOL; stdcall; external winhttpdll;
function WinHttpCheckPlatform(): BOOL; stdcall; external winhttpdll;

There are still a couple more missing ones:

WinHttpCrackUrl
WinHttpCreateUrl
WinHttpSetStatusCallback
WinHttpTimeFromSystemTime
WinHttpTimeToSystemTime
Gad D Lord
  • 6,130
  • 10
  • 50
  • 98

2 Answers2

11

If you want to implement an HTTP client access in your application, you may consider several choices:

  • Use the provided Indy components;
  • Use third-party components like Synapse, ICS or your own WinSock-based wrapper;
  • Use WinINet;
  • Use WinHTTP.

For our ORM, for its HTTP/1.1 connection layer, we tried to avoid external dependencies, and did not have the need of all Indy's features and overhead.

We first wrote our own WinSock wrapper, then tried out WinInet. When used on our testing benchmark, we found out that WinINet was dead slow.

Then we tried WinHTTP, the new API provided by Microsoft, and we found out this was blazing fast. As fast as direct WinSock access, without the need of writing all the wrapper code.

So here is our OpenSource WinHTTP wrapper, in the unit named SynCrtSock. Tested from Delphi 5 up to XE.

You'll see that we used the same generic class for both WinINet and WinHTTP. In fact, both libraries are very close.

See this article for details. There is a note about automatic proxy retrieval.

Edit: with the upcoming Delphi XE2, you'll be able to cross-compile to Mac OS X. In this case, it does perfectly make sense to use "abstract" classes, like SynCrtSock. Under Windows, it will use WinHTTP, but under Mac OS X, it will call the socket API. To make your code compile, you'll just to adjust the class type, not your code.

Arnaud Bouchez
  • 40,947
  • 3
  • 66
  • 152
  • 1
    It was exactly your blog post which made me think migrating to WinHttp. I found what I needed in your SynCrtSock unit: – Gad D Lord Jul 18 '11 at 09:09
  • And some versions of WinInet have a dreaded timeout bug! WinInet can't be trusted to work on all your client PCs, don't use it unless you want to have 30-60 second freezes that can only be fixed by getting your users to upgrade their installed version of Internet Explorer. – Warren P Jul 22 '11 at 15:54
  • @Warren P. Microsoft does recommend that people use `IServerWinHttpRequest`. It's a re-written version that is more stable and secure, with more features for controlling proxy setttings and timeouts. (which is why it's called **server** - it's more stable which is what you want when running it on a server) – Ian Boyd Jul 22 '11 at 21:27
  • @Ian What is this `IServerWinHttpRequest`? There is no google resource about it... from the "server" word, it's perhaps because WinHTTP can be used within Services or on Server side, whereas WinINet was designed for Client side. – Arnaud Bouchez Aug 09 '11 at 11:33
  • @Arnaud Bouchez: i'm sorry, i meant `IServerXMLHTTPRequest` (http://msdn.microsoft.com/en-us/library/ms762278(v=VS.85).aspx) (as opposed to `IXMLHTTPRequest`). You can use `IXMLHttpRequest` to fetch regular HTML content. `IXMLHttpRequest` was built on the **WinInet** API. *"Unlike `XMLHTTP`, however, the `ServerXMLHTTP` object does not rely on the **WinInet** control for HTTP access to remote XML documents. `ServerXMLHTTP` uses a new HTTP client stack. Designed for server applications, this server-safe subset of WinInet offers the following advantages: ..."* – Ian Boyd Aug 09 '11 at 14:20
  • @Ian AFAIK IServerXMLHTTPRequest is about XML management, implemented in MSXML. It is calling WinHTTP internally, instead of WinINet, as stated by http://msdn.microsoft.com/en-us/library/ms761351(v=vs.85).aspx So you'd better use the original library, i.e. WinHTTP. ;) – Arnaud Bouchez Aug 09 '11 at 18:27
  • @Arnaud Bouchez: Nevermind then! :) i was thrown off by Warren P talking about a "timeout bug", thinking he was referring to **WinHttp** (the subject of this question). i see that `IWinHttpRequest` *is* the same "newer" library as `IServerXMLHttpRequest`; and already has the same support for setting custom timeouts. – Ian Boyd Aug 09 '11 at 21:26
7
  • Project
  • Import Type Library
  • Microsoft WinHTTP Services, version 5.1 (Version 5.1) C:\Windows\system32\winhttp.dll

And then use it:

var
   http: IWinHttpRequest;
   szUrl: WideString;
begin
   szUrl := 'http://stackoverflow.com/questions/6725348/winhttp-delphi-wrapper';

   http := CoWinHttpRequest.Create;
   http.open('GET', szUrl, False);
   http.send(EmptyParam);

   if (http.status = 200) then
       ShowMessage(http.responseText);

So:

  • it it is out of the box - using the out of the box tools
  • it is open-source - you're free to modify the source as you like
  • it's the TLB
Ian Boyd
  • 220,884
  • 228
  • 805
  • 1,125
  • 2
    ... and there will be some unnecessary overhead by using the COM Wrapper instead of the C WinHTTP library. And COM could be a nightmare in a multi-threaded service. I'd rather call the C API in a Delphi software. The COM interface already changed (e.g. 5.0 is deprecated), so perhaps you'd have problems in the future... – Arnaud Bouchez Jul 17 '11 at 19:10
  • 2
    5.0 was superseded over 10 years ago; this is 5.1, which has been stable. There is no overhead calling methods of an in-process COM object - it's as fast as calling an object's method in Delphi. If you don't like the performance hit of calling a function through a virtual method table then you wouldn't be using Delphi either. – Ian Boyd Jul 18 '11 at 13:57
  • Overhead is not in the asm `call` itself of course, but in the COM instance creation and finalization (for instance to handle enhanced security in Vista/Seven), and parameter conversion (OleStr/WideString do have a cost). And you have to call CoInitialize in every thread, which may be tricky in a multi-threaded service. If you have a direct C-like wrapper, it will be faster than the COM version, which was intended for Visual Basic and scripting use. – Arnaud Bouchez Jul 18 '11 at 16:06
  • Spinning up 100,000 threads i benchmark `CoInitialize` and `CoWinHttpRequest.Create` at 2.89µs (0.092µs to initialize the apartment, and 2.79µs to construct a `WinHttp` object). Strings in Delphi already are Wide (at least the version of Delphi the author is talking about). But if you add in 100,000 conversions of `AnsiString` to `WideString` that's another 0.246µs per string. i'm not sure where you're getting the idea that 3.5µs is a crushing liability; enough to justify not using a standard, well-tested, supported, hardened helper object that's been around for over a decade. – Ian Boyd Jul 19 '11 at 06:52
  • Strings in Delphi 2009+ are Unicode encoded, but there is still a conversion from `UnicodeString` to `WideString`. It's not the Unicode conversion which matters, but the `WideString` allocation: BSTR do not use FastMM4 but much slower `SysAllocStringLen` WinAPI call. Under Vista/Seven it's faster than under XP, but it's still slower than a Delphi `string`. Of course, it doesn't matter when retrieving HTTP content! So I agree with you that COM is not a speed bottleneck here, even if I always prefer not use COM interfaces when I have a plain C API available. – Arnaud Bouchez Jul 19 '11 at 12:03
  • It's not COM you seem to have an issue with: it's object-oriented programming. You would pass up a C++/COM/Delphi object if a plain C API is available. Personally i *prefer* classes over flat C functions - as does the original poster since his question is about Delphi XE. – Ian Boyd Jul 19 '11 at 13:03
  • How would you extend this to save the results to disk? (Asked question here: http://stackoverflow.com/questions/6795915/save-a-file-downloaded-via-winhttp-to-disk-using-delphi-xe) – Warren P Jul 22 '11 at 20:47
  • @Ian The C flat functions are perfectly Object-Oriented, like most of the Windows API, by the way. You just use an handle instead of an object instance. If you take a look at my projects, you'll see that I only use OOP, and encapsulated the WinHTTP API with pure Delphi classes. For instance, the same Delphi class is able to connect using WinINet, WinHTTP, or plain sockets. That is OOP, and more convenient than direct COM import in user code. For instance, using COM won't work on Mac OS X, whereas my classes will be able to, via the sockets version. OOP is not only "object", but abstraction. – Arnaud Bouchez Aug 09 '11 at 11:40
  • First it was COM is too slow. Then it was strings that are too slow. Now it's because you can't port x86 dll binaries to Mac? This is a *Delphi* question. – Ian Boyd Aug 09 '11 at 14:25
  • @ArnaudBouchez What are you talking about? `WiniNet` can be used without any kind of interfaces.. – John Lewis Feb 14 '15 at 17:16
  • @JohnLewis this was exactly my point. But its flat API is object oriented, even in plain c. Please refer to my https://github.com/synopse/mORMot/blob/master/SynCrtSock.pas unit to see how I encapsulated both WinInet and WinHttp in two classes inheriting most of its behavior from an abstract parent class. Using such a thin oop layer sounds definitely closer to the Liskov substitution principle. – Arnaud Bouchez Feb 14 '15 at 20:11
  • @ArnaudBouchez You can probably inline it to make it even faster. :) I also see no cookie manager class. WinInet shares cookies and other stuff with the system. – John Lewis Feb 14 '15 at 20:46
  • @JohnLewis The fact that the WinINet/WinHttp API is Object Oriented is obvious, even if you do not want to understand what it means. This unit is low level: cookies and headers are handled in a neutral way above this layer, so that you can use plain socket, WinHttp and WinINet depending on your choices. You change the class type, and you instantiate the expected API. It follows the Liskov substitution principle. BTW WinInet is just awfully slow and not working from a service. The fastest is direct socket access. – Arnaud Bouchez Feb 17 '15 at 07:13