Name Space Providers

Name Space Providers

In Chapter 8, we showed you how an application can register and resolve services within a name space, which is an especially powerful feature for services that might be dynamically created on the network. Unfortunately, the existing name spaces available are limited in their usefulness. The Winsock 2 specification, however, provides a method for creating your own name spaces in which you can handle name registration and resolution in whatever manner you prefer.

This is accomplished by creating a DLL that implements the nine name space functions. These functions all begin with the NSP prefix and are companions to the RNR functions from Chapter 8. For example, the name space function equivalent to WSASetService is NSPSetService. After the DLL is created, it is then installed into the system catalog with a GUID that identifies the name space. Once this is done, applications can register and query services in your name space.

With Windows XP, a new registration and name resolution function was added: WSANSPIoctl. This new function allows for applications to initiate a lookup via the WSALookupService APIs and then use the returned handle in a call to WSANSPIoctl to receive information about that request. Name space providers are not required to implement their own NSPIoctl but can do so if they wish. Throughout our discussion, we will focus on the nine NSP functions that must be implemented but will cover NSPIoctl later.

In this section, we'll first present how to install a name space provider, and then we'll describe the functions a name space provider must implement. Finally, we'll present a sample name space provider as well as a sample application that registers and resolves services.

Installing a Name Space Provider

A name space provider is simply a DLL that implements the name space provider functions. Before applications can use a name space, you must make the system aware of it via the WSCInstallNameSpace function. Conversely, once a provider is installed, you can either disable it or remove it altogether from the system catalog using the functions WSAEnableNSProvider and WSAUnInstall-NameSpace, respectively. We will describe each of these functions next.

WSCInstallNameSpace

This function is used to install a provider into the system catalog and is declared as

int WSCInstallNameSpace (      LPWSTR lpszIdentifier,      LPWSTR lpszPathName,      DWORD dwNameSpace,      DWORD dwVersion,      LPGUID lpProviderId  );

The first thing that you will notice is that all string parameters are wide character strings. Actually, all name space providers are implemented using wide character strings. We'll talk more about this later. The lpszIdentifier parameter is the name of the name space provider. This is the name that is enumerated when you call WSAEnumNameSpaceProviders, which we saw in Chapter 8. The lpszPathName parameter is the location of the DLL. The string can include environment variables, such as %SystemRoot%. The dwNameSpace parameter is a numeric identifier for the name space. For example, the header file NSPAPI.H defines constants for other well-known name spaces, such as NS_SAP for IPX SAP. The dwVersion parameter sets the version number for the name space. Finally, lpProviderId is a GUID that identifies the name space provider.

Upon success, WSCInstallNameSpace returns 0; upon failure, the function returns SOCKET_ERROR. The most common failures are WSAEINVAL, which indicates that a name space with that GUID already exists; and WSAEACCESS, which indicates that the calling process does not have sufficient privilege. Only Administrators group users can install a name space.

WSCEnableNSProvider

This function is used to modify the state of a name space provider. It can be used to enable or disable the provider. The function is declared as

int WSCEnableNSProvider (      LPGUID lpProviderId,     BOOL fEnable ); 

The lpProviderId parameter is the GUID identifier for the name space that you want to modify. The fEnable parameter is a Boolean value indicating that you should either enable or disable the provider. A disabled provider is unable to process queries or registrations.

Upon success, WSCEnableNSProvider returns 0; upon failure, the function returns SOCKET_ERROR. If the provider GUID is invalid, WSAEINVAL is returned.

WSCUnInstallNameSpace

This function removes a name space provider from the catalog. The function is defined as

int WSCUnInstallNameSpace ( LPGUID lpProviderId ); 

The lpProviderId parameter is the GUID for the name space to remove. If the GUID is invalid, the function fails with WSAEINVAL.

Implementing a Name Space

