This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


April 1998

Microsoft Systems Journal Homepage

Download Apr98VPcode.exe (40KB)

In addition to developing the coolest Windows-based development tools with Stingray Software, George teaches seminars with DevelopMentor. Scot is a co-founder of Stingray Software. George and Scot are co-authors of MFC Internals (Addison-Wesley, 1996).

Q I want to call a certain kind of function from Visual Basic. The function opens up a dialog box, accepts some text and returns the text to the Visual Basic-based program. The dialog box is in an MFC DLL I created with Visual C++. How can I export a member function? What function within the DLL should be called?
Robert Ginns
via CompuServe
A Thanks for the question, Robert. You're hitting upon some fundamental issues that come up when you try to integrate components written in two different languages like C++ and Visual Basic®. There are actually several ways to get Visual Basic working with dialogs (or some other C++ class) inside your MFC DLL. In this example, we'll create a simple dialog in MFC, stick it in a DLL, and then invoke the dialog from Visual Basic. Figure 1 shows what the end result will look like.
Figure 1 Dialog in a DLL
Figure 1 Dialog in a DLL


      In answering your question, we'll cover several important topics, including how you can call DLL functions from Visual Basic, what happens when you export a C++ class from a DLL, and why you might want to use COM to do something like this. Let's start by reviewing what it takes to call DLL functions from Visual Basic.

Calling DLL Functions from Visual Basic
      As you probably already know, dynamic linking is a fundamental technology within Windows®. DLLs let you put a collection of functions in a separate binary module and have client code link to the code at runtime (instead of having library code actually copied into the final executable). There are many advantages to doing this. The two main advantages are eliminating redundant executable code on the user's system and the ability to fix code in the field by just dropping in a new DLL.
      As a language, Visual Basic includes syntax for linking to DLLs at runtime and calling functions from within the M. This facility allows you to use DLL functions almost as easily as you use Visual Basic's native functions. In fact, this is the technique you'd use to call regular Windows API functions from a Visual Basic-based application. To use functions that reside in a DLL, you only need two things: the function prototypes and the DLL itself. The following code shows the prototype and implementation of a function that might live in a DLL:


 // This is in the header file
 void WINAPI TestFunction(char* lpszText);
 
 // This is in the source file
 void WINAPI TestFunction(char* lpszText) {
    if(lpszText) {
       strcpy(lpszText, "What's up?");
    }
 }
      Of course, Visual Basic syntax is somewhat different than C++ syntax. Visual Basic uses the Declare keyword for prototyping a function. Unlike C++ (in which all subroutines are really functions), Visual Basic distinguishes between functions that return values and functions that return void. That is, Visual Basic syntax includes two keywords for prototyping subroutines: Sub and Function. The name of the function or subroutine comes next. If the function isn't native to the Visual Basic runtime, then the keyword Lib tells Visual Basic the name of the DLL containing the function (that is, which library to load). Finally, the arguments being passed to the function are listed in parenthe Ses. This is the Visual Basic prototype of TestFunction:

  Declare Sub TestFunction Lib "visprog" (ByVal lpBuffer_ As String)
      Notice how the Visual Basic syntax really simplifies things. To call this function from a C++ program, you'd have to do one of two things. First, you could call LoadLibrary on visprog.dll and then call GetProcAddress to get the address of TestFunction. Alternately, you could use visprog's import library (visprog.lib) to resolve the addresses. If you have the function prototyped correctly in Visual Basic, then the Lib keyword does all the work for you. Armed with this knowledge, how can you access an MFC dialog from Visual Basic?

Accessing an MFC Dialog from Visual Basic
      You can use three approaches to invoke an MFC dialog box from Visual Basic. First, you could export a single function from the DLL that does the whole enchilada. That is, you could write a single function to be called by the Visual Basic-based program that initializes the dialog, shows the dialog, and returns the data to the program. If the Visual Basic-based program needs a bit more flexibility, you could write a series of functions for creating and manipulating the dialog. Finally, you could expose the dialog via a COM interface. Each approach has advantages and disadvantages.

