The basics of using a VFP COM component from .NET are relatively simple, but the more objects we use, the harder it gets. In order to make one's life easier from the .NET side, the component must be built a certain way from the VFP side. For instance, it's very common to create objects on-the-fly in VFP, given the ease of doing so. However, these objects cannot be consumed from .NET without writing extra code. This article covers some aspects of how the developer can improve the COM Interop experience when consuming VFP COM components from .NET.

Before reading this article, make sure you're familiar with some of the basics of COM Interop. You can find great articles about it on the VFP Conversion web-site (www.vfpconversion.com).

A Common Scenario

One common scenario for VFP developers is using business objects that expose data to be consumed by the User Interface (UI) or any other process. Many times the data is exposed by a member of the business object (often called "oData", or something like that), and this member may be an object created on-the-fly (for instance, by using the SCATTER command).

Things get a bit hard because .NET is a strongly-type language, and, in order to have a better experience using our VFP classes, we have to write some more code (aside from what we normally need for VFP).

First of all, we should always declare the type for parameters and return values in methods. If we don't do so, they will appear as being of type "variant" in the Type Library, and since a variant could be anything (any type), the developer from the .NET side will have to know what the exact type of everything is in order to be able to use it.

All of the properties defined in our objects will be seen in uppercase by .NET, and if we're writing code in C# (which is a case-sensitive language) we cannot type the properties in mixed-case mode (therefore, we'd see a property LERROR, instead of lError, for instance. Design guidelines for .NET determine that one should not use Hungarian notation ? in the example, the property should be named just Error ? but that's outside the scope of this article). VFP provides a way to put some more information in COM's Type Libraries so that this sort of issue gets resolved. The way to do that is by using the COM_Attrib array. For example, consider the following simple business object class named Biz (only a small part of the class is listed for the sake of clarity in the example):

*-- Abstract parent class for other Business Classes.
Define Class Biz As Session

*-- Keeps a reference to the data object.
oData = Null

*-- Properties for error handling info.
lError = .F.
cErrorMsg = ""

*-- Property's attributes for COM interface.
Dimension lError_COMATTRIB[4]
lError_COMATTRIB[1] = COMATTRIB_READONLY
lError_COMATTRIB[2] = "Indicates whether an error "+;
                            "has occurred inside the object."
lError_COMATTRIB[3] = "lError"
lError_COMATTRIB[4] = "Boolean"

Dimension cErrorMsg_COMATTRIB[4]
cErrorMsg_COMATTRIB[1] = COMATTRIB_READONLY
cErrorMsg_COMATTRIB[2] = "Info about last error."
cErrorMsg_COMATTRIB[3] = "cErrorMsg"
cErrorMsg_COMATTRIB[4] = "String"

Dimension oData_COMATTRIB[4]
oData_COMATTRIB[1] = .F.
oData_COMATTRIB[2] = "Keeps master data object."
oData_COMATTRIB[3] = "oData"
oData_COMATTRIB[4] = "IDispatch"

Enddefine

In the COM attrib array we define things like the proper capitalization of properties, some help text for it, and the property's type. Refer to the VFP help file in order to get a deeper explanation on the COM_ATTRIB array.

Notice that the oData property is defined as "IDispatch". That means that we will not know the exact type of the object nor its members until runtime. In the architecture used by this business object, the object has an oData member that holds data for a given entity. That member is created during runtime (using the SCATTER command), so when we create the COM component, its Type Library won't have any information about the members of that oData object. (That is, it doesn't know which properties or methods the object has.) That is a problem from the .NET side, with it being strongly-typed, since we can't access members of oData directly because they won't be known until runtime.

Accessing the oData members using Reflection

So, how do we get around that? .NET has a feature called Reflection, which we can use to query objects during runtime in order to access members that the compiler wasn't aware of during compile time. This can be used in order to access the members on the oData object. You'll find information on how to do that in the following article by Rick Strahl: http://www.eps-cs.com/VFPConversion/Article.aspx?quickid=030034. In a few words, the idea presented on the article is that you create a helper method that wraps up the reflection code required to access the members on the object, so that the developer using the object would write something like this:

