Using .Net Components via COM from Visual FoxPro
By Rick Strahl
West Wind Technologies
rstrahl@west-wind.com
The .Net framework from Microsoft has now been released for almost a year and there is tremendous interest in the new technology. While the technology is fresh and new it's also difficult to start the process of moving to this new platform, especially if you're dealing with existing data and code. .Net provides a number of ways that allow integration with existing technologies, specifically via COM. A few months ago I introduced you to the topic of calling VFP COM components from .Net applications which is the most common scenario especially for Web applications utilizing ASP.Net. This month I'll show you the flipside: Accessing .Net components from Visual FoxPro which provides yet another mechanism you can utilize to gradually migrate or check out .Net technology.
While I think for the time being it will be more common to call Visual FoxPro components from .Net there can also be a number of benefits for calling .Net components from VFP. Most importantly it gives the opportunity for those of you that are trying to get your feet wet with .Net a chance to integrate the new technology into existing applications without having to completely re-write an application and starting from scratch. Building components in .Net is one of the easier things to do when starting out and having specific small features to learn is significantly easier than learning the entire framework as part of starting a full application from scratch.
Do you need .Net from your VFP apps?
But before you start jumping into .Net components called from VFP, you should think hard about your reasons for doing so. While there are many 'cool' features that are easy to implement, you should realize that you can probably access that same functionality through other non-.Net mechanisms as well. It might not be as easy as in .Net, but for real life applications not requiring an install of the .Net framework are probably easier to install, administer and manage than those that do. If your only goal is to access some minor feature that .Net provides you're probably better of skipping it if there are alternatives within VFP, COM or even via more manageable 3rd party tools.
Why do I say this? There's overhead. If you integrate .Net into your VFP application you should realize that you will incur the full requirements of the .Net platform:
- .Net Framework must be installed
You have to make sure that the framework is installed and if your application is distributed it will have to probably provide the 40-50meg or so runtime (20+meg download/installl) with it as few machines to date have the framework installed. You have to make sure that the proper version of the .Net runtime is installed if you don't install it yourself. Version 1.1 of the .Net Framework is about to be shipped (or already shipping) and it'll be interesting to see how much code breaks because of the version change as it's the first rev that will bring the full brunt of .Net versioning to bear.
- Hardware requirements
The .Net framework requires fairly hefty hardware to run on especially memory. First loaded .Net COM components tend to run around 3-4 megs for the first instance loaded into your app. Subsequent hits are much smaller. Actual memory usage of the component loaded depends on how many dependent assemblies the component loads. 3-4 megs is average in my experience for basic System type components. Graphics components can run closer to 10 megs once activated. Many components create resources that never release boosting memory usage as you go. For example, opening HTTP or TCP/IP connections starts additional threads and memory buffers in the runtime. This memory is never reclaimed. Typical apps that use these features will incur about a 7-10meg memory hit. Additionally .Net components are slow on the first hit as they are compiled on the fly by the .Net Just-In-Time (JIT) Compiler, which also consumes memory which is only reluctantly reclaimed.
- Registration of components
.Net COM components don't register like standard COM components and must use .Net specific tools (RegAsm.exe or the appropriate runtime libraries) to register a component. If your component needs to be distributed as a 'public' reuse component you have to sign it and publish it in the Global Assembly cache, which requires a good deal of work and requires an installer that understands the .Net runtime. Even private application assemblies hosted in the same directory as the application must be registered and require code that can perform this task if the installer doesn't provide integrated support for it.
The point is that by including .Net with your VFP application, you're essentially requiring two separate development runtimes to be installed and all the administration issues that go with it.
Publishing a .Net component to COM
Using .Net components from Visual FoxPro is very easy. The basics of exporting a .Net component to COM and then referencing and accessing it from VFP is no more involved than creating a COM component in VFP or Visual Basic. However, when you go beyond the basics of passing complex types and datasets around things get a little more involved.
Using .Net objects from VFP is a simple matter of creating a class and publishing the class as a COM object. Let's start with a simple, but useful example that I had use for on my Web site: A small component to create some image manipulation features for my Web site. The component shown below can create thumbnail images, rotate, resize and retrieve image info from images of various formats. The first example shown in Listing 1 creates a thumbnail from an image located on disk and writes it out to a new file. Note that source files are truncated removing error handling and a few other non-essential code blocks.
Listing 1 (C#): A class method to create a thumbnail image from an image
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace wwDotnetUtils {
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("wwDotnetUtils.wwImaging")]
public class wwImaging {
public string ErrorMsg = "";
public bool Error = false;
public bool CreateThumbnail(string lcFilename,
string lcThumbnailFilename,
int lnWidth, int lnHeight) {
Bitmap loBMP = new Bitmap(lcFilename);
ImageFormat loFormat = loBMP.RawFormat;
decimal lnRatio;
int lnNewWidth = 0;
int lnNewHeight = 0;
//*** If image is smaller than thumbnail just return it
if (loBMP.Width < lnWidth && loBMP.Height < lnHeight) {
loBMP.Save(lcThumbnailFilename);
return true;
}
System.Drawing.Image imgOut =
loBMP.GetThumbnailImage(lnWidth,lnHeight,
null,IntPtr.Zero);
loBMP.Dispose();
imgOut.Save(lcThumbnailFilename,loFormat);
imgOut.Dispose();
return true;
} } }
CreateThumbnail() takes input and output filenames and the size for the thumbnail to be created. There's some code omitted here for brevity that deals with maintaining the image's aspect ratio. I've also removed the error handling code in all code snippets for brevity. You can check the source code to see the full code. The actual image conversion is actually handled natively through the .Net framework by using the CreateThumbnailImage() method of the Bitmap or Image objects. This method creates a new image object that can then simply be saved to disk with the Save() method. CreateThumbnailImage takes the Height and Width parameters and a pointer to a callback function (null) and another pointer to a data block which is uncharacteristically typed as a Void * (an unmanaged type). The latter needs to be passed as IntPtr.Zero rather than null. This class and method is a plain .Net class that can be called from any .Net application.
Now, to export the object to COM we need to perform a couple of tasks:
- Tell the project to export .Net types (.Net classes) to COM
- Tell the class how to create the COM object(s) to export via attributes (optional)
I'm using a couple attributes on the class to explicitly force several options of how the COM class interface is published to override the default behavior for COM exports. The ClassInterfaceType.AutoDual attribute forces the COM object to be created using dual interfaces which creates both IDispatch (Late binding) and CoClass (Early binding) interfaces. The default is AutoDispatch, which works fine for VFP late binding usage via CREATEOBJECT(), but doesn't provide the interfaces necessary to use Intellisense, so overriding this attribute is important. The alternative is to explicitly create an Interface for all published methods of the class, then create a separate class with the implementation, which is obviously more tedious. Unless you have a specific need for the interface definition there is no need to go this route.
The ProgId is also overridden explicitly - the default is the namespace plus the name of the class, which in the case above is exactly the same as the default. I like to override this because in many cases the namespace may not be the name I would choose for the server's ProgId.
The attributes of the class are not essential to have types exported into COM. When a .Net assembly is exported to COM by default all classes are exported regardless of whether the attributes are available. But if not provided defaults are used and that may not be desirable.
The actual export is performed by using TLBEXP.EXE which creates a typelibrary (which is optional) and then using REGASM.EXE to register the component in the registry. Note that you can register just about any .Net assembly this way providing that the COM export attributes weren't explicitly disabled. .Net COM components are plain .Net assemblies that only through their registry entries and the type library are exposed to COM.
The easiest way to do this is with Visual Studio.Net. Within the IDE at the project level you can simply specify that you want to register your component for COM interop and VS takes care of compiling and registering the component each time for you (Figure 1).
http://www.programmersheaven.com/articles/rickshaw/dotnetfromvfp/image001.gif
Figure 1 - Visual Studio.Net can automatically compile your project into a COM component by setting the 'Register for COM interop' option in the Project's Build options. To get this dialog right click on the project and select Properties.
VS will export any type in the project so you should take care to create your COM components in separate projects that only contain the classes you want to expose.
The class attributes in the source code determine how the additional proxies used by the .Net COM runtime are invoked the object. Unlike other COM objects, if you check the ClassId in the registry you'll find it doesn't actually point at your DLL, but rather at a generic .Net framework DLL - mscoree.dll as shown in Figure 2. This DLL provides the Runtime Callable Wrapper (RCW) that is actually invoked as a COM object. The assembly and types within it are then accessed indirectly through a proxy that the RCW provides.
http://www.programmersheaven.com/articles/rickshaw/dotnetfromvfp/image002.gif
Figure 2 - The registry entry for the .Net COM component points at a generic runtime COM dll (Default key) - mscoree.dll, rather than your DLL. The generic runtime then loads the assembly based on the Codebase key and sets up the appropriate proxies to make passthrough calls to the .Net types.
This process is entirely transparent for your application however. To instantiate this .Net component from Visual FoxPro you simply do:
oImg = CREATEOBJECT("wwDotNetUtils.wwImaging")
? oImg.CreateThumbnail("d:\temp\sailing.jpg","d:\temp\tn_sailing.jpg",400,400)
You'll notice that as you type this that you get full Intellisense on the object as you type in your content as the export from .Net exported.
.Net COM Component Lifetime
To understand the lifetime of the .Net component run Visual FoxPro and instantiate the .Net component, then release it by setting the object reference to .null.:
oImg = CREATEOBJECT("wwDotNetUtils.wwImaging")
? oImg.CreateThumbnail("d:\temp\sailing.jpg","d:\temp\tn_sailing.jpg",400,400)
oImg = .Null.
At this point you should have released your reference to the object. Now go into VS.Net and create a new method in our class:
Listing 2 (C#): Rotating an image
public bool RotateImage(string lcFileName,int lnRotation)
{
Bitmap loImage = new Bitmap(lcFileName);
loImage.RotateFlip((RotateFlipType) lnRotation);
loImage.Save(lcFileName);
return true;
}
Now recompile your project. You'll end up with several errors on the compilation step to the effect that the files to be created are still in use. Huh? You've released the object.
Well, not quite. .Net loads the .Net runtime into a process - in our case VFP or your custom VFP application. Inside of that process it creates an AppDomain to host your .Net component. AppDomains cannot be unloaded until the .Net runtime shuts down and .Net components loaded via COM do not automatically shut down the .Net runtime. This means that .Net leaves your .Net assembly loaded in memory until you shut down VFP or your app.
In addition, the .Net runtime controls the lifetime of the actual objects you instantiate via COM as well because the runtime uses the .Net garbage collector to decide when to completely release an object. So if the .Net COM component uses a large amount of resources you will find that these don't release immediately when your objects go out of scope.
This means you have to be careful about releasing resource from your .Net COM components wisely as part of COM method calls or by providing explicit COM calls.
Debugging .Net classes from VFP
Because of the intermediary runtime that controls .Net objects loaded through COM it's actually quite easy to debug .Net components even when they are loaded through COM. In fact the process is no different than debugging any other .Net component with the exception that the calling program is not located in the same solution.
To debug a .Net COM object you set up the .Net project that hosts your COM exposes component by having it run VFP7.EXE (or whatever version) as the startup application. Any calls into the COM DLL or Exe can then trigger breakpoints in the .Net code.
To set this up in Visual Studio .Net you can do the following (Figure 3):
- Go into the source code and set a breakpoint on any line of code that is called through COM
- Select the project in the VS.Net Solution Explorer
- Right click and select properties
- Go to Configuration Properties | Debugging
- Set the Debug Mode to Program
- Set the Start Application to your VFP executable (IDE or your own EXE)
- Set the Working Directory to where you want VPF to start
- Set any command line arguments you may need.
http://www.programmersheaven.com/articles/rickshaw/dotnetfromvfp/image003.gif
Figure 3 - You can debug .Net components with calls made from Visual FoxPro by configuring VFP as the project's startup application. When your VFP code calls the .Net code VS.Net will stop on any breakpoints.
Once this is configured properly you can now start your project by pressing the Run button (f5) in VS.Net, which will fire up Visual FoxPro or your application. You can create an instance of the .Net COM object make a call on it and the VS.Net debugger will stop on any breakpoints set in the .Net code.
If you've ever tried to debug COM objects called from VFP before, I think you will appreciate how easy this process is compared to not at all being able to debug a COM object by any means. Debugging in .Net in general is a pure joy - simply because any kind of code can be debugged even if it spans multiple projects or as in this case multiple application environments.
Returning .Net built-in objects
Now let's take a look at how we can return share types of data between VFP and .Net. Let's add a new method that returns a .Net native object from the COM component. Listing 3 shows the GetImageInfoBitmap method that returns a reference to the .Net Bitmap object as a result.
Listing 3 (C#): Returning a .Net Bitmap object
public Bitmap GetImageInfoBitmap(string lcImageFile) {
Bitmap loBMP = new Bitmap(lcImageFile);
wwImageInfo loInfo = new wwImageInfo();
loInfo.Width = loBMP.Width;
loInfo.Height = loBMP.Height;
loInfo.HorizontalPixelResolution = loBMP.HorizontalResolution;
loInfo.VerticalPixelResolution = loBMP.VerticalResolution;
return loBMP;
}
Shut down VFP, and then re-compile the project again and bring up Visual FoxPro. Then bring up Task Manager and select VFP7.exe (or whatever version). Make sure you can see the memory usage (click View|Select Columns if it's not showing). Then go into the Fox command window and type these commands (with an image of your choice) one at a time and observe memory usage:
o = CREATEOBJECT("wwDotnetUtils.wwImaging")
loBMP = o.GetImageInfoBitmap(
"d:\temp\sailing.jpg")
loBMP = o.GetImageInfoBitmap(
"d:\temp\sailing.jpg")
loBMP = o.GetImageInfoBitmap(
"d:\temp\sailing.jpg")
Notice that everytime you do this the memory usage jumps quite noticeably depending on the size of the image. Even though we're effectively releasing the loBMP object each time we're making a new assignment the memory usage does not go down. Even doing the release explicitly like this:
loBMP = o.GetImageInfoBitmap(
"d:\temp\sailing.jpg")
loBMP = .NULL.
doesn't improve this scenario. This means that even though we are releasing the object in VFP, it's not completely releasing in .Net. It appears that not even the garbage collector in .Net is kicking in to release the memory.
Why is this happening? When we load an image the image is rasterized internally by the Bitmap object so the raw byte stream is used. This is what causes the big memory usage. When .Net releases objects it marks them for garbage collection, but doesn't immediately call the equivalent of the Destroy() method which is called only when the garbage collector finally releases the object.
To avoid the enormous resource use in this example above you have to manually release resources used by the bitmap object by calling the Dispose() method of the Bitmap object:
loBMP.Dispose()
Like most .Net classes Bitmap supports a Dispose() method that cleans up resources used, although it doesn't mean that the object reference is released or 'disposed' at all. Dispose is .Net talk for 'Object: clean up behind yourself'. Adding loBMP.Dispose() before the ENDFOR keeps memory usage reasonable by cleaning up the bitmap data even if the object reference itself is not released inside the .Net runtime that manages the lifetime of the COM called .Net object. Most resource intensive .Net objects include a Dispose() method that you should call from your code. It's also a good idea that if you create .Net components that require cleanup that you implement the IDisposable interface and add a Dispose() method to that .Net object as .Net internally has mechanisms that allow for automatic cleanup of objects that support this interface (in C# with the using structured statement).
Creating and Returning custom objects from .Net
You can also avoid this memory usage scenario by returning your own objects that you can control better from client code. Let's add another method to the class called GetImageInfo that returns the Height and Width and Screen Resolution of an image by returning a custom object of our own. Listing 4 shows the method and the object.
Listing 4 (C#) - Returning info about an image as an object
public wwImageInfo GetImageInfo(string lcImageFile) {
Bitmap loBMP = new Bitmap(lcImageFile);
wwImageInfo loInfo = new wwImageInfo();
loInfo.Width = loBMP.Width;
loInfo.Height = loBMP.Height;
loInfo.HorizontalPixelResolution = loBMP.HorizontalResolution;
loInfo.VerticalPixelResolution = loBMP.VerticalResolution;
loBMP.Dispose();
return loInfo;
}
The object is defined as follows:
public class wwImageInfo {
public int Height = 0;
public int Width = 0;
public float HorizontalPixelResolution = 0;
public float VerticalPixelResolution = 0;
}
Recompile the project, shut down VFP and start her back up. Then try this code:
o = CREATEOBJECT("wwDotnetUtils.wwImaging")
loImage = o.GetImageInfo("d:\temp\sailing.jpg")
? loImage.Height
? loImage.Width
? loImage.HorizontalPixelResolution
You've just retrieved the content of the object passed back from .Net. But notice that you didn't get Intellisense on the returned object. The reason for this is that we need to provide the proper attributes just like for the main wwImaging class in order to bring the type information interfaces back for VFP to evaluate. Change the class header to:
[ClassInterface(ClassInterfaceType.AutoDual)]
class wwImageInfo {... rest of class here}
Then shut down VFP and recompile the class and try the above code again. Voila, Intellisense now works.
In this code the cleanup code is internal to the .Net class GetImageInfo() method which means your Fox code doesn't have to worry about cleanup and the object passed to VFP is very light weight as it only contains a couple of properties. In many cases this is a better solution to the resource use of components as the lifetime management and resource clean up can be internalized in the .Net code.
Passing objects to .Net methods
Now let's pass information the other way: Let's see what we need to do to pass VFP objects to .Net in the course of a method call as a parameter. Listing 5 shows some simple code that passes a VFP object to .Net.
Listing 5 (VFP): Calling a .Net Component with object parameters
o = CREATEOBJECT("DotNetCom.DotNetComPublisher")
loCust = CREATEOBJECT("cCustomer")
? o.ReturnCustomer(loCustomer) && prints content of Company field
DEFINE CLASS cCUSTOMER as Custom
Company = "West Wind"
oData = CREATEOBJECT("Custom")
FUNCTION Load(lnPk as Int) as void
USE tt_cust
SCATTER NAME THIS.oData MEMO
ENDFUNC
ENDDEFINE
The problem here is that when we pass this object to .Net whether it's a generic SCATTER NAME object or even whether it is a full VFP object like the cCustomer object, .Net won't know how to receive this parameter as a typed parameter. You can
not simply do:
public string ReturnCustomer(object loObject) {
//*** loObject is passed from VFP - retrieve Company field and return
return loObject.Company
}
Instead you have to use indirect referencing using Reflection to return the value like this:
return loObject.GetType().InvokeMember(
"Company",BindingFlags.GetProperty,null,loObject,null);
This method is an indirect, late binding approach to access properties and methods on objects dynamically at runtime. It passes the name of the property, the type of call to make (GetProperty), the binding flags, the object on which to invoke the property and parameters which are used only for method calls. To simplify this process I created wrapper classes for GetProperty(), SetProperty() and CallMethod() which were covered in the previous article and are stored in a static class ComUtils. Some of the method code for this class is shown again in Listing 6 for context.
Listing 6 (C#): Dynamic access methods in the ComUtils class
public static object GetProperty(object loObject,
string lcProperty) {
return loObject.GetType().InvokeMember(lcProperty,
BindingFlags.GetProperty,null,loObject,null);
}
public static object SetProperty(object loObject,
string lcProperty,params object[] loValue) {
return loObject.GetType().InvokeMember(lcProperty,
BindingFlags.SetProperty,null,loObject,loValue);
}
public static object CallMethod(object loObject,
string lcMethod, params object[] loParams) {
return loObject.GetType().InvokeMember(lcMethod,
BindingFlags.InvokeMethod,null,loObject,loParams);
}
With this method we can now return the property value as:
public string ReturnCustomer(object loObject)
{
//*** loObject is passed from VFP - retrieve Company field and return
return (string) ComUtils.GetProperty(loObject,"Company");
}
Next Page