A name space must implement all nine name space functions that map to the RNR functions covered in Chapter 8. In addition to implementing these functions, you must also develop a method for persisting the data. That is, you must maintain the data beyond the instance of the DLL. Every process that loads the DLL receives its own data segment, which means that data stored within the DLL cannot be shared between instances. (Actually, it is possible to share information between multiple applications that have loaded the DLL, but this practice is discouraged.) For more information about DLLs, see Programming Applications for Microsoft Windows, 4th Edition, by Jeffrey Richter (Microsoft Press, 1999). Remember from Chapter 8 that there are three types of name spaces: dynamic, persistent, and static. Obviously, implementing a static name space might not be a good idea because it disallows programmatic registration of services. Later in this chapter, we'll present some ideas about how to maintain the data that the name space needs to persist.

You must also understand the importance of using wide character strings in all name space provider functions. This not only includes string parameters to functions but also strings within the RNR structures, such as WSAQUERYSET and WSASERVICECLASSINFO. You might be wondering how this is possible because when an application registers or resolves a name it can use either the normal (ASCII) or the wide character (UNICODE) version of the RNR functions and structure. Either version works because all ASCII calls go through an intermediate layer that converts all strings to wide character strings. This is true on function call and return. That is, if WSAQUERYSET is returned to the calling application—as with WSALookupServiceNext—any data that the name space provider returns is originally UNICODE and is converted to ASCII before returning from the function call. You can see that if your application uses RNR functions, calling the wide character versions will be faster because no conversions are required.

Of the nine functions that a name space provider must implement, only seven map to Winsock 2 RNR functions, as shown in Table 12-5. The remaining two functions are for initialization and cleanup. Once the name space is installed into the system, applications can use it by specifying either the GUID under which the name space was installed or the name space identifier that is also specified during installation. An application then makes calls to the standard Winsock 2 RNR function, as described in Chapter 8. When one of these functions is called, the equivalent name space provider function is invoked. For example, when an application calls WSAInstallServiceClass, which references the GUID for a custom name space, the function NSPInstallServiceClass for that provider is invoked. In the next section, we'll cover each of the name space functions.

Table 12-5 Mapping RNR Functions to NSP Functions

Winsock Function

Equivalent Name Space Provider Function

WSAInstallServiceClass

NSPInstallServiceClass

WSARemoveServiceClass

NSPRemoveServiceClass

WSAGetServiceClassInfo

NSPGetServiceClassInfo

WSASetService

NSPSetService

WSALookupServiceBegin

NSPLookupServiceBegin

WSALookupServiceNext

NSPLookupServiceNext

WSALookupServiceEnd

NSPLookupServiceEnd

WSANSPIoctl

NSPIoctl

NSPStartup

The NSPStartup function is called whenever the name space provider DLL is loaded. Your name space implementation must include this function, and it must be exported from the DLL. Any per-DLL data structures required for the provider to operate can be allocated when this function is called. NSPStartup is prototyped as

int NSPStartup (      LPGUID lpProviderId,      LPNSP_ROUTINE lpnspRoutines  ); 

The first parameter, lpProviderId, is the GUID for this name space provider. The lpnspRoutines parameter is an NSP_ROUTINE structure that your implementation of this function must fill out. This structure provides function pointers to the other eight name space functions that belong to your provider. The NSP_ROUTINE object is defined as

