Channels ▼
RSS

Tools

Delphi 4 and the WNet API

Source Code Accompanies This Article. Download It Now.


Dec98: Delphi 4 and the WNet API

Fritz is a Windows NT system developer specializing in networked and distributed applications for Nims Associates Inc. in Denver, Colorado. You can contact him at fritzl@ uswest.net.


Sidebar: Workgroups versus Domains

The Microsoft Windows NT registry provides a convenient means of consolidating configuration information for systems and applications. Even nicer is the ability to connect to remote registries and make changes from afar using regedt32. What's not so nice is that if you have a large number of computers to manage, you have to connect to each separately in order to make any edits -- even if all of their administrative logins are the same.

My usual programming domain is the realm of the invisible -- Windows NT services, server-side development, and network protocol implementations. For jobs such as these, just about any language will do. However, when an interface is called for, my tool of choice is Inprise's Delphi, which allows for rapid interface development and can implement system-level code like a champ. Object Pascal (Delphi's language) provides all of the object orientation most programmers will ever need (or use). It also provides a resource protection (try-finally) and exception handling (try-except) syntax that I still haven't found matched in any C++ environment.

I used Delphi to write my multimachine remote registry editing tool based on the Win32 WNet API. This set of functions supports workgroup/domain browsing in order to select and connect to computers. In this article, I'll focus on how that browser was implemented, first in Delphi 3.0 and later in Version 4.0. The source code and related files for the complete application are available electronically; see "Resource Center," page 3. I'll also examine the WNet API, discuss some of the differences between Delphi 4.0 and previous versions, and look at authentication differences between workgroups and domains.

Porting to Delphi 4.0

The registry editing application I present here was originally written under Delphi 3.0. When Version 4.0 was released, I quickly moved to upgrade the application. What I discovered was that Delphi 4.0's new features (additions to the IDE, changes to the Visual Component Library [VCL], and to the Object Pascal language itself) meant the port involved more than simply recompiling.

The most significant change I encountered relates to the new unsigned 32-bit data types DWORD and LongWord. In Delphi Versions 2 and 3, the data type integer was defined as a signed 32-bit value. Since DWORD and UINT, derived types, were defined as "integer," they were computationally identical to the base type. Since I rarely dealt with large integer values, I wasn't concerned by the distinction. Hindsight can be quite instructive.

With Delphi 4.0, variables and arguments declared by the WinAPI as DWORD or UINT are, in fact, unsigned integers. This leads to some interesting issues when porting code from previous versions of Delphi; for instance, how many of us have looked at the MessageBeep function in the API help and seen the line:

0xFFFFFFFF Standard beep using

the computer speaker

We all know that 0xFFFFFFFF means "-1," right? Yes, if you were dealing with signed integers, but the declaration of MessageBeep is:

function MessageBeep(uType:

UINT): BOOL; stdcall;

That UINT didn't used to matter, but using -1 with 4.0 as the parameter generates the compiler error: [Error]: Constant expression violates subrange bounds. A similar gotcha occurs when you try to compare a DWORD value with an integer value. For instance, operations like Example 1 generate warnings from the compiler, such as: [Warning]: Comparing signed and unsigned types - widened both operands.

Since many WinAPI functions and constants are defined as DWORD and UINT variables, chances are it'll take some time for you to work out all of the comparison and typecasting issues in larger projects.

Other new 4.0 language features include dynamic arrays, method overloading, default parameters, a 64-bit integer data type, and greater precision for the Real data type. Such features make Delphi a serious contender as a general-purpose language.

The WNet API

WNet, a high-level network API that comes with Windows 95/NT, allows for enumeration of network resources and authentication/connection to those resources. If you want to integrate network drive and printer mapping (or similar functionality) in your programs, WNet is the API to focus on.

WNet functions divide network resources into containers and connectable resources. Containers are groupings of other resources; for example, a workgroup or (NT) domain is a logical grouping of computers (see the accompanying text box entitled "Workgroups versus Domains"). On the other hand, a workstation or server is a container of shares. The shares (print, drive, database, and so forth) are connectable resources that can be accessed by authenticated users. Interestingly, you can connect to a computer without connecting to any particular resource. This is due to the administrative Default Shares, which can be seen in the Server Control Panel (see the names that end with "$" in Figure 1).