Using a Single Entry Point
      As far as straightforward DLL linking is concerned (that is, if you want to avoid COM), the easiest approach is to expose a single entry point that performs all the dialog operations on behalf of the Visual Basic-based program. The C++ source code is pretty straightforward. It just creates the dialog box and invokes it by calling DoModal (after calling AFX_MANAGE_STATE so the dialog resources can be found). Figure 2 shows the C++ function that invokes the dialog. There's nothing special about the dialog class—it's just a plain vanilla one created through ClassWizard.
      To use the dialog, the Visual Basic-based program just needs to provide a prototype of the function and call it (perhaps in response to a button press). This is the Visual Basic source code for invoking the dialog box using the GetInfo function exported by the DLL:


 Rem This goes in the "general" section
 Declare Sub GetInfo Lib "visprog" (ByVal hWnd As Long, _
                               ByVal lpBuffer As String)

       Rem This is the button handler for MainForm Private Sub GetTheInfo() Dim txt As String
       txt = Space$(512) GetInfo MainForm.hWnd, txt End Sub
The DLL shows the dialog without flinching. Having the dialog available to the program at all is definitely a good thing. What if the client code needs a bit more control? Perhaps your Visual Basic-based application needs to create an instance of the dialog and hold on to it for a while, manipulating it from time to time. In addition, it's always better to use an object-oriented approach. Let's see if there's a way to accomplish that.

Exporting C++ Classes from a DLL
      So what's preventing us from using the C++ class itself? As it turns out, there's a convenient way to export classes from within C++: a special declaration and a keyword. Just insert _declspec(dllexport) between the class keyword and the actual class declaration. This code shows the C++ source for exporting the dialog class wholesale from the DLL:


 class _declspec(dllexport) CSomeMFCDialog : public CDialog {
 // Construction
 public:
    CSomeMFCDialog(CWnd* pParent = NULL);
    // standard constructor
 
 // Dialog Data
    //{{AFX_DATA(CSomeMFCDialog)
    enum { IDD = IDD_SOMEDIALOG };
    CString    m_strText;
    //}}AFX_DATA
 
    void SetData(char* lpszText);
    void GetData(char* lpszText);
 
 // Everything else that goes within an MFC dialog class
 };
      Using the special declaration is actually quite convenient. If you use this declaration in your DLL, then you can write a C++ program that employs the dialog class as though it was linked through static linking. For example, you can use operators new and delete without a hitch.
      This is, in fact, the approach taken by MFC to share its classes within the MFC DLLs. When you develop an application and tell AppWizard you want to use MFC in a shared library, you're telling AppWizard to generate source code that generates links to the MFC DLL. Most of the MFC classes are exported from the DLL in this manner. However, using this approach from Visual Basic doesn't wash for two main reasons. The first and most obvious reason is that Visual Basic doesn't have an operator new like C++ does. How do you create an instance of the CSomeMFCDialog class? There's no way to do this in Visual Basic.
      The second problem has to do with name mangling and isn't quite so obvious. Here's what happens when you expose the dialog class using _declspec(dllexport). The compiler simply takes each of the class's member functions and exports the M using the C++ mangled type. So, even if you could find a way to instantiate the class from Visual Basic, using it would be a pain. The following shows the names of the member functions as they are exported by the compiler:

 ??0CSomeMFCDialog@@QAE@PAVCWnd@@@Z
 ??1CSomeMFCDialog@@UAE@XZ
 ??_7CSomeMFCDialog@@6B@
 ??_FCSomeMFCDialog@@QAEXXZ
 ?DoDataExchange@CSomeMFCDialog@@MAEXPAVCDataExchange@@@Z  
 ?GetData@CSomeMFCDialog@@QAEXPAD@Z
 ?GetDispatchMap@CSomeMFCDialog@@MBEPBUAFX_DISPMAP@@XZ  
 ?SetData@CSomeMFCDialog@@QAEXPAD@Z
 DllCanUnloadNow
 DllGetClassObject
 DllRegisterServer
 GetInfo
Can you imagine typing in the Visual Basic prototypes for the Se? You'd have to get every single character correct. In addition, there's a hidden parameter in each of the Se functions that you'd need to account for—each of the Se functions includes an implicit this pointer. In all practicality, the Se function names are really only for use by the Visual C++® compiler to resolve addresses in the DLL. This is definitely not the way to expose the C++ class to Visual Basic. There must be a better way.

