Introduction

I'm very new to C# as a programming language and to .NET as a framework. Coming
as I did from a C++ and MFC background I researched the alternatives and hinted
to my family that a copy of Tom Archers 'Inside C#' (2nd edition) would be a
much appreciated Christmas present. The hints were taken and it arrived in my
Christmas stocking. A good read and very well worth the money that I don't
offically know about . Having read the book from cover to cover it was time to write some real C# code.
But I'm of the school of thought that says that one cannot truly learn a new
programming language by using examples targetted at just one or another feature
of the new language. I need a real project. Otherwise, if I attempt something and
it doesn't work the way I imagine it should it's all too easy to write it off. A real project presents
challenges that must be solved before the project can be said to be complete.

The Project

I chose a project that's pretty much going to be out of date in maybe a
years time, probably rather less. It's a SETI monitor that automatically discovers computers on my local network and
monitors the progress of instances of SETI running on each computer. This article doesn't present that project (though a future article might).

Instead, this article focusses on a class I wrote as part of the project to enumerate network resources. In Win32 you do this by using the
WNetOpenEnum() API followed by (probably) multiple calls to the WNetEnumResource()
API.

A not so quick search of the .NET documentation didn't reveal any classes
implementing this functionality so it was time to write my own. Bear in mind
that this code was written by a veteran C++ programmer trying to move to C#

Moving a C++ function to C#

I have written code using the WNetOpenEnum() and WNetEnumResource() API's many times in the past, always in C++. So I was starting
from the standpoint of having written working code but without necessarily understanding all the nuances of the API. After all, if it works following the
MSDN examples tailored to my needs one moves on to other stuff right?

So I started with working C++ code. I won't present the code here. But let's discuss the API's. The first API is WNetOpenEnum(). The
C/C++ prototype looks like this.

I won't go into all the details of the various parameters but I'll pass lightly over them. The scope parameter specifies whether you want to
enumerate global resources, remembered resources and suchlike. The type parameter lets you restrict enumerated resources to disk, print or all.
Usage lets you specify such things as containers. (See the MSDN documentation for full details).

The lphEnum is a pointer to a handle to an enumeration context which is used in subsequent calls to WNetEnumResource().

That leaves the lpNetResource parameter.

All the parameters apart from the lpNetResource parameter map directly onto C# datatypes. The lpNetResource parameter doesn't. So
let's look at the C/C++ definition of that type.

This is a literal copy of the NETRESOURCE structure from the C/C++ header file with the syntax massaged to make it digestible to the C# compiler. Well not
quite. Each DWORD member has been changed to an enum type, where the enum values are defined elsewhere within the class. The enum values are in turn
literal copies of the relevant C/C++ values massaged to make them digestible to the C# compiler. I did it this way for two reasons. The first is that I believe even the
original C++ structure should have been defined in this way. Changing the DWORD's into enum's buys some compile time parameter checking for free.
The second reason is that (at least in Visual Studio), Intellisense will kick in and help me remember the constants.

That's pretty straightforward. Simply set up a call using the various enum values for the type of enumeration you want, pass it an instance of the
NETRESOURCE structure and a reference to a place to store the returned enumeration handle. Calling code might look like this:

This is where things get interesting. If you have another look at the NETRESOURCE structure you'll see that it contains 4 pointers to strings. An
obvious questions is where do those pointers point? In other words, where is the memory allocated? A few moments thought reveals that they can't be pointing at
strings inside the OS because in all likelihood those strings are in someone elses memory space. The API has to copy the strings into memory visible to the calling
process. Therefore, the memory into which the strings are copied must be allocated by the calling process. However the documentation says nothing about how large each
string buffer should be or even if we should point each pointer at a buffer allocated in our memory space. A close reading of the MSDN documentation for the
WNetEnumResource() reveals this in the remarks.

An application cannot set the lpBuffer parameter to NULL and retrieve the required buffer size from the lpBufferSize parameter. Instead, the application
should allocate a buffer of a reasonable size (16 kilobytes is typical) and use the value of lpBufferSize for error detection.

Aha! Obviously the WNetEnumResource API reserves the first few bytes of the memory buffer for an instance of the NETRESOURCE structure and
copies the strings into the same buffer after that structure. So we're expected to allocate a buffer large enough to hold the NETRESOURCE structure
and all 4 strings. In C++ this is trivial. And doubtless, as I become more proficient with C# and the .NET Framework, it'll become trivial there too

Incidentally, this also reveals how old these API's are. I'm sure the structure would have used BSTR's to solve the memory allocation problem had they
existed at the time this structure was defined.

A hidden gotcha

Another close reading of the documentation for WNetEnumResource() says that if you set the lpCount to X the function will return
X results if they will fit into the buffer. If not it will return as many results as will fit in the buffer and set the value pointed at by lpCount to
the number of returned results. But in practice it doesn't seem to work that way. All my tests in C++ return just 1 result per call regardless of how large the
buffer is and what I specify as the desired count. *shrug*

The WNetCloseEnum() function

As you'd expect, having opened a handle and used it, you're required to close it when you've done with it. That's done with the WNetCloseEnum() API.

