Microsoft Visual C#.NET

The SocketTools Library Edition provides a C# module in the Include folder named cstools6.cs which can be included with your projects. This defines a namespace called SocketTools which contains classes for each of the libraries, as well as the constants and error codes. It also defines some standard Windows API functions which can be helpful when using some of the SocketTools functions.

The first thing you will notice is that the API is slightly different in C#. The client handles are defined as unsigned integers, and many of the functions use the String and StringBuilder classes rather than the typical pointer to an array of characters found in C++. In addition, the function names themselves have been changed slightly and the names of constants have been changed to be more consistent with C# naming conventions. The function names have been altered so that in most cases the protocol name which prefixes the function has been removed. For example, the Hypertext Transfer Protocol library has a function called HttpGetData. The equivalent function in C# would be called as HttpClient.GetData. The function FtpPutFile would be called as FtpClient.PutFile and so on. In addition, constants have been changed from all upper-case to mixed case names defined either in the Constants class, or the specific protocol class. For example, instead of using the constant FTP_ERROR, you would use FtpClient.ftpError instead. It is recommended that you read through the cstools6.cs module to become familiar with the naming conventions used.

String Arguments

Although the SocketTools libraries are unmanaged code, for the most part you won’t have to be concerned about interop functions or marshalling data. However, there are a few special considerations that programmers should be aware of when using the SocketTools API. The first deals with string buffers that are passed by reference and modified by the function. An example of this would be the GetErrorString 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:

TCHAR szError[256];
DWORD dwError;
INT nLength;

dwError = HttpGetLastError();
nLength = HttpGetErrorString(dwError, szError, 256);
MessageBox(NULL, szError, "Error", MB_OK | MB_ICONEXCLAMATION);

In C#, the GetErrorString function in the HttpClient class uses the StringBuilder class as the string argument. The equivalent code in C# would be:

StringBuilder strError = new StringBuilder(256);
uint dwError;
int nLength;

dwError = HttpClient.GetLastError();
nLength = HttpClient.GetErrorString(dwError, strError, 256);
MessageBox.Show(strError.ToString(), "Error",
                MessageBoxButtons.OK,
                MessageBoxIcon.Exclamation);

Make certain you use the System.Text namespace which defines the StringBuilder class. Also keep in mind that if you want to pass a StringBuilder variable to a function that expects a String, you should use the ToString method.

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 C#, the equivalent code would look like this:

byte[] byteBuffer = new byte[1024];
uint dwLength;
int nResult;

dwLength = (uint)byteBuffer.Length;
nResult = HttpClient.GetData(hClient,
                             "/index.html",
                             byteBuffer,
                             ref dwLength,
                             0);

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

String strBuffer = (new ASCIIEncoding()).GetString(byteBuffer, 0, (int)dwLength);

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.

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 C# by using the System.Runtime.InteropServices marshalling classes. There are also some helper functions defined in the SocketTools.Win32 class which can be used to dereference and release global memory handles.

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 C# would look like this:

uint hgblBuffer = 0;
uint dwLength = 0;
int nResult;

dwLength = (uint)byteBuffer.Length;
nResult = HttpClient.GetData(hClient,
                             "/index.html",
                             ref hgblBuffer,
                             ref dwLength,
                             0);

if (nResult != HttpClient.httpError)
{
    IntPtr lpBuffer = Win32.GlobalLock(hgblBuffer);
    String strBuffer = Marshal.PtrToStringAnsi(lpBuffer);
    
    // Do something with the data and then unlock and
    // release the handle when it is no longer needed
    
    Win32.GlobalUnlock(hgblBuffer);
    Win32.GlobalFree(hgblBuffer);
}

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 C# 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. 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. However, C# doesn't permit you to simply pass the address of a function in the same way that you can in C++. 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 dwParam)

In C#, the equivalent method would be defined inside the class as:

private void EventHandler(uint hClient,
                          uint nEventId,
                          uint dwError,
                          uint dwParam)

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 void httpEventHandler(uint hClient, uint nEventId,
                              uint dwError, uint dwParam)
{
    switch (nEventId)
    {
        case HttpClient.httpEventProgress:
       {
           HttpClient.TransferStatus httpStatus = new HttpClient.TransferStatus();
           HttpClient.GetTransferStatus(hClient, ref httpStatus);
           
           progressBar1.Value = (int)((double)(100.0 *
               (double)httpStatus.dwBytesCopied /
               (double)httpStatus.dwBytesTotal));
       }
       break;
    }
}

This event handler checks to see if the event ID indicates that it is a progress event, and if it is, creates a TransferStatus structure and calls GetTransferStatus to determine the number of bytes which 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 defined in the class called EventDelegate. The delegate is created using code like this:

HttpClient.EventDelegate httpEventProc = 
   new HttpClient.EventDelegate(httpEventHandler);

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

int nResult = HttpClient.RegisterEvent(
                hClient,
                HttpClient.httpEventProgress,
                httpEventProc,
                0);

The first argument to RegisterEvent 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.


Copyright © 2008 Catalyst Development Corporation. All rights reserved.