The ActiveX EXE Way of Prime Numbers
The Sieve Client program also uses ActiveX EXE components. When you look at the “Performance” sidebar on page 551, it’s easy to get the impression that I created the EXE versions of the component for no other reason than to persuade you never to use them. Well, not exactly. I do want to stress the toll for using EXE components, but there are situations in which the toll is worth paying.
Sending data from a DLL to a program is fast because the program and the DLL are in the same address space. System DLLs like USER32.DLL and GDI32.DLL are very efficient. COM adds some overhead for ActiveX DLLs, but it’s still fast. An EXE server, on the other hand, is a completely different program, and COM has to set up a rather complex communication system called marshaling to make data transfers work. The overhead of sending data across process boundaries is high, and, of course, the overhead for machine boundaries is even higher.
That doesn’t mean you always want DLL components. Microsoft Excel isn’t a DLL, although clearly its Automation clients would run faster if it were. If your server does nothing but serve, build the DLL server. If your server is primarily a program and secondarily a server, build the EXE server. If your server must communicate with clients on other machines (possibly running different operating systems), you have only one choice: EXE server. If your server must create multiple threads, build the EXE server. The server described in “The server side of file notification” in Chapter 11, for example, is an EXE server so that its operation won’t affect the performance of its clients.
The Sieve Client program creates an artificial situation. It uses six different sieve components and a local class—all of them containing the same code. Normally, you choose the most appropriate format for all clients rather than forcing one client to accommodate all possible servers. Let’s just say that the Sieve Client has exotic tastes.
The steps for creating an EXE server are almost the same as those for creating a DLL server. To make the comparison a little more realistic, let’s assume that the SieveBasExeN component is actually a program whose main purpose is to calculate prime numbers and hand them over directly to users through a user interface. But as a sideline, the program can also provide the same numbers to any outside clients that need them. In other words, SieveBasExeN works a lot like Microsoft Excel.
To create the server, I started with the same steps you saw earlier. I created a new ActiveX EXE project containing CSieve and renamed the project SieveBasExeN. This project also needs a form for its user interface, so I inserted the FSieveClient form and renamed it FSieveBasExeN. This form doesn’t need to get prime numbers from 11 different sources, so I removed the combo box and all the code that references it, leaving only the code to access one sieve class, CSieveBasExeN.
In an ordinary program, I would set FSieveBasExeN to be my startup form, but you can’t have a form as the startup object in an ActiveX EXE server. I had to add a standard module with a Sub Main and have Sub Main load the form:
Sub Main()
If App.StartMode = vbSModeStandalone Then
Dim frmSieve As New FSieveBasExeN
frmSieve.Show
End If
End Sub
The form won’t be loaded if the EXE is started by an ActiveX client such as the Sieve Client program. Following are a few other settings differences (as shown in Figure 10-3):
-
On the General tab, the Startup Object must be set to Sub Main.
-
The Unattended Execution settings are disabled because a form can’t be unattended. But if this server (like the DLL version) didn’t have a user interface, you could set it for unattended execution and set options to control how it uses threads for its multiple objects. None of this applies to the sieve server because there is only one class in the component and you’re not likely to create multiple instances of it. These settings aren’t the only way you can create multiple threads in Visual Basic—I’ll explore these interesting possibilities in “Threads
and Synchronization” in Chapter 11.
-
On the Component tab, set Standalone as the Start Mode when testing the component as a stand-alone application. Set ActiveX Component when testing the component as a server. When you create the compiled server, the client will always do the right thing by itself—connect to the visible server if it is running, or start the server without a user interface if it is not running. These settings enable you to test both
scenarios in the IDE. They have no effect on the finished server.
-
The Instancing property for the class could be set to either SingleUse or MultiUse. I could have created separate p-code and native code
versions with each of these settings and timed the differences in their performance, but the sieve classes will usually have only one instance, so this setting doesn’t matter much. I randomly chose MultiUse. You need to study the implications when creating real EXE servers.
Figure 10-3. Project settings for the SieveBasExeN server.
C++ Sieve Servers
Any server that you can write in Visual Basic you can also write in C++. But is it worth the trouble? Well, you’re asking the right person. I’ve written the Sieve server four different ways in C++, and it was a struggle every time.
The first edition of this book came with a raw C++ version. I borrowed some boilerplate code, but I had to put everything together myself into a dense, unintelligible mass of code. Never again. The Microsoft Foundation Classes (MFC) version was a lot easier to write and understand, but its performance was unsatisfactory. Next I converted to the ActiveX BaseCtl framework—the same code base used to create the controls provided with Visual Basic 5. And finally I rewrote it with the ActiveX Template Library (ATL). I described this version in the August, 1996, issue of Visual Basic Programmer’s Journal.
I believe MFC and ATL will be the tools of choice for writing ActiveX components, so those versions are provided on the disk. The performance of the raw and BaseCtl versions was about the same as the ATL version anyway. The MFC version is a different matter. As the “Performance” sidebar on page 551 shows, it is actually considerably slower than the native code Visual Basic DLL. This is an object lesson in the value of dual interfaces, as described in Chapter 3. The only thing MFC has to recommend it for COM development is that it’s easier than ATL. But what’s the point when Visual Basic is so much easier than C++?
It’s difficult and not very meaningful to compare Visual Basic and C++ code, but let’s do it anyway. The SIEVE.CLS file contains 77 lines of code. The SIEVE.CPP and SIEVE.H files from the ATL version provide the same functionality in about 191 lines. The Visual Basic code is definitely more compact and readable. The C++ version has to do some COM setup that isn’t directly related to prime numbers. If you count only the executable lines that calculate prime numbers, the two versions are roughly the same. In either case, the code you see is the tip of the iceberg. A lot more C++ code hides in the COM DLLs. With the Visual Basic version, you see even less of the iceberg but mostly it’s the same iceberg. The Visual Basic library code that connects Visual Basic servers to the COM DLLs is written in C++, and it must perform essentially the same tasks as the ATL framework code surrounding the C++ class.
You can see that the extra work put into the ATL version pays off in better performance, but it’s not an overwhelming advantage and I’d still choose Visual Basic if that were the only factor. But the ATL version wins big in one other way. The component is about 16 KB in size with no dependencies. The Visual Basic native code version is about 11 KB, but it requires a run-time DLL of about
1.3 megabytes. If your clients are Visual Basic programs, you have to have
that DLL anyway. But if you’re writing for other clients, the ATL version has a certain appeal.
You don’t have to worry about debugging the CSieveBasExeN class because its code is the same as the CSieve class and that’s already debugged. But in real life, you will need to debug EXE servers, and the technique is significantly different than for DLL components. Instead of putting the project in a VBG, you start one instance of Visual Basic containing the component and then start
another instance containing the client. Set breakpoints in each project, and switch back and forth between the appropriate IDE. This is easier said than done, and there will be times when you wish for a simple little DLL component.
If you have Visual Basic Enterprise Edition, you can make the ActiveX EXE version work across machines by using DCOM.
I didn’t try this, but in theory the server should work without any code changes. You could then add DCOM performance statistics to the numbers presented in the “Performance” sidebar on page 551. I don’t think this would be particularly informative. Your results would depend more on the speed of your network connection than on any code in the server or client.