Calling Visual FoxPro COM components
from Visual C++

by Rick Strahl, West Wind Technologies
http://www.west-wind.com/

code for this article:
http://www.west-wind.com/files/C_COM.zip

last updated: August 18, 1998

Table of Contents

Visual FoxPro Sample Server
Early and Late Binding
IDispatch and Variants
Low level access
CComDispatchDriver ATL wrapper
VC++ Type Library Imports

Visual C++ and COM integration

A few months ago I was working on an Internet Server API (ISAPI) application that needed to dynamically load and unload a COM object I had previously created in Visual FoxPro. Since ISAPI is a low level Win32 API any ISAPI extension needs to be implemented in a low level language like Visual C++. Since Microsoft has placed such high importance on COM it would seem that accessing a COM object from VC++ should be pretty straight forward. Unfortunately, Visual C++ does not make this process as easy as it could be. In fact, other C++ compilers like Borland C++ can make short work of calling COM objects directly using syntax that's almost identical to the syntax you use from Visual FoxPro or Visual Basic. Visual C++ takes a more puritan and low level approach, which requires at least a basic understanding of how COM and some of the support technologies work beneath the covers. In this article, I show you how to call a COM object from Visual C++ in several different ways. In the process we'll look at some of the details of how COM works and some special issues and how they relate to Visual FoxPro COM server even though most of the discussion applies to calling any kind of COM object.

A sample Visual FoxPro COM component

Let's start off by creating a very simple sample server that demonstrates the basics of passing parameters in and out of a COM object and returning values back to the client:

*********************************************************
DEFINE CLASS COMTest AS Relation OLEPUBLIC
*********************************************************
***  Function: Demonstrates calling VFP COM object from
***            VC++ 6.0. Demonstrates method and property
***            access for various types.
*********************************************************
cCurrentPath = ""
nValue = 0
* COMTest :: Init
FUNCTION Init
THIS.SetCurrentPath()
ENDFUNC
* COMTest :: SetCurrentPath
FUNCTION SetCurrentPath
LPARAMETERS lcPath
lcOldPath = SYS(5) + CURDIR()
IF !EMPTY(lcPath)
   cd (lcPath)
   THIS.cCurrentPath = SYS(5) + CURDIR()
ELSE
   THIS.cCurrentPath = lcOldPath   
ENDIF
RETURN lcOldPath
ENDFUNC
* COMTest :: GetCurrentPath
FUNCTION GetCurrentPath
RETURN SYS(5) + CURDIR()
ENDFUNC
* COMTest :: AddNumbers
FUNCTION AddNumbers
LPARAMETERS lnValue1, lnValue2
THIS.nValue = lnValue1 + lnValue2
RETURN THIS.nValue
ENDFUNC
* wwLog :: IsTrue
FUNCTION IsTrue
LPARAMETERS llTrue
RETURN llTrue
ENDFUNC
ENDDEFINE

Notice that I made this class OLEPUBLIC to indicate I want it to compile into a COM object when I create a project from it. I used a RELATION object instead of CUSTOM to produce a lightweight server. Relation has the fewest properties of any object which translates into fewer exposed COM methods and properties and faster load time, but you can use any class you see fit. You can also create classes in the Class Designer and use the OlePublic flag in the Class Info dialog to let the class be exposed to COM.

