White Papers                  Home |  White Papers  |  Message Board |  Search |  Products |  Purchase | News |  
 

Using .NET Components via COM
from Visual FoxPro (or other COM client)

 

By Rick Strahl

http://www.west-wind.com/
rstrahl@west-wind.com

 

Last Update: December 24, 2005

 

Code for this article:

http://www.west-wind.com/presentations/vfpdotnetinterop/DotNetFromVFP.zip

 

Additional Visual FoxPro .Net Interop topics:

 

Advanced COM Interop:

   1. Passing objects between FoxPro and .NET COM Components
   2. Handling .NET Events in FoxPro via COM Interop
   3. Creating multi-threaded components .NET for COM Interop with Visual FoxPro
   4. Calling .NET Components from Visual FoxPro with wwDotnetBridge

 

 

 

 

 

 

Note: The code listings in this article use C# for .NET code and Visual FoxPro for COM or client code.

 

 

If you find this article useful, consider making a small donation to show your support  for this Web site and its content.

 

 

 

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:

 

  1. Tell the project to export .Net types (.Net classes) to COM
  2. 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).

 

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.

 


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):

 

  1. Go into the source code and set a breakpoint on any line of code that is called through COM
  2. Select the project in the VS.Net Solution Explorer
  3. Right click and select properties
  4. Go to Configuration Properties | Debugging
  5. Set the Debug Mode to Program
  6. Set the Start Application to your VFP executable (IDE or your own EXE)
  7. Set the Working Directory to where you want VPF to start
  8. Set any command line arguments you may need.

 

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");

}

 

If you have to return multiple object levels (ie. loObject.oData.Company) you have to drill into each object individually. For example:

 

public string CallAndReturnCustomer(object loObject)  {  

bool llResult = (bool) ComUtils.CallMethod(loObject,"Load",4)

 

Object loData = ComUtils.GetProperty(loObject,"oData")

return (string) ComUtils.GetProperty(loData,"Company");

}

 

Messy, huh? Reflection natively supports only getting a property on the current object hierarchy level so you can retrieve oData.Company with one call to GetProperty. To simplify this process there are also methods called GetPropertyEx and SetPropertyEx which support '.' syntax for property names and recursively walk the object hierarchy. So you could do something like this:

 

return (string) ComUtils.GetPropertyEx(loObject,"oData.Company");

 

Is this always necessary? It depends on whether you provide an import COM type library in the .Net project or not and whether you publish your parameter types as COM objects. If you want to be able to pass a known type that is published in the VFP code as a COM object then you can import the type library and reference that type explicitly. But if you simply pass a 'generic' object you will always have to use Reflection's InvokeMember (or the GetProperty/GetMethod shortcuts) to access the object's members.

 

Publishing types via COM is a lot of work though especially if your application isn't already a COM object of some sort – for example a standalone desktop application likely will not publish anything to COM. But you might consider the extra effort of exposing objects via COM even if you otherwise wouldn't to allow the type library to be published and have it accessible to .Net. In addition to easier type access you also gain the ability to access the type in .Net with Intellisense which is otherwise lost to you.

Working with the DataSet object

Things get even more tricky when you start dealing with the some of the complex objects that .Net itself publishes. For example consider Listing 7 that returns an ADO.Net DataSet from the SQL Server Pubs database.

 