string myValue = (string)this.GetProperty(oData,"CompanyName");

Going with that approach means that:

You don't get to use the Object.Property notation (since the access happens through calling the helper method - GetProperty);

You don't get IntelliSense support to inspect the properties available on the oData object;

There's a performance hit on accessing the property that way, which may or may not be an issue, depending on how often you access the property.

On the upside of this approach, there's nothing else one must do from the VFP side in order to use the object.

Since Rick has presented that way of doing it (with more details and everything), I'll present a different solution, so that the readers can decide on whatever fits better their needs.

Implementing Interfaces to create a strongly-typed oData object

The solution I'll present in this article is to explicitly define the interface of the oData object. Say we have an Employee business objects, which inherits from the Biz class. The oData object gets created on-the-fly, with properties representing fields from the Employees table. The first step then is to create a class (here named EmployeeData) that represents the oData object that gets created during runtime for the Employee business object. This class looks like so:

*-- Interface for Employee's data.
Define Class EmployeeData As Session OlePublic

*-- Properties that map to fields in the database.
EmployeeID = Null
LastName = Null
FirstName = Null
Address = Null
City = Null
PostalCode = Null
Country = Null

*--
Dimension EmployeeID_comattrib[4]
EmployeeID_comattrib[1] = COMATTRIB_READWRITE
EmployeeID_comattrib[2] = "Employee ID"
EmployeeID_comattrib[3] = "EmployeeID"
EmployeeID_comattrib[4] = "long"
*--
*--
Dimension LastName_comattrib[4]
LastName_comattrib[1] = COMATTRIB_READWRITE
LastName_comattrib[2] = "Last Name"
LastName_comattrib[3] = "LastName"
LastName_comattrib[4] = "String"
*--
*--
Dimension FirstName_comattrib[4]
FirstName_comattrib[1] = COMATTRIB_READWRITE
FirstName_comattrib[2] = "First Name"
FirstName_comattrib[3] = "FirstName"
FirstName_comattrib[4] = "String"
*--
*--
Dimension Address_comattrib[4]
Address_comattrib[1] = COMATTRIB_READWRITE
Address_comattrib[2] = "Address"
Address_comattrib[3] = "Address"
Address_comattrib[4] = "String"
*--
*--
Dimension City_comattrib[4]
City_comattrib[1] = COMATTRIB_READWRITE
City_comattrib[2] = "City"
City_comattrib[3] = "City"
City_comattrib[4] = "String"
*--
*--
Dimension PostalCode_comattrib[4]
PostalCode_comattrib[1] = COMATTRIB_READWRITE
PostalCode_comattrib[2] = "Postal Code"
PostalCode_comattrib[3] = "PostalCode"
PostalCode_comattrib[4] = "String"
*--
*--
Dimension Country_comattrib[4]
Country_comattrib[1] = COMATTRIB_READWRITE
Country_comattrib[2] = "Country"
Country_comattrib[3] = "Country"
Country_comattrib[4] = "String"
*--
*--
Enddefine

The EmployeeData class' purpose is to expose the oData's interface, so that when the type library for the COM object gets created, it has more information about the types contained in it.

In short, this "interface" is basically a class that has one public property for each table field exposed by the oData object. And for each property we add a COMAttrib array so that the Type Library for the interface will get created properly.

Automating the creation of the interface for the object

In order to make the creation of such a class a lot easier, a small code-generator program can be written to save the developer from writing some tedious code. An example of such program is listed below. This program will take a table name and a class name, and will generate the interface class for us. After that, we just have to verify the class created to see if it looks fine. This program can definitely be enhanced by creating a better mechanism to map the database's data type to COM's data type. Also, a visual interface for it would be great. The idea is: that's definitely something that can be automated!

Local lnConn, lcTable, lcClassName