Wrapping the DLL Functions
      One way to solve the Se problems is to specify C-style entry points (that is, non-mangled and non-C++ entry points) to the DLL. In addition to providing the Visual Basic-based client with a way to hold on to the dialog and manipulate it from time to time, this is also a way around the problems of operators new and delete and name mangling. Instead of working with the class wholesale, the DLL exports several functions for working with the dialog.
      Because operators new and delete don't work between C++ and Visual Basic, the dialog API needs to include functions for both creating and deleting the dialog. The API should also include a function to show the dialog object, as well as any other functions for manipulating the dialog. Because there's no way to share raw C++ classes with Visual Basic, treat the dialog box object (the CSomeMFCDialog object) as an opaque pointer. Figure 3 illustrates how you might accomplish this.
      The first parameter to each of the Se functions is a long. This is the handle to the dialog box. The Visual Basic-based program will treat the long as an opaque handle. That is, the program will blindly call the Se functions using the dialog handle it gets back from CreateSomeMFCDialog. However, the code in the DLL understands this long to be a pointer to a live CSomeMFCDialog object. This is similar to the way the real Windows API works. Figure 4 illustrates how a Visual Basic-based program might do this.
      While this approach works well, it introduces some overhead on both sides of the binary fence. For example, the C++ code includes one DLL entry point for every operation the client can perform on the dialog box. On the client side, the client has to go through the trouble of getting a dialog handle, keeping track of the dialog handle, using the API functions correctly, and deleting the dialog handle when done. The third approach, using a COM interface, overcomes the Se issues.

Implementing a COM Interface
      COM is Microsoft's standard protocol for integrating independently developed software components. Without going into the gory details, COM provides a way to share objects between Visual Basic and C++. The easiest way to add a COM interface to your MFC components is to check ClassWizard's Automation option when you create new classes. This adds the infrastructure necessary to turn the C++ class into a COM class. For more information on how MFC implements COM, see our book MFC Internals (Addison-Wesley, 1996).
      CSomeMFCDialog was created by using ClassWizard with the Automation option selected, so it's a COM class that implements the IDispatch interface, which Visual Basic knows how to use just fine. CSomeMFCDialog implements a text property (that is, the dialog's text data) and a method named DispDoModal. (Naming the method DoModal would cause a conflict with CDialog's regular DoModal function.) Both the Text property and DispDoModal were added to CSomeMFCDialog through ClassWizard. In addition to the normal Automation support for the dialog box class, you may need to add the DECLARE_DYNCREATE and DECLARE_OLECREATE macros to CSomeMFCDialog's header file and add the IMPLEMENT_OLECREATE macro to the implementation file; ClassWizard is sometimes reluctant to add the M for you. You invoke the dialog box from Visual Basic using COM like this:


 Private Sub UseAutomation_Click()
     Dim Dlg As Object
     Set Dlg = CreateObject("SomeMFCDialog")
     
     Dlg.Text = txt
     Dlg.DispDoModal
     TextFromDlg.Caption = Dlg.Text
 End Sub
      Using COM Automation simplifies many aspects of linking from the client side. The prototypes are gone. Visual Basic reads the type information included with a COM class to figure out how to call methods on the class. In addition, the actual name of the DLL is missing, too. That's because the names and paths to COM DLLs are managed by the registry. the Only information the client needs to know is the name of the Object (used in the CreateObject function) and what methods may be invoked.
      Finally, you may choose to implement normal interfaces on your C++ class—Visual Basic understands more than just IDispatch. If your COM interfaces use automation-compatible data types as parameters, Visual Basic will have no problem understanding type information and using the Object.

Conclusion
      Visual Basic and Visual C++ reflect two radically different styles of development. While Visual Basic is a higher-level environment especially suitable for implementing user interfaces, Visual C++ is known for providing greater programming control and performance (though it sometimes takes a bit longer to code in C++). DLLs are separate binary modules that allow developers to share code at runtime easily.
      If you have Visual Basic-based client code that needs to use a C++ class (like a dialog box) living within a DLL, you basically have three options. First, you can write a single function that invokes the dialog and returns the results to the 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 that manipulate the C++ object. This approach entails providing a handle to the client code and writing a single entry point for each member function you want to use from the client. 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 upside 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 the Se days is based on COM. Using COM from Visual Basic is a good place to start.

Have a question about programming in Visual Basic, Visual FoxPro, Microsoft Access, Office, or stuff like that? Send your questions via email to visual_developer@stingsoft.com or George Shepherd at 70023.1000@compuserve.com.

From the April 1998 issue of Microsoft Systems Journal.