Listing 7 (C#): Method that returns a DataSet

public System.Data.DataSet GetAuthorData(string lcID) {

   if (lcID == "" )

      lcID = "%";

 

   DataSet ds = new DataSet();

 

   string cConnection=this.cConnectionString;

   SqlConnection   oConn = new SqlConnection(cConnection);

  

   SqlDataAdapter oAdapter = new SqlDataAdapter();

   oAdapter.SelectCommand =

      new SqlCommand("select * from Authors" +

             "where au_id like '" +

              lcID + "%'",oConn);       

   oConn.Open();

   oAdapter.Fill(ds,"Authors");

   oConn.Close();

   return ds;

}

 

Recompile the DotNetCom project again and now go back into Visual FoxPro and try the following code:

 

o = CREATEOBJECT("DotNetCom.DotNetComPublisher")

loDs = o.GetAuthorData("172-32-1176")

? loDS.GetXml()

 

This code retrieves the DataSet from the Pubs database and then retrieves an XML string of the data. We've retrieved a DataSet object from .Net and are manipulating it in Visual FoxPro. You can access the methods and properties of the Dataset as you would expect. However, also notice that Intellisense is not working on the Dataset because .Net doesn't explicitly export it. This makes figuring out what properties are available of the full Dataset object tricky.

 

In addition, collection based values are not accessible in the same way as you can from inside of .Net. The following does not work in VFP:

 

? loDS.Tables["Authors"].Rows[0]["au_lname"]  && Doesn't work!!!

 

Named collections are common to native .Net objects, but I've been unable to use named collection items through VFP. You can however use them through positional collection values. The following does work:

 

? loDS.Tables.Item(0).Rows.Item(0).Item(1)

 

There may be another way to get at the collection with names but I couldn't find it. This at least allows access to the features of the DataSet even though it can be hard to maintain code like this. Whether this is useful to you or not is really quite another issue. The above code allows you basic access to the DataSet object via COM and even allows you to update the data in Fox code, but there's no way that you can update the dataset directly from the DataSet reference as you need to get to the DataAdapter. This means you have use other business object-like methods like UpdateData() in your .Net 0bject to receive the updated DataSet and then use the method's code to actually write the data back to the database. This works, but really can make for fairly convoluted logic. Unless absolutely necessary for isolated instance I would recommend you don't use the DataSet object as a parameter passed across COM. If you are still interested in this sort of interaction I've provided a sample form in the download files (COMAuthors.scx) that demonstrates how to retrieve a dataset, bind to it, modify the data and update it back using the .Net COM sample.


Another option along the same lines is to use XML to pass the data back and forth. Especially Visual FoxPro 8's XMLAdapter object could be a good fit for this as you can pass back a DataSet into a single or multiple VFP cursors then update them convert them back into UpdateGram XML and send them back to the .Net business object. The advantage is that you can use VFP cursor syntax to manipulate and bind to the data avoiding the pain of accessing the COM object. But that's a topic for separate article in the future…        

Using .Net classes that don't work with COM

Datasets expose some of their functionality but not all of them. Same is true for most other .Net components. Specifically it's very difficult or in many cases simply not supported to access components that expose collection objects.

 

There are two problems of why this is occurring: First most .Net built-in objects make extensive use of overloaded methods (same methodname – different parameters/return values). Since COM doesn't support overloaded methods only one of the methods in question can be exposed with the original name. When you export a .Net object to COM it'll expose overloaded methods with a '_2',' _3',' _4' etc. postfix. So if you have two methods with overloaded parameters for GetAuthorData() you'll end up with GetAuthorData() and GetAuthorData_2(). Unfortunately this is a function of the type library exporter not of .Net natively and most native objects that .Net exposes don't support this luxury. In this case it tends to be the method with the most parameters that makes it, which makes even easy to use .Net methods often complicated to use.

 

Second, many of the native .Net objects aren't surfaced to COM. For example, the DataSet has the ability to access collection items both by name and by index number which is basically an operator overload of the [] expression. But VFP can only use one of them in this case using indexed keys. With other classes – like the Reflection classes for example you simply cannot access the collections at all. In yet other cases properties and methods simply are not exposed to COM at all.

 

I recently needed some functionality to read .Net type information in order to import it into HTML Help Builder which is a documentation tool. Help Builder already supports importing data from COM objects and VFP classes, so adding the ability to also import from .Net components would be a useful addition. This is fairly easy with Reflection where in C# you can do something like this:

 

Assembly loAssembly;

loAssembly = Assembly.LoadFrom("d:\temp\someassembly.dll");

 

foreach (TypeInfo loType in loAssmbly.GetTypes()) {

    MessageBox.Show(loType.Name);

    Foreach (MethodInfo loMethod in loType.GetMethods() {

          MessageBox(loMethod.Name);

}

}

 

You can loop through all the type info including methods, properties, parameters, return values etc. It's very powerful and a signature feature of the type system in .Net, where you can get complete type information about any object at runtime. It would have been nice if I could simply return this info to VFP directly by simply creating a COM exposed method that returns a .Net assembly:

 

public Assembly GetAssembly(string lcFilename) {

  Assembly loAssembly;

  loAssembly = Assembly.LoadFrom(lcFilename);

  return loAssembly;

}

 

You can indeed receive this assembly inside of VFP with the following code:

 

loParse = CREATEOBJECT("wwReflection.TypeParser")

loAssembly = oParse.GetAssembly("D:\temp\wwscripting.dll")

loTypes = loAssembly.GetTypes()
? loTypes.Length

? loTypes.item[0].Name

 

You even get Intellisense on the Assembly object returned. But unfortunately there's where the features end. The Types object is only partially accessible and you can't call further methods like GetMethods() to retrieve further info from it. What works and doesn't is further complicated because Intellisense doesn't work on the returned object.

 

What to do, if you really need this functionality? I decided that I needed to create a set of wrapper classes that are properly exposed via COM. The base object TypeParser creates sub-objects that somewhat mimic the format of the Reflection classes with some built in formatting into a format that Help Builder expects for type information. I won't divulge this lengthy code here, but I'll show the class interfaces for these objects to give you an idea of how this works in Figures 4 taken from the VS class browser. The code is included with the source for this article if you're curious what the implementation looks like or if you have a need to parse .Net types via COM.

 

Figure 4 – The wrapper interface for the wwReflection classes that expose the most common information of Reflection to COM. This is an application specific interface that performs additional tasks such as parsing XML documentation files from within the C# code.

 

Besides exposing this interface to COM which makes it accessible to Visual FoxPro, creating the wrappers also allowed me to perform post processing of the info that Reflection provides into a format that Help Builder expects. For example, parameters are returned as a parameter array as well as an easier to use parameter string (eg. cParm1 as String, lParm2 as bool). The code can also parse an optional C# XML Documentation file that can be generated from the inline code comments when compiling a project in Visual Studio.Net.

 

Ultimately it wasn't much more work to create this wrapper class in C# as it would have been to access the Reflection classes in VFP had they worked. I would have had to write this sort of fix up code anyway. By writing the code in C# I created a class interface that matched the various import routines from other sources such as VCX and COM classes which resulted in reusing much of the import code without changes. So, although it may seem frustrating that you can't access some objects directly in .Net it's often possible to get the functionality you need. Writing wrappers doesn't have to be an 'extra step' in your development process if you build it with application specific features in mind to start with.

Publishing Events from .NET for use in VFP

Another aspect that’s not easily addressed via .NET COM Interop is publishing events from .NET components and consuming them in Visual FoxPro. As we’ve seen above, .NET exposes objects to COM by wrappering them into a Com Callable Wrapper that acts as a proxy object to your main .NET class. Events on .NET objects are essentially delegates – function pointers – that point at a potentially hooked method that will receive a notification when the event is fired. In .NET this process works by creating a delegate that points at a method in your code and hooking this delegate to the event.

 

For example, if you have an event called SendMailMessages you can hook up a delegate that points at an implementation method that is then called when the event fires:

 

this.SendMailMessages += new EventHandler( this.OnSendMailMessages );

 

EventHandler is a delegate and this.OnSendMailMessages is a method in the class that is called in response.

 

If you open a .NET COM component in the Object browser you’ll find that the Typelibrary contains Add_ and Remove_ methods for the event – but no actual event handlers. These methods are meant to allow you to attach and remove and event delegate, which is useless to us in Visual FoxPro (but might be workable in C++).

 

COM handles events very differently than .NET through Connection Point interfaces. Connection point interfaces are special event handling interfaces that are connected to an object that fires events and are then used to ‘sink’ the fired events.

 

In Visual FoxPro you can handle COM events using the EVENTHANDLER() function. For example, to implement events of the Internet Explorer Application object you might do:

 

oIE = CREATEOBJECT("InternetExplorer.Application")

  

oIEEvents = CREATEOBJECT("WebBrowserEvents")

EVENTHANDLER(oIE,oIEEvents)

 

 

DEFINE CLASS WebBrowserEvents AS session OLEPUBLIC

   IMPLEMENTS DWebBrowserEvents2 IN "SHDOCVW.DLL"

 

   PROCEDURE DWebBrowserEvents2_StatusTextChange(Text AS STRING) AS VOID;

             HELPSTRING "Statusbar text changed."

   ENDPROC

 

     … All other methods of this interface must be implemented

ENDDEFINE

 

You have a base object (oIE) and you have a connection point object (oIEEvents) and you use the EVENTHANDLER() function to tie the two together. Now when IE fires an event the event is routed to our custom class that implements the event interface. So when you navigate to a new page you might fire DWebBrowserEvents2_BeforeNavigate2.

 

By default .NET is not able to route events using Connection Point interfaces because the delegate based model doesn’t map directly to the Connection Point model without some overhead. However, there’s a way to make a .NET component expose an object via Connection points, but it’s not automatic and requires that you write some extra code. Specifically you have to mark up the .NET class with an attribute and create the event interface explicitly.

 

Let’s look at an example. In your samples there’s a wwSMTP class that sends email. This class fires a couple of events SendMailMessages and SendMailMessagesObject. Both events are used to communicate with a calling application and provide minimal progress messages such as Mail Sending Started, Message Sent or Message Send operation failed etc.

 

The basic signature for the relevant class methods looks like this:

 

Listing 7.1: An SMTP Mail client that fires a couple of simple events

[ClassInterface(ClassInterfaceType.AutoDual)]

[ComSourceInterfaces( typeof(IwwSmtpEvents) )]

[ProgId("DotNetCom.wwSmtp")]

public class wwSmtp

{

… other implementation detail left out

 

/// <summary>

    /// Message Event that is fired as an email message is sent

    /// </summary>

    public event delSendMailMessages SendMailMessages;

    public delegate void delSendMailMessages(SendMailMessageModes Mode,

                                               string Message);

               

    /// <summary>

    /// Another version that accepts an object

    /// </summary>

    public event delSendMailMessagesObject SendMailMessagesObject;

    public delegate void delSendMailMessagesObject(SendMailEventArgs e);

 

    /// <summary>

    /// Sends a message with events firing SendMailMessagesEvents

    /// </summary>

    public void SendMailWithEvents()

    {

          // *** Create EventArgs to pass back to caller

                SendMailEventArgs SMArgs = new SendMailEventArgs();

                bool Result = false;

 

                Result = this.Connect();

                if (!Result)

                {

                      if (this.SendMailMessages != null)

                      {

                            // *** Send a Failure Message

                            SMArgs.Mode = SendMailMessageModes.SendMailFailed;

                            SMArgs.Message = "Couldn't connect to server." +

                                              this.ErrorMsg;

                            this.SendMailMessages(SMArgs.Mode,SMArgs.Message);

                            this.SendMailMessagesObject(SMArgs);

                      }

                      return;

                }

               

                if (this.SendMailMessages != null)

                {

                      SMArgs.Message = "Starting Message Send Operation";

                      this.SendMailMessages(SMArgs.Mode,SMArgs.Message);

                      this.SendMailMessagesObject(SMArgs);

                }

 

                Result = this.SendMessage();

 

                this.Close();

 

                if ( !Result )

                {

                      if (this.SendMailMessages != null)

                      {

                            SMArgs.Mode = SendMailMessageModes.SendMailFailed;

                            SMArgs.Message = "Message Sending failed: " +

                                             this.ErrorMsg;

                            this.SendMailMessages(SMArgs.Mode,SMArgs.Message);

                            this.SendMailMessagesObject(SMArgs);

                      }

                      return;

                }

 

                // *** Code inserted to exaggerate the time it takes to send email

                Thread.Sleep(5000);

 

                /// Check whether we have handlers

                if (this.SendMailMessages != null)

                {

                      SMArgs.Mode = SendMailMessageModes.SendMailCompleted;

                      SMArgs.Message = "Message sending completed with no errors";

                      this.SendMailMessages(SMArgs.Mode,SMArgs.Message);

                      this.SendMailMessagesObject(SMArgs);

                }

 

                return;           …

    }

}

 

If you look at the code in SendMailWithEvents you’ll see that the method fires events as part of the Sendmail process by making calls to the event itself with it appropriate delegate function signature (defined in delSendMailMessages above):

 

this.SendMailMessages(SMArgs.Mode,SMArgs.Message);

 

which fires an event if one is hooked up.

 

If I compile this class and call this code from VFP I might do something like this:

 

LOCAL loSMTP as DotNetCom.wwSmtp

loSMTP = CREATEOBJECT("DotNetCom.wwSMTP")

 

loSMTP.MailServer="mail.myserver.net"

loSMTP.ServerPort = 2525

loSMTP.Username = "rickstrahl"

loSMTP.Password = GetSystemPassword()

 

loSMTP.SenderEmail = "rstrahl@west-wind.com"

loSMTP.SenderName = "Rick Strahl"

 

loSMTP.Recipient = "rickstrahl@hotmail.com"

loSMTP.Subject = ".NET Test Email"

loSMTP.Message = "Email from .NET at FoxPro DevCon"

 

loSmtp.SendMailWithEvents()

 

you will find that the message gets sent just fine, but there are no events that are fired. This makes perfect sense, because we haven’t hooked up any events.

 

To make .NET export COM events you have to add an attributed to the class:

 

[ComSourceInterfaces( typeof(IwwSmtpEvents) )]

 

You specify an event source interface by specifying the interface that will be used to act as the event source. Note that the type you specify must exist as the compiler fixes up the sourcing of the event interface. So the next step then is to actually implement this event interface explicitly in .NET by implementing each of the events we want to expose.

 

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

public interface IwwSmtpEvents

{

    [DispId(1)] void SendMailMessages(SendMailMessageModes Mode,string Message);    [DispId(2)] void SendMailMessagesObject(SendMailEventArgs e);

}

 

These event methods should match the actual events and event delegate signatures that the main class defines. In a way you can think of this interface as overlaying the main class’ events and getting called when the events are called.

 

.NET internally manages hooking up the events and routing them when a COM client connects to these interfaces. Let’s see how we can capture these events in Visual FoxPro. We start by implementing the event interface. You can create this interface by opening the Object Browser in Visual FoxPro, pointing at the TypeLibrary for your compiled assembly and selecting the Event interface. You can then drag and drop the interface into a VFP PRG file and it will automatically implement each of the methods as empty methods.

 

 

Figure 4.1: The published event interface displayed in the VFP Object browser. You can use the EVENTHANDLER() function to connect to this event interface.

 

Notice that the wwSMTP class implements this event interface which means that events fired from wwSMTP are routed into this event interface.

 

If you implement this interface in VFP it looks like this:

 

Listing 7.2: Implementing an event interface in VFP.

DEFINE CLASS IwwSmtpEvents AS session OLEPUBLIC

   IMPLEMENTS IwwSmtpEvents IN "DotNetCom.wwSMTP"   && Changed from hardcode path

 

   PROCEDURE IwwSmtpEvents_SendMailMessages(Mode as Integer, Message as String) ;

             AS VOID

       ? "SendMailMessages Event:"

       ? Mode

       ? Message

   ENDPROC

   PROCEDURE IwwSmtpEvents_SendMailMessagesObject(EventArgs as

                                        DotNetCom.SendMailEventArgs) AS VOID

      ? "SendMailMessagesObject Event:"

      ? EventArgs    && Object

      ? EventArgs.Message

      ? EventArgs.Mode

   ENDPROC

ENDDEFINE

 

To make the events actually fire change the code to call the SendMailRunWithEvents to:

 

loEvents = CREATEOBJECT("IwwSmtpEvents")

EVENTHANDLER(loSmtp,loEvents)

  

loSmtp.SendMailRunWithEvents()

 

Now when you run this code you should see the events firing and writing output to the screen as each step is performed.

 

So to recap: Event handling for .NET COM objects is not explicit and you need to decorate the object in question with the [ComSourceInterfaces( typeof(IwwSmtpEvents) )] attribute and you have to implement an interface that matches any events that you want to publish. This means that event publishing is something that must be explicitly performed at design time – it’s not something that you can hook up to an existing object later on at runtime.

 

If you have a third part .NET object that fires events but that you don’t have a source code for, and you want to publish events for you can’t do it directly through this interface. You can however create a small wrapper object that simply republishes the events of the base object by connecting to the original object’s events and then forwarding them to its own internal events that you can publish with the mechanism described here. Messy but doable and not a lot of work either.

Multi-threading in .NET

The Email class is a good example of an implementation that could benefit from asynchronous operation. It would be really useful to send an email without having to wait for completion since the process can take a few seconds locking up the main application while waiting from the mail to submit to the mail server. Since we’re already publishing events of the process it would make sense that we should allow to perform this mail sending task in the background – on another thread.

 

There are a number of different ways that you can fire off new threads in .NET, all of them very easy. I’ll show briefly how to create a new thread and call the method we used above. Let’s create an asynchronous version of the SendMailWithEvents method:

 

Listing 7.3: Sending an email on a new thread is easy!

using System.Threading;


 

public void SendMailAsyncWithEvents()

{

    ThreadStart delSendMail = new ThreadStart(this.SendMailWithEvents);

    Thread myThread = new Thread(delSendMail);

    myThread.Start();

 

    /// If you want to use the Thread Pool you can just use a delegate

    /// delSendMail.BeginInvoke(null,null);

}

 

Creating a brand new thread in .NET is childishly simple. You can use the ThreadStart() delegate to point at a target function that gets fired when the new thread starts. The ThreadStart() delegate is a function pointer a method that has no input parameters and no return values – void input and void output which means of course that you can’t pass any data to the thread method. Since our SendMailWithEvents() method matches this signature we can call this method directly. No parameters? How does it know what to send?

 

.NET is truly multi-threaded so even though we start the method on a new thread, the new thread still has access to the current wwSMTP object and therefore can read the properties of this object which are needed to send the email. If your actual work methods have parameters you might have to create a wrapper method that accepts no parameters and then passes the appropriate parameters extracted from the class interface or configuration settings to the actual worker method.

 

There are other ways that you can create a new thread as well – you can use a Delegate which runs off the internal .NET Threadpool and can be more efficient especially for quick tasks that don’t tie up threads. This is always a worry with the .NET Threadpool because the pool is limited to a default of 25 threads that can only be changed with a configuration switch on the application.

 

Ok. Go ahead and change the wwSmtp calling code in Visual FoxPro to:

 

loEvents = CREATEOBJECT("IwwSmtpEvents")

EVENTHANDLER(loSmtp,loEvents)

 

*loSmtp.SendMailWithEvents()

loSMTP.SendMailAsyncWithEvents()

 

You should now see your code return immediately to you after the SendMailAsyncWithEvents() call. Your cursor should return to the Fox command window and the events now show up on the desktop window in the background – courtesy of the separate thread running in the background in .NET.

 

Think how easy that was, and what the possibilities are for this sort of functionality. It really opens the possibilities for adding multi-threaded code to your Visual FoxPro applications. It’s even possible to handle operations from the threads in Visual FoxPro by firing events back into Visual FoxPro.

Automating .NET Windows Forms – a special case

.NET doesn’t support building EXE COM servers so it’s not as easy as it was in say Visual FoxPro or VB to simply create a COM public form and have it exposed as a COM object and fire away at the methods and properties.

 

While .NET can’t create COM Exes it can create Windows Forms in DLLs and you can actually access these forms directly if you compile your DLL to be COM creatable (Register for COM Interop):

 

[ClassInterface(ClassInterfaceType.AutoDual)]

[ProgId("WinFormCom.Test")]

public class WinFormCom : System.Windows.Forms.Form

 

The key here is that you have to stick this form into a DLL project, not into the EXE where it can’t be directly instantiated via COM. So what I did is create a second project for the WinForms app (Windows Control Project) and added the Windows Form to be automated into this project. This is the project that needs to be marked for COM interop (Register for COM Interop in the Build Properties of the project).

 

Once that’s done you can now instantiate your WinForm directly. From VFP:

 

o = CREATEOBJECT("WinformCom.Test")

o.Show()

 

This works fine if all you want to do is display the form MODALLY and run it. The problem is that you can’t run the form in the background this way, because the Show() method blocks the call when no event loop (Application.Run() and the like) is present.

 

If you want the form to run and keep active while control returns to your client COM application, you will have to resort to using a new thread in the server and starting a full event loop on that new thread. That way the form will run independently of a reference that you’ve created and you get a reference to the form passed back to you.

 

To do this you need two new methods on your form (actually I’d move these off to a controller COM object but for demonstration these methods are on the form).

 

Listing 7.4: Controlling a Windows Form Asynchronously

private WinFormCom LiveForm = null;

 

public WinFormCom RunForm()

{

    // Create instance on this thread so

    // we can pass it back to caller

    this.LiveForm = new WinFormCom();

 

    Thread t = new Thread( new ThreadStart( this.RunThread ));

    t.ApartmentState = ApartmentState.STA;  // IMPORTANT!

    t.Start();

 

    return this.LiveForm;

}

 

public void RunThread()

{

    // Show the form

    this.LiveForm.Show();

    Application.Run();

}

 

Application.Run() is sort of like a READ EVENTS in FoxPro as it sets up an event loop. You don’t want to run this event loop on the COM thread because like READ EVENTS Application.Run() will block indefinitely.

 

Note that you need an instance field for the LiveForm so that we have a persistent reference that the thread routine can work with and we can pass back to Visual FoxPro.

 

You can now run the Windows form from the COM client like this:

 

o = CREATEOBJECT("WinformCom.Test")

loForm = o.RunForm()

loForm.Top = 130

loForm.Left = 330

 

and watch the form move. You can switch back to the form while your code in the VFP interface runs simultaneously (or your sitting at the command prompt).

 

Voila, you can now access the WinForm directly via COM. Note that you can’t get at the controls of the form directly because controls are defined private by default. If you need access to the contained controls you will need to expose them yourself with wrapper methods or by implementing a FindControl() decorator style method.

 

A couple of things that you need to be aware of as well. You need be very careful about object lifetime in this scenario. Killing the top level reference will not automatically kill the factory created form reference. So the proper sequence of getting rid of the form is:

 

loForm.Close()

loForm = null

o = null

 

But even that will not really clean up everything. If you use Task Manager you can watch the number of threads created in the application. As you call RunForm() each time the thread count goes up. But when you close the form the thread count does not go away. The reason for this is that we called Application.Run() without a parameter, without a specific form it is watching for termination. So we need to explicitly tell the application to stop processing the message loop with this code in the form:

 

private void WinFormCom_Closing(object sender, System.ComponentModel.CancelEventArgs e)

{

      Application.ExitThread();

}

 

This will shut down the message loop and properly clean up if the above sequence is used.

 

Summing up, yeah it’s a bit more work to call a Windows Form from a COM client application, but it’s definitely possible. Note that if you already have an EXE and all of your WinForms are compiled into it, you can easily expose those forms through a wrapper DLL. The DLL can then simply set a reference to the EXE and expose forms using this same approach mentioned above using a thread method.

Installing .Net components

Ok, you've decided that you want to utilize .Net and you've integrated the code into your app. But you're not even done yet. You need to still install this component on client machines. This process can be painful if you don't use a .Net capable installer or if you build an application that can work with or without the .Net COM components.

 

First you need to decide whether you want to publish your component as a private component or one that goes into the Global Assembly Cache (GAC). If you chose the latter you have to sign the control with a private key and you will have to use a tool that can install the component into the GAC in addition to registering it later. If you choose a private component that is accessible from your application directory/path, you can simply use RegAsm /CODEBASE to register the component for COM.

 

If you use a .Net capable installer for your VFP application like the Visual Studio Install project you can easily register .Net COM components, but you will have to figure out exactly what VFP files you need to ship to get the runtime and support files registered. For .Net COM components you can select the vsdraCOMRelativePath setting to register a private .Net COM component, vsdraCOM registers a component that's installed in the GAC as a global component. Functionally there's little difference between these modes except that private components require the RelativePath setting (equivalence for the /CODEBASE attribute of RegAsm).

 

One problem you may run into here if you build an install for a machine that uses .Net Components conditionally. For example, Help Builder works without .Net and simply can't use the features of the .Net type importer if .Net is not installed. Make sure that you mark your .Net COM component as non-essential in the install otherwise the setup will fail. Your mileage with all of this will vary depending on the installer you use. InstallShield Express for VFP 7 for example, can't do any of this.

 

The other option to a .Net capable installer is to manually register .Net COM components. This is not as easy as it may seem because first of all you need to detect whether .Net is installed and then where you can find RegAsm.exe to perform the registration process on your own. Listing 8 shows a function that provides the tools you need to check for .Net and find the framework path:

 

Listing 8 (VFP RegisterComponent.prg): Finding the .Net version and framework path

************************************************************************

* wwUtils :: IsDotNet

****************************************

***  Function: Returns whether .Net is installed

***            Optionally returns the framework path and version

***            of the highest installed version.

************************************************************************

FUNCTION IsDotNet(lcFrameworkPath,lcVersion)

LOCAL loAPI as wwAPI

 

lcVersion = ""

lcFrameworkPath = ""

 

loAPI = CREATEOBJECT("wwAPI")

lcWinDir = loAPI.getSystemdir(.t.)

lcVersion = loAPI.Readregistrystring(HKEY_LOCAL_MACHINE,"Software\Microsoft\ASP.Net","RootVer")

IF ISNULL(lcFrameworkpath)

   RETURN .F.

ENDIF

 

lnAt = AT(".",lcVersion,3)

IF lnAt > 0

   lcVersion = SUBSTR(lcVersion,1,lnAt) + "0"

ENDIF

 

lcFrameworkPath = loAPI.Readregistrystring(HKEY_LOCAL_MACHINE,"Software\Microsoft\ASP.Net\"+lcVersion,"PATH")

IF ISNULL(lcFrameworkPath)

   lcFrameworkPath = ""

ELSE

   lcFrameworkPath = ADDBS(lcFrameworkPath)

ENDIF  

 

RETURN .T.

*  wwUtils :: IsDotNet

 

Listing 9 is a more high level function that uses IsDotNet to actually check for and register .Net COM component. This function checks to see if .Net is installed and if it isn't calls RegAsm to register the component. After it's done it checks to see if the COM component now exists and returns the result from that request.

 

Listing 9 (VFP RegisterComponent.prg): Registering a .Net Component

************************************************************************

* RegisterDotNetComponent

****************************************

***  Function: Registers a .Net COM component using RegAsm

***            by retrieving the runtime directory and executing

***            RegAsm from there.

***      Pass: lcDotNetDll - The .Net assembly to register

***            lcProgId    - One of the ProgIds to register

***                          used to check if the component registered

***                          "UNREGISTER" to unregister a component

***            @lcError    - Pass by reference in order to get error info

************************************************************************

FUNCTION  RegisterDotNetComponent(lcDotNetDLL as String, lcProgId as String,;

                                  lcError as String)  ;

                                  as Boolean

LOCAL lcFrameworkPath, lcVersion

 

IF lcProgId = "UNREGISTER"

   llUnregister = .T.

   lcProgId = ""

ELSE

   llUnregister = .F.

ENDIF

 

*** if the object is already registered exit

IF !EMPTY(lcProgId) AND ISCOMOBJECT(lcProgId)

   lcError = ""

   RETURN .T.

ENDIF

 

*** Try to register

lcFrameworkPath = ""

 

IF !IsDotNet(@lcFrameworkPath)

   lcError = "DotNet Framework not installed or path not found."

   RETURN .F.

ENDIF

 

lcRun = ShortPath(ADDBS(lcFrameworkPath) + "regasm.exe")

IF EMPTY(lcRun)  && File doesn't exist

   lcError = "Couldn't find RegAsm.exe at:" + CHR(13) +;

           lcFrameworkPath + "regasm.exe"

   RETURN .F.

ENDIF

 

IF llUnregister

   lcRun = lcRun + [ "] +  ShortPath(FULLPATH(lcDotNetDll)) + [" /unregister]

ELSE

   lcRun = lcRun + [ "] +  ShortPath(FULLPATH(lcDotNetDll)) + [" /codebase]

ENDIF

_cliptext = lcRun

 

WAIT WINDOW "Hang on. Trying to register " + ;

            JUSTFNAME(lcDotNetDLL) + " ..." + CHR(13) +;

            "This may take a few seconds..." NOWAIT

 

*** Long command lines must run 'windows console'

IF LEN(lcRun) > 115

      RUN /n &lcRun

      *** Wait long enough to start runtime and register

      *** this can be really slow so allow enough time

      INKEY(20)  

ELSE

      RUN &lcRun

ENDIF     

 

WAIT CLEAR

 

IF EMPTY(lcProgId)

   RETURN .T.

ENDIF

 

llResult =  IsComObject(lcProgId)

IF !llResult

   lcError = "Registration of wwReflection.dll failed." + CHR(13) + CHR(13) + ;

             "The command line has been pasted into your ClipBoard"

ENDIF

 

RETURN llResult

* RegisterDotNetComponent

 

This allows you dynamically register the component at runtime on a need only basis which can be useful. This code includes a number of helper functions which can be found in the source file and in the wwApi library included in the samples.

 

Another issue that you need to consider when installing components is compatibility with specific versions of the .Net runtime. Any signed assembly is tied to a specific version of the .Net runtime unless specifically set to run on later versions. This means that just having the runtime may not be enough to allow your component to run – it may have to be a specific version. Private components do not have this problem, so for components that specifically apply to your application private components seem a better choice regardless what Microsoft's documentation says about COM components being better deployed as public GAC components.

Do you .Net?

That is the question. When building VFP applications you really need to think carefully of whether you want to integrate with .Net technology because it can become a pain to get the technology installed at this point. Once .Net becomes more ingrained in the operating system itself the choices to utilize it will be an easier one as the infrastructure will be in place.

 

Web applications too run in a controlled environment and there it's easy to install .Net and utilize components even if they have to be manually installed. As more and more apps are migrating to the Web server this is one place where the deployment issues are not paramount.

 

From a VFP developer perspective, .Net offers a number of opportunities to extend the functionality of your toolkit significantly without having to leave Visual FoxPro behind. It offers a great opportunity to gradually move into .Net and get a feel for what this environment has to offer…

  


Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Web and distributed application development and tools with focus on Windows Server Products, Visual FoxPro, .NET and Visual Studio. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for Visual FoxPro and West Wind HTML Help Builder. He's also a Microsoft Most Valuable Professional, and a frequent contributor to magazines and books. He is co-publisher and co-editor of CoDe magazine, and his book, "Internet Applications with Visual FoxPro 6.0", is published by Hentzenwerke Publishing. For more information please visit: http://www.west-wind.com/ or contact Rick at rstrahl@west-wind.com.

 

 

 

 

 

If you find this article useful, consider making a small donation to show your support  for this Web site and its content.

 

 

  White Papers                  Home |  White Papers  |  Message Board |  Search |  Products |  Purchase | News |