If your application only needs access to drive shares, the WNet WNetConnectionDialog function displays a dialog that lets you browse and connect, prompting for login information if needed. WNetDisconnectDialog lets you break the connection. In some instances, this can be a labor saver. However, I needed to work with more than one target machine at a time. To do this, I built a workgroup/domain browser for selecting multiple target machines.

The Workgroup/Domain Browser

When implementing a browser, you start by enumerating the containers visible to your computer. For my project, I had to do this in two stages.

  1. List top-level containers (Microsoft Windows Network and Novell Network).

  2. Enumerate the Windows network containers to find the available workgroup and domain names.

The lynchpin of most of the WNet API is the NETRESOURCE structure (TNetResource as defined in the Delphi Windows.DCU unit), which receives information about each object found. Listing One (listings are located at the end of this article) presents two enumeration loops -- WNetOpenEnum through WNetCloseEnum. I used Delphi resource protection blocks around both enumeration loops to make sure that enumeration handles (and whatever memory resources they represent) get released. By specifying RESOURCE_GLOBALNET and RESOURCETYPE_ANY when opening the enumeration handles, Listing One sees everything that's out there. To get the names of available objects, WNetEnumResource fills an array of NETRESOURCE records. Once this array is populated, I iterate through it to fill up my Containers listbox with the names provided by the lpRemoteName field of the record.

Once I had a list of the workgroups and domains on my network, the next step was to get the names of the machines within these containers when the container is named. Again it's WNetOpenEnum and WNetEnumResource that do the work. The difference this time is that you already know the name of the container you want to start from, and (at least in my case) I wanted to see the connectable objects within those containers -- computers. To view shared drives or printers, another enumeration for specific machines would be required.

To accomplish this, I first populated a NETRESOURCE record with some defaults, assigning dwUsage to RESOURCEUSAGE_CONNECTABLE to look for shared objects and the desired container name in the lpRemoteName field. The handle returned by WNetOpenEnum will force WNetEnumResources to see only those computers within this container. To list the names in Universal Naming Convention (UNC) style format (\\MACHINE, for instance), you use the RESOURCEDISPLAYTYPE_GENERIC flag (see Listing Two). The names are populated into the Computer Names listbox, and a browser as in Figure 2 is born.

Connecting

Connecting to other machines on the network is only a function call away -- WNetAddConnection2. Why the "2"? Because, according to win32.hlp, WNetAddConnection is a legacy function. Notice that all computer names are accessed using UNC style "\\" format for this function. (See the text box "Workgroups versus Domains" for details on what constitutes a valid name.)

The last element for making a connection is authentication information, commonly known as a "username and password." The utility I wrote only needed to know one set of authentication information, since the administrator accounts on our computers had identical logins (see Figure 3). Providing a NULL username and password will cause the connection to attempt to use the cached authentication information, that is, the username and password you entered when you logged onto your computer.

Once you know where you are going and who you need to be when you get there, it's time to reach out and touch someone. Once again, the first step is to populate a NETRESOURCE structure. In my experience, WNetAddConnection2 corrupts the contents of the NETRESOURCE structure after it attempts a connection. Since this tool is not mapping a network drive or printer, there is nothing required in the lpLocalName field of the structure. If it were such a connection, the code could set the local name to a drive letter or printer name and the last parameter, fdwConnection, to CONNECT_UPDATE_PROFILE in order to mark this connection as persistent, meaning that the system would attempt to reconnect on subsequent logins. My program then goes ahead with its business, closing the connection using WNetCancelConnection2 when finished.

Recall that I wrote the application to edit remote registries. A look at the registry functions reveals the RegConnectRegistry function that takes a machine name argument. What the API help file doesn't tell you is that you must either already have an authenticated connection to the machine or your current username and password (as cached in the network subsystem) must be able to log into the target machine. In my code, I use the Delphi TRegistry object for registry handling. It has a RegistryConnect method that is based upon the RegConnectRegistry function. It also provides a useful set of methods for the rest of the registry interface.

