Now that .NET is here you've undoubtedly had the urge to use or at least play with the new functionality that the platform provides. Unfortunately migrating to .NET from Visual FoxPro (or most other development languages) is a big step that requires a steep learning curve. Integration between the old and the new will be crucial as a first step to provide for the ramp up time that's needed to get up to speed on the new platform as well as providing vital links between old and new applications. In this article Rick looks at the most common ways that you can use to integrate logic and data between Visual FoxPro and .NET.
Like any new development platform or environment, .NET introduces a brand new environment and many new concepts for developing applications. To us as developers this usually means that there's a large learning curve involved in understanding the new platform, finding the the most efficient ways of performing common tasks and working around the limitations of the new environment. Although Microsoft will be pushing hard to convert as many developers to .NET as quickly as possibility, it's obvious that the transition to an all .NET world will take a fair amount of time to accomplish.
For this reason, it's vital that there are mechanisms readily available to share non-managed .NET code with managed .NET code and vice versa. If you've been reading the trade press, you've undoubtedly heard that Web Services are one of the biggest features of .NET and one of the big selling points of Web Services of course is the fact that they can be accessed by many different kinds of development environments both old and new. However, Web Services are only one of the options available for interoperating and probably the least likely to be useful for extending the life of old applications.
As in previous updates of development environments data access through standard data access components make it possible to directly use data from various different data sources including Visual FoxPro. Visual FoxPro 7 sports a new OleDb driver which improves significantly on some of the limitations of the VFP ODBC driver and makes it a capable provider for accessing data directly from .NET applications even if there are a number of features that are useful for .NET that are missing.
Prior to .NET, COM has been the primary interop mechanism that applications could use to communicate amongst each other and .NET provides extensive support for COM. You can both access COM components from .NET and expose .NET components through COM through its Runtime Callable Wrapper (RCW) which provides a COM wrapper around the .NET runtime.
I'll go over each of these approaches and in the process point out some issues that you need to watch out for. As all tools geared toward providing backwards compatibility they rarely provide all the functionality that was provided by the original implementations, so some things require special attention or changes to properly work in a .NET interop environment.
Although this article discusses Interop mechanisms in general I'll focus more specifically on ASP.NET as this is likely the most used platform of .NET technology at this point. Keep in mind that the same principles apply to Windows Forms or even Console applications. ASP.NET however introduces some additional considerations because it is a server based technology and requires special considerations for performance and scalability. So I'll use ASP.NET in this article as my vehicle to describe most of these technologies.
Accessing VFP Data with OleDb This hardly qualifies as an eye opening topic, but OleDB support in Visual FoxPro 7.0 has been significantly updated over the existing ODBC implementation that makes it more realistic to use VFP data via a generic driver, especially in an ASP.NET Web environment.
By using the OleDb driver you have the ability to use ADO.NET and the DataSet style implementation given some limitations that I'll discuss later on. Performance of the driver is adequate although as we'll see still a bit of a bottleneck compared to native performance using pure Visual FoxPro code or code loaded from COM objects. The new OleDb provider is also multi-threaded and can run multiple queries simultaneous, which was probably the biggest problem with the ODBC driver when used in ASP and now ASP.NET applications.
To demonstrate the basic principles of OleDb access against a Fox data source I'll build a small and simplistic sample ASP.NET Web Form applet that lets you browse and edit entries in the TASTRADE sample database shipped with Visual FoxPro.
File Access requirements for ASP.NET If you plan to use ASP.NET to access VFP via OleDb you have to make sure that your data directories are properly configured to allow the ASPNET user full access to your data paths. ASP.NET runs under this user by default although you can change the actual account in Web.config if you choose. Whatever account you use this account must be able to have full NTFS access rights in the data directories where data is read from and written to. You can use Explorer and the Security tab to accomplish this.
Simple DataSet retrieval But before I do this let me just give you a small example that demonstrates the basics of creating a DataSet with VFP data on an ASP.NET form. This example consists of a very simple ASP.NET page that contains only two controls: a label to show error information (lblErrorMsg) and a DataGrid (oCustList) to display the data from a query against VFP data. The following code is the CodeBehind page that uses a separate GetCustomerList() method on the form to perform the data retrieval into a DataSet (Listing 1).
As you can see it takes a fair amount of code in the GetCustomerList() method to do this, but the code is pretty straight forward. You start with a connection object that is passed an OleDb connection string that points at the VFP OleDb driver and a path to a DBC or a free table directory (if you don't include a DBC tables are treated as free tables).
Next a DataAdapter is created. The DataAdapter is the 'data retrieval' object. It knows how to talk to the data source and return the data in a specified format. The adapter is specific to the backend used, just as the connection is. Hence the class name is OleDbDataAdapter. If you go against SQL Server you can use the SqlDataAdapter instead, which is the SQL Server specific implementation that bypasses OleDb. With VFP you have to use the OleDb adapter and connection objects.
The adapter acts as the intermediary that's used to query the data from the database and return a result to the application. Natively a DataAdapter returns a DataReader, which is basically a stream of forward only data. Using the DataReader to retrieve the data is the most efficient way to get the data if you only need to display data and don't need any special filtering or jump through the data.
The DataReader stream is also used internally by the DataAdapter and can be used to create other objects which can populate their own data control objects. One of these and probably the most important one in .NET is the DataSet. In this case I'm using the DataAdapter's Fill() method to 'fill' the data into a DataSet object. Notice that the DataSet is not prefixed by OleDb or Sql. This is because a DataSet is a generic object that is completely disconnected from the parent datasource. Think of the DataSet as a container for multiple cursors - DataSets can run and hold results from multiple queries simultaneously. So when a new query is executed you need to tell the DataAdapter what name to give the table created into the DataSet.
Once a DataSet has been created you can access its data directly either using programmatic access or by databinding to a data aware control. Many of the user interface controls in .NET have intrinsic support for DataSets. Here I'm using a DataGrid to bind the data and simply display a simple list of the data in an HTML table through the DataGrid Server Control object (oCustList) by using:
this.oCustList.DataSource = this.oDS.Tables["CustomerList"];
DataSets are made up of Tables, Rows and Columns (and a few other things like Relations and Constraints). Here I'm binding to a table in the DataSet. Tables inside of a DataSet have an automatic data view that represents a layout that determines how the table renders into the DataGrid. The default iew is very basic and displays all the fields in the table in a grid format with the field names as headers. The result is very raw, but useful for quickly seeing the data displayed (Figure 1). To customize the view you have to set properties on the DataGrid either in the <asp:DataGrid> tag or by setting properties on the object. I'm not going into the details of the datagrid here but rather focus on the data access issues.
You can also access the data directly via code. A DataSet contains a collection of Tables, which in turn contains a collection of Rows. The Rows then contain the individual fields. The following snippet demonstrates:
DataRow loRow = this.oDS.Tables["CustomerList"].Rows[0]; // first data row int lnRowCount = this.oDS.Tables["CustomerList"].RowCount;
string lcCompany = loRow["company"].ToString(); decimal MaxOrdAmt = (decimal) loRow["maxordamt"];
Note that you have to cast the data items to a specific type as the actual fields are objects. You can use ToString() on strings, but you'll need to use casts for most other datatypes.
Take 2: A little more generic The code above is fine, but it gets repetitive in a hurry, so rather than write it each time it's a good idea to isolate some of the data access code into a separate business object layer. This is overly simplistic for this article's purposes, but demonstrates the basics of what it takes to simplify data access and separate the business logic from the ASP.NET page.
The following example creates a small ASP.NET application that displays a list of customers and lets you browse through the list and view and edit each of the customers. You can also add new customers. I'll demonstrate querying, updating and finally adding a customer using a stored procedure in this example all using the simple business object template.
To do this I use a very simplified business object called tasCustomer that's subclassed from aBusObj, which is a very minimal helper business object class that simplifies some of the queries and update operations.
 Figure 1 - This customer form browses and allows editing of customers from the TasTrade sample application.
This Web Form performs two separate data tasks: Retrieval of the list displayed and the update operations on the individual customers.
Since this is ASP.NET I'll use DataSets for these operations and also take advantage of the fact that the form's viewstate can persist the list for me without going back to the database. In essence the form loads the list of customers only once, and then persists the list as part of the form's viewstate that is passed back and forth over the wire. This view state also includes settings of the list such as the currently selected item(s). This works well for smallish lists like this one, but I wouldn't recommend this same approach for long lists as you are essentially trading bandwidth for server resources against a database operation. If you don't use ViewState you have to manually retrieve the data each time for the list, and then set the selected item based on the form variable returned from the list (Request.Form("oCustomerList")).
Ok, let's see how we retrieve this data using the VFP OleDb driver. As I mentioned I'm using a simple business object implementation to help with my work here. In the ASP.NET form (CustomerForm.aspx) the first thing I do is run an Execute to return the list if the form is not in PostBack mode. PostBack mode for an ASP.NET form means that the form is not being loaded for the first time and returning values from the form - in other words the user has clicked a button. The first time through the form will not be in PostBack mode and we'll have to run the query. To do so I use in the form's Page_Load event method (Listing 2 from CustomerForm.aspx.cs).
This code fires off in the Page_Load of the ASP.NET page and then calls the LoadCustomerList() method to actually perform the data retrieval and binding of data tied to the list. The Execute method on the business object handles running the query. The business object provides several methods such as Open() and Execute() that remove some of the code that is required in setting up a DataSet and returns the actual DataSet as an object member oDS. oCustomer.oDS holds the result and as you can see in the code above the "customer" result table is then bound to the list box. Both a display field and a key field are bound - we'll use the key field later on to retrieve a specific customer.
The key methods of the base business object are set up as shown in Listing 3 (from tasCustomer.cs).
The Open() and Close() metods basically deal with setting up the connection and making sure that the connection strings are valid. The Execute() method then runs the specified query and creates a cursor in the business object's DataSet object. If you'll recall a DataSet can contain more than a single table, so if you run multiple queries they can all be stored in a single dataset. If you have multiple business objects they can also potentially share the same DataSet object reference. Note that Execute tries to remove the table name before creating the result so that if a cursor by the same name exists already it's overwritten - otherwise an exception would occur.
With this code in place running a query then becomes as easy as calling the Execute method with a SQL statement. In this case the SQL statement retrieves the customer list, and then assigns the result table to the listbox of the TasTrade customer form.
Note that all the methods in the aBusObj class are virtual, which means they can be overridden by the implementation classes. For example, the Open method is overridden in the tasCustomer class to set properties on the VFP OleDb session. In order to do this you have to actually run a command against the VFP data source using language commands such as SET EXCLUSIVE OFF, SET DELETED ON etc (Listing 3.1 from tasCustomer.cs).
Databinding record data fields Once the list has been loaded the next step is to retrieve an individual customer for display. To accomplish this task I use ASP.NET databinding, which is somewhat limited if you compare it to databinding in desktop applications. Keep in mind ASP.NET databinding is really a one way affair meant only to bind data for display - to read data back you have to explicitly extract the data from the controls that it's held in.
To do the databinding I use a combination of the Server control expressions (<%# %> syntax) and code in the ShowCustomer method. ShowCustomer() get fired from the form's SelectedIndex_Changed event method (Listing 4 from CustomerForm.aspx.cs).
The business object LoadCustomer method is fired which does little more than run an Execute and retrieve the single record using the existing business object (Listing 5 from tasCustomer.cs).
This single record table row is then assigned to the dtrCurrent property of the form, which in turn is databound to the ASP.NET server controls.
DataBinding in .NET to textbox controls is one way which means that data is bound for display only. This makes sense given the stateless nature of Web requests and display and update happening usually in the same request.
Databinding to text box controls is accomplished by setting the Value tag of the ASP.NET control in the ASP.NET display page. For example the Company field is bound as follows in CustomerForm.aspx:
<asp:TextBox id="txtCompany" runat="server" Text='<%# this.dtrCurrent["company"] %>'> </asp:TextBox>
Note that databinding is not automatic - it doesn't occur until you explicitly call the DataBind method of the individual control or the Page object. Above the ShowCustomer() method calls the DataBind() method of the page (this.DataBind())to force all the controls to bind. This is good too, because of the way that the form works. First the list loads, and no items are available yet. An item is loaded only after the list is loaded and the first item can be selected.
This is important to understand as you can't bind to a datasource that is not loaded yet or else the page will fail. ASP.NET gives you full control on when the actual data is bound while allowing you to set the binding expression any time. Hey, that's a request I've had for VFP forms forever and it actually works here.
Updating the data As I mentioned above ASP.NET data is not really databinding in the traditional sense because it's only one way. So in order to update data you have to read the values explicitly from the controls and back into the underlying data source. Here I read the data back into the DataRow object after pressing the save button (Listing 6 from CustomerForm.aspx.cs).
The code then defers to the business object to save the data back to the VFP data source.
Up to now our VFP datasource has had no issues in interaction with ADO.NET. Unfortunately for any kind of data updates the VFP OleDb provider has a few missing features that make an otherwise simple process a lot more code intensive than it is say with a SQL Server datasource.
ADO.NET supports the concept of database schemas being retrieved along with the data when a query is run. So, normally when you run a query against a datasource the Primary key info is retrieved. This makes it possible to automatically update a datasource because the DataAdapter can use the schema information to decide how to update the DataSource based on the key info.
Unfortunately the VFP OleDb provider in its current incarnation does not support this information and you're required to manually create INSERT and UPDATE statements. The following work with SQL Server, but not with VFP. With SQL Server you can do the following:
OleDbDataAdapter oDSAdapter = new OleDbDataAdapter("select * from customer", this.oConn);
/// This builds the Update/InsertCommands for the data adapter OleDbCommandBuilder oCmdBuilder = new OleDbCommandBuilder(oDSAdapter);
lnRows = oDSAdapter.Update(this.oDS,"Customer");
The OleDbCommandBuilder is an object that can automatically build SQL INSERT/UPDATE/DELETE commands if the datasource supports schema information. But this doesn't work with the VFP OleDb driver as this information is not provided.
To work around this you have to manually assign the INSERT/UPDATE/DELETE statements. The following example demonstrates the UPDATE command (limited to a couple of fields).
string lcSQL = "UPDATE customer SET company='" + oRow["company"].ToString() + "' " + "where cust_id='" +oRow["cust_id"]+ "'";
oDSAdapter.UpdateCommand = new OleDbCommand(lcSQL,this.oConn);
Once that's done the oDSAdapter.Update() method call succeeds.
Of course if you have to go through all the trouble of updating the UPDATE command manually you might as well just execute the UPDATE command manually with less code without requiring the DataSet to be directly involved at all:
string lcSQL = "UPDATE customer SET company='" + oRow["company"].ToString() + "' " + "where cust_id='" +oRow["cust_id"]+ "'";
OleDbCommand oCommand = new OleDbCommand(lcSQL,this.oConn); oCommand.ExecuteNonQuery();
Schema retrieval functionality is scheduled to be provided in the next version of the VFP OleDb provider, but currently we're stuck with having to manually write our own UPDATE/INSERT statements. Putting all of this together the SaveCustomer method looks like Listing 6.1 (tasCustomer.cs).
Using Stored Procedures As a last issue here I want to demonstrate calling a Stored Procedure in a VFP DBC to perform the Insert operation for a new customer. Again, standard syntax that's supported with SQL Server doesn't work with the VFP OleDb provider at this time, so you need to use explicit syntax to call the stored procedure.
I kept this method to only a few field values to keep these code snippets short enough for display - you'd probably want to add all the remaining fields. The VFP Stored Procedure looks like this added to the TasTrade sample data (testdata.dbc):
When you click on the Add button of the ASP.NET form the currently active data is stored to the database by calling the AddCustomer method of the business object.
private void cmdAdd_Click(object sender, System.EventArgs e) { if (!this.oCustomer.AddCustomer(this.txtCompany.Text,this.txtContact.Text, Convert.ToDecimal( this.txtCredit.Text)) ) { this.lblErrorMessage.Text = this.oCustomer.cErrormsg; } }
AddCustomer in the business object then calls the stored procedure explicitly (Listing 7 - tasCustomer.cs).
I ran into some problems here with this code that worked fine in VFP, but didn't at first with .NET due to the NULL requirements. The OleDb provider string doesn't support provider options so the only way to set these particular settings is to actually run a command against the connection. To do this I overrode the Open() method of the aBusObj class to do the following (tasCustomer.cs):
Interestingly enough I also ran into a major bug in the driver before I added the code above. The INSERT from the stored procedure would fail with an error: Field TITLE cannot contain Nulls. The Command's ExecuteNonQuery() would throw an exception indicating that the stored procedure call failed, yet when I checked the table I ended up with a new record anyway! This issue appears to be specific to this NULL error as other errors like violating the Primary key rule did not cause this behavior. Microsoft is aware of this issue and looking into it.
OleDb Summary The VFP OleDb provider makes it possible to access Visual FoxPro data through the .NET environment but in its current implementation it doesn't provide all the functionality that ADO.NET offers. This doesn't exclude VFP from .NET integration, but it does make it more code intensive to use VFP data than say SQL Server or even Access (Jet) data. Plan on writing SQL statements by hand as opposed to using ADO.NET's auto-update features at least in this release of the driver. Microsoft has indicated that they are planning to support more of the .NET specific features in future versions of the driver - they are aware of some of the limitations and are working to address them at this time so we can look forward to fuller .NET support.
COM Interoperability
For many years the main call from Microsoft has been to build component based applications based on the Component Object Model (COM) by creating components in your language of choice and then exposing those components or classes as COM objects to the operating system.
.NET bucks this trend by using a whole new mechanism of interoperability via the Common Language Runtime which provides intermediary byte code that is compatible with multiple languages that can output .NET capable byte code that is compiled into executable binaries on the fly by the runtime. What this means is that the days for binary incompatibility are gone. As are some of the issues that have plagued the COM infrastructure - namely the issue of versioning and DLL Hell.
However, COM remains very important even with .NET both as a backwards compatibility feature as well as a mechanism to provide interop with the runtime as a whole. For example, many of the scalability features of the Windows operating system are still implemented using the COM+ system. .NET itself also uses COM+ underneath to implement many of the Enterprise features like distributed transaction management, context and process management and many security features. What's exciting here is though that the intricacies of COM+ are mostly hidden behind more user friendly .NET classes that expose the functionality through class interfaces or attribute based descriptors.
However, COM usage in .NET for the developer is played way down and the focus in .NET is on building .NET classes that perform functionality rather than calling out to 'legacy' COM components to perform business logic or other tasks. COM interop is provided more as a backward compatibility feature than a 'moving forward' feature. But because there's a large, large investment in COM by many organizations, COM support in .NET is fairly strong.
The good news is that you can access most COM components in .NET without any fuss. It isn't quite as easy as it was say in Visual FoxPro/Visual Basic with late binding and CreateObject(), but with a little bit more work COM interop is easily accomplished. The not so good news though is that there's a performance penalty for using COM objects in .NET resulting in reduced performance when compared to previous COM based technologies. It's clear that the focus of .NET is not necessarily to provide the best hosting environment to COM components but rather to provide a mechanism to allow applications to co-exist until they can be rebuilt with .NET.
COM interop in .NET is a two way affair - you can call COM components from .NET and you can call .NET components from classic COM capable applications. Let's look at each of the approaches, how they work and what they offer to the developer.
Calling COM components from .NET This mechanism is likely to be the most commonly used functionality of COM interop as it allows you to integrate existing functionality into new applications that are being built or migrated to the .NET platform.
This process uses internal Interop classes in the .NET framework to link and bind to COM components. The standard process to accomplish this is to import the COM component which creates a compiled wrapper .NET class that implements the COM objects interfaces as a .NET class.
Because .NET is a type safe environment this .NET interface is necessary to allow access to the class methods and properties. This satisfies the compiler for type safety through mapping all the COM properties/parameters/return types to the appropriate .NET types. This process is similar to early binding in traditional COM client, but yet it is somewhat different. Behind the scenes the wrapper class makes the appropriate Interop class calls to call the COM component and then maps the parameters and return values to the wrapper class.
Those Interop classes can also be accessed directly via code to provide the equivalent of Late Binding, but be advised that this can take a lot of code to do. It's similar to using C++ and the IDispatch COM interface to query information about a class and then calling the method signature with generic parameters that need to be translated and typecast into the proper typesafe values that the compiler can deal with. This code can be messy and I wouldn't recommend it for anything but objects that aren't properly described inside of their respective type libraries (more on this later).
To demonstrate let's create a simple object that I've used in the past for ASP COM interop and walk through creating the object in VFP and then making it available in .NET to an ASP.NET page/application. The class is very simple and doesn't do anything fancy - the purpose here is to demonstrate the operation of the interop mechanism not to show what you can do with it. I'll leave that for some future article <g>.
I'll create a COM object here and show a few methods at a time. To review the basics of creating a COM object, I'll start with a simple object that serves as a Counter manager that stores and increases named counter values. The first step is to create the object as a COM capable class (listing 9 - asptools.prg).
Remember that's it's real easy to create a COM object in VFP simply by setting the OLEPUBLIC attribute on the DEFINE CLASS statement of a PRG based class or by setting the OLE Public checkbox on the class property settings of a VCX based class. However, it's equally important to set up your COM object properly to run in the hosted environment, especially if that hosted environment happens to be a service such as Internet Information Server (IIS). The INIT method of the object sets the environment and saves some state information that preserves the startup location of the application and set's the path to it so we can find the data located there.
To create this class as a COM object, add this class to a project (I named mine ASPDemos) and COMPILE it into a Multi-Threaded COM Server or use BUILD MTDLL AspDemos from AspDemos.
Once this is done you can now use the object from VFP with:
oDemo = CREATEOBJECT("AspDemos.AspTools") ? o.IncCounter("CodeDemo") && 1 ? o.IncCounter("CodeDemo") && 2 ? o.IncCounter("EssentialFoxDemo") && 1 ? o.IncCounter("CodeDemo") && 3
For most of you this is nothing new. But note that if you are building COM objects for use with .NET there is at least one major difference: You MUST use VFP 7.0 and it's typing features to describe your parameter and return value types, or else your methods will not be accessible properly.
Importing the COM object into .NET Ok, once your object is built you need to import it into .NET. The easiest way to do this is to use Visual Studio.NET and the References selection in the Project manager. Right click on the References tree item and Add Reference. Then pick the COM tab and select your object from the list, or if it's not registered yet, browse to it on disk (Figure 2).
 Figure 2 - Importing a COM object into .NET is as easy as selecting the object from the installed COM components or physically selecting a typelibrary/dll from disk. This process creates a .NET wrapper class for your COM object that is treated as any other .NET object.
Once the object has been added to your project you can access it pretty much like any other .NET object. For ASP.NET pages you'll have to do a couple of additional things to get it to run properly. Since I'm using VS.NET the actual ASP.NET page contains nothing more than the page header, while all the logic goes into a codebehind class. The main thing on the ASPX page header is the PAGE directive:
<%@ Page ASPCOMPAT=true language="c#" Codebehind="ComInterop.aspx.cs" AutoEventWireup="false" Inherits="ASPInterOp.ComInterop" %>
This tells the CLR that the actual code that runs on this page is stored in COMInterop.aspx.cs. This is where I'll put the code to demonstrate the use of the COM object I created above.
There's another very important directive in the PAGE tag above! The ASPCOMPAT=true attribute tells the ASP.NET page that it is hosting COM components that don't conform to the ASP.NET threading model. Visual FoxPro (and Visual Basic 6) COM objects are Single Threaded Apartment (STA) objects, while .NET natively runs ASP.NET requests on MultiThreaded Appartment (MTA) threads. This requires the .NET framework to make some changes into how requests are handled by essentially switching the request into STA compatible mode. I'll talk more about this a little later in relation to how this affects performance.
ASPCOMPAT also provides compatibility to classic the ASP COM object environment by providing the appropriate Context objects that were supported to retrieve the ASP intrinsic objects before. In particular the ObjectContext object provider which uses the COM+ IApplicationContext object interface is provided to COM objects which would otherwise not be available. I'll come back to this in a bit as well.
The code behind page then contains all the logic of the page with the ASPX page only containing the display elements. I want to keep things simple so I created a simple form that lets you increment counters, delete them and show them all. The final running form is shown in Figure 3.
 Figure 3 - The COM Counter sample demonstrates using a basic COM object in a Web form.
The actual page is made up of a textbox (txtCounterName), a label (lblCounter), a set of operational buttons (btnIncrement, btnDelete, btnShowAll) and an empty label control that is filled from the COM object - in this case with an HTML string that shows the table you see in Figure 3. There's also a label between the table and the buttons called lblErrorMsg that is used for status information. For example, when you delete a counter this label is updated with a message that says that the counter was deleted.
Let's start with how the basic incrementation mechanism works. Here's the Page Load and Increment button click code (Listing 10 - ComInterop.aspx.cs).
Top of Form
Bottom of Form
Ok, let's look at what happens here. First make sure the aspdemos namespace which represents the imported COM object, is added to your class so that you can access the class. With the namespace and reference added our VFP class behaves just like any other .NET class including full access to Intellisense (Figure 4).
 Figure4 - Once imported your COM object looks like any other .NET object with full access to Intellisense.
It's important that you define your methods with proper Type information using VFP 7.0's parameter typing.
FUNCTION IncCounter(lcCounter as String, lnSetValue as Integer) as Integer
If you don't provide the type information, the method will not import properly and show up without parameters and return values - and will fail at runtime when called. (Note: as of .NET Framework SP1 this has been fixed - methods with variants will import with object parameters, which still are a pain to use so make sure you use typed parameters/returns whenever possible (ie. all the time!!!)).
Properties names are translated to upper case so nCounter becomes NCOUNTER. Remember C# is case-sensitive so this makes a big difference. Better yet, always use Intellisense to provide you with the proper property names.
Note that all properties are returned as type Object unless you specifically type them with VFP's _ComAttrib functionality part of the class definition. Type object is similar but not quite the same as a Variant in COM. Object types can contain different sub types (such as strings, ints etc), but they are cannot directly be accessed by their sub-type. In order to use the variable safely in C# you have to explicitly cast it in many cases:
int lnCounter = (int) oVFP.NCOUNTER + 10; this.txtCounterProperty.Text = lnCounter.ToString();
If you use the object variable multiple times it's probably more efficient to store it into a properly typed variable first, then use it as you intend to. It's both easier to work with and faster in execution.
Changing Code in your COM object When it comes time to make a change to the COM object, things are very similar to the way it was with classic ASP - in order to compile the COM object you need to restart the Web Service or at least the Web application that hosts the COM component.
The easiest way to do this for me is to run the IISRESTART utility. And for this purpose I use an Intellisense script (tied to the IIS key sequence) that simply does:
RUN /n4 IISRESET
IISRESET is a IIS 5.0 and later utility that has a number of options for restarting the Web server. It can restart all the services or with the proper options any of the specific services. By the way, it can also be used to reboot a machine reliably. The nice thing about running IISRESET from Fox is that it runs in a separate window that starts and finishes when it's done so there's no more user interaction required.
While the reset is working let's add another method to our server to delete a counter (Listing 11 - asptools.prg).
Then rebuild your server with BUILD MTDLL AspDemos from ASPDemos. To hook up this method I'll edit the visual ASPX page add the btnDelete button and add the following event code to the click event:
Before you add this code you might want to quickly recompile the project (Ctrl-Shift-B) to force the AspDemos type library code change to reflect in the .NET class and in the Intellisense list. Once you recompile the DeleteCounter() method is available, otherwise it is not. This means that VS.NET rebuilds your COM wrapper every time you make a change to it, which is convenient. But realize that a compile is required to make this happen - changing the class and not recompiling in .NET will likely result in a runtime error or COM version conflict (depending on if and how the COM interface has changed).
This is different from classic ASP - the COM object in .NET is early bound which means it's bound at compile time through the wrapper class. Any changes to the COM object require a recompile, so making a change to the COM object always require two steps:
Change and recompile the VFP COM object
Recompile the .NET COM client application
Types of data to return I think you will find that if you want to use ASP.NET efficiently, you probably want to use COM objects as business objects as opposed to an output generator. The two examples I've shown so far are - albeit very simple - business object methods that return business data directly back to your application.
You may also want to return display output back to the ASP page. For example, you may run a query and based on this query generate HTML in your VFP code. While I don't think this is the best of ideas for ASP.NET Web Form applications, you can do it very easily as the following code demonstrates.
Start by creating another method on your COM object that returns in this case the list of counters (Listing 13 - asptools.prg):
This method runs a query and then calls a method in the sample called ShowCursor() which generically generates an HTML table from the data. This HTML string is then returned back to the client.
In the .NET form add a blank label control and call it lblCounterList. We then assign the control the HTML string that is returned from the ShowCounters() call to the COM object.
Now, if you run this form you'll notice something interesting if you're not familiar with Web forms. After you click Show All once the list pops up and even if you click one of the other buttons, the list stays on the form. But also notice that the value of the counters don't change in the list even though you may have clicked Increment a few times and the actual counter values has been upped.
What's happening here is something called Viewstate. Viewstate holds the content of server variables - including labels - in an encoded, hidden form variable that is posted back to the server. So the HTML string is compressed and sent back to the server decoded there and then redisplayed. What this means is that the list that you see if you don't click on the Show All button is a static string rather than a live result retrieved from the COM object. It gets re-posted from its previous form state. As such it gets to be out of sync in this scenario.
There are two ways to solve this issue. You can either check to see if the list is active and if so always reload it from the COM object. Or, probably more efficiently, turn off the Viewstate for the label control and show the list only when the user explicitly asks to see it by clicking on the Show All button which is the approach I took here.
This is a good example of how Viewstate affects your ASP.NET Web forms and data displayed in it - you will find you may have to write a fair amount of code to properly handle data especially if you're dealing with data intensive controls/fields on a form and deciding on whether to support Viewstate on these controls for ease of use or ending up sending a ton of data over the wire to persist the state. A trade off you have to work out for your specific scenario.
Returning non COM objects One important area is that of returning objects back through COM interop and .NET. Specifically if you want to return objects that aren't defined in the type library of the COM object you are publishing. I do this quite frequently with code shown in Figure 15 (asptools.prg).
In classic ASP or a COM capable client you can simply do this:
Set oVFP = Server.CreateObject("aspdemos.asptools") Set loCustomer = oVFP.GetCustObject("4") Response.Write(loCustomer.COMPANY)
But this doesn't work in ASP.NET because the SCATTER NAME object is not defined in the Type library that was imported by VS.NET to create the class wrapper. .NET has no idea how to reference the returned object directly.
So how do we do this? The pointer returned is an IDispatch object pointer and we have to access this object indirectly now because .NET can't do the automatic mapping as VBScipt/Jscript ASP was able to do. Unfortunately, this is a bit more work in .NET (Figure 16 - ComInterop.aspx.cs).
Messy, huh? Unfortunately this is the only way to retrieve values that aren't explicitly available in the type library. As you might expect this mechanism is fairly slow as well, as .NET must look up the type info on the object before retrieving the data. To do so it uses Reflection, which is .NET's extensive type discovery and parsing mechanism. In order to use the above code make sure you add the
using System.Reflection;
namespace to your source code.
You can simplify the above code a little by using a wrapper method like this (Listing 17 - ComInterop.aspx.cs).
private object GetProperty(object loObject,string lcProperty)
{
return loObject.GetType().InvokeMember(
lcProperty,BindingFlags.GetProperty,null,loObject,null);
}
which reduces the application code to:
this.lblErrorMsg.Text = (string) this.GetProperty(loCustomer,"COMPANY");
which is considerably more user friendly.
Calling methods dynamically works the same way. The AspTools class includes a method called ReturnObject which can return any object available in the project, whether it's defined as a COM object or not. Let's say you return a full VFP object that isn't a COM object. Listing 18 shows a simulated business object class that simply loads a customer object into an oData member using a Load method (Listing 18 - asptools.prg).
We can now use our COM object to return a reference to this class with the ReturnObject method of the ASPTools class:
If you now compile the project you can do the following from the VFP command window:
oDear = CREATEOBJECT("aspdemos.asptools") oCust = oDear.ReturnObject("aspCustomer") ? oCust.Load("4") ? oCust.oData.Company ? oCust.oData.Careof
We're dynamically creating a VFP object returning it over COM and then call a method on this object, which in turn sets the oData member with the actual values for the retrieved customer.
With .NET we have to use the Reflection interface to perform all these tasks. Let's start by looking at how to make the method call:
The method call returns a value or true or false which is stored into the label. As with the property retrieval the Reflection functionality is used to dynamically access the COM object and call the method. This time we need to specify a parameter list in an object array which is the last parameter shown here. The single parameter here is a string of "4". As with the property retrieval this code is messy and hard to remember so I created a wrapper method called CallMethod:
This code relies on C#'s ability to pass variable parameters to a method using a parameter collection that can be parsed at runtime. It takes the object name and method call to be used for the InvokeMember() call and then builds a new object array with all remaining arguments which are the actual parameters to the method call.
Using those two helper methods lets look at how we can handle the Fox code from above in .NET (Listing 22 - ComInterop.aspx.cs).
Ah, much better. Less code and much more readable and you might actually remember this syntax. As you can see using this sort of logic you can drill down into any kind of object that returns data dynamically but you will have to know its exact interface - there won't be any Intellisense to help you out. This applies not just to FoxPro objects returned as non-COM objects, but also to many system type objects that Windows publishes such as ADSI (Active Directory) and WMI (Windows Management Instrumentation) which publish their provider specific interfaces dynamically without support in type libraries. As long as you know what the names of properties and methods are you can retrieve the data easily.
Note that there a number of other invocation modes availalable including the ability to read 'fields' (as opposed to properties) and set property/field values both on .NET and COM objects dynamically. All this is possible through the Reflection interface that .NET publishes and this provides powerful runtime support for type discovery.
Passing ASP.NET objects to VFP COM objects If you've used COM with classic ASP you might recall that ASP used to publish a couple of interfaces that made it possible to access the ASP intrinsic objects such as Request, Response, Server, Application and Session etc. directly from within VFP code. With ASP.NET this functionality is still supported through the ASPCOMPAT directive on an ASP page.
The IScriptingContext interface no longer works. IScriptingContext in classic ASP caused two methods - OnStartPage and OnEndPage - to fire when an ASP request occurred. OnStartPage automatically passed Context object as a parameter. The Context object provides the ability to retrieve the various intrinsic objects. This interface no longer appears to work, even though the ASP.NET docs state that it should work (no word on whether this is a bug in .NET or VFP).
More recently Microsoft recommended using the ObjectContext interface instead of IScriptingContext, and this interface properly works with VFP COM objects. The ObjectContext class provides the same functionality as the IScriptingContext Context container object in addition to provide COM+ services and transaction management. The ObjectContext object is based and uses the COM+ services to provide the functionality to VFP.
To access the object in VFP and retrieve ASP object you can use the following method of the AspTools class example as a reference (Listing 23 - asptools.prg).
To call this from ASP.NET simply use the code in Listing 23.1 (AspObjects.aspx).
if (Request.Form["btnSubmit"] != null )
ASPToolsClass oVFP = new ASPToolsClass();
The code in the page checks to see to only access the Fox server if the form was submitted, then calls the method and echos back the request information in HTML format as generated by the Fox server.
For those of you familiar with classic ASP and COM note that there are a few subtle differences in behavior of the various objects provided. For example, you can no longer access the various collections without parameters. For example, Request.QueryString() used to return the full querystring in ASP, but fails in ASP.NET. Basically any access of a collection item that doesn't exist returns a NULL rather than a null string (""), which means you may need additional error handling if you can't guarantee that a value is actually available. Otherwise things like this:
Request.Form("txtUnPostedVar").Item()
Will fail with a data type error as the Item method doesn't exist on a null reference.
Furthermore be aware that if you use the Response object in combination with ASP.NET Web forms that the results may not be exactly what you expect. Web Forms do not use the standard Response stream and place text only after the entire page has rendered. So if you use Response.Write before the page has rendered the output from it will likely end up at the very top of the page before the <html> tag of the ASP.NET page. Using Response.Write from within VFP thus only makes sense if the COM object will generate the entire HTTP output, or if the calling ASP.NET page also uses Response output rather than the Web Forms engine (ie. no Server Controls).
Passing DataSets as XML between VFP and .NET As described earlier.NET uses a new data access metaphor in the ADO.NET architecture by utilizing DataSets and XML to pass data around applications. For VFP applications this means that the easiest way to pass data to .NET often is in the form of XML. VFP's CURSORTOXML() is capable of exporting data in a format that .NET understands and can import into a DataSet fairly easily. Consider the following method returning a cursor in XML format to the ASP.NET page(Listing 23.1 - asptools.prg).
To use this data in .NET in a DataSet you can load a new DataSet object from the returned XML using code like the following (Listing 23.2 - ComInterop.aspx.cs).
The ReadXml method of the DataSet can read XML returned from VFP and import into a DataTable with all of the type information intact. For example, if you returned the MaxOrdAmt field you can check the type with:
this.lblErrorMsg.Text = loDS.Tables[0].Rows[0]["MaxOrdAmt"].GetType().Name;
which returns 'decimal' which is .NET's translation for the currency type in VFP.
What about passing data the other way around? From .NET to VFP? I'll show you how to pass a DataSet from .NET to VFP and manipulate the data directly out of the DataSet a little later in the 'Calling .NET components from VFP section'. This process works, but can be tricky to work with because you have to know the exact interfaces of the DataSet and the exact layout of the data.
You can also pass XML as a string back to the VFP application. Unfortunately VFP has no built in way to utilize this XML directly using say XMLTOCURSOR(). There are a couple of problems with turning DataSets into VFP data - the schema information of the datasets do not necessarily contain all the data needed to create a VFP cursor from the data, so a parser would have to make two passes through the data to create the field sizes. This can create problems with field sizing and has potential performance problems.
One way that you can import DataSets in VFP from XML is to use wwXML with some manual intervention. Consider the following ASP.NET page that creates a DataSet from the SQL Server Pubs database then passes the DataSet to VFP via an XML string (Listing 23.3 - ComInterop.aspx.cs).
The code in the VFP COM object uses the XML to create a cursor, and then just to demonstrate that it got the data returns it as an HTML table. You can use wwXML (included with the samples) which has a method called DataSetXMLToCursor to create a VFP cursor from an individual DataTable contained within a DataSet. Because of the limitations of the schema published by a DataSet, wwXML requires that you pre-create the cursor that the data is imported to (Listing 23.4 - asptools.prg).
Note the CREATE CURSOR command to pre-create the table that you're importing to. wwXML uses the structure of the table to import the XML data into the cursor thus avoiding some of the schema limitations. But this limitation also means that you have to know what type of data you're expecting to import beforehand.
Debugging your COM server Debugging in .Net is just as complicated as it is in ASP classic as the COM object is a compiled application that runs inside of IIS, so by default you can't debug the COM server. However, with a little trickery you can actually debug your components by using the VFP IDE to instantiate the object for you and return it to the ASP.Net page. This idea was originally from Maurice de Beijer who posted an interesting article on how to pass control to VFP via Accessibility objects ( http://www.theproblemsolver.nl/aspdevelopmentanddebugging/index.htm).
I had problems with this approach and found a slightly different, but similar way to accomplish the same thing by adding a property and a couple of methods to my COM server:
This approach basically instantiates an instance of the Visual FoxPro IDE as a COM object and creates an instance of the object and returns that instance back to the ASP.Net page.
To access this functionality from .Net you'd use code like this:
Voila - you can debug your VFP COM server in real time. Set a breakpoint in any of the methods called and the debugger will pop up. Note that you will not be able to directly access the new reference and have to use the indirect method calling mechanisms described earlier like CallMethod() and GetProperty() if you are using C# or VB.Net with Option Strict. The reason for this is that the ASPToolsClass is a wrapper for the actual COM object, but not the COM object itself. This means the COM object can't be cast to an ASPToolsClass object and you need to use indirect method and property access.
However, with this in place you can now set breakpoints in your code and the debugger will pop up for you as needed. Any failures too will pop up an error dialog in the VFP IDE and you can examine the state of the application in the debugger. While not optimal because of the indirect object reference requirement for you object, this should nevertheless be a big help in debugging COM components. FWIW, this process would be easier in Visual Basic which can deal with untyped COM objects when Option Strict is off.
This works in classic ASP as well, and is in fact a little easier.
<% SET oVFP = Server.CreateObject("aspdemos.asptools") set loVFP = oVFP.GetDebugInstance Response.Write( loVFP.IncCounter("EssentialFox",0) ) oVFP.CloseDebugInstance() %>
Here you can call methods directly and you can simply rename your main server reference to something else and assign the debug object to the object var instead which means you can debug by simply adding the GetDebugInstance() and CloseDebugInstance() calls.
ASP.NET and VFP COM object performance ASP.NET changes the mechanism of how COM objects are accessed considerably compared to classic ASP. As a result COM interop from ASP.NET tends to be somewhat slower than it was with classic ASP.
The main difference is that ASP.NET uses the Multi-Threaded Apartment (MTA) model to run internal components, while classic ASP uses the Single Threaded Apartment (STA) model. Visual FoxPro (and Visual Basic) COM objects are STA components - each object instance runs on a single thread and must always be accessed on the same thread that created it. MTA components can run on any thread and be invoked from different threads, because they are assumed to be thread safe. The MTA provides for potentially higher performance as the system has to do much less work to fire a method of an object as it doesn't have to keep track of which thread created the component. The MTA model is a big reason for the improved performance of ASP.NET as a Web backend engine in combination with the compiled nature of the code that it runs.
Natively ASP.NET runs in the MTA model and in order to run VFP/VB components the ASPCOMPAT page directive is required to switch the page in STA model operation. This has several implications. First pages that use ASPCOMPAT must be managed differently than pages that are running in MTA - the system once again must track threads and make sure components run on the proper threads that they were created on. This switch affects performance of the individual page as well as the overall ASP system while any pages that require ASPCOMPAT are active.
In addition, ASP.NET is optimized for managed code and calling out into unmanaged COM components requires some overhead.
All of this is to say that running COM components in ASP.NET is going to be slower than running them in classic ASP applications. I ran a few tests on the IncCounter example I previously mentioned. To keep things simple I only used Response.Write() output code when I tested the performance with roughly identical ASP and ASPX pages. I tried the component both with and without registering it under COM+. To test I ran Visual Studio Test Center, created a test script with just a sinlge page in it (the ASP or ASPX page). I then ran through 5 sets of 5 minute tests against a single URL and took the average value from these tests (which were very consistent BTW). The following Test Machine was used: Pentium III 1ghz, 512 megs memory Windows 2000 Advanced Server SP2). I also made sure that the ASP.NET application was re-added to the .NET project and recompiled each time the component was moved in and out of COM+. The ASP .NET application also was set to Release mode and the configuration settings with debug settings turned off - IOW, optimized for production testing (incidentally not doing so as I did when I ran the first few tests didn't affect performance all that much - less than 2% on these test).
I kept the tests really simple to measure mainly the COM overhead. The classic ASP code is just:
<% SET oVFP = Server.CreateObject("aspdemos.asptools") Response.Write( oVFP.IncCounter("EssentialFox",0) ) %>
The ASP.NET code is:
private void Page_Load(object sender, System.EventArgs e) { ASPToolsClass oVFP = new ASPToolsClass(); Response.Write( oVFP.IncCounter("EssentialFox",0) ); }
Here are the results:
Classic ASP
ASP.NET
Plain COM
118 rps
99 rps
COM+ Component
61 rps
67 rps
Notice that classic ASP is about 20% faster for plain operation. It's interesting that the COM+ component access is considerably slower both for classic ASP and ASP.NET so much so that the overhead probably removes the actual performance differences between Classic and ASP.NET. In fact with COM+ components .NET is actually slightly faster.
The ASP code here is very simple - it simply instantiates the object and does a Response.Write() on the result from IncCounter(). This demonstrates basically the call overhead of the methods plus a basic data access operation in the VFP code. The numbers change drastically if you now add a few calls to these methods
<% SET oVFP = Server.CreateObject("aspdemos.asptools") Response.Write( oVFP.IncCounter("EssentialFox",0) ) Response.Write( oVFP.IncCounter("EssentialFox1",0) ) Response.Write( oVFP.IncCounter("EssentialFox2",0) ) Response.Write( oVFP.IncCounter("EssentialFox3",0) ) Response.Write( oVFP.IncCounter("EssentialFox4",0) ) %>
Classic ASP
ASP.NET
118 rps
84 rps
COM+ Component
48 rps
58 rps
Notice how the performance of classic ASP stays almost stable even with the additional COM calls- it appears that most of the overhead is in the creation of the object with very little overhead for additional method calls. Calling methods is fairly fast and doesn't affect performance much. With ASP.NET however, repeated COM calls cause additional overhead as code is crossing the managed to unmanaged boundaries on each COM method call or property access performance drops significantly for every added COM call.
But look at the COM+ numbers. In all scenarios running a COM+ component seems to slow down performance significantly and the difference between ASP and ASP.NET actually switched to an advantage for .NET. But performance stays very steady regardless of accesses to the component and in fact ASP.NET is faster with COM+ components.
Regardless, of the slower numbers with .NET keep in mind that the even the lower numbers are rather acceptable in terms of performance! If you can sustain even the slowest 40 requests a second on a single server is still close to 3.5 million hits in 24 hour period, which is impressive for the test hardware it's running (midrange desktop machine w/o fully optimized hardware).
But regardless it's important to understand that there is a performance penalty to pay when using ASP.NET over classic ASP. In fact, if you're building an application that's primarily using COM objects to perform their business logic or even have applications that serve all your HTML content from within a COM object (some backend engines use ASP as a Web front end) classic ASP may actually be a much better choice for performance reasons. Comparing plain COM operation with lots of method calls to COM components you can see that performance can multiple times faster with classic ASP.
Keep in mind that this test mainly tests for COM call overhead not the actual speed of the code running. As you run requests that run more code inside the VFP component the call overhead becomes less important. But if you're accessing lots of properties or short quick method calls (which often is typical for business object access) you will see performance issues similar to this test.
Accessing .NET objects through COM
In the last section I showed you how you can integrate COM objects into .NET - now let's look at going the other way by creating objects or accessing native .NET objects from within a COM client application like Visual FoxPro.
Do you need .NET from your VFP apps? Before you do this, you should think hard about your reasons for doing so. .NET provides many new features in an easy to access mechanism, but realize that you can probably access that same functionality through other non-.NET mechanisms as well. If your only goal is to access some feature that .NET provides you're probably better of skipping it. The other scenario of course is true interop between two applications or operation between an application upgrade that is gradually being moved to .NET.
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 installedYou have to make sure that the framework is installed and if your application is distributed it will have to probably provide the 25meg or so runtime 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. Hardware requirementsThe .NET framework requires fairly heft hardware to run on especially memory. Typical .NET components will incur about 30 meg memory footprint for the first one loaded (which is the runtime, plus .NET services, plus your actual components footprint. Additional components or EXEs will have smaller footprints.
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. The point is that by including .NET with your VFP application, you're essentially requiring two separate development runtimes to be installed and in some cases it may actually be better to go all the way to .NET or stick completely with VFP/Win32/COM.
Publishing a .Net component to COM As with COM interop from .Net using .Net objects from VFP appears to be a simple matter of creating a class and publishing the class as a COM object. However, there's more to it than this especially if you are planning to pass native .Net objects back to VFP. The type differences between COM and .Net make it difficult and sometimes not possible to access all the features that the .Net framework offers in your VFP applications.
This means that in many cases you may not be able to directly publish your .Net business or system classes into COM, but rather require wrappers that expose the functionality in a COM compatible way. I'll give an example of this concept a little later.
But let's start with a cool example that I had use for on my Web site: A small component to create thumbnail images from existing image files as shown in Listing 24.
CreateThumbnail() takes input and output filenames and the size for the thumbnail to be created. Rather than sizing the image absolutely and distorting the image this method tries to maintain the aspect ratio by figuring out which side to reduce the passed parameter. So if the picture is wider the width value is used if it is higher the Height value is used. The bulk of the code deals with calculating this ratio and the right size for the image to create. The actual image conversion is actually handled natively through the .Net framework by using the CreateThumbnailImage method of the bitmap or image objects which 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.
I'm using a couple attributes on the class to explicitly force several options of how the COM class interface is published. If these two options were left out defaults would be used. The ClassInterfaceType.AutoDual 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 usage, 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 essential to have types exported into COM. In addition you need to force the .Net framework to actually emit the type library. When this code compiles it really only creates a standard .Net class. If you're using Visual Studio.Net you can simply specify that you want your project to be compiled as a COM class (Figure 5).
 Figure 5 - 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.
If you don't use VS.Net you can the RegAsm.exe utility to perform this same functionality. Both mechanisms create a type library for all the types defined in your project and create the appropriate registry entries.
The class attributes in the source code determine how the additional proxies used by the .Net COM runtime are invoke 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. Other keys then define the location of the actual DLL that the generic .Net COM object runtime must load and invoke. Figure 6 shows what the registry entry looks like.
 Figure 6 - The registry entry for the .Net COM component points at a generic runtime COM dll - mscoree.dll, rather than your DLL. The COM runtime loads the actual assembly and sets up the appropriate proxies.
Returning and accessing Objects from .Net You can also return complex data like objects from .Net. Let's add another method to the class called GetImageInfo that returns the Height and Width and Screen Resolution of an image by returning an object as shown in Listing 25:
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; }
Now recompile the class in VS.Net. The first thing that you'll notice is that you won't be able to do this if VFP is still running. Even if you released the object in Visual FoxPro the object will remain locked in memory.
According to Microsoft the COM Callable Wrapper that instantiates COM objects will never release the objects that have been loaded by unless the Interop layer explicitly forces these objects to unload. This needs to be done on a low COM level using a specific interface that .Net components can publish IDisposable. However, this cannot be directly accessed through VFP so there's no way to unload .Net COM objects at this time. No way except shutting down Visual FoxPro that is. For what it's worth the same is true of Visual Basic and other COM clients. This may be addressed in future versions but it is an issue now. Note that if you load a .Net COM object you incur the memory hit of the .Net runtime (about 3 megs) once. Any subsequent .Net objects loaded will have very little memory impact.
Ok, so shut down VFP and start her back up. Then try this code:
o = CREATEOBJECT("DotNetCom.wwImaging")
|