*-- Table that we want to retrieve columns.
lcTable = "employees"

*-- Name of the class that will be created.
lcClassName = "EmployeeData"

*-- Connect to the database and get
lnConn = SQLExec(SQLStringConnect([driver=sql server;server=(local);database=northwind;trusted_connection=true;]),;
[EXEC sp_columns @table_name = ']+ lcTable +['], [curTemp])

Set Textmerge On
Set Textmerge To Memvar lcProps

*-- List all fields in the table creating properties for a class.
SCAN
Text noshow
<< Alltrim(Column_Name) >> = null
EndText
ENDSCAN

Set Textmerge To
Set Textmerge To Memvar lcAttribs

*-- Go back to first row.
Locate

*-- Create COMAttrib array for each property.
SCAN
Text noshow
*--
Dimension << Alltrim(Column_Name) >>_comattrib[4]
<< Alltrim(Column_Name) >>_comattrib[1] = COMATTRIB_READWRITE
<< Alltrim(Column_Name) >>_comattrib[2] = "Maps to << Alltrim(Column_Name) >>"
<< Alltrim(Column_Name) >>_comattrib[3] = "<< Alltrim(Column_Name) >>"
<< Alltrim(Column_Name) >>_comattrib[4] = "<< Alltrim(Type_Name) >>"
*--
EndText
ENDSCAN

Set Textmerge Off
Set Textmerge To

*-- Create main definition for the class that will be created.
Text to lcOutput TEXTMERGE NOSHOW
Define Class << lcClassName >> as Session OlePublic

<< lcProps >>

<< lcAttribs >>
EndDefine
EndText

*-- Store results in the clipboard.
_ClipText = lcOutPut

SQLDisconnect(lnConn)

Making use of the interface

The Employee business object will have to implement this interface, and, because of that, we have to compile the interface in a different project than the one where the Employee class is defined. (Otherwise the VFP compiler complains that it cannot find the interface.) For this example, I've created a new project called DataObjectInterfaces, where I keep all the interfaces for data objects.

We can benefit from the EmployeeData class even in VFP. When we're using the Employee business object in a VFP program, we can see the oData member in IntelliSense, but we don't see oData's members. By using that interface class we get IntelliSense support, like so:

LOCAL loBiz as "employee" of "classes\bizemployee.prg"
loBiz = NewObject("employee", "classes\bizemployee.prg")

LOCAL loData as "employeedata" of "classes\datainterface-employee.prg"
loData = loBiz.oData

This is what the code does: it declares a variable loData of type EmployeeData, and then it stores loBiz.oData to that variable. From that point on, everytime we type a "." (dot) after loData, we see the properties exposed by the data object. That's big help since we don't have to remember the properties available to such object, and also avoids mistyping a property name.

Now, after having compiled that class as a COM component, we have to change our Employee class in order to make it implement that interface. Our class definition will have to look like this:

Define Class Employee As Biz Of Biz.prg OlePublic
Implements Iemployeedata In "DataObjectInterfaces.EmployeeData"

And somewhere in the body of the class we have to implement every single member defined by the interface. Such implementation should look like this:

Protected Procedure Iemployeedata_get_EmployeeID() As Number;
HELPSTRING "Maps to EmployeeID"
Return This.oData.EmployeeID
Endproc

Protected Procedure Iemployeedata_put_EmployeeID(eValue As Number @);
HELPSTRING "Maps to EmployeeID"
This.oData.EmployeeID = eValue
Endproc

Every property in a COM class is ultimately implemented as a pair of "get" and "set" (in VFP, "put") methods. That's sort of what Access and Assign methods do for properties in VFP classes. So, in the code above, the "get" method for the EmployeeID property returns This.oData.EmployeeID, and the "put" method assigns eValue (which is the parameter received by that method) to This.oData.EmployeeID. That must be done for every single property exposed by the interface. The creation of such code is also something that could be automated by some small program.

Automating the creation of the Runtime Callable Wrapper