To test the class you can run the code in VFP first to make sure it works correctly (we'll do the same thing in C++ code later):

o=CREATE("COMTest")
? o.cCurrentPath
o.SetCurrentPath("c:\temp")
? o.GetCurrentPath()
o.SetCurrentPath("c:\wwapps\wc2\webtools")
? o.GetCurrentPath()
? o.AddNumbers(2,5)
? o.IsTrue(.T.)

To build this class into a COM object you simply add the PRG file (or the VCX if you use a Visual Class) to a project called C_COM. Build an Out Of Process server, so build the server into an EXE file. Once you compile the project you have now created a Visual FoxPro COM object with a ProgId of C_COM.COMTest that you can use with:

o=CREATE("C_COM.COMTest")

in the code above. Notice that when you do this Visual FoxPro now starts up the COM object externally as an EXE file with a 4 meg footprint - you can see it in task manager as a separate task. Check out the output and look at the original startup path, which is always going to be your Windows\SYSTEM or WinNT\SYSTEM32 directory instead of the servers location on disk.

I built an EXE file, because I think that for typical applications where VFP object access is required by C++, multi-threaded access may be required. This was certainly the case in my ISAPI extension code. Since EXE servers are separate processes it's easy to get multiple instances of VFP to run simultaneously even though there's the 4 meg+ overhead for each instance. While In Process DLL servers are slightly faster in call times and parameter passing only one method call per server can run at a time inside of a given process. For an ISAPI application taking many simultaneous requests this is akin to suicide.

For the discussion below it doesn't make a difference whether you're using an In Process or Out of Process Component. The mechanics are identical and COM handles all of the logistics of creating the correct server behind the scenes based on how the server was compiled and registered.

All bound up - Early and Late Binding

When you do CreateObject() in Visual FoxPro or other COM enabled client, COM attaches to the Visual FoxPro COM object through its IDispatch interface at runtime, which allows for dynamic type and method discovery. This is called Late Binding as the decision to link the class ids and method names occurs at runtime rather than at compile time. Late binding always translates the ProgId and the individual method calls and parameters at runtime checking to see whether each is available on the method call about to be made.

When a server is loaded through Early Binding a reference to the server's Class and Method Ids is linked at compile time. This ties specific interface Ids to the calling client code. Because ClassIds are essentially hard coded (implicitly by the compiler) the calling code is closely tied to the server. Any changes in the server's interfaces that cause the ClassId and/or VTable (physical memory layout of the method pointers) to change, break the client application unless the code is recompiled with the updated interface information. Because Early Binding can directly call interface methods it's somewhat faster than Late Binding, which must perform a look up on each method and property call and potentially convert parameters and return values into Variant types. This overhead can be significant, but keep in mind that it applies only to passing parameters and retrieving return values, not the actual operation of your server code.

Visual FoxPro's COM client functionality only allows for Late Binding via CreateObject and CreateObjectEx (in VFP 6.0), while C programs and Visual Basic/VBA can do both Late and Early Binding against Visual FoxPro created COM objects. If the language of choice supports Early Binding you should consider it only if you're dealing with a stable component that doesn't change often: System components that come with the operating system or third party components that have clearly defined and somewhat fixed interfaces that are unlikely to change or be properly remapped by the vendor. Early binding however is a bad choice for typical custom application components that change frequently causing the public interface and ClassIds to change. With Early Binding any such change will require a recompile, while Late Binding will allow compatibility as long as the existing interfaces are designed with backwards compatibility (parameters and return values) in mind.

The IDispatch Interface and Variants

Visual FoxPro servers implements its method and properties using the Idispatch base interface. Idispatch is a high level interface that's required for Late binding by languages like Visual FoxPro (VFP doesn't support early binding) and scripting engines like VBA, VBScript and JavaScript. Idispatch has several rules about how objects can be accessed. In particular parameters and return values can only be those types that are supported by the Variant data type. The following table shows the more common types:

Variant Type ID VFP Type Comments
VT_EMPTY empty This means .F. in VFP
VT_I4 Long All integer values in VFP
VT_BSTR String Strings are in wide chars and need conversion for use as C strings.
VT_BOOL Logical .T. or .F.
VT_DATE DateTime DateTime
VT_DISPATCH Object Ref Other COM object references that may be passed in or out as parameters.
VT_VARIANT | VT_BYREF Variant Only applicable in C
VT_ARRAY | VT_<type> COM Array Arrays. This works well only in VFP 6.0. Use COMARRAY() to force arrays to 0 or 1 based.

