Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Debugging a FoxPro COM Object under ASP.NET


:P
On this page:

Some time ago I posted some code that you could use with ASP.NET to debug COM objects somewhat interactively in Visual FoxPro. The old approach used an old trick to instantiate VisualFoxPro.Application from within IIS and then actually launching code to instantiate an object created inside of VFP and passing this object back to the ASP.NET application. At the time this worked under ASP.NET 1.1, but it turns out under IIS 7 and ASP.NET 2.0 this approach runs into some problems due to some security changes.

As I'm dusting off my old ASP.NET -> COM session code I took another look at this problem in ASP.NET 2.0 and took a slightly different approach by creating a separate Debug object that instantiates from within IIS rather than having VFP instantiate the object, pass it back to ASP.NET and then having ASP.NET invoke the debug instance. This process actually reduces one layer of abstraction from the old implementation.

To use this functionality looks something like this:

protected void btnDelete_Click(object sender, EventArgs e)
{
 
    if (this.chkDebug.Checked)
    {
        using (VfpIdeDebug vfp2 = new VfpIdeDebug())
        {
            vfp2.AutoClose = true;
 
            object debug2 = vfp2.GetDebugInstance(fox.cAppStartPath, 
                                "foxsampleServer.prg",
                                "foxSampleServer");
 
            object val = wwUtils.CallMethodCom(debug2, "IncCounter", "SWFox", -1);
 
            this.lblCounterValue.Text = val.ToString();
 
            return;
        }
    }
    else
    {
        this.fox.IncCounter(this.txtCounter.Text, -1);
        this.txtCounter.Text = "";
        this.lblCounterValue.Text = "n/a";
    }
}

The code in the upper if block is the debugging code that is equivalent to the single line below. It's obviously a little more verbose and because of the indirection and accessing of a FoxPro object (rather than the COM Callable Wrapper that .NET creates) use of Reflection is required to access the debuggable instance.

When the code above is run the VFP IDE will pop up and if you have any breakpoints in the code that runs, the debugger will stop there. The code internally works by creating the VisualFoxPro.Application instance and initializing the environment based on several parameters that are passed in to the debug instance creation method so that the object in question can be instantiated. The 3 parameters are:

  • The Path where the FoxPro code should run ( results in CD and SET PATH TO)
  • Optional name of a startup PRG file that is executed to set the environment
  • The name of the class that is to be instantiated and is to be returned as the debug instance

Note that if you want to make multiple method calls or access multiple properties you can just make multiple calls against the object instance in the C# code (using Reflection) and they will all fire against the code inside of the VFP IDE.

The idea is that VisualFoxPro.Application gets instantiated, and the DoCmd and Eval are used to set up the FoxPro environment so that an instance of the desired class can be instantiated as a pure code class. The instance is created as FoxPro PRG or VCX class that is dynamically created by VFP (ie. this is NOT a true COM instance but a plain old VFP object) and then passed back to ASP.NET. ASP.NET then has access to this object and can call into it.

The object is not the same as the COM object you might other wise have registered with ASP.NET so the only way to call a method or access a property is through Reflection. This is required because the instance returned is dynamic - created by VFP on the fly and therefore not strongly typed. It's not the same type as the COM Interop Wrapper that .NET creates when importing a COM object, although it has the same signatures. Several helper functions in wwUtils let you more easily do this. Above wwUtils.CallMethodCom will call a method with any number of parameters and return a value.

Now when this method call is made it is made against the instance that was created inside of VisualFoxPro.Application and if you have a breakpoint inside of any of the code there that breakpoint will hit and VFP will stop in the debugger. Further, you can actually change source code on the fly and on the next hit that code will actually run without recompilation.

There are a couple of requirements for this to work:

  • IIS must run in SYSTEM context
  • ASP.NET must run in Full Trust
  • You have to run DCOMCNFG on VisualFoxPro.Application and set Impersonation to the Interactive User

