COM Interop between Visual FoxPro and .NET seems trivial at first, but the devil is in the details.
Simple COM calls using methods and properties with simple parameters are easily accomplished. However, once you start dealing with complex objects - objects with hierarchies or specific object types required by .NET as parameters or properties, you start running into problems. This article looks at some of the issues that you need to look out for when dealing with objects in applications that call .NET COM objects from Visual FoxPro.
A while back I wrote an article on the basics of calling .NET COM components from Visual FoxPro. This article introduced the basics of creating .NET Components, exposing them to COM and then calling the COM component from Visual FoxPro. In this new series of articles, I’ll expand on those original concepts by demonstrating a few more complex Interop scenerios.
This time I’ll look at a few more advanced issues of COM interop, starting with a close look at passing complex objects between Visual FoxPro and .NET.
A quick review of the basics
Let’s start with a quick review of how COM Interop works in .NET. In order to provide backwards compatibility the .NET runtime provides the ability to publish most .NET components as COM objects.
The .NET runtime accomplishes this through the COM Callable Wrapper (CCW), which is a wrapper around a plain .NET type that exposes it through COM. The idea is that you simply create a standard .NET component and then export that component to COM.
The process works like this:
- You create a .NET component
- You optionally mark up the component with attributesto tell it how to export to COM
- You run the TlbExp utility to create a Type Library
- You run RegAsm to register the component with COM
A very simple class exported to COM is shown in Listing 1.
Once you’ve compiled the code, you can use the RegAsm utility is a special COM registration tool that is specific to .NET and is required to register .NET COM components. .NET components require a special registry key for locating the assembly to instantiate and RegAsm registers the component properly. If you’re developing in VS.NET, it handles all the details of registration for you. All you need to do is check the ‘Register for COM Interop’ flag and VS will do all the work for you. If you deploy your application you will need to somehow register the component through RegAsm, either through an Installer that knows about .NET COM objects, or by manually running RegAsm as a post install step. For more details see the article mentioned earlier.
Once registered the component becomes available as a COM object and you can easily instantiate it using CreateObject() in Visual FoxPro.
loDN = CREATEOBJECT("DotNetCom.FirstServer")
? loDN.HelloWorld("Rick")
Behind the scenes the .NET Runtime is invoked when you instantiate this object and the .NET runtime in turn instantiates your .NET component for you, with the CCW acting as a proxy between the COM interfaces and the actual .NET Component. Figure 1 shows the workings of this process.
Once you have a reference to the object you can simply fire away on the COM interface, calling methods accessing properties and fields and generally work with the interface. For the most part this process is very straight forward.
Things you need to watch out for are
- The .NET runtime locks into VFPOnce you load a .NET COM component you can’t unload this component completely. This means during .NET COM development you have to shut down Visual FoxPro to reload a .NET COM component.
- .NET Components may not unload related resourcesThe .NET Garbage Collector cleans up objects and object state inside of the .NET instance running. This means when you delete an object the actual object and it related resources may unload immediately. For example, if you load a Bitmap object which has associated memory buffers, and simply clear the reference, the associated memory buffer data does not release immediately. Using the Dispose() method, when available, can help here to explicitly unload related resources.
- Not all types work in Visual FoxProSome data types - specifically value types and some specialized collection types - don’t work in Visual FoxPro when accessed over COM. Also some array types passed from COM to Visual FoxPro cannot be modified and sent back with new or removed elements.
- Passing types from FoxPro to .NET is usually not type safeUnless you have VFP COM components that you pass to .NET you cannot easily pass strongly typed objects created in Visual FoxPro to .NET. FoxPro objects pass as generic ‘object’ references and have to be accessed with Reflection in most cases.
All of the topics above were covered in my previous article, so if this is new to you take a look at this article.
Passing Complex types between Visual FoxPro and .NET
When working on business applications the process of passing data in the form of objects between Visual FoxPro and .NET is crucial. Passing object data from .NET to Visual FoxPro is fairly easy, and you can simply consume most objects passed from .NET in Visual FoxPro. To consume a .NET object you simply pass it back from .NET.
public Customer ReturnCustomer()
{
Customer Cust = new Customer();
Cust.Name = "Rick Strahl";
Cust.Company = "West Wind";
Cust.oAddress.StreetAddress =
"32 Kaiea Place\r\nPaia, HI 96779";
Cust.oAddress.Phone = "808 579-1234";
Cust.CreditLimit = (decimal) 10000.50;
return Cust;
}
You can access this object in Visual FoxPro easily enough simply by accessing the property values. To ensure that the .NET object has Intellisense support make sure you define the class definition for any type you might return with the following attribute.
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Customer { … }
FoxPro can see most .NET objects without any problems. Some limitations include value types or structures. Visual FoxPro can’t access these objects because they are passed by value rather than by reference and this functionality is not supported through the IDispatch COM interfaces used by VFP. For your own objects this likely won’t be a problem, but it might be for some internal .NET types which often use internal value types for representing data.
Passing objects to .NET is more tricky
Probably the biggest issue with COM Interop to .NET from Visual FoxPro is the lack of type support when passing complex objects to .NET. When you pass a FoxPro object to .NET that object comes across as a generic object type. object in .NET is similar to a Variant; it’s a generic type that is dynamically interpreted at runtime. In order to access this object in .NET you have to use Reflection, which is a dynamic runtime type discovery and invocation mechanism.
Let’s look at a simple scenario. Let’s say you have a dynamically constructed object in FoxPro and you pass it to .NET. Listing 2 shows an example of this scenario.
Note that this is a VFP 8 and later EMPTY object with properties added, but it could also be a CUSTOM class you designed in the class designer or created in a PRG with fixed property, or even a SCATTER NAME object - it really doesn’t matter how this object was created as long as it is a FoxPro class. I like EMPTY classes for ‘message’ style objects because the object interface doesn’t include default members like Name, Class etc.
In .NET then you have a method that receives this object in a method that is shown in Listing 3.
Notice that the method receives a parameter of type object, which is the only way that you can send a pure FoxPro object to .NET. Because the object is not strongly typed you have to use Reflection to access the object’s properties and methods. Reflection contains functionality that allows to ‘reflect’ over an object and its members and ‘invoke’ them to get or set property or field values or calling a method with parameters and receiving a result value.
Reflection is a bit tedious as you can see in the first extraction line in the method in Listing 2. It’s also much slower than direct method or property access. To facilitate the syntax some, I introduced a class ComUtils that simplifies the process of accessing object members more easily via Reflection wrapper methods. While using Reflection is a hassle it works fine. The ComUtils class as well as the samples shown here are included in the code download.
Things get problematic if the .NET method you are trying to call requires you to pass an object of a specific type. What if the signature of the above method looks like this:
public string PassCustomerObject(Customer Cust)
{
return Cust.Name + Environment.NewLine +
Cust.Company + Environment.NewLine +
Cust.oAddress.StreetAddress;
}
In this example, .NET has a Customer class definition (shown in Listing 4) that matches the FoxPro class described in Listing 2 and the method expects an object of this type as a parameter. This makes the .NET code a lot cleaner for sure; no more Reflection. But unfortunately it now becomes impossible to call this method directly from your FoxPro code. There’s no way that you can cast your FoxPro object to this .NET customer object.
The only workaround for this common problem is to not use FoxPro objects, but rather to use COM objects to pass the data to .NET. Remember that the .NET project is exposing its objects to COM so you can explicitly return a DotNetCom.Customer object. A partial listing of the .NET Customer object is defined in Listing 4.
With this object definition exposed to COM, you can now create it in FoxPro and pass it back to .NET, simply by changing the GetCustomer method in the FoxPro code shown in Listing 2 to:
FUNCTION GetCustomer
LOCAL loCustomer as DOTNETCOM.Customer
loCustomer = CREATEOBJECT("DotNetCom.Customer")
RETURN loCustomer
In addition, we have to change the call from PassRecordObject to PassCustomerObject:
#IF !USE_COM_CUSTOMER
? loNet.PassRecordObject(loCustomer)
#ELSE
? loNet.PassCustomerObject(loCustomer)
#ENDIF
The rest of the code in Listing 2 can be left alone. And voila, we can now pass a strongly typed object parameter to .NET.
Instead of directly instantiating the object, you can also do this with factory methods on the .NET object that returns an instance of the object in question. This may be desirable with business objects or complex objects that have child objects that must be initialized before being ready for use.
Passing Visual FoxPro COM objects to .NET - it works, but…
In the previous example, I created a .NET COM object, and passed that back to the .NET class. This means that .NET has to expose this object to COM in order for this mechanism to work.
What about the other way around? What if an object is defined in Visual FoxPro as COM and .NET needs access to it via parameters or return values? Is this possible? Yes and no. In some simple situations it works, but there are some serious limitations.
You can compile any Fox objects that you might need to pass to .NET into a COM object. Once you have built your COM object you can import that FoxPro COM object into .NET using TlbImport (or in Visual Studio simply pick the COM object), which creates .NET wrapper classes for each of the COM objects imported. Once this is done, you can use these wrapper types as input parameters and result values for functions in .NET. When called, COM and the CCW perform the proper type conversions for you.
This approach works fine as long as you can expose all of your objects into COM and you are willing to take the hit for using COM objects explicitly for all object references that are passed to .NET. Note that you should use CREATEOBJECTEX() for each COM instantiation to ensure the object is created as true COM object rather than a FoxPro object.
loCustomer = CREATEOBJECTEX("MyApp.Customer","")loNet.PassCOMCustomerObject( loCustomer )
Understand the limitations
While this sounds reasonable, it’s important to understand that Visual FoxPro’s COM implementation has some major limitations when dealing with complex objects. It’s not possible in Visual FoxPro to create COM object hierarchies that are represented in the type library. Even simple compound objects like the Customer/Address object relation I showed above cannot be presented as COM hierarchy with Visual FoxPro natively (unless you hack the type library). There’s no way for FoxPro create a member object that is exposed as a specific COM type. This means you can’t use this approach even in the simple example shown above. It only works with flat objects.
Another major problem is that Visual FoxPro cannot define COM method signatures that return COM objects that are defined in the current project. You can only reference COM objects that are externally available. This means you can’t create COM objects that do things like this:
DEFINE CLASS Customer as Custom OLEPUBLIC
FUNCTION Create() as MyProject.Customer
RETURN CREATEOBJET( "MyProject.Customer" )
FUNCTION GetAddress(PK as integer) as MyProject.Address
RETURN CreateObject( "MyProject.Address" )
ENDDEFINE
DEFINE CLASS Address as Custom OLEPUBLIC
...
ENDDEFINE
The code above fails because MyProject.Customer and MyProject.Address exist in the current project and FoxPro looks in the registry for these COM objects. Since you are currently compiling these projects they don’t exist and aren’t registered yet. VFP will compile the type library for the GetAddress object as returning a generic object variant.
Why am I showing this here? After all we’re talking about calling .NET COM objects from Fox code, not the other way around. Well, .NET objects might need to call back into your FoxPro objects to perform business logic or do other tasks and this is where it’s important that data can be passed around both ways and that’s where this situation becomes problematic.
A hack of a workaround:
Calvin Hsia, lead developer on the FoxPro team, posted a WebLog entry that describes a work around solution to the strong typing problem, by using Help String Values and explicitly assigning the ProgId references and type library type definitions. It works but it requires a separate tool you need to run (or hook with a project hook) everytime you compile. It also requires VS.NET in order to work. You can find this article on Calvins Blog.
Another, more work intensive solution - wrapper types
In my own work I have a FoxPro based product called Help Builder that provides COM interoperability by extending the product with Add-ins that can be written in .NET (as well as FoxPro). The Add-in architecture provides access to the Help Builder ‘business object’ and IDE component. The user can automate these objects via custom Add-ins. Help Builder is a FoxPro application, and the Add-In Processing essentially passes a reference to the IDE object to the Add-in both in .NET and the FoxPro Add-in engines.
Assuming you are a .NET developer and you want to create an Add-in with this API, you wouldn’t want to use Reflection to access the functionality. FoxPro’s COM type library export was of little use because the type library created could not reflect the hierarchy of objects. The root IDE object contains a reference to the Help business object, which in turn contains a reference to the current topic which contains each of the actual topic fields.
I needed a solution to make this object available in a strongly typed manner. It’s a FoxPro object and there’s no way for me to first instantiate the object in .NET, so I had no choice but to use this FoxPro COM object in .NET. So how can I expose this object as a strong .NET type?
The solution for me was to create a set of wrapper classes that work by retrieving data from a generic COM object reference. The idea is that I create wrapper instances of each of the objects that I want to expose - wwHelpForm (IDE), wwHelp (Help), wwHelpTopic and so on. Each of these objects has a special constructor that receives an instance of the generic COM object when it is instantiated. The instance is stored and then each of the methods and property definitions calls into this instance using Reflection return data or call methods on the original COM object.
There is a base class that provides the plumbing and implementation classes that make the appropriate Reflection calls. Listing 5 shows the wwHelpComWrapper base class.
This simple class only has a member for storing the COM reference and for releasing the object cleany. The concrete class implementations each provide a custom constructor that takes a COM object reference as a parameter and implements an optional factory method to allow quick creation of the object. Listing 6 shows an excerpt of the wwHelp class implementation.
The static CreateInstance method is a factory method that basically uses late binding to instantiate the COM object and automatically creates the wrapper type and returns an instance of it. This greatly simplifies construction of the object instance that is ready to go for accessing the COM functionality.
The constructor receives a generic COM object instance as a parameter and stores it in the m_Inst property. Any access to a class property or method then simply calls out via Reflection against this instance and calls the underlying member in the COM object. So to retrieve a property wwUtils.GetPropertyCom (which is identical to ComUtils.GetProperty) is called with m_Inst as the object reference to retrieve the value from. To call a method, wwUtils.CallMethod() is called.
Repeat for all the properties of the object I want exposed. While this is a tedious, manual process, it actually turned out that manually creating the wrapper allowed cleaning up the interface and made it more .NET compliant. For example, all the Fox properties have type prefixes (cFilename, oTopic etc.) which is not common practice for .NET objects. I was able to remove the prefixes for my new property names so now there are Filename and Topic properties instead. I was able to filter out some members I didn’t want to have show up in the Add-in interface and I was able to create overloaded methods to handle parameter settings that are more logical than the FoxPro variable parameter lists required. Overall I ended up with a more natural and cleaner class interface than the raw COM interface.
Finally I can also express hierarchical object relationships. The wwHelp object includes a Topic member which is defined like this:
public wwHelpTopic Topic
{
get
{
return new wwHelpTopic(
wwUtils.GetPropertyCom(m_Inst,"oTopic") );
}
}
This creates a new instance of wrapper object from the wwHelpTopic object and passes it back to the caller. Once all this is done, I can do the following in .NET code to access my object directly with very little, natural feeling code:
string TopicId = "_012dda125";
wwHelp Help = wwHelp.CreateInstance();
Help.Load(TopicId);
string Body = Help.Topic.Body;
This example actually loads the COM object. However, in the Add-in code the object is passed from Visual FoxPro to .NET. The add-in itself in FoxPro is launched by calling into a set of helper methods in .NET. The FoxPro code is minimal and is shown in Listing 7.
This code calls yet another wrapper class - wwReflection which I also introduced in the last article - to dynamically invoke a method in an assembly. The goHelp parameter in the Execute call is an instance of the Help Builder IDE. It’s the root reference and the parameter the Add-in receives. The object is passed as a plain Visual FoxPro object.
The key code in the Execute method takes the goHelp COM instance and creates a wwHelpForm COM wrapper instance from it, which is then passed to the user created add-in. The following snippet demonstrates the dynamic instantiation and method call in .NET:
object Instance = Activator.CreateInstance(TypeRef);
// *** Convert object into strongly typed object
Westwind.wwHelp.wwHelpForm Form =
new Westwind.wwHelp.wwHelpForm(Parameter);
bool Result = (bool) Instance.GetType().InvokeMember(
BindFlags,null,
Instance,new object[1] { Form } );
return Result;
The final step is to create an actual Add-in. Here’s a very simple Add-in that demonstrates the wrappered interface:
public bool Activate(wwHelpForm HelpForm)
{
// *** Demonstrate objects
wwHelp Help = HelpForm.Help;
wwHelpTopic Topic = HelpForm.Help.Topic;
// *** Let's update the topic text
Topic.Body = DateTime.Now.ToString() +
"\r\n" + Topic.Body ;
HelpForm.Help.SaveTopic();
MessageBox.Show( HelpForm.Config.FtpServer );
// *** Refresh the topic display
HelpForm.GoTopic();
return true;
}
The HelpForm parameter is the Help Builder IDE - the goHelp parameter that was passed from FoxPro, stored into the wwHelpForm wrapper. Once it arrives this single object gives access to the entire Help Builder IDE and API giving the user full access to automate the interface and do whatever they need.
So there you have it - a strongly typed representation of a FoxPro object, that exposes a complex object hierarchy and doesn’t rely on Visual FoxPro type library support. It’s not an automatic process to build these wrapper objects, nor is it the most efficient way to access them, but it provides the most flexibility and functionality that otherwise is simply not available.
On the downside this approach requires some diligence to make sure that if the FoxPro objects change that the .NET wrapper object is kept in sync. It also relies on your ability to create and extend the .NET object. You need to be able to use these new wrapper classes in the .NET code. This approach obviously won’t work if you are given a .NET Object that you have no control over.
No Control over the .NET code
If you have no control over the .NET code and you need to call into the .NET object, but can’t because you can’t coerce the type, then what do you do? The last straw is to create a .NET wrapper that calls into the .NET object on the behalf of your FoxPro object. The idea is that the wrapper becomes the proxy that translates between the parameters or property values that are accessible to VFP and the ones that .NET uses and cannot be accessed by VFP. For example, if .NET returns a value type you might create a mirrored class that duplicates the value types properties and pass that to and from FoxPro. Again this can be a lot of manual grunt work, but it can get you past the hurdles if need be.
Objects are farther than they appear
COM Interop with .NET is easy in concept, but if you go beyond the basics you’ll find that the issues are more complicated than it first appears. Most of the issues revolve about Visual FoxPro’s limited ability to create appropriate type libraries for anything but the simplest types.
These are serious issues that you have to consider and should understand before you embark on a large scale COM Interop project. Passing data between the two environments is a crucial aspect of Interop. The devil’s in the details - know the limitations before you get too deep and make sure you can work around them.
In this first article in this series I’ve looked closely at a fairly complex topic. In the next installment we’ll a little more fun when I’ll look at Event Handling and Multithreading with .NET COM Components.