Idispatch interface calls require that all arguments are passed as Variants - if the actual parameters or return values for the methods or properties are of other types the Idispatch::Invoke() COM function will do the type conversion for you. For Visual FoxPro this is a non-issue, because VFP COM objects can only receive and return variant values anyway. Other more type aware languages like Visual Basic can use explicit types like Long, Boolean, Currency, Date etc. For C programmers Idispatch has a major limitation: It doesn't support pointers, structures, classes or native arrays (although Variant Arrays are supported). However, Idispatch does support passing other Idispatch pointers, which does give the ability to reference other more complex structures through another Idispatch enabled COM object.

Calling all COM servers

So how do access our COM object from C/C++ code? There are a number of different ways to get at the server. I'll discuss the following three:

  • Low Level Access
    Accessing the server using the Win32 native COM API calls. This is a very tedious process and one prone to many errors. This code requires a ton of support code to make all the proper type and string conversions as well as providing for error checking for each COM call.
  • Using COM Smart Pointers and the CCOMDispatchDriver Class
    Visual C++ 6.0 improves on the smart pointer support that Visual C++ 5.0 first introduced by providing specialized COM Smart Pointers that handle specific COM interfaces. For Visual FoxPro servers the CcomDispatchDriver class is extremely useful as it provides a mechanism for late binding to a server at runtime. SmartPointers let you use late binding.
  • Importing the Type Library and using a C++ Class wrapper
    Visual C++ has the ability to import type libraries and create a C++ class wrappers that makes the appropriate COM calls on your behalf. The class is linked to the type library providing an early bound server. With #import you can access both methods and properties just as you can with any other C++ class pointer. Importing results in early binding.

Getting started with Visual C++

I'll set up a new project called C_COMClient and then add three source files that demonstrate each of these three mechanisms for accessing the COM object above:

  1. Start up Visual C++ and Create a new project using the Project Wizard. Select Win32 Application and select Create an Simple Win32 application project. This creates a project and adds the standard pre-compilation header and a WinMain routine, but creates an otherwise empty project.
  2. Open up the StdAfx.h header file and comment out the following line:
    #define WIN32_MEAN_AND_LEAN. This line removes the required COM include files. Since we need access to COM, take out this line.
  3. Modify the C_COMClient.cpp to look like this:

    #include "stdafx.h"

    /// Forward Declarations
    BOOL ImportTlb(void);
    BOOL RawDispatch(void);
    BOOL DispatchDriver(void);

    int APIENTRY WinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPSTR     lpCmdLine,
                         int       nCmdShow) {
      /// Required on each thread before COM can be used
      CoInitialize(0);
      //  ImportTlb();
      DispatchDriver();
      //  RawDispatch();
      /// Required to clean up COM resources 
      CoUninitialize();
      return 0;
    }
  4. Then add three new CPP files called C_COMRaw.cpp, C_COMDispatch.cpp and C_COMImport.cpp which implement the ImportTlb, RawDispatch() and DistpatchDriver functions. For simplicity I didn’t create header files for these but simply added the forward declarations at the top of the main source file.
  5. Before you can compile this emtpy project you need to implement an empty function prototype for each of the functions. Here's RawDispatch:

    BOOL RawDispatch(void) {
       return TRUE;
    }

  6. I'll look at the three implementations in the sections below. You can find the entire project on this month's Companion Resource CD.

Raw IDispatch access

If you are interested in learning about COM it's quite revealing to walk through the manual steps of low level COM access through IDispatch. It takes about a hundred lines of code to instantiate the server and actually make a method call if you count all the code necessary to handle errors and conversion from Ansi strings to wide character OLE strings. COM doesn't use ANSI character strings and instead requires all character parameters to be passed as Unicode (double byte) strings, which is rather tedious using native Win32 APIs. If you're using Idispatch, which is the most common way to access Visual FoxPro servers, you also need convert any parameters and return values to and from Variants.

We don't have enough room to print the full code here, but here are the highlights of making method calls through the Idispatch interface using the raw COM APIs. Keep in mind that code below doesn't show the character conversions and error handling. You can check out the C_COMRaw.cpp file for the actual code and some helper functions in COMHelpers.cpp I built to make this easier.