The remainder of the utility (available electronically) is a registry action editor that lets you define individual actions, optionally saving them as macros. By looping through the machine list, connecting to the remote machines, applying my registry actions, and finally disconnecting I can perform many administrative duties from the comfort of my office. And isn't that what sysadmins really want to do?

Conclusion

If you need to map drives, attach to printers, or perform administrative attachments, the WNet API provides a straightforward interface. Using Delphi, I was quickly able to write a functional tool for managing the registries and (by extension applications) on a large number of computers.

The same network techniques can be applied to a variety of management tasks. Of course, they can also be used to wreak havoc, too. To prevent misuse of the Windows NT remote management facilities, be sure to secure your administrative logins, apply all of the service packs and fixes appropriate for your environment, and encourage users who share resources to make informed decisions about access permissions.

Acknowledgment

Thanks to Tom Seago, who uses the tool presented here on a daily basis and provided invaluable assistance and testing during its development.

DDJ

Listing One

try    { do the enumeration of containers }
    if WnetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, nil, hEnum) <>
                     NO_ERROR then
                exit;
    { now enumerate the containers  }
    iBufSize := sizeof(aBuf);
    iEntries := 64;  { 64 entries at a gulp }
    while WNetEnumResource(hEnum, iEntries, @aBuf[0], iBufSize) = NO_ERROR
do begin
     for i := 0 to (iEntries-1) do begin
       if ((aBuf[i].dwUsage and RESOURCEUSAGE_CONTAINER) =
                RESOURCEUSAGE_CONTAINER) and
       (pos('Microsoft Windows', string(aBuf[i].lpRemoteName)) = 1) then begin
               { enum workgroups and domains }
               try
               if WnetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
                          @aBuf[i], hEnum2) <> NO_ERROR then begin
               showmessage('Error enumerating Microsoft Network Resources!');
                           exit;
                      end;          
                      iBufSize := sizeof(aBuf2);
                      iEntries2 := 64;
                      while WNetEnumResource(hEnum2, iEntries2, @aBuf2[0], 
                                               iBufSize) = NO_ERROR do begin
                      for j := 0 to (iEntries2 - 1) do
                       lbContainers.items.add(string(abuf2[j].lpRemoteName));
                           iEntries2 := 64;
                        end;
                     finally
                        WNetCloseEnum(hEnum2);
                     end;
                 end;
             end;
             iEntries := 64;
        end;
finally
        WnetCloseEnum(hEnum);
end;
     

Back to Article

Listing Two

try   screen.cursor := crHourGlass;
   with rNetRez do begin
      dwScope := RESOURCE_GLOBALNET;
      dwType  := RESOURCETYPE_ANY;
      dwDisplayType := RESOURCEDISPLAYTYPE_GENERIC; 
      dwUsage := RESOURCEUSAGE_CONNECTABLE; 
      lpLocalName := nil;
      lpRemoteName := pchar(lbContainers.items[lbContainers.itemindex]);
      lpComment := nil;
      lpProvider := nil;
  end;
  if WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
                                 @rNetRez, hEnum) <> NO_ERROR then begin
       ShowMessage('Could not enumerate container: ' + 
                                 string(rNetRez.lpRemoteName));
       exit;
   end;
     
   iBufsize := sizeof(abuf);
   iEntries := 64;
   while WNetEnumResource(hEnum, iEntries, @aBuf[0], iBufSize) = NO_ERROR
do begin
        for i := 0 to (iEntries -1) do
        lbMachines.items.add(string(aBuf[i].lpRemoteName));
        iEntries := 64;
   end;
finally
   WnetCloseEnum(hEnum);
   screen.cursor := crDefault;
end;
     

Back to Article

Listing Three

zeromemory(@rNetRez, sizeof(TNetResource));with rNetRez do begin
        dwType := RESOURCETYPE_ANY;
        lpLocalName := nil;
        lpProvider := nil;
end;
try
        sMachineName := lbMachines.items[i]; 
        rNetRez.lpRemoteName := pchar(sMachineName);
        iRet := WNetAddConnection2(rNetRez, pPassword, pUsername, 0);
        { 
                do stuff 
        }
finally
        WNetCancelConnection2(pchar(sMachineName), 0, true);
end;

Back to Article


Copyright © 1998, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video