typedef struct _NSP_ROUTINE  {     DWORD                    cbSize;     DWORD                    dwMajorVersion;     DWORD                    dwMinorVersion;     LPNSPCLEANUP             NSPCleanup;     LPNSPLOOKUPSERVICEBEGIN  NSPLookupServiceBegin;     LPNSPLOOKUPSERVICENEXT   NSPLookupServiceNext;     LPNSPLOOKUPSERVICEEND    NSPLookupServiceEnd;     LPNSPSETSERVICE          NSPSetService;     LPNSPINSTALLSERVICECLASS NSPInstallServiceClass;     LPNSPREMOVESERVICECLASS  NSPRemoveServiceClass;     LPNSPGETSERVICECLASSINFO NSPGetServiceClassInfo;     // NSPIoctl is a new API added in Windows XP     LPNSPIOCTL               NSPIoctl; } NSP_ROUTINE, FAR * LPNSP_ROUTINE;

The first field, cbSize, indicates the size of the NSP_ROUTINE structure. The next two fields, dwMajorVersion and dwMinorVersion, are included for versioning your provider. The versioning is arbitrary and serves no other purpose. The provider sets the rest of the entries to their respective function pointers. For example, the provider assigns its NSPSetService function address (no matter what it is named) to the NSPSetService field. The names of your provider functions can be arbitrary, but their parameters and return types must match the provider definition.

The only action required of an NSPStartup implementation is filling in the NSP_ROUTINE structure. Once the provider completes this and any other initialization routines of its own, it returns NO_ERROR if everything is successful. If an error occurs, the NSPStartup implementation returns SOCKET_ERROR and sets the Winsock error code. For example, if a provider attempts and fails to allocate memory, it calls WSASetLastError with WSAENOBUFS as the parameter and then returns SOCKET_ERROR.

This might be a good time to discuss error handling in your provider's DLL. All of the functions you must implement for a provider return NO_ERROR upon success and SOCKET_ERROR upon failure. If you determine that the call fails, set the appropriate Winsock error code before returning. If you fail to do this, any application attempting to register or query services using your name space provider will report the failure of an RNR function but WSAGetLastError will return 0. This will cause tremendous trouble for applications that attempt to handle errors gracefully; 0 is certainly not an expected return value upon error.

NSPCleanup

This routine is called when the provider's DLL is unloaded. Within this function, you can free any memory allocated in the NSPStartup routine. This routine is defined as

int NSPCleanup ( LPGUID lpProviderId ); 

The only parameter is your name space provider's GUID. Other than cleaning up any dynamically allocated memory, you're not required to do anything in this function.

NSPInstallServiceClass

The NSPInstallServiceClass function maps to WSAInstallServiceClass and is responsible for registering a service class. NSPInstallServiceClass is defined as

int NSPInstallServiceClass (      LPGUID lpProviderId,      LPWSASERVICECLASSINFOW lpServiceClassInfo  );

The first parameter is the provider's GUID. The lpServiceClassInfo parameter is the WSASERVICECLASSINFOW structure that is being registered. Your provider has to maintain a list of service classes and has to ensure that a service class doesn't already exist using the same GUID within the WSASERVICECLASSINFOW structure. If the GUID is already in use, the provider must return the error WSAEALREADY. Otherwise, the provider should maintain this service class so that other RNR operations can refer to it.

The majority of the remaining name space provider functions refer to an installed service class.

NSPRemoveServiceClass

This function is the complement of the NSPInstallServiceClass function and removes the specified service class. This name space function maps to WSARemoveServiceClass. The function is declared as

int NSPRemoveServiceClass (      LPGUID lpProviderId,      LPGUID lpServiceClassId  ); 

As in the previous function, the first parameter is the provider's GUID. The second parameter, lpServiceClassId, is the service class GUID that is to be removed. The provider must remove the given service class from its storage. If the service class specified by lpServiceClassId is not found, the provider must generate the error WSATYPE_NOT_FOUND.

NSPGetServiceClassInfo

The NSPGetServiceClassInfo function maps to the WSAGetServiceClassInfo function. It retrieves the WSANAMESPACE_INFOW structure associated with a GUID. The function is defined as

int NSPGetServiceClassInfo (      LPGUID lpProviderId,      LPDWORD lpdwBufSize,      LPWSASERVICECLASSINFOW lpServiceClassInfo  ); 

Again, the first parameter is the provider's GUID. The lpdwBufSize parameter indicates the number of bytes contained in the third parameter, lpServiceClassInfo. On input, the third parameter is a WSASERVICECLASSINFOW structure that contains the search criteria specifying which service class to return. This structure can contain either a service class name or the GUID for the service class to return. If the provider finds a match, it must return the WSASERVICECLASSINFOW structure in lpServiceClassInfo and should update lpdwBufSize to indicate the number of bytes being returned.

If, given the criteria, no service classes are found, the call should fail and set WSATYPE_NOT_FOUND as the error. In addition, if a service class does match but the supplied buffer is too small, the provider should update the lpdwBufSize parameter to indicate the correct number of bytes required and the error WSAEFAULT should be set.

NSPSetService

The NSPSetService function maps to WSASetService and either registers or removes services from the name space. The function is defined as

int NSPSetService (      LPGUID lpProviderId,      LPWSASERVICECLASSINFOW lpServiceClassInfo,      LPWSAQUERYSETW lpqsRegInfo,      WSAESETSERVICEOP essOperation,      DWORD dwControlFlags  );

The first parameter is the provider's GUID. The lpServiceClassInfo parameter is the WSASERVICECLASSINFOW structure to which this service belongs. The lpqsRegInfo parameter is the service to either register or delete depending on the operation specified in the fourth parameter, essOperation. The last parameter, dwControlFlags, might specify the flag SERVICE_MULTIPLE that can modify the specified operation. See Tables 8-4 and 8-5 in Chapter 8 for a description of the possible essOperation and dwControlFlags values.

The name space provider first verifies that the supplied service class does exist. Then, depending on which operation is specified, appropriate action is taken. For a full description of valid essOperation values as well as the effect of dwControlFlags on them, see the section on service registration in Chapter 8, which discusses WSASetService. Your provider's NSPSetService function handles these flags accordingly.

If your service provider is updating or deleting a service that cannot be found, the error WSASERVICE_NOT_FOUND is set. If the provider is registering a service and the WSAQUERYSETW structure is invalid or incomplete, the provider generates the WSAEINVAL error.

This function is one of the most complicated name space provider functions to implement (next to NSPLookupServiceNext). The provider must maintain a scheme for persisting the services that can be registered and must allow the NSPSetService function to update this data.

NSPLookupServiceBegin

The NSPLookupServiceBegin function is associated with the NSPLookupServiceNext and NSPLookupServiceEnd functions and is used to initiate a query of your name space. This function maps to WSALookupServiceBegin and establishes the criteria for your search. This function is prototyped as

int NSPLookupServiceBegin (      LPGUID lpProviderId,    (continued)     LPWSAQUERYSETW lpqsRestrictions,      LPWSASERVICECLASSINFOW lpServiceClassInfo,      DWORD dwControlFlags,      LPHANDLE lphLookup  );

As with previous functions in this section, the first parameter is the provider's GUID. The lpqsRestrictions parameter is the WSAQUERYSETW structure that defines the parameters for the query. The third parameter, lpServiceClassInfo, is the WSASERVICECLASSINFOW structure containing the schema information for the service class in which the query is to take place. The dwControlFlags parameter takes zero or more flags that affect how the query is performed. Again, for information on WSALookupServiceBegin and the different flags that can be used, refer to Chapter 8. Note that not all of the flags make sense for every provider. For example, if your name space does not support the notion of container objects, you don't have to worry about those flags dealing with containers. (A container is simply a way of conceptually organizing the services—what constitutes a container is open to interpretation.) Finally, lphLookup is an output parameter, which is a handle that defines this particular query. The handle is used in the subsequent calls to WSALookupServiceNext and WSALookupServiceEnd.

When implementing NSPLookupServiceBegin, keep in mind that the operation cannot be canceled, and it should complete as quickly as possible. Therefore, if you need to initiate a network query, a response should not be required to return successfully.

The provider should save the query parameters and associate a unique handle with the query for later reference. In addition to saving the handle and the query, the provider should maintain state information. We'll explore the significance of this in our discussion of the next function, NSPLookupServiceNext.

NSPLookupServiceNext

Once a query has been initiated with WSALookupServiceBegin, an application calls WSALookupServiceNext, which in turn calls the name space provider function NSPLookupServiceNext. This call is what actually searches for results that match the search criteria registered for this query. The function is defined as

int NSPAPI WSALookupServiceNext (      HANDLE hLookup,      DWORD dwControlFlags,      LPDWORD lpdwBufferLength,      LPWSAQUERYSET lpqsResults  ); 

The first parameter, hLookup, is the query handle returned from WSALookupServiceBegin. The dwControlFlags parameter can be the flag LUP_ FLUSHPREVIOUS, which indicates that the provider should discard the last result set and move to the next one. Typically, an application requests that the last result set be discarded when the application cannot supply a large enough buffer for the results. The next parameter, lpdwBufferLength, indicates the size of the buffer passed as the last parameter, lpqsResults.

When NSPLookupServiceNext is triggered, the provider should look up the query parameters identified by the handle hLookup. Once the query parameters are retrieved, a search should be initiated for all registered services within the service class specified by the query that match the supplied criteria. As we mentioned in the section on NSPLookupServiceBegin, the state of the query should be saved. If there are multiple matching entries, a calling process calls WSALookupServiceNext multiple times, and with each call your provider needs to return a data set. When there are no more matches, the provider returns the error WSA_E_NO_MORE. It is also possible to cancel a query in progress if the application makes a call to WSALookupServiceEnd from another thread while a call to WSALookupServiceNext is in progress. In this event, NSPLookupService-Next should fail with the error WSA_E_CANCELLED.

NSPLookupServiceEnd

After a query has been completed, NSPLookupServiceEnd is called to end the query and release any underlying resources. This function is defined as

int NSPLookupServiceEnd ( HANDLE hLookup ); 

The single parameter to the function is hLookup, which is the handle to the query that is to be closed. If the given handle cannot be found (for example, if it's an invalid handle), the call must fail with the error WSA_INVALID_HANDLE.

NSPIoctl

The last function is NSPIoctl, which is not required to develop a name space provider. If a NSP decides not to implement this function, the cbSize field of the NSP_ROUTINE should be set to the size of the structure without the LPNSPIOCTL pointer. This can be done with the following code:

lpnspRoutine->cbSize = FIELD_OFFSET(NSP_ROUTINE, NSPIoctl));

As we saw in Chapter 8, the WSANSPIoctl API is new to Windows XP and is currently used only by the NLA name space, which provides notification when information about the current network changes.

The function definition for the NSP equivalent NSPIoctl is

INT NSPIoctl(     HANDLE          hLookup,     DWORD           dwControlCode,     LPVOID          lpvInBuffer,     DWORD           cbInBuffer,     LPVOID          lpvOutBuffer,     DWORD           cbOutBuffer,     LPDWORD         lpcbBytesReturned,     LPWSACOMPLETION lpCompletion,     LPWSATHREADID   lpThreadId     );

This function provides a method for exposing miscellaneous commands from the name space, and it is completely up to the name space developer to determine how the input and output parameters work. For example, if an NSP wanted to expose some statistics, such as number of entities registered or number of queries performed, it could do this by implementing NSPIoctl and allowing applications to query it via WSANSPIoctl with its own defined ioctl code and output buffer structure.

The other benefit to this function is that it allows asynchronous notification through the lpCompletion parameter. This structure allows the calling application to specify an overlapped structure, completion routine, window, or completion port to receive notification of completion. Again, it's up to the NSP developer to determine what information is being registered for notification, but this function could allow asynchronous name resolution—something that cannot be done via the WSALookupService functions. In Chapter 8, you saw how an application can register to receive notifications when the local network information changes.

If a name space chooses to implement this function, it must save the input and output buffers as well as the WSACOMPLETION information. Then when the ioctl completes (either immediately or at some later point), the output information should be copied to the supplied buffer (or an error returned if it's not large enough), and the notification routine (if supplied) should be signaled.

Depending on how the service is implemented, there are several ways to process these asynchronous completion events. If the NSP is notifying the application from the DLL, it is simple. To send a message to a window, use PostMessage. To queue an APC, QueueUserApc is used. To signal an event, SetEvent is called. Lastly, to notify a completion port, PostQueuedCompletionStatus is used. These functions are regular Windows APIs and more information about them can be found in the Platform SDK.

The situation becomes more difficult if the application is notified from a service or separate process that persists the name space data. Services often operate under a different user group and may not have access to certain resources. Writing Windows services is beyond the scope of this book. For more information, consult the Platform SDK or Programming Server-Side Applications for Windows 2000 by Jeffrey Richter and Jason D. Clark (Microsoft Press, 2000). In the next section, we'll discuss the sample NSP, which is implemented as a separate process but does not expose the NSPIoctl function.

Name Space Provider Example

In the previous sections, we covered the steps for creating your own name space and touched on some of the important name space creation issues, such as methods for data persistence. However, developing an entire name space provider can be complicated, and the rest of this chapter will be devoted to our example name space provider. Although the example is not the fastest or most optimized code, it illustrates the topics that require the most attention. In addition, we kept it simple so it's easy to follow and understand.

The example provider is located on the companion CD in the NSP directory in the files MYNSP.H, MYNSP.CPP, and MYNSP.DEF. These three files make up the name space DLL. In addition to the DLL, you'll find the name space service that is a Winsock server responsible for handling requests from the DLL. This server, which maintains the service registration data, is found in the file MYNSPSVC.CPP. Two additional files, NSPSVC.CPP and PRINTOBJ.CPP, are used by both the DLL and the service and contain support routines for marshaling and demarshaling data sent on a socket between the service and the DLL. Marshaling and demarshaling data will be explained later in this section. In addition to these two files, you'll find their accompanying header files, NSPSVC.H and PRINTOBJ.H, which contain the function prototypes for the support routines. Finally, the file RNRCS.C is a modified sample from Chapter 8 that registers and looks up services in our custom name space. Note that when we refer to the name space provider as a service, we are not implying that the sample code is a true Windows service.

In the following sections, we will discuss how our name space is implemented. First, we'll give an overview of the method we chose to persist the data. This overview will be followed by an examination of how the actual name space DLL is structured as well as how to install the name space. Then we'll cover the implementation of the name space service. Finally, we'll look at how an application performs service registrations and queries to our custom name space.

Data Persistence

For our name space, we chose a separate Winsock application to maintain the name space information. In each of the name space functions implemented in the DLL, a connection is made to this process and data is transacted to complete the operation. For simplicity, this process runs locally (the service listens on the loopback address 127.0.0.1). In an actual implementation, our name space service's IP address would be accessible via the Registry or some other means so that when an application invoked the name space, it could connect to the service wherever it was running. For example, with DNS, the IP address of the DNS server is either set statically or obtained during a DHCP request.

Of course, writing a service to maintain the information is not the only option available. You could maintain a file on the network that keeps the necessary information; however, this is probably not the best option because performance is then bound by disk operations. One performance limitation of our sample name space is that it establishes TCP connections to the service. A production-quality implementation is more likely to use a connectionless datagram protocol such as UDP to improve performance. Of course, this would involve additional programming requirements—such as ensuring that dropped packets are retransmitted—but the overall performance gains would be considerable.

Name Space DLL

Before we look at how the name space service is implemented, let's take a look at the name space DLL. Each name space provider requires a unique GUID, and ours is defined in MYNSP.H. In addition to the unique identifier, we need a simple integer identifier for our name space. This identifier can be used in the dwNameSpace field of the WSAQUERYSET structure, as you saw in Chapter 8. The GUID and name space identifier are

GUID MY_NAMESPACE_GUID = {0x55a2bd9e,0xbb30,0x11d2,                           {0x91,0x66,0x00,0xa0,0xc9,0xa7,0x86,0xe8}                          }; #define NS_MYNSP 66

These values are important because applications that want to use this name space must specify these values in their Winsock calls. Of course, an application's developer can specify these values directly or retrieve them with a WSAEnumNameSpaceProviders call. (See Chapter 8 for more information.) Also, be aware that if an application performs an operation specifying the NS_ALL name provider, the operation occurs on all installed name providers. You should keep this in mind because several Windows applications, such as Internet Explorer, perform queries on all installed name providers. Be very careful, therefore, when testing a name provider. A poorly written name provider can cause system-wide problems. In addition, the GUID and name space identifier values are important because they are required to install the name provider.

Now let's take a look at the NSP functions implemented in MYNSP.CPP. For the most part, these functions are quite similar except for the startup and cleanup functions, NSPStartup and NSPCleanup. The startup function simply initializes the NSP_ROUTINE structure with our custom name space functions. The cleanup routine does nothing because no cleanup is necessary.

The rest of the functions require interaction with our service to either query or register data. When communication with the service is necessary, follow these steps:

  1. Connect to the service (via the MyNspConnect function).

  2. Write a 1-byte action code. This indicates to the service which action is about to take place (such as service registration, service deletion, and query).

  3. Marshal parameters and send them to the service. The type of parameters sent will depend on the operation. For example, NSPLookupServiceNext sends the query handle to the service so that it can resume the query, whereas NSPSetService sends an entire WSAQUERYSET structure.

  4. Read the return code. Once the service has the necessary parameters to perform the requested operation, the return code (success or failure) of the operation is returned. The file MYNSP.H defines two constants for this purpose: MYNSP_SUCCESS and MYNSP_ERROR.

  5. If the requested operation was a query and the return code was success, read and demarshal the results. For example, NSPLookup- ServiceNext returns a WSAQUERYSET structure if a matching service is found.

As you can see, implementing the DLL is not overly complicated. The NSP functions must take the parameters and process them, which in our case is to pass this information to the name space service. After this, it is up to the service to perform the requested operation. However, we have glossed over one difficult operation that must be performed: sending data over a socket. Normally, there aren't any special requirements for sending data, but when sending entire data structures, there are. Most of the name space functions take either a WSAQUERYSET or a WSASERVICECLASSINFO structure as a parameter. This object must be sent or received on the socket connection to the service. This presents some difficulty because these structures aren't contiguous blocks of memory. They contain pointers to strings and other structures that can be located anywhere in memory, as illustrated in Figure 12-5. You need to take all of these pieces of memory—wherever they are—and copy them into a single buffer one after another. This is known as marshaling data. On the receiving end, this process has to be reversed. The data read needs to be reassembled into the original structure, and the pointers have to be “fixed” so that they point to valid memory locations on the recipient's machine.

Figure 12-5 Marshaling data

For our name space provider, we provide functions to marshal and demarshal both the WSANAMESPACEINFO and WSAQUERYSET structures. These functions are located in NSPSVC.CPP and are used by both the name space DLL and the name space service (because both sides need the capability to marshal and demarshal these structures). All four functions are self-explanatory—we won't cover them in depth here.

Installing the Name Space

Installing a name space provider is the most trivial step in the entire process. The file NSPINSTALL.C is a simple installation program. The following code installs our provider:

ret = WSCInstallNameSpace(L"Custom Name Space Provider",     L"%SystemRoot%\\System32\\Mynsp.dll", NS_MYNSP, 1,      &MY_NAMESPACE_GUID); if (ret == SOCKET_ERROR) {     printf("Failed to install name space provider: %d\n",                      WSAGetLastError()); }

The only parameters to the call are the provider's name, the DLL's location, the integer identifier, the version, and the GUID. After installation, the only requirement is to make sure that the name space DLL is located where you say it is. The only error that's a real possibility is trying to install a name provider with a GUID that's already in use by another provider.

Removing a name space provider is even easier. The following code snippet from our installation program removes our provider:

ret = WSCUnInstallNameSpace(&MY_NAMESPACE_GUID); if (ret == SOCKET_ERROR) {     printf("Failed to remove provider: %d\n", WSAGetLastError()); }

Name Space Service

The name space service is the real guts of the name provider. This service keeps track of all registered service classes and service instances. When a user's application triggers the name space DLL, it connects to the name space service to perform the operation. The service is simple. Within the main function, a listening socket is established. Then, within a loop, connections are accepted from instances of the name space DLL. For simplicity, only a single connection is handled at a time. This also prevents you from having to synchronize access to the data structures that maintain the name space information. Again, a real provider would not do this because it degrades performance, but it does make the example easier to understand. Once a connection is accepted, the service reads a single byte from the name space DLL that identifies the action to follow.

Within the loop, the action is decoded and parameters are passed from the name space DLL to the service. From there, the requested actions are performed. These actions aren't complicated. The code is easy to follow, and by examining the steps for each possible action you can determine how the service works—we don't need to go into detail here. However, we will examine the structures that maintain the information. There are only two data types that name space providers are concerned about: the WSASERVICECLASSINFO and WSAQUERYSET structures. As you have seen, the majority of the RNR functions reference one or the other of these two structures in their parameters. As a result, we maintain two global arrays—one for each structure type—along with a counter for each.

When the DLL requests to install a service class, the name service provider's main function first calls LookupServiceClass, a support routine defined in MYNSPSVC.CPP. This function iterates through the array of WSASERVICECLASSINFO structures, ServiceClasses. If a service class is found with the same GUID, it returns an error (which the DLL translates as WSAEALREADY). Otherwise, the new service class is added to the end of the array and the dwNumServiceClasses counter is incremented.

During the deletion of a service class (as when installing a service class), the main function calls LookupServiceClass. In this case, however, if the service class is found, the code moves the last service class in the array to the location of the deleted class. The code then decrements the counter. One aspect that is not specifically covered in the Winsock 2 specification for name space providers is what happens when a service class is to be deleted but there are still services registered that refer to it. How you choose to handle this is up to you. Our example name space won't allow the removal of a service class if there are services registered that reference it.

The same principle that's used for maintaining WSASERVICECLASSINFO structures is also used for keeping track of WSAQUERYSET structures. There is an array of these structures named Services, as well as a counter named dwNumServices. The addition and deletion of services is handled in the same manner that it is for service classes.

The last bits of information that the service must maintain are for queries. When an application initiates a query, the query parameters must be maintained for the life of the query and assigned a unique handle. This is necessary because WSALookupServiceNext refers to the query by the handle only. The other piece of information that must be kept is the state of the query. Each call to WSALookupServiceNext can return a single information set. The code must remember the last position within the Services array where data was returned so that subsequent calls to WSALookupServiceNext begin where the previous call left off.

Querying the Name Space

The last part of our name space sample is the file RNRCS.C. This is a modified version of the name registration and resolution example presented in Chapter 8. We've made only a few changes to make the example as simple as possible. The first change causes the code to enumerate the installed name space providers but to return only the NS_MYNSP provider. Second, when registering a service, RNRCS.C enumerates only the local IP interfaces to use as the address of our service. Our service provider supports the registration of any SOCKADDR type. Finally, for service registration, this example does not create an instance of the service; it just registers the name. Otherwise, this example behaves like the Chapter 8 example.

Running the Example

Once all the examples have been compiled, installing and using the provider is simple. The following command installs the provider:

Nspinstall.exe install

Of course, don't forget to copy MYNSP.DLL to %SystemRoot%\System32. Once the name space is installed, an instance of the service must be running to query and register services. This is done by the following command:

Mynspsvc.exe

Now you can query and register services using RNRCS.EXE. Table 12-6 shows some commands that you should execute and the order you should follow. This sequence of commands registers two services and then performs a wildcard query and a specific query. Then the command sequence queries for each of the two services and deletes them. Finally, we perform a wildcard query to illustrate that the services have been deleted.

Table 12-6 Running the Sample Name Space

Command

Meaning

RNRCS.EXE -s:ASERVICE

Register the service ASERVICE.

RNRCS.EXE -s:BSERVICE

Register the service BSERVICE.

RNRCS.EXE -c:*

Query for all registered services.

RNRCS.EXE -c:BSERVICE

Query only for services named BSERVICE.

RNRCS.EXE -c:ASERVICE -d

Query only for services named ASERVICE, and delete them if found.

RNRCS.EXE -c:BSERVICE -d

Query only for services named BSERVICE, and delete them if found.

RNRCS.EXE -c:*

Query for all registered services.



Network Programming for Microsoft Windows
Network Programming for Microsoft Windows (Microsoft Professional Series)
ISBN: 0735605602
EAN: 2147483647
Year: 2001
Pages: 172
Authors: Anthony Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net