WinHttp Delphi wrapper
Asked Answered
C

2

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
Crack answered 17/7, 2011 at 16:53 Comment(4)
Isn't the third one something that Delphi will create for you automatically when you import the type library?Fourdimensional
I also found useful tek-tips.com/faqs.cfm?fid=7493Crack
Please post the solution in the answer section, not the question.Fourdimensional
What did you do with the missing functions? Is it impossible to import them?Lazos
G
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.

Gauldin answered 17/7, 2011 at 19:5 Comment(7)
It was exactly your blog post which made me think migrating to WinHttp. I found what I needed in your SynCrtSock unit:Crack
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.Elma
@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)Roxane
@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.Gauldin
@Arnaud Bouchez: i'm sorry, i meant IServerXMLHTTPRequest (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: ..."Roxane
@Ian AFAIK IServerXMLHTTPRequest is about XML management, implemented in MSXML. It is calling WinHTTP internally, instead of WinINet, as stated by msdn.microsoft.com/en-us/library/ms761351(v=vs.85).aspx So you'd better use the original library, i.e. WinHTTP. ;)Gauldin
@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.Roxane
R
9
  • 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 := 'https://mcmap.net/q/1059166/-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
Roxane answered 17/7, 2011 at 18:17 Comment(13)
... 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...Gauldin
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.Roxane
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.Gauldin
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.Roxane
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.Gauldin
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.Roxane
How would you extend this to save the results to disk? (Asked question here: #6796415)Elma
@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.Gauldin
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.Roxane
@ArnaudBouchez What are you talking about? WiniNet can be used without any kind of interfaces..Pyrex
@JohnLewis this was exactly my point. But its flat API is object oriented, even in plain c. Please refer to my 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.Gauldin
@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.Pyrex
@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.Gauldin

© 2022 - 2024 — McMap. All rights reserved.