The latter is required so that VFP will pop up on the desktop rather than in the callers context which otherwise would be SYSTEM and so invisible.

Creating a VisualFoxPro.Application .NET Wrapper

So what does this VfpIdeDebug class look like? It's actually pretty simple - all it is is a slight wrapper around the VisualFoxPro.Application object as .NET class whose main purpose is to manage the object lifetime of the VFP application object. The object allows keeping the Visual FoxPro instance alive between debug requests which preserves your Resource and environment settings between hits and actually allows you to iteratively change FoxPro code without recompilation. Just realize that there's a difference between the Reflection code you write with the Debug object or the  direct COM object.

Here's the code for the VfpIdeDebug class:

using System;
using Westwind.Tools;
using System.Runtime.InteropServices;

namespace FoxAspNet
{

    /// <summary>
    /// A debug helper class that can be used to create instances of a FoxPro
    /// object and debug it in Visual FoxPro.
    /// 
    /// 
    ///  using (VfpIdeDebug vfp2 = new VfpIdeDebug() )
    ///  {
    ///     vfp2.AutoClose = true;
    /// 
    ///     // *** Get an instance of your COM server
    ///     object debug = vfp2.GetDebugInstance(fox.CAPPSTARTPATH as string,"foxsampleServer","foxSampleServer");
    /// 
    ///     // *** Use Reflection to access methods and properties -  this method will debug if you
    ///     // *** you have a breakpoint set in IncCounter
    ///     object val2 = wwUtils.CallMethodCom(debug, "IncCounter", "SWFox", 0);
    ///
    ///      return;
    ///   }
    /// 
    /// Requirements:
    /// 
    /// * MUST BE RUNNING IIS UNDER SYSTEM ACCOUNT (Network Service will not work)
    ///   OR USING THE VISUAL STUDIO WEB SERVER
    /// 
    /// * Set DCOM CNFG SECURITY FOR VISUAL FOXPRO.APPLICATION
    ///   SET IMPERSONATION TO: INTERACTIVE USER
    /// 
    /// * Note this class is NOT thread safe. Do not hit this code with multiple
    ///   instances simultaneously. The assumption is you debug one request at
    ///   a time (in fact you can't easily do it any other way), but don't forget
    ///   to unhook the debug instance once you're done debugging.
    /// </summary>
    public class VfpIdeDebug : IDisposable
    {
        /// <summary>
        /// Closes the VFP IDE Instance after the request
        /// </summary>
        public bool AutoClose
        {
            get { return _AutoClose; }
            set { _AutoClose = value; }
        }
        private bool _AutoClose = false;

        /// <summary>
        /// Error message set on an error
        /// </summary>
        public string ErrorMessage
        {
            get { return _ErrorMessage; }
            set { _ErrorMessage = value; }
        }
        private string _ErrorMessage = "";

        /// <summary>
        /// Instance of the VFP COM object
        /// </summary>
        public object ComInstance
        {
            get
            {
                if (InternalInstance != null && this.IsAlive())
                    return InternalInstance;

                InternalInstance = this.CreateComInstance();
                return InternalInstance;
            }
            protected set { _ComInstance = value; }
        }
        private object _ComInstance = "";

        /// <summary>
        /// Instance of the VFP COM object
        /// </summary>
        protected static object InternalInstance = null;


        public static VfpIdeDebug Create()
        {
            return new VfpIdeDebug();
        }

        /// <summary>
        /// Internally used to create a managed COM instance
        /// wrapper for the Visual FoxPro Application object
        /// </summary>
        /// <returns></returns>
        protected object CreateComInstance()
        {
            object comInstance = null;
            try
            {
                comInstance = wwUtils.CreateComInstance("visualfoxPro.Application");
                wwUtils.SetPropertyCom(comInstance, "Visible", true);
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
                return null;
            }

            return comInstance;
        }

