Borland Delphi | ||
The SocketTools Library Edition provides a Delphi unit in the Include folder named cstools10.pas which can be included with your projects. This defines the constants and functions in the SocketTools libraries. String ArgumentsAn important consideration when using the SocketTools libraries in Delphi is how string arguments are being used by the function. In most cases, the string is provided as input to the function, such as the hostname or address of a server to establish a connection with. However, in some cases the string is passed to the function as an output buffer into which the function copies data. An example of this would be the HttpGetErrorString function, which is used to obtain a description of a specific error. In C/C++ you would allocate a character array and pass it to the function, such as: VOID ShowError() { TCHAR szError[256]; DWORD dwError; dwError = HttpGetLastError(); if (dwError > 0) { HttpGetErrorString(dwError, szError, 256); MessageBox(NULL, szError, "Error", MB_OK); } } Delphi has a type called PAnsiChar which is a pointer to a null terminated array of characters. It was created primarily as a compatibility type to work with the Windows API and C/C++ dynamic link libraries, and this is the type that the SocketTools functions use to represent strings. The equivalent code in Delphi would be: procedure ShowError; var dwError: LongWord; szError: Array [0..255] Of AnsiChar; begin dwError := HttpGetLastError(); if dwError > 0 then; begin HttpGetErrorString(dwError, szError, 256); ShowMessage(szError); end; end; Early versions of Delphi had helper functions such as StrNew to help convert and allocate null terminated strings. However, those functions have been deprecated and in most cases current versions of Delphi will automatically convert between character arrays and its native String type. There are also a number of functions available which are designed specifically to work with null terminated strings. Refer to your language reference for more information about how Delphi uses null terminated strings. Byte Array ArgumentsA number of SocketTools functions use byte arrays, either as an input argument to the function, or as an output argument which will contain data when the function returns. An example of this is the HttpGetData function, which will access a resource on the server and return the contents of that resource in a byte array passed to the function. For example, the following code in C++ would return the first 1024 bytes of the index page on a web server: BYTE byteBuffer[1024]; DWORD dwLength; INT nResult; dwLength = sizeof(byteBuffer); nResult = HttpGetData(hClient, "/index.html", byteBuffer, &dwLength, 0); If the function is successful, the byteBuffer array will contain the first 1024 bytes of the index page. In Delphi, the equivalent code would look like this: var nResult: Integer; byteBuffer: Array [0..1023] Of Byte; dwLength: LongWord; begin dwLength := 1024; nResult := HttpGetData(hClient, PAnsiChar('/index.html'), @byteBuffer, dwLength, 0); end; In this example, you will notice that the string was explicitly cast to a null terminated string using PAnsiChar. This is necessary because the HttpGetData function is overloaded and Delphi will complain that the function cannot be found if the cast is omitted. If you wanted to convert the data in the byte array to a String type, you might think that you could do something like this: strBuffer := PAnsiChar(@byteBuffer); This may appear to work, but it would depend on there being a null character to terminate the byte array and it could result in garbage characters appearing at the end of your string or cause the program to attempt to read memory that it has not allocated. To convert the byte array into a string, use the following: SetLength(strBuffer, dwLength); Move(byteBuffer, strBuffer[1], dwLength); Note that you should only do this if the data returned by the function is actually text. In this example, it is acceptable to do because the byte array contains the HTML text for the index page. Global Memory HandlesIn addition to using byte arrays, some SocketTools functions can use global memory handles (HGLOBALs) to exchange large amounts of data. Using the Windows API, global memory handles are allocated by the GlobalAlloc function, dereferenced by the GlobalLock function and released by the GlobalFree function. These handles can be used in Delphi with the helper functions defined in the SocketTools unit. An application may choose to use a global memory handle instead of a pre-allocated buffer if the amount of data is very large, or the total amount of data that will be returned is unknown at the time the function is being called. Consider the call to the HttpGetData function used in the previous example. A pre-allocated buffer of 1024 bytes was passed to the function, and it copied up to that amount of data into the buffer. However, what if you wanted the complete page and did not know how large it was? You could attempt to determine the size of the page that was being requested using the HttpGetFileSize function, and then use that value to allocate a buffer. However, this incurs additional overhead and it is not always possible to get the size of a resource on a web server. Another alternative would be to simply allocate a very large buffer, but this could result in the application allocating large amounts of memory that it doesn't use and you would still run the risk that it wouldn't be large enough. The solution for this problem is to use a global memory handle rather than a pre-allocated buffer. Instead of copying the data into a buffer, the function allocates a global memory handle and stores the contents in the memory referenced by that handle. When the function returns, it passes the handle back to the caller. The caller then dereferences the handle to access the memory, and releases the handle when it is no longer needed. Here is an example of how it would be used in C/C++: HGLOBAL hgblBuffer = NULL; DWORD dwLength = 0; INT nResult; nResult = HttpGetData(hClient, "/index.html", &hgblBuffer, &dwLength, 0); if (nResult != HTTP_ERROR) { LPBYTE lpBuffer = (LPBYTE)GlobalLock(hgblBuffer); // Do something with the data and then unlock and // release the handle when it is no longer needed GlobalUnlock(hgblBuffer); GlobalFree(hgblBuffer); } Note that the global memory handle is initialized to NULL and the length argument is initialized to zero. This is important to do because this is how the function knows that it should be returning a global memory handle instead of copying data into a buffer. If you forget to initialize those arguments, the function will fail and may cause the application to terminate with a general protection fault. The equivalent code in Delphi would look like this: var nResult: Integer; hgblBuffer: LongWord; dwLength: LongWord; pszBuffer: PAnsiChar; begin hgblBuffer := 0; dwLength := 0; nResult := HttpGetData(hClient, PChar('/index.html'), hgblBuffer, dwLength, 0); if nResult <> HTTP_ERROR then begin pszBuffer := PAnsiChar(GlobalLock(hgblBuffer)); { Do something with the data and then unlock and release the handle when it is no longer needed } GlobalUnlock(hgblBuffer); GlobalFree(hgblBuffer); end; end; It is important to remember to unlock and release the global memory handle when you are no longer using it. If you forget to release them, the application will have a memory leak. Event HandlersSocketTools uses events to notify the application when some change in status occurs, such as when data is available to be read or progress notifications during a file transfer. An event handler is simply a callback function which has a specific set of arguments, and the address of that function is passed to the RegisterEvent function in the library. The first step to creating an event handler is to create a function which matches the signature of the callback function defined by the SocketTools event notification function. When the event handler is called, SocketTools will pass the handle to the client session, an event identifier to specify which event occurred, an error code value if an error occurred, and the user-defined value that was specified by the caller when the event was registered. In C++, the callback function would be declared as: VOID EventHandler(HCLIENT hClient, UINT nEventId, DWORD dwError, DWORD_PTR dwParam) In Delphi, the equivalent function would be defined as: procedure EventHandler( hClient: Integer; nEventId: Integer; dwError: LongWord; dwParam: LongWord); stdcall; Note that the stdcall has been specified for the function. This is important, because the SocketTools libraries require the stdcall calling convention, as do any Windows API functions which use callback functions. Failure to specify the correct calling convention can prevent the callback function from being invoked or can cause the application to terminate abnormally. Let's create an event handler that updates a progress bar as a file is being downloaded from an HTTP server. To do this, we'll create a procedure called HttpEventHandler: procedure HttpEventHandler( hClient: Integer; nEventId: Integer; dwError: LongWord; dwParam: LongWord); stdcall; var httpStatus: HTTPTRANSFERSTATUS; nPercent: Integer; begin if nEventId = HTTP_EVENT_PROGRESS then begin HttpGetTransferStatus(hClient, httpStatus); nPercent := Trunc(100.0 * httpStatus.dwBytesCopied / httpStatus.dwBytesTotal); Form1.ProgressBar1.Position := nPercent; end; end; This event handler checks to see if the event ID indicates that it is a progress event, and if it is, calls HttpGetTransferStatus to determine the number of bytes that have been copied so far. This is used to calculate a percentage and a progress bar control is updated with that value. Now that the event handler has been written, the next step is to register the event with the library so that it is called during the transfer. To do this, the HttpRegisterEvent function is called to enable notification for the progress event: nResult := HttpRegisterEvent( hClient, HTTP_EVENT_PROGRESS, @HttpEventHandler, 0); The first argument to HttpRegisterEvent is the handle to the client session. The second argument is the event ID for which you want to enable notification, the third argument is the address of the callback function, and the fourth argument is a user-defined value. That same value is passed to the event handler as the dwParam argument. Your event handler is now registered, and SocketTools will call your event handler during the process of downloading or uploading a file to notify you of the progress of the transfer. |
||
Copyright © 2023 Catalyst Development Corporation. All rights reserved. |