Threading Issues
My program manages the Bonjour callbacks in a fairly ugly fashion. When my browsing activity starts, I create a timer that fires once every 250 milliseconds. I process up to 10 callbacks in that timer call, then exit. This continues until there are no pending browser or resolution requests; at which time, I kill the timer.
Depending on your use of DNS-SD, you may find that this is not as efficient as you'd like. If this is the case, you might find it useful to move your message pump code to a separate thread.
Once you do that, you can wait on all your callbacks by calling select with a long or infinite timeout. This will block your callback thread until it has actual work to do resulting in a better use of CPU time.
There are some obvious downsides to this approach. Clearly, you have to use some sort of locking mechanism on the data structures that are shared between your callback thread and the rest of your program. And the use of the select() statement with an infinite timeout is complicated by the possibility that you may be making or canceling browsing or resolution calls while your program runs.
A good way to deal with both of these problems is to invoke a socket-based message-passing protocol between the callback thread and the other components of your program. If you restrict your interface to messages, you don't have to worry about locking access to shared data. And because you are using a socket for communications, your select() statement will be used to activate the thread when new messages arrive.
Character Sets
The days when DNS was limited to seven-bit ASCII characters are long gone. Service instances are encoded as UTF-8, and can use whatever Unicode characters they like. In Figure 3, you can see the effects of that when I browse for instances of iTunes:
Note that OS X users have so-called curly quotes in their library instance names, and curly quotes are definitely outside the range of seven-bit ASCII. DNS-SD collects the names as UTF-8 encoded strings, and sends them to the console in that format.
By default, the Windows cmd.exe window doesn't render UTF-8 properly, but changing the code page to 65001 results in the correct rendering.
In my sample program, I deal with this with a two step approach. First, my program is built using the Unicode libraries, ensuring that I am able to render Unicode output properly. To conform with Microsoft's C++ paradigms, I use CString for all my Unicode strings, and wrap all my string literals in the _T() wrapper.
This works fine for my UI, but I can't use strings built of wchar_t to communicate with the Bonjour SDK it expects eight bit characters with UTF-8 encoding. In my program, I always use the C++ std::string class when I am working with 8-bit characters that might be encoded in UTF-8. When it comes time to render one of those strings in my Unicode context, all I have to do is use the handy CA2T macro with the CP_UTF8 parameter, and things work properly.
Library Issues
The design of the Bonjour SDK imposes some uncomfortable restrictions on you when it comes to building your C or C++ program. Because you are linking directly to code found in the dnssd.lib library, you have to ensure that your program and that library link against the same version of the C runtime library. And for the Bonjour SDK under Windows, this means you must link with the static, multithreaded, release version of the library.
You'll see the problem in this right away when you create an MFC project and try to build with dnssd.lib. By default, the project generator will probably have you using MFC in a shared DLL, and using the Multithreaded Debug DLL version of the C libraries. When you try to build like this, you will get some unpleasant error messages:
1>LINK : warning LNK4098:
defaultlib 'msvcrtd.lib' conflicts with use of other libs;
use /NODEFAULTLIB:library
1>LINK : warning LNK4098:
defaultlib 'LIBCMT' conflicts with use of other libs;
use /NODEFAULTLIB:library
A full-featured SDK would provide libraries built for multiple scenarios, and you would pick the one of your choice depending on your build parameters. But with the Bonjour SDK, you don't get this choice, so you need to ensure that your project follows a few guidelines:
-
Under Configuration Properties/General, field Use of MFC needs to be set to Use MFC in a Static Library for both debug and release builds.
Under Configuration Properties/C++/Code Generation, field Runtime Library needs to be set to Multi-threaded (/MT) for both debug and release builds.
Under Configuration Properties/C++/Preprocessor, field Preprocessor Definitions, the constant _DEBUG needs to be changed to NDEBUG for Debug configurations.
To build a project that uses the SDK, you will also need to add dns_sd.lib to your list of linker inputs, add dns_sd.h to your header files, and add the appropriate directories in the configuration under Configuration Properties/C++/General/ in field Additional Include Directories, and under Configuration Properties/Linker/General/ in field Additional Include Directories.
Overview Of the Demo Program
I've included the full source for a project that will build with Visual Studio 2010, as long as you have the Bonjour SDK installed. It browses all available services on the network and displays the information about them in tree form.
The program starts by kicking off a browser for _services._dns-sd._udp. The results are processed in member function IterateServiceTypes(). As each new service type is discovered, it is added to the tree, and a call to DNSServiceBrowse()is made to discover all instances of that service type. The callback for that browse call is member function IterateServiceInstances().
In IterateServiceInstances(), I add the instance to the tree, then call DNSServiceResolve(). This function operates much like the browse function, but it actually gets the DNS record for the service. This record contains the host name, service port, and a list of name/value pairs that a service can advertise as part of its record. You can see those values put to good work with service types like _ipp._tcp, in which printer parameters are exposed as part of service discovery.
ResolveInstance() is the callback routine that receives the information about the service instance. The host name, port, and name/value pairs are added to the tree; then one final call is made to a Bonjour SDK entry called DNSServiceGetAddrInfo(). This function resolves the IP address for the given host name. The address is stuffed into the tree in callback function GetAddress().
Conclusion
DNS Service Discovery is powerful tool, but Windows programmers might be put off by the lack of a nicely packaged SDK. Using this simple example program might be a good way to get comfortable with a powerful tool and a good multi-platform alternative to UPnP.
Mark Nelson is an engineer with Cisco Systems and a contributing editor to Dr. Dobb's.