        /// <summary>
        /// Creates a Debug instance of the object requested in the Visual FoxPro ide
        /// </summary>
        /// <param name="VfpPath">FoxPro Path to set so classes/data can be found</param>
        /// <param name="InitializationProgram">PRG to execute (in path) that initializes app</param>
        /// <param name="ClassToCreate">Name of the class to create.</param>
        /// <returns></returns>
        public object GetDebugInstance(string VfpPath, string InitializationProgram, string ClassToCreate)
        {
            if (ComInstance == null)
                return null;

            try
            {
                wwUtils.CallMethodCom(this.ComInstance, "DoCmd", "cd " + VfpPath);
                wwUtils.CallMethodCom(this.ComInstance, "DoCmd", "set path to " + VfpPath);

                if (!string.IsNullOrEmpty(InitializationProgram))
                    wwUtils.CallMethodCom(this.ComInstance, "DoCmd", "do " + InitializationProgram);

                object debug = wwUtils.CallMethodCom(this.ComInstance, "Eval", "CREATEOBJECT('" + ClassToCreate + "')");

                return debug;
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
            }

            return null;
        }

        /// <summary>
        /// Creates a Debug instance of the object requested in the Visual FoxPro ide.
        /// This version accepts a DebugInstance and attempts to create the object
        /// from the known properties of this object.
        /// 
        /// Assumes that the Classname is also the name of the PRG file that can be
        /// used to 'initialize' the COM object completely.
        /// </summary>
        /// <param name="AspBaseObject"></param>
        /// <returns></returns>
        public object GetDebugInstance(object AspBaseObject)
        {
            if (this.ComInstance == null)
                return null;

            string Class = wwUtils.GetPropertyCom(AspBaseObject, "cClass") as string;

            return this.GetDebugInstance(
                    wwUtils.GetPropertyCom(AspBaseObject, "cAppStartPath") as string,
                    Class, Class);
        }


        /// <summary>
        /// Maps VFP DoCmd() method
        /// </summary>
        /// <param name="VfpCommand"></param>
        /// <returns></returns>
        public bool DoCmd(string VfpCommand)
        {
            if (ComInstance == null)
                return false;

            try
            {
                wwUtils.CallMethodCom(this.ComInstance, "DoCmd", VfpCommand);
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
                return false;
            }

            return true;
        }

        /// <summary>
        /// Maps VFP's Eval() function
        /// </summary>
        /// <param name="EvalExpression"></param>
        /// <returns></returns>
        public object Eval(string EvalExpression)
        {
            if (ComInstance == null)
                return null;

            object result = null;
            try
            {
                result = wwUtils.CallMethodCom(this.ComInstance, "Eval", EvalExpression);
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
                return false;  // mimic VFP response
            }

            return result;
        }

        /// <summary>
        /// Maps VFP object's Visible property
        /// </summary>
        public bool Visible
        {
            get
            {
                if (this.ComInstance == null)
                    return false;

                return (bool)wwUtils.GetPropertyCom(this.ComInstance, "Visible");
            }
            set
            {
                if (this.ComInstance == null)
                    return;

                wwUtils.SetPropertyCom(this.ComInstance, "Visible", value);
            }
        }



        /// <summary>
        /// Checks to see if the VFP COM instance is still alive by 
        /// explicitly checking one of the VFP COM instance properties (Visible)
        /// </summary>
        /// <returns></returns>
        protected bool IsAlive()
        {
            if (InternalInstance == null)
                return false;

            try
            {
                wwUtils.GetPropertyCom(InternalInstance, "Visible");
            }
            catch
            {
                return false;
            }

            return true;
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (this.AutoClose)
                this.Unload();
        }