Before you can use COM in an application you need to initialize the COM subsystem with:

CoInitialize(0);

This function must be called for every thread in application that needs to use COM. All mechanisms described here require that this line of code preceeds any use of COM in your application. You can CoUninitialize(); unloads the COM resources related to the matching CoInitialize().

To create an instance of a server you first need to get its ClassId. Since we most commonly use ProgIds (like visualfoxpro.application), you need to convert it:

hr = CLSIDFromProgID(lpWideCharProgId, pclsid);

Once you have the ClassId you can now create an instance of the object:

hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IUnknown, 
                     (LPVOID *) &punkObj);

This call to CoCreateInstance retrieves an interface pointer to the COM object's Iunknown interface. Iunknown is the base interface, which all other COM interfaces must support. Once you have the pointer to Iunknown you now have to ask for the actual interface that you would like to work with. In this case we want to work with Idispatch, which is a dynamic interface that lets us figure out which methods to call at runtime.

hr = punkObj->QueryInterface(IID_IDispatch, (void**)&pdispObj);

punkObj->Release(); // done with Iunknown Interface

After the call to QueryInterface we now have a pointer to the Idispatch interface which allows use to access our COM object indirectly through the Idispatch method interface.

Notice the Release() call on the Iunknown interface pointer. COM works through reference counting and every time you ask for an interface pointer via CoCreateInstance or QueryInterface COM automatically calls AddRef() to update the reference count. Release() is the corresponding function that decreases the reference count and should be called whenever you're done with an Interface pointer. When the last interface pointer is released the reference count goes to 0 and the object actually unloads, but prior to that last Release the object will continue to be active.

All this to do the equivalent of Visual FoxPro's CreateObject()! With the Idispatch pointer in hand we can now get at our server and call a method. Unfortunately this also can be a fair amount of work. The first step is retrieve an ID to the method or property you're trying to call.

hr = pdispObj->GetIDsOfNames(IID_NULL, &pwzMethod, 1, LOCALE_USER_DEFAULT,
                             &dispidMethod);

This call goes out and looks at the type library to retrieve the ID for the method or property. Note that you can cache this result value if you call the same method repeatedly so you don't have to keep calling GetIDsOfNames.

Once you have the ID you then need to call Idispatch->Invoke() to call the server. The tricky part here is that all parameters to the server need to be packaged up into variants first and passed in a structure that contains all of the variants.

DISPID dispidMethod;
DISPPARAMS dispparms;
VARIANTARG varg[1];
VARIANT vResult;
EXCEPINFO excep;

VariantInit(&varg[0]);   // Variant Array of Arguments
VariantInit(&vResult);   // Simple Variant

// Setup parameters
dispparms.rgvarg = varg;
dispparms.rgdispidNamedArgs = NULL;
dispparms.cArgs = 1;
dispparms.cNamedArgs = 0;

// Push in reverse order
varg[0].vt = VT_I4 ;    // Long see variant table for codes
varg[0].lVal = dwParameter;

hr = pdispObj->Invoke(dispidMethod, IID_NULL, 
                       LOCALE_USER_DEFAULT,
                       DISPATCH_METHOD, &dispparms, &vResult, 
                       &excep, NULL);

dwResult = vResult.lVal;  // Peel out Long from variant struc

 

can get quite messy as each type has to be converted to its own variant and then dropped into the parameter structure. If you're using strings they have to be converted to the BSTR type first, then attached to a Variant. Invoke() is the work method that goes out and makes the call on your server which is the equivalent of a simple one line VFP method call:

lnValue = oServer.GetProcessId(1)

If the call succeeded vResult will contain the return value from your FoxPro COM object call and you can retrieve that value using the vResult structures. Note that string results come back as BSTR values that need to be converted back into C style strings. COMHelpers.cpp contains a pair of work functions for this: BSTRToString and StringToBSTR.