After having implemented the interface we're good to compile the components and create the Runtime Callable Wrappers (or RCWs for short) that will be used by .NET. By using VS .NET's IDE to create the RCW, we end up with an RCW created with default values, such as default name for the DLL and the namespace that the classes are put in. By creating RCWs manually, using the Type-Library Importer tool that comes with the .NET Framework SDK(tlbimp.exe), we have much finer control over the process. In order to make my life easier, I created a CreateProxy.prg, which automates the tasks of building my VFP DLL and creating the RCW for me, so I always run this little program every time my VFP COM component gets changed. The program looks like the following:

Local lcOutputProjFolder, lcProjFolder

*-- This is where the RCW is going to be put in.
lcOutputProjFolder = [C:\Inetpub\wwwroot\VFPNTierSample\bin\]

*-- This is where the VFP COM project is located.
lcProjFolder = [C:\Development\Projects\Samples\Classes\]

*-- Create the Interfaces DLL first.
CreateProxy(lcProjFolder, "DataObjectInterfaces", lcOutputProjFolder)
*-- Then create the business objects DLL.
CreateProxy(lcProjFolder, "BizObjects", lcOutputProjFolder)


Function CreateProxy(lcProjFolder, lcProject, lcOutputDir)

Local lcDLL, lcNETDir

*-- This is where the .NET SDK is located.
lcNETDir=Sys(5)+"\Program Files\"+;
   Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin\"

      *-- This is the DLL that we want to access from .NET
lcDLL = ["] + lcProjFolder + lcProject + ".dll" + ["]

      *-- We build the VFP COM DLL.
Build Mtdll &lcDLL ;
FROM &lcProject recompile

*-- We use WSH to run the type-library importer.
Local oWSH As "WScript.Shell"
oWSH = Createobject("WScript.Shell")

lcTLBImp = ["] + lcNETDir + [tlbimp.exe" ] + lcDLL + [ /out:"] +;
lcOutputDir+[interop.] + JustFname(lcDLL) +;
     [" /namespace:] + lcProject

oWSH.Run(lcTLBImp)
Endfunc

Note that after running this, it's a good idea to build the .NET project again so that any changes to classes can be seen right away in the code editor.

Consuming the objects form .NET

Once we have added references in the .NET project to the COM components (in case we hadn't done that yet), we can use our class like so (this is C# code):

// Declare variable and create business object.
EmployeeClass oBiz = new EmployeeClass();

// Call method to retrieve employee.
if (oBiz.GetEmployeeByPK(employeeID))
{
// Get a reference to the business object as a data interface.
Iemployeedata oData = oBiz as Iemployeedata;

// If we got the reference properly, we can start using the data.
if (oData != null)
{
this.txtLastName.Text = oData.LastName;
this.txtFirstName.Text = oData.FirstName;
this.txtAddress.Text = oData.Address;
this.txtCity.Text = oData.City;
this.txtRegion.Text = oData.Region;
this.txtPostalCode.Text = oData.PostalCode;
this.txtCountry.Text = oData.Country;
this.txtPhone.Text = oData.HomePhone;
}
}

Even though the syntax is different from VFP, it's fairly easy to understand where the object gets created, and how methods are called. The one thing that is a bit harder to understand is how to get access to the oData members. That's done by getting a reference to the EmployeeData interface (as in Iemployeedata oData = oBiz as Iemployeedata), and from that point on we can use the business object as an EmployeData object, and therefore, access its members using early-binding (as oppose to late-binding). That means we benefit from IntelliSense support and a little bit less overhead when accessing the members (compared to using Reflection).

Bear in mind that this approach will serve you for whatever objects you may have in VFP and want to expose in .NET in a strongly-typed fashion.

Conclusion

The VFP developer will face some difficulties when using COM interop to access VFP components from .NET, and this article tries to show how some of those difficulties can be overcome. Make sure to read the other resources suggested by this article to make sure you know about the available options, and then decide on which approach will work better for the scenario you're implementing.

Claudio Lassala