This is almost trivial. We pass a NETRESOURCE structure and a bunch of constants to the function. We then open the enumeration and if that
succeeds we go into a loop calling WNetEnumResource() for each network resource. For each resource whose type matches the type passed as a parameter we add the remote name member of the structure to an array. For each resource which has the
ResourceUsage.RESOURCEUSAGE_CONTAINER bit set we recursively call the function.

But wait a minute! What's with the Marshal.AllocHGlobal() call? That's to allocate a block of memory to serve as the buffer which the
WNetEnumResource() API will treat as a NETRESOURCE structure followed by buffer space for the strings.

When we get the buffer back after the call we use Marshal.PtrToStructure() to copy the NETRESOURCE portion of the buffer into the structure
instance passed to the function. When the copy is done the string members of the structure still point at the strings allocated within the buffer. I
struggled mightily with this (doubtless because of my C++ background). It took seeming ages to understand that even with the unsafe keyword bracketing
code the compiler wasn't about to let me simply cast the buffer into a NETRESOURCE structure without some .NET Framework intervention. My thanks go to
Heath Stewart for pointing me in the right direction.

Other things about the class

Because the class is used to enumerate a list of servers it makes sense for it to implement the IEnumerable interface to allow it to be used inside a
foreach statement. I used a trick Tom Archer mentions in his book. Because the class uses an ArrayList to store the list of enumerated servers
and that class already implements IEnumerable my GetEnumerator() implementation simply returns the enumerator for the ArrayList.

Which example would enumerate all shares on the network. If the second parameter were changed to ResourceType.RESOURCETYPE_PRINT a list of all printer
shares would be returned. The sample project download is compiled to show a list of servers on the network. So the relevant code looks like this.

Control of enumerated results

The bunch of constants that are passed to the EnumerateServers function are used for fine control of the output. The following tables are from
the MSDN Library April 2000.

The ResourceScope constants and their meanings are:

Value

Meaning

RESOURCE_CONNECTED

Enumerate currently connected resources. The dwUsage member cannot be specified.

RESOURCE_GLOBALNET

Enumerate all resources on the network. The dwUsage member is specified.

RESOURCE_REMEMBERED

Enumerate remembered (persistent) connections. The dwUsage member cannot be specified.

The ResourceType constants are:

Value

Meaning

RESOURCETYPE_ANY

All resources.

RESOURCETYPE_DISK

Disk resources.

RESOURCETYPE_PRINT

Print resources.

The ResourceUsage constants are:

Value

Meaning

RESOURCEUSAGE_CONNECTABLE

The resource is a connectable resource; the name pointed to by the lpRemoteName member can be passed to the
WNetAddConnection() function to make a network connection.

RESOURCEUSAGE_CONTAINER

The resource is a container resource; the name pointed to by the lpRemoteName member can be passed to the
WNetOpenEnum() function to enumerate the resources in the container.

And, finally, the ResourceDisplayType constants are:

Value

Meaning

RESOURCEDISPLAYTYPE_DOMAIN

The object should be displayed as a domain.

RESOURCEDISPLAYTYPE_SERVER

The object should be displayed as a server.

RESOURCEDISPLAYTYPE_SHARE

The object should be displayed as a share.

RESOURCEDISPLAYTYPE_GENERIC

The method used to display the object does not matter.

The last set of constants, ResourceDisplayType are confusing. The documentation seems to say that it's a hint as to how to display the data. But it
actually seems to behave as a filter.

You apply various combinations of these constants to filter the results returned by the network enumerator. For example, if all you're interested in is a list
of servers on the network you'd use ResourceScope.RESOURCE_GLOBALNET, ResourceType.RESOURCETYPE_ANY, ResourceUsage.RESOURCEUSAGE_CONTAINER
and ResourceDisplayType.RESOURCEDISPLAYTYPE_SERVER.

Note that the class presented in the download includes some constants that aren't documented in MSDN but that are present in the C++ header files. In particular
there are many more constants defined for the ResourceDisplayType than MSDN documents. On my system most of them result in nothing being returned at all, but
then this is my system, I have a workgroup, not a domain or an Active Directory. I included them for the sake of completeness.

Acknowledgements

I'd like to thank Marc Clifton for reviewing this article for me. As it was my first C# article I was somewhat nervous about posting it so I chose to ask an MVP
for review and advice. Marc was very helpful and patient.

Share

About the Author

I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.

I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.

Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.

Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.

I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.

I ran into the same problem. Especially workgroups that don't have a browse master (or one that's offline) and browsable computers that aren't responding. Those kind of (semi-)unreachable resources just keep waiting until the WNet-function times out, with that timeout being 30 seconds or so.

If only there were a function to set the timeout to like 2 seconds. Because practically a server that doesn't respond within 2 seconds, can be considered either offline or way to slow to browse in the first place.

I have been trying to solve on how to detect IP addresses based on my installed printer drivers around my network but I am getting frustrated each day..heheh.. Win32_Printer, Win32_NetworkAdapter and Win32_TCPIPPrinter failed to get the information that I want..

if you're kind enough share your knowledge to email it to me at janverge@gmail.com