/// Converting a VARIANT BSTR result to C string
char *lpzPath;
char lcBuffer[512];
/// Creates a new string buffer on the heap
hr = BSTRToString(vResult.pbstrVal,0,&lpzPath);
strcpy(lcBuffer,lpzPath);
…
/// Must release the memory allocated for the string value
HeapFree(GetProcessHeap(),0,lpzPath);
/// Must release interface reference once we're done with it
pdispObj->Release();

If you properly released your interface pointers for each time QueryInterface was called your object should now disappear. Keep in mind that in multithreaded situations your object may still have outstanding references from other threads and may force the object not to unload immediately. Also, remember that in C++ you have to handle memory management and reference cleanup yourself. If you don't call Release() because your code crashed somewhere that object will continue to stick around regardless of the fact that pointer went out of scope.

In the following sections I'll show you easier ways to do the same job with much less code and headaches. So, why would you bother to mess with this low level stuff?

  • Because this code is low level it's lean and mean. There's no additional overhead for intermediate mapping calls but you have to direct access to the COM APIs. If speed is your ultimate concern the native code will save some clock cycles.
  • The Visual C++ COM classes are powerful, but they leave out some functionality. If you need to create servers via DCOM on remote machines or you need to marshall Interfaces across processes or apartments the manual way is the only way to do it. Keep in mind that you can mix and match the COM classes and manual code as the COM classes expose the COM interface pointer.
  • It's good to understand how the low level APIs work before working with the COM smart pointers.

Using the CComDispatchDriver ATL Class

The raw access approach is a lot of work. It's easy to forget to handle some conversion correctly or forget some error handling code. To make life easier Visual C++ and the Active Template Library expose the CComDispatchDriver class. You still can't call methods directly because that's the nature of Idispatch, but single method calls can handle the actual Invoke access in a single method call. Furthermore, you can take advantage of ATL's support for Variant and BSTR classes that ease the conversions required by wrapping these COM data types into easy to use classes that use simpler assignment syntax. _variant_t and _bstr_t let you simply assign values and have them automatically converted to the proper variant and BSTR types. Here's what our code looks like now:

#include <windows.h>
#include <comdef.h>
#include <atlbase.h>
CComModule _Module;  // extern if declared elsewhere
#include <atlcom.h>
BOOL DispatchDriver(void) {
  CComDispatchDriver  oServer;
  CComPtr<IDispatch>  lpTDispatch;
  _variant_t vResult;
  char lpResult[512];
  DWORD dwResult = 0;
  HRESULT hr = 0;
  char lpzError[256];
  char lpzError1[128];
  hr = lpTDispatch.CoCreateInstance(_bstr_t("C_Com.ComTest"));
  if ( FAILED(hr) ) 
      goto Exit;   // All errors need checking!
   /// Assign the interface ptr to the Dispatch Driver
  oServer = lpTDispatch;  
  /// Call with no parameters
  hr = oServer.Invoke0(_bstr_t("GetCurrentPath"),&vResult);
  if ( FAILED(hr) ) 
	  goto Exit;
  strcpy(lpResult,_bstr_t(vResult));  // assign to C String
  /// Call with 1 parameter
  hr = oServer.Invoke1(_bstr_t("SetCurrentPath"),
	               &_variant_t("c:\\temp\\"),&vResult);
  // Note: Numerics must be forced to Longs!
  hr = oServer.Invoke2(_bstr_t("AddNumbers"),&_variant_t(5L),
	               &_variant_t(10L),&vResult);
  dwResult = vResult.lVal;
  hr = oServer.GetPropertyByName(_bstr_t("cCurrentPath"),
	                           &vResult);
  hr = oServer.PutPropertyByName(_bstr_t("cCurrentPath"),
	                           &_variant_t("c:\\"));
  /// Retrieve a property value
  hr = oServer.GetPropertyByName(_bstr_t("cCurrentPath"),
                                 &vResult);
Exit:
  if (FAILED(hr)) {
     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,0,hr,
     		   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                   lpzError1,sizeof(lpzError1),0);
     	sprintf(lpzError,"An error occurred:%s",hr,lpzError1);
	MessageBox(0,lpzError,"Raw Dispatch Interface Error",48);
	return FALSE;
   }
   return TRUE;
}