        /// <summary>
        /// Unloads the VFP COM Object
        /// </summary>
        public void Unload()
        {
            if (this.ComInstance != null)
            {
                try
                {
                    wwUtils.SetPropertyCom(this.ComInstance, "Visible", false);
                    Marshal.ReleaseComObject(this.ComInstance);
                    this.ComInstance = null;
                }
                catch { this.ComInstance = null; }
            }
        }
        #endregion
    }
}

There are a couple of overloads of the key GetDebugInstance method one of which allows passing in a special type of object that includes a property cAppStartPath for the COM server's 'home' path and an internal cClass property that is the class to instantiate with an assumption that the class can also be used to establish its own environment as a PRG file. This simpifies the call to simply passing in the existing COM object as a parameter.

Notice also that this code holds the COM object in a static reference by default which means that the object is actually held alive between requests. This means by default the debug instance does not go away when the VfpIdeDebug object goes out of scope and is disposed. If you prefer to close out each time you can set the AutoClose() property to true which will unload the object each time.

The majority of the code above deals with maintaining the lifetime of the COM object and making sure that it's alive before making any actual calls against it.

There you have it - this is pretty useful and I've found this mechanism much more reliable than the previous approach I had used. In testing with a handful of objects and even passing some fairly complex objects back out of VFP it all seems to work. Pretty well.

It's still a pain in the ass to debug like this compared to just hooking up a debugger directly stepping into code, however, this sort of thing can be a real live saver if you ever run into a problem with a COM server that is difficult to debug  without stepping into the code.

Hopefully some of you find this useful.

Download the code

Posted in ASP.NET  FoxPro  

The Voices of Reason


 

hassan
October 04, 2017

# re: Debugging a FoxPro COM Object under ASP.NET

Hi, I've created a DLL COM object in Foxpro and want to use it in ASP.NET Core, so far I've been unable to find a solution and packaging it with Nuget didn't work either whereas I can use this same dll easily in standard ASP.NET. Is there any way to use a Foxpro DLL COM object in ASP.NET Core? or Is there any difference between ASP.NET Core and ASP.NET in using COM objects?

Thanks


Rick Strahl
October 04, 2017

# re: Debugging a FoxPro COM Object under ASP.NET

@hassan - I haven't tried it but I would expect that to not work, since ASP.NET Core doesn't support STA apartment threading. Without that you can't call MTDLL components on multiple threads without corruption unless you schedule the components on your own.

Why do you want to do this anyway? It'd be better to just stick with classic ASP.NET for Windows specific and especially COM specific code.

+++ Rick ---


hassan
October 07, 2017

# re: Debugging a FoxPro COM Object under ASP.NET

We are trying to build a web application with the latest microsoft and javascript technologies, and our research showed that .NET Core is the choice since it seems that it has some advantages over classic ASP.NET(support for docker and microservices, being open source and multi platform...) But a huge part of our current business module is written in Foxpro and in the first step, we also wanted to use this business module along with our new code which is totally written in .NET. So we are trying to use our business module as COM objects and use them in ASP.NET Core. So do you think there is any way to use COM objects in any language( other than .NET) is usable or will be usable(in the future that .NET Core is expanding) in .NET Core?

Thanks


hassan
October 07, 2017

# re: Debugging a FoxPro COM Object under ASP.NET

Or is there any other workaround for this dilemma? such as writing a part of web application which is connected to foxpro business module with classic .NET and the new part with .NET Core and somehow connect these two parts together?

Thanks Hassan


Rick Strahl
October 07, 2017

# re: Debugging a FoxPro COM Object under ASP.NET

Sure you can always build Web based APIs to interface between an old and new application. Run the old app on classic ASP.NET, run the new app on ASP.NET Core. It works, but hell that's going to be painful.

If you have to support FoxPro - use a platform that it works with, which is classic ASP.NET or something like Web Connection that's designed to work around the limitations of the STA threading model of FoxPro. But even that has limitations.

If you want to move forward into modern technologies, ditch the FoxPro code and re-write it. It'll only drag you down unless you continue running on tech that it's supported on.

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2019