Visual Basic

Using COM Instead of Normal DLL Entry

In Visual B++, you can define a more structured interaction with other languages by using COM for your communications. That is, you can use objects defined via a type library that have been implemented in other programming languages like Visual C++. (Indeed, it's difficult these days to not use COM in any Visual C++ development, just as it is in Visual B++.)

However, using COM you can also use objects defined in Visual B++ from, say, Visual C++ or Visual J++. Of course, COM is Microsoft's standard protocol for integrating independently developed software components, so here's a better look at how the connectivity is achieved.

Connectivity Example

Let's say that we use Visual B++ to create an ActiveX DLL that contains a single class called CTest. Let's also say that CTest has a property (defined both as a procedure and as a public data item), a method (sub), and an event. Create the sub with no parameters, name the sub DoIt, and within the sub invoke Visual B++'s MsgBox routine with App.EXEName as an argument. How can this be consumed by a Visual C++ developer?

First compile the project to create the ActiveX DLL. Let's call the project ObjTest, so now we have OBJTEST.DLL. That does it for the Visual B++ piece. Notice that I haven't elaborated on the process here, as I'm assuming that this is not your first foray into building ActiveX components.

Next start Visual C++ and create a new MFC AppWizard workspace (select New from the File menu, then select MFC AppWizard (exe) from the Projects tab). Name the workspace Test. Using the wizard that starts when you click OK, add whatever options you want. If this is your first experience with MFC, C++, or Visual C++ (or all the above), I'd suggest you select Single Document in Step 1 and check the Automation checkbox in Step 3; leave the default choices for all the other options. When you click the wizard's Finish button, the wizard will build an application for you. The resulting application is Windows API, C++, and MFC code. Don't expect to see anything similar to the Visual B++ interface.

Next use the ClassWizard to add the necessary C++ classes to mirror your Visual B++-generated OBJTEST CTest class.

  1. Select ClassWizard from the View menu.
  2. In the MFC ClassWizard dialog box click the Add Class button and select From A Type Library.
  3. Select OBJTEST.DLL from the File Open dialog box. The next dialog box should show _CTest and __CTest highlighted, since it should be your only class. Click OK. The ClassWizard will now add more C++ source code to your project.

Now that all the automatic code has been built, select Find In Files from the Edit menu, enter AfxOleInit, and then hit Enter. Double-click the line that appears in the Find In Files 1 tab in your Output window to go to the code that the IDE found. The code will look something like this:

// Initialize OLE libraries
  if (!AfxOleInit())
      return FALSE;

This code was inserted because you checked the Automation checkbox in Step 3 of the AppWizard. Before an application can use any of the OLE system services, it must initialize the OLE system DLLs and verify that the DLLs are the correct version. The AfxOleInit routine does all we want it to. We need to run this routine, of course, because we're about to talk to-automate-our OBJTEST DLL via OLE and COM.

Navigate through the code to someplace suitable where you can add the necessary code to automate the DLL. I suggest you use Find In Files to locate void CTestApp::OnAppAbout(). This is a class function (I can't really explain what this is here, so please just carry on) that's used to create the application's About box. Replace the two lines of code between the C-style braces ({}) with the following:

_CTest * pCTest = new _CTest;
  if(FALSE == pCTest -> CreateDispatch("ObjTest.CTest"))
      AfxMessageBox("Can't create dispatch table.");
      pCTest -> DoIt();
      pCTest -> DetachDispatch();
      pCTest -> ReleaseDispatch();

You also need to add a statement at the top of the file to include the new header file:

#include "ObjTest.h"

Next build and run the application. If you followed all my instructions exactly you shouldn't have any trouble building the application. If you do, go over the instructions once more.

So what just happened, from the top?

Using ClassWizard to add the type library caused Visual C++ to add some C++ classes to your project to mirror those in the DLL. Visual C++ will have named these classes the same as your Visual B++ classes prefixed with an underscore. _CTest is our main class.

The code we added to the About box routine creates a new _CTest C++ class instance and stores its address in memory in the variable pCTest (a pointer to a _CTest object, if you will). The C++ class inherits some routines (think of Implements), one of which we now call: pCTest -> CreateDispatch(). This routine connects C++ functions defined in our C++ class _CTest with interfaces in a CTest object-the Visual B++ object, that is. We then call DoIt, our routine that does something. You should see the message OBJTEST appear in a message box when this is called (when you select About from the Help menu). Since we're basically through with our Visual B++ class now, we disconnect ourselves from it, which is what DetachDispatch and ReleaseDispatch do.

The class __CTest (two underscores) contains the event we defined.
Can these routines in the C++ classes be called from routines in other languages, perhaps from C? The answer is "Yes," because you can export the routines (make them available as "ordinary exports") by using a class modifier such as Class _declspec(dllexport) _CTest{};. However, this exposes the C++ class methods and properties via their decorated names; each will also be expecting to be passed an instance of the class type via a pointer parameter called this. All in all, not very easy.

Calling C++ DLLs from Visual B++

DLLs are separate binary modules that allow developers to share code easily at run time. If you have Visual B++_based client code that needs to use a C++ class (like a dialog box) that lives within a DLL, you basically have three options.

The first option is to write a single C++ function that invokes the dialog box and returns the results to the Visual B++ client. The advantage to this approach is that both the client code and the server code are fairly straightforward. The disadvantage to this approach is that the client code doesn't have a whole lot of control over the dialog box.

The second option is to provide a set of functions in the C++ DLL that manipulate the C++ object. With this approach each function must provide the client code with a handle, and the client code must write a single entry point for each member function it wants to use. The advantage to this approach is that the client has fairly good control over the dialog box. The downside is that the DLL code has to provide wrappers for each class member function (most tedious!), and the client has to keep track of the handle.

The third option is to have the C++ class within the DLL implement a COM interface. The advantage of this method is that the client code becomes greatly simplified. The client gets to use the C++ class in an object-oriented manner. In addition, most of the location and creation details are hidden from the client. This approach means buying into COM. However, that's generally a good thing because just about everything coming out of Redmond these days is based on COM. Using COM from Visual B++ is a good place to start.