As with a lot of ATL, CComDispatchDriver is not complete – in order to instantiate the object you need to use another class first to create an interface pointer. The returned pointer can then be passed to a CComDispatchDriver object to access the Dispatch interface.

Hence, you have to first create an instance with a plain COM smart pointer:

hr = lpTDispatch.CoCreateInstance(_bstr_t("C_Com.ComTest"));

and then pass the return pointer to the CcomDispatchDriver to activate the object:

oServer = lpTDispatch;

Note that both of lpTDispatch and oServer are classes that wrap the interface pointers. They're using overloaded operator = to allow them to be treated as if they were standard interface pointers so the assignment above works.

Once you have the interface pointer assigned to oServer it can now be used to call the Invoke and Get/Put property helper methods. Invoke comes in several flavors: Invoke0, Invoke1 and Invoke2 for a max of 2 parameters to be passed – if you need more parameters you need to subclass and create additional methods on the CCOMDispatchDriver class. These methods package up the input parameters and unbundle the output parameters as needed, but everything must be done using variants.

I'm using the ATL _variant_t class, which allows you to assign most types directly to a variant structure. _variant_t can be used in place of a VARIANT or VARIANTARG pointer type argument. Most commonly you'll use the variant class to automatically format the input parameters. You don't need to use the class though: plain VARIANTs will do just fine, but it's a little more work.

All method names need to be passed in as COM BSTRs, so the _bstr_t class is used to quickly convert regular C strings to BSTRs. You can do this manually, or as we did before in the Raw interface calls by using the COMHelpers.cpp functions to convert the strings for you if you don't want to use the classes. The methods to look at are BSTRToString() and StringToBSTR().

One note about both the _variant_t and _bstr_t classes: Neither one of these supports binary strings that contain NULLs. BSTRs can contain NULL values, but neither class acknowledges the absolute size of the string. It's impossible to retrieve a string beyond a null. So if you need to return or send in binary data you may well be stuck creating your BSTR and Variants manually or using the helper functions in COMHelpers.cpp.

Using type library imports

If you can use early binding with your server the #import statement is the probably the easiest way to get your server hooked into Visual C++ code. In fact, it only takes a few lines of code and gives the ability to call methods directly on an object reference.

Note
Early binding using the type library import will work only with Visual FoxPro 6.0 servers. There's a problem with the type libraries that Visual FoxPro 5.0 creates, which causes the imports to generate incorrect class information and the compiler to generate lots of errors.

The Visual C++ specific #import statement takes a full path to your server's type library as an argument. When you compile this code, VC++ creates a set of special header and implementation files from your type library. The headers wrap the interface and the CoClass with a straight C++ wrapper so calling any methods and properties is done using syntax very similar to a C++ class. VC++ creates .TLH and .TLI for the header and implementation respectively. If you're interested at all in low level COM calls required, the TLH and TLI files is an excellent source for seeing how to natively call the COM interfaces directly using the smart pointer classes described in the previous section.

If you take a look at the generated files you'll find that VC++ basically makes underlying COM calls for you wrapping the error handling by throwing standard C++ exceptions with a special COM exception object. It also handles type conversions for you. With Visual FoxPro this irrelevant, as VFP can only take and return VARIANT arguments so the class only generates VARIANT wrappers that still need to be converted to and from standard types.

Here's what our server code looks like using the #import type library directive:

/// Creates wrapper class TLH/TLI files in 
/// Debug or Release directories
#import "c:\wwapps\wc2\webtools\c_com.tlb"
void ImportTlb() {
    // Class is created in this namespace (lowercase)
    using namespace c_com; 
    _variant_t vResult;   // Work Variant object
    char lcPath[MAX_PATH];
    long lnValue = 5;
    bool llValue;
     // Pointer - I + lowercase class name + Ptr
     IcomtestPtr oServer;
	
     // NOTE: calling the object - not the pointer! 
     oServer.CreateInstance("c_com.ComTest");
    // Getting a property - now we're using the pointer!
    vResult = oServer->GetCCURRENTPATH();
    /// Storing to a standard C zero terminated string
    strcpy(lcPath,_bstr_t(vResult));
    /// straight method call with static string parameter
    oServer->setcurrentpath(&_variant_t(OLESTR("c:\\wwapps\\wc2\\webtools")));
	/// straight method call with C string parameter
    char lcPath2[MAX_PATH]="c:\\wwapps\\wc2\\webtools";
    oServer->setcurrentpath(&_variant_t(_bstr_t(lcPath2)));
	/// method call with result value
	vResult = oServer->getcurrentpath();
	
	strcpy(lcPath,_bstr_t(vResult));
     /// Call server with multiple numeric arguments
	/// Note: Variants support only longs no integers
	vResult = oServer->addnumbers(&_variant_t(1L),&_variant_t(lnValue));  
    
	
     /// Convert variant to a long
	lnValue = vResult.lVal;
	
	vResult = oServer->istrue(&_variant_t(true));
	llValue = bool(vResult);
      // Unload the server - Optional. Will Auto Release when object releases
      oServer->Release();
}

The type library conversion essentially creates a smart COM pointer for you. The pointer is created through a macro, which is a little confusing because the following reference to the pointer appears nowhere directly in the generated files:

IcomtestPtr oServer;

The name of the Smart Pointer is always the name of the interface in the type library plus Ptr. Visual FoxPro interfaces are always named the lowercase name of the class plus an uppercase I as the Interface prefix. The Smart Pointer is actually an object that encapsulates and wraps the COM interface for you so it is treated much like you would any other dynamically allocated class. To instantiate the actual object you call the object's CreateInstance method:

oServer.CreateInstance("c_com.ComTest");

Note that your referencing the Smart Pointer object (.) in this call, rather than a reference to your server's COM interface (which would be the indirection operator ->). This call instantiates your COM server and then returns a reference to the Interface, storing it in an internal pointer member. Through some slick operator overloading you can now access the COM interface using the -> indirection operator:

vResult = oServer->getcurrentpath();
oServer->setcurrentpath(&_variant_t(_bstr_t(lcPath2)));

As you can see all method calls are made directly on the object reference indirection operator. The COM smart pointer overrides the -> operator to pass the reference to the Interface to the class methods which actually use low level COM API functions (plus error handling) to call the interface methods on your behalf. Input and output parameters are packaged up for you and then reassigned to the appropriate parameters and return values of the method call.

The code above demonstrates how to retrieve Properties and make method calls. As with the CComDispatch example you'll find that a lot of code deals with the conversion to and from variants and OLE string types. But as before the _variant_t and _bstr_t make this relatively easy even if it does mean some nested class wrappers.

Summary

Accessing your Visual FoxPro COM objects from C++ has enormous potential. It gives you the means to build scalable applications that can take advantage of system level technology and still allow Visual FoxPro to be part of the solution. COM makes this connection from C code to Visual FoxPro very straight forward in terms of logic.

In summary, use the following guidelines for building C code that accesses COM objects:

  • If Early Binding is an option, by all means use the Type Library Import mechanism. It's by far the easiest way to access COM objects from VC++.
  • Use CComDispatchDriver for accessing Late Bound servers via Idispatch. You should be able to use this class for most if not all of your Idispatch needs.
  • If you need special handling of the Interface then you may have to use the low level APIs. The ComHelpers provided on the CD should help with some of the repetitive conversion tasks.
  • You can mix and match. All smart pointer classes expose the underlying COM interface pointers, so you can get at the low level APIs when needed.
  • Take advantage of the _variant_t and _bstr_t objects. They work well as long as you don't need to support binary strings.