Microsoft Visual Basic .NET  
 

The SocketTools Library Edition provides a Visual Basic module in the Include folder named cstools10.vb which can be included with your projects. This defines the constants and functions in the SocketTools libraries.

String Arguments

An important consideration when using the SocketTools libraries in Visual Basic 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. For example, the InetGetLocalName function stores the local host name into a string parameter. A Visual Basic programmer may write code that looks like this:

Dim strLocalName As String
Dim nLength As Integer

nLength = InetGetLocalName(strLocalName, 256)

Although this code looks correct, it will invariably result in a general protection fault or some other unpredictable error. The problem is that although the strLocalName variable has been defined, no memory has been allocated for it. To do this, you need to declare the string as:

Dim strLocalName As String = New String(Chr(0), 256)
Dim nLength As Long

nLength = InetGetLocalName(strLocalName, 256)

This will create a string that is filled with null characters. However, when the function returns the string, it will be padded with those null characters and they should be removed. The complete example would be written in Visual Basic as:

Dim strLocalName As String = New String(Chr(0), 256)
Dim nLength As Long

nLength = InetGetLocalName(strLocalName, 256)
strLocalName = Strings.Left(strLocalName, nLength)

Because the function returns the length of the string, the Left method can be used to truncate the string using that length. However, if the function does not return the string length, you'll need to use the TrimEnd method to trim the string at the terminating null character, such as:

' Trim string up to the terminating null character
strLocalName = strLocalName.TrimEnd(vbNullChar.ToCharArray())

The first method is more efficient, but requires that the function return the number of characters it copied into the string. The second method is slightly less efficient, but will work even if the function does not return the string length.

Byte Array Arguments

A 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 webserver:

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 Visual Basic.NET, the equivalent code would look like this:

Dim byteBuffer(1024) As Byte
Dim dwLength As Long
Dim nResult As Long

dwLength = UBound(byteBuffer)
nResult = HttpGetData(hClient, _
                      "/index.html", _
                      byteBuffer(0), _
                      dwLength, _
                      0)

In C++, byte arrays can be used interchangeably with ANSI strings. However, in Visual Basic.NET you will need to use the System.Text.Encoding class to convert a byte array into a string. For example:

Dim strBuffer As String
Dim Encoding As System.Text.Encoding = _
    System.Text.Encoding.GetEncoding(1252)

strBuffer = Encoding.GetString(byteBuffer).Substring(0, wwLength)

This would convert the contents of the byte array into a String. 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. Note that the encoding is also explicitly set to code page 1252 (ISO Latin 1), which ensures that any characters with the high bit set are converted correctly.

Global Memory Handles

In 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 Visual Basic.NET with the helper functions defined in the SocketTools module.

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 Visual Basic.NET would look like this:

Dim hgblBuffer As Integer = 0
Dim dwLength As Integer = 0
Dim nResult As Long

nResult = HttpGetData(hClient,
                      "/index.html",
                      hgblBuffer,
                      dwLength,
                      0);

If nResult <> HTTP_ERROR Then
    Dim lpBuffer As IntPtr
    Dim byteBuffer() As Byte
    ReDim byteBuffer(dwLength - 1)

    ' Lock the global memory handle and copy the
    ' contents of the buffer into the byte array
    lpBuffer = GlobalLock(hgblBuffer)
    CopyMemory(byteBuffer(0), lpBuffer, dwLength)

    ' Unlock and release the global memory handle
    GlobalUnlock(hgblBuffer)
    GlobalFree(hgblBuffer)
    
    ' Do something with the data
End If

If you wanted to convert the contents of the global memory buffer into a string, the Marshal class has a helper function which enables you to do this easily. To use it, you should import System.Runtime.InteropServices and then use the PtrToStringAnsi method. For example:

If nResult <> HTTP_ERROR Then
    Dim lpBuffer As IntPtr
    Dim strBuffer As String
    
    ' Lock the global memory handle and use the
    ' PtrToStringAnsi function to return a string
    lpBuffer = GlobalLock(hgblBuffer)
    strBuffer = Marshal.PtrToStringAnsi(lpBuffer)
    
    ' Unlock and release the global memory handle
    GlobalUnlock(hgblBuffer)
    GlobalFree(hgblBuffer)
    
    ' Do something with the data
End If

It is important to remember to unlock and release the global memory handle when you are no longer using it. Those handles are not managed by the Common Language Runtime (CLR) garbage collector, so if you forget to release them, the application will have a memory leak.

Because you are dealing directly with memory buffers, the normal safety checks performed by Visual Basic are not available, such as making sure you are not exceeding the bounds of an array. It is recommended that you always test your code carefully and always save your current project before debugging or executing the program.

Event Handlers

SocketTools 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 HttpRegisterEvent function in the library. However, Visual Basic .NET doesn't permit you to simply pass the address of a function in the same way that you can in C++ or Visual Basic 6.0. Instead, you need to use what are called delegates. In .NET, a delegate is a reference type that is used to encapsulate a method that has a specific set of arguments, known as its signature. Although delegates can seem confusing at first, it is easiest to think of them as function pointers. They're basically used in the same way that function pointers are used in C++, except that they're type-safe.

The first step to creating an event handler is to create a method in your class 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 Visual Basic.NET, the equivalent method would be defined inside the form or class as:

Private Sub EventHandler(ByVal hClient As IntPtr, _
                         ByVal nEventId As Integer, _
                         ByVal dwError As Integer, _
                         ByVal dwParam As IntPtr)

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 method called HttpEventHandler:

Private Sub HttpEventHandler(ByVal hClient As IntPtr, _
                             ByVal nEventId As Integer, _
                             ByVal dwError As Integer, _
                             ByVal dwParam As IntPtr)
    Select Case nEventId
    Case HTTP_EVENT_PROGRESS
        Dim httpStatus As HTTPTRANSFERSTATUS
        HttpGetTransferStatus(hClient, httpStatus)
        ProgressBar1.Value = CInt(100.0# * _
            CDbl(httpStatus.dwBytesCopied) / _
            CDbl(httpStatus.dwBytesTotal))
    End Select
End Sub

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 create the event delegate for the method. For each of the SocketTools networking libraries that support event notification, there is a delegate type defined. The delegate is created using code like this:

Dim httpEventProc As HttpEventDelegate = New HttpEventDelegate(AddressOf HttpEventHandler)

The httpEventProc variable is now like a function pointer which can be passed to HttpRegisterEvent in order to enable notification for that event:

nResult = HttpRegisterEvent( _
             hClient, _
             HTTP_EVENT_PROGRESS, _
             httpEventProc, _
             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 event delegate, 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.