Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Hosting the .NET Runtime in Visual FoxPro


Interop with .NET from FoxPro can be accomplished fairly easily by using COM Interop. With COM Interop you can take advantage of .NET's COM Callable Wrapper to wrap up .NET components and make them available as COM objects. I've written extensively about this interaction (here and here) and while this process works reasonably well it has one very annoying problem: COM Registration requirements.

If you want to use .NET Components through COM interop you have to specifically access objects that are visible to COM and are registered on the machine. Since .NET internally has rather few components that are exposed as COM objects and most components and libraries don't expose to COM directly, if you ever want to interoperate with these types of components you'll always have to create a wrapper component first in .NET that can act as a proxy between your unmanaged application the managed component. Often this can be a good thing - giving you a much cleaner interface, but at other times it's also just just overkill and quickly results in a conglomeration of excessive number of shim classes.

In addition, COM Registration of .NET Components is also a bit complicated in that .NET Components require a special utility RegAsm to register rather than just the standard regsvr32 so getting components registered and updated can be a pain. It's not exactly what you'd call xCopy deployment if COM registration is involved.

Avoiding COM Instantiation - Hosting the CLR directly

So... wouldn't it be cool if you could instead load any .NET component (ok, not *any and all* but not just COM registered ones) and without having to register a custom component on the machine first? In other words - simply be able to access a .NET directly from an unmanaged application rather than explicitly requiring COM ids?

It turns out this is quite easy if you host the .NET runtime directly in your unmanaged application. So rather than having to instantiate an object through COM and a ProgId/ClassId you can instantiate the object through the .NET CLR Hosting mechanism and then pass back an IDispatch (or IUnknown) pointer back to the host application.

To do this requires some low level code and the easiest way to do this is with a small shim C++ DLL. The DLL will provide a couple of simple exported functions that allow loading of the CLR and instantiating a type which is then passed back as an IDispatch pointer to VFP. Surprisingly creating this code in C++ is pretty simple. Here's a single source file that exposes standard Win32 functions callable directly from VFP that does the trick:

#include "stdafx.h"

#include <stdio.h>
#include <atlbase.h>
#include <mscoree.h>

#import "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\Mscorlib.tlb" raw_interfaces_only    
using namespace mscorlib;

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { return TRUE; } /// Globals - assume single instance for this DLL CComPtr<ICorRuntimeHost> spRuntimeHost = NULL; CComPtr<_AppDomain> spDefAppDomain = NULL; /// Assigns an error message DWORD WINAPI SetError(HRESULT hr, char *ErrorMessage) { if (ErrorMessage) { int len= strlen(ErrorMessage); LoadStringRC(hr,(LPWSTR)ErrorMessage,len/2,0); sprintf((char *)ErrorMessage,"%ws",ErrorMessage); return strlen(ErrorMessage); } strcpy(ErrorMessage,""); return 0; } /// Starts up the CLR and creates a Default AppDomain DWORD WINAPI ClrLoad(char *ErrorMessage, DWORD *dwErrorSize) { if (spDefAppDomain) return 1; //Retrieve a pointer to the ICorRuntimeHost interface HRESULT hr = CorBindToRuntimeEx( NULL, //Retrieve latest version by default L"wks", //Request a WorkStation build of the CLR STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | STARTUP_CONCURRENT_GC, CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**)&spRuntimeHost ); if (FAILED(hr)) { *dwErrorSize = SetError(hr,ErrorMessage); return hr; } //Start the CLR hr = spRuntimeHost->Start(); if (FAILED(hr)) return hr; CComPtr<IUnknown> pUnk; //Retrieve the IUnknown default AppDomain hr = spRuntimeHost->GetDefaultDomain(&pUnk); if (FAILED(hr)) return hr; hr = pUnk->QueryInterface(&spDefAppDomain.p); if (FAILED(hr)) return hr; return 1; } // *** Unloads the CLR from the process DWORD WINAPI ClrUnload() { if (spDefAppDomain) { spRuntimeHost->UnloadDomain(spDefAppDomain.p); spDefAppDomain.Release(); spDefAppDomain = NULL; spRuntimeHost->Stop(); spRuntimeHost.Release(); spRuntimeHost = NULL; } return 1; } // *** Creates an instance by Name (ie. local path assembly without extension or GAC'd FullName of // any signed assemblies. DWORD WINAPI ClrCreateInstance( char *AssemblyName, char *className, char *ErrorMessage, DWORD *dwErrorSize) { CComPtr<_ObjectHandle> spObjectHandle; if (!spDefAppDomain) { if (ClrLoad(ErrorMessage,dwErrorSize) != 1) return -1; } DWORD hr; //Creates an instance of the type specified in the Assembly hr = spDefAppDomain->CreateInstance( _bstr_t(AssemblyName), _bstr_t(className), &spObjectHandle ); *dwErrorSize = 0; if (FAILED(hr)) { *dwErrorSize = SetError(hr,ErrorMessage); return -1; } CComVariant VntUnwrapped; hr = spObjectHandle->Unwrap(&VntUnwrapped); if (FAILED(hr)) return -1; CComPtr<IDispatch> pDisp; pDisp = VntUnwrapped.pdispVal; return (DWORD) pDisp.p; } /// *** Creates an instance of a class from an assembly referenced through it's disk path DWORD WINAPI ClrCreateInstanceFrom( char *AssemblyFileName, char *className, char *ErrorMessage, DWORD *dwErrorSize) { CComPtr<_ObjectHandle> spObjectHandle; if (!spDefAppDomain) { if (ClrLoad(ErrorMessage,dwErrorSize) != 1) return -1; } DWORD hr; //Creates an instance of the type specified in the Assembly hr = spDefAppDomain->CreateInstanceFrom( _bstr_t(AssemblyFileName), _bstr_t(className), &spObjectHandle ); *dwErrorSize = 0; if (FAILED(hr)) { *dwErrorSize = SetError(hr,ErrorMessage); return -1; } CComVariant VntUnwrapped; hr = spObjectHandle->Unwrap(&VntUnwrapped); if (FAILED(hr)) return -1; CComPtr<IDispatch> pDisp; pDisp = VntUnwrapped.pdispVal; // *** pass the raw COM pointer back return (DWORD) pDisp.p; }

Not a lot of code here - this library exposes 4 public methods through its DLL interface that are callable directly through Win32 DLL semantics. In FoxPro this means these functions are accessible with DECLARE DLL.

The first two methods are to load and unload the CLR. The load routine bootstraps the CLR and then loads a default AppDomain into which any types will be loaded later. The references to the Host and the AppDomain are global references that stick around - there's an assumption that there's only one AppDomain loaded and managed through this particular DLL which should be fine for a VFP application, but you can call ClrUnload to release the AppDomain and shut down the CLR (although it will not actually completely shut down this way - once the loaded the CLR never completely unloads). Shutting down an AppDomain is a good way to release memory of all the components hosted and assemblies hosted within it.

The key worker methods are the ClrCreateInstance and ClrCreateInstanceFrom methods which are the main external interface and can be used alone to to bootstrap the CLR if necessary and then hand back an object reference to the type specified to the client.

These function load a type into the .NET AppDomain and return it to the host via an IDispatch pointer. CreateInstance works of an assembly name - which is either a local file (without extension) in application's root folder, or a fully qualified assembly name (ie. System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089) and the name of a type - like System.Net.Mail.MailMessage for example. CreateInstanceFrom uses a fully qualified path to the assembly instead and allows loading from arbitrary locations on disk given appropriate permissions and trust settings for the application.

These functions basically use the AppDomain COM reference to load up a new instance of the type. They then turn that return type into a variant and cast it as an IDispatch pointer that is then passed back to the caller. The IDispatch pointer is returned cast as an unsigned integer so it can be easily returned to Visual FoxPro.

In FoxPro then  the SYS(3096) function can be used to take a numeric pointer value and can turn it back into an IDispatch pointer. The pointer then acts just as you would expect a COM object to behave and voila you now have access to a .NET object. Here are the two wrappers, both of which return a pointer to the created type (or NULL on failure):

************************************************************************
*  CreateInstance
****************************************
***  Function: Creates an instance of a .NET class by full assembly
***            name or local file name.
***    Assume: 
***      Pass: lcLibrary - either one of these formats
***                        System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
***                        MyAssembly (where the assembly file is in the App's start path)*** 
***            lcClass   - MyNamespace.MyClass
***    Return: Instance or NULL
************************************************************************
FUNCTION CreateClrInstance(lcLibrary,lcClass,lcError)
LOCAL lnDispHandle, lnSize
DECLARE Integer ClrCreateInstance IN ClrHost.dll string, string, string@, integer@

lcError = SPACE(2048)
lnSize = 0
lnDispHandle = ClrCreateInstance(lcLibrary,lcClass,@lcError,@lnSize)

IF lnDispHandle < 1
   lcError = LEFT(lcError,lnSize)
   RETURN NULL 
ENDIF

RETURN SYS(3096, lnDispHandle)
ENDFUNC

************************************************************************
*  CreateClrInstanceFrom
****************************************
***  Function: Creates an instance of a .NET class by referencing
***            a fully qualified assembly path.
***      Pass: lcLibrary - fully qualified path to the assembly 
***                        including extension
***            lcClass   - MyNamespace.MyClass
***    Return: Instance or NULL
************************************************************************
FUNCTION CreateClrInstanceFrom(lcLibrary,lcClass,lcError)
LOCAL lnDispHandle, lnSize

DECLARE Integer ClrCreateInstanceFrom IN ClrHost.dll string, string, string@, integer@

lcError = SPACE(2048)
lnSize = 0
lnDispHandle = ClrCreateInstanceFrom(lcLibrary,lcClass,@lcError,@lnSize)

IF lnDispHandle < 1
   lcError = LEFT(lcError,lnSize)
   RETURN NULL 
ENDIF

RETURN SYS(3096, lnDispHandle)
ENDFUNC

With this code in place you can now start instantiating types from the .NET runtime or your own assemblies. You're still dealing with COM and all the COM related issues when making these calls, but there is no need to instantiate COM registered components by the ClassId/ProgId. In other words using this approach you can load and access most .NET objects DIRECTLY.

Here's a very simple example sending email trough the new System.Net mail client in .NET 2.0 using these wrappers:

CLEAR

lcSysDll = "System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

lcError = ""

loMsg = CreateInstance(lcSysDLL,"System.Net.Mail.MailMessage",@lcError)
loSmtp = CreateInstance(lcSysDLL,"System.Net.Mail.SmtpClient",@lcError)

IF ISNULL(loMsg) OR ISNULL(loSMTP)
  ? lcError
  RETURN
ENDIF

*** Show Class Names from .NET
? loMsg.ToString()
? loSMTP.ToString()

loSmtp.Host = "mail.frogbyte.net"
loSmtp.Send("lola@run-run.com","morris@softunderbelly.com","Hello World","Body this, body that")
 

Now remember this happens now without explicit COM registration - you are simply calling the DLL to host the CLR in VFP and then make calls to the returned instances. All the interaction still occurs over COM boundaries, but the requirement for a ProgId or ClassId for invokation of the object has been removed.

Notice that I set lcSysDll to the full .NET assembly name which is required for any signed component and any component in the Global Assembly Cache. If you're new to .NET you may be wondering where to find these Ids. I highly recommend you immediately download Reflector which is an assembly browsing utility. One of the things it does is provide detailed information about each assembly including the assembly name. Reflector will also be VERY useful as you use .NET components, as it shows you the classes and members available in the library as well as a disassembler that can even show you how the code works. If you're using .NET in any way Reflector is the first tool you need to have!

What these wrapper functions mean is that you can create objects from the .NET Base Class Library (BCL) directly, but more importantly you can also create a new .NET DLL and access it without having to register it for COM interop - no more polluting of the registry with gazillions of COM interfaces you need to expose just to get access to a few method calls.

More Flexibility - Creating one more wrapper

There are a few problems with the code above. First the error reporting from DLL code doesn't provide clean messages which is somewhat important for the actual Assembly loading code. There's a function LoadStringsRC() that is supposed to translate error information properly, but I couldn't get this to return proper errors. Error reporting is pretty important though - if an assembly or type fails to load you certainly will want to know why.

Another issue is that - as written - the C++ code can only instantiate objects that have a parameterless constructor. Any object that requires parameters in its constructor cannot be instantiated through this mechanism. Surprisingly there are quite a lot of objects in .NET that fall into this category. While it's possible to fix this in the call to CreateInstance and CreateInstanceFrom calls in the C++ code it's quite a bit of work to make this happen with SAFEARRAYS in C++ (not my forte <s>).

So another approach that is possibly more easily extended and also has the benefit of providing much richer error information is to use another proxy loader in the form a generic .NET class that acts as a loader. So in this scenario you use the C++ DLL code to load the .NET Bridge Loader and then hang on to this loader to load any other objects.

The advantage here is that it's much easier to create this client code for various constructor parameter combinations and it can also serve as a utility function container. Errors are captured inside of the .NET code so you can get more complete error information on the instantiation.

The wrapper is again very simple. Here it is:

using System;
using System.Runtime.InteropServices;
using System.Reflection;

namespace Westwind.WebConnection
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class wwDotNetBridge
    {
        /// <summary>
        /// Returns error information if the call fails
        /// </summary>
        public string ErrorMessage
        {
          get { return _ErrorMessage; }
          set { _ErrorMessage = value; }
        }
        private string _ErrorMessage = "";


        /// <summary>
        /// Creates an instance from a file reference with a parameterless constructor
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstanceFromFile(string AssemblyFileName, string TypeName)
        {
            return this.CreateInstanceFromFile_Internal(AssemblyFileName, TypeName, null);
        }

        /// <summary>
        /// Creates an instance from a file reference with a 1 parameter constructor
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstanceFromFile_OneParm(string AssemblyFileName, string TypeName, object Parm1)
        {
            return this.CreateInstanceFromFile_Internal(AssemblyFileName, TypeName, new Object[1] { Parm1 } );
        }

        /// <summary>
        /// Creates an instance from a file reference with a two parameter constructor
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstanceFromFile_TwoParms(string AssemblyFileName, string TypeName, object Parm1, object Parm2)
        {
            return this.CreateInstanceFromFile_Internal(AssemblyFileName, TypeName, new Object[2] { Parm1 , Parm2} );
        }


        /// <summary>
        /// Creates a new instance from a file file based assembly refence. Requires full
        /// filename including extension and path.
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        protected object CreateInstanceFromFile_Internal(string AssemblyFileName, string TypeName)
        {
            return this.CreateInstance_Internal(AssemblyFileName, TypeName, null);
        }


        /// <summary>
        /// Creates a new instance from a file file based assembly refence. Requires full
        /// filename including extension and path.
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstance(string AssemblyFileName, string TypeName)
        {
            return this.CreateInstance_Internal(AssemblyFileName, TypeName, null);
        }
        /// <summary>
        /// Creates a new instance from a file file based assembly refence. Requires full
        /// filename including extension and path.
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstance_OneParm(string AssemblyFileName, string TypeName, object Parm1)
        {
            return this.CreateInstance_Internal(AssemblyFileName, TypeName, Parm1);
        }
        /// <summary>
        /// Creates a new instance from a file file based assembly refence. Requires full
        /// filename including extension and path.
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        public object CreateInstance_TwoParms(string AssemblyFileName, string TypeName, object Parm1, object Parm2)
        {
            return this.CreateInstance_Internal(AssemblyFileName, TypeName, Parm1, Parm2);
        }


        /// <summary>
        /// Routine that loads a class from an assembly file name specified.
        /// </summary>
        /// <param name="AssemblyFileName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        protected object CreateInstanceFromFile_Internal(string AssemblyFileName, string TypeName, params object[] args)
        {
            this.ErrorMessage = string.Empty;

            object server = null;

            try
            {
                if (args == null)
                    server = AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(AssemblyFileName, TypeName);
                else
                    server = AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(AssemblyFileName, TypeName, false, BindingFlags.Default, null, args, null, null, null);
            }
            catch (Exception ex)
            {
                this.ErrorMessage = ex.Message;
                return null;
            } 

            return server;
        }



        /// <summary>
        /// Routine that loads an assembly by its 'application assembly name' - unsigned
        /// assemblies must be visible via the .NET path (current path or BIN dir) and
        /// GAC assemblies must be referenced by their full assembly name.
        /// </summary>
        /// <param name="AssemblyName"></param>
        /// <param name="TypeName"></param>
        /// <returns></returns>
        protected object CreateInstance_Internal(string AssemblyName, string TypeName, params object[] args)
        {
            this.ErrorMessage = string.Empty;

            object server = null;
            try
            {                
                if (args == null)
                    server = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(AssemblyName, TypeName);
                else
                    server = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(AssemblyName, TypeName,false,BindingFlags.Default, null,args,null,null,null);
            }
            catch(Exception ex)
            {
                this.ErrorMessage = ex.Message;
                return null;
            }

            return server;
        }
    }
}


In this version there are multiple versions for each of the CreateInstance and CreateInstanceFrom methods that can take multiple parameters. Although .NET supports method overloading, COM doesn't do a good job of exposing it (it uses a dynamic numbering scheme but it's not obvious which signature gets mapped to which extension number), so rather than using method overloading this code uses explicit method names for each of the parameter signatures. I created three versions for each for none through two parameters in the constructor. This should be sufficient for a large percentage of  types.

This is now a custom class and it doesn't need to be registered with COM even though  I have the ComVisible and ClassInterface attributes defined. Although the object is not registered through COM the ClassInterface still is useful as it provides Visual FoxPro with Intellisense of types even when 'dynamically' turning objects into IDispatch pointers rather than explicit COM instantiation.

With this code you can now do things like this:

*** Create Bridge object first - hang on for later reuse!
lcError = ""







lnDispHandle
= ClrCreateInstanceFrom(FULLPATH("wwDotNetBridge.dll"),"Westwind.WebConnection.wwDotNetBridge",@lcError,@lnSize)
loObject = SYS(3096, lnDispHandle)
? loObject.ToString()

loAct = loObject.CreateInstance_OneParm("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",;
"System.Text.RegularExpressions.Regex","a.c")
if ISNULL(loAct)
? loObject.ErrorMessage
RETURN
ENDIF


loMatches
= loAct.Match("sd-accs.abc.") ? loMatches.Count

The code doesn't look much different than before - the only difference is that we load up an instance of the bridge object first and once that object is loaded it, rather than the C++ DLL code, will be used to instantiate any .NET objects. The result is the same, but you do get more flexibility and better error reporting.

It's nice to see that this works so well with very little effort. I have a number of uses of this right away. I've been very light of my mixing of .NET code and Fox code, but with the ability to directly access .NET DLLs without having to go through COM registration of components makes the prospect of calling into .NET code a whole more useful and efficient. This is especially true for Web applications which due to security environments often cannot access COM objects or more commonly can not easily get COM objects registered on a server.

I've provided the code for both the C++ DLL and the .NET wrapper objects and a rough sample. I haven't extensively tested this stuff out yet, so I'm sure there will be improvements in the future <s>. Also please realise that many .NET objects in the BCL especially do not work well in combination with Visual FoxPro. This especially true of structs, raw array types and nested collections/lists of any type. For this reason you might find that many standard .NET objects don't work with this mechanism directly and you may still need to create wrapper objects. However, given that you can now more easily create these wrappers and call them without registration you're still way ahead <g>...

This is all kind of preliminary, the result of a few hours of experimentation. There are a few other things that would be useful and given then .NET wrapper probably not that difficult to add. For example, it'd be nice to be able to access static functions on things like the Environment class, or utility functions in common libraries. I'll leave that excercise for another day <s>...

You can download the code from:

http://www.west-wind.com/files/tools/misc/clrhost.zip

Make Donation
Posted in .NET  COM  FoxPro  


Feedback for this Post

 
# re: Hosting the .NET Runtime in Visual FoxPro
by Josh Stodola July 02, 2007 @ 7:14am
Nice explanation! Thanks...
# DotNetSlackers: Hosting the .NET Runtime in Visual FoxPro
by DotNetSlackers Latest ASP.NET News July 02, 2007 @ 8:57am
# re: Hosting the .NET Runtime in Visual FoxPro
by Raghu July 02, 2007 @ 11:11pm
I built the clrhost.dll in VS 2005. But when I run the t1.prg from foxpro, It is giving error
"Canot find entry point clrCreateInstance in the Dll"
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl July 02, 2007 @ 11:29pm
Make sure the names match. You'll also need a module definition file to export the functions. The project provided at the bottom should compile and just work.

Note it's VS 2003 project (this came out of the wwIPStuff codebase which is still using VS2003 to keep the binary size down), but it should work just fine in VS 2003 once it's been converted.
# re: Hosting the .NET Runtime in Visual FoxPro
by Raghu July 03, 2007 @ 3:59am
I used the wwDotNetBridge.dll and it works.

CLEAR
DO WCONNECT
DO wwDotNetBridge

LOCAL loBridge as wwDotNetBridge

loBridge = CREATEOBJECT("wwDotNetBridge")

*** Load an assembly
? loBridge.LoadAssembly("System") && 'System' is a special name
? loBridge.cErrorMsg

? loBridge.LoadAssembly("System.Windows.Forms")
? loBridge.InvokeStaticMethod("System.Windows.Forms.MessageBox","Show","Hello World","Title is it")
? loBridge.cErrorMsg

In the above, I didnt find wwDotNetBridge prg in the downloaded attachement.
Is that spearate ?

I just want check the InvokeStaticMethod call. Is that possible with wwDotNetBridge.dll ?

Thanks
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl July 09, 2007 @ 10:44pm
The code in this download doesn't have all those methods. It only has the DLL and a very small .NET wrapper.

The full functionality of wwDotNetBridge (which is not discussed in this blog post) will be part of the West Wind Client Toolset and Web Connection. I'm still working out the details of all of that.
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl July 10, 2007 @ 10:51am
I've updated the code in the Zip file today and added a more complete wwDotNetBridge front end class into the mix. The download now also includes the compiled DLL (oops <s>).
# re: Hosting the .NET Runtime in Visual FoxPro
by Allard January 14, 2008 @ 8:21am
I tried this method and it works great. Problems are starting when you try to instantiate more than one object of the same class. The second object seems to corrupt the memory of the first and errors like "1440 OLE exception error Exception code c0000005. OLE object may be corrupt" are occurring. This must have something to do with the appdomain, but we did not succeed in debugging this error. So we tried reg-free com and that works fine.
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl January 14, 2008 @ 12:40pm
@Allard - give me an example of what you're doing exactly. There should be no problem instantiating more than one instance - I do this all the time.
# re: Hosting the .NET Runtime in Visual FoxPro
by Allard January 15, 2008 @ 2:13am
I create a com visible wrapper class. In this class I define a static class "oCalculator" like this Private WithEvents oCalculator As New OWS.TvPlan.TvPlanDC.bCalculator. oCalculator exposes methods and properties to the wrapperclass. The wrapperclass handles all communication with the com-client (foxpro 9). In foxpro I call the following code each time I want an instance:

Declare Integer ClrCreateInstanceFrom IN clrhost.dll string, string, string@, integer@

lcError = Space(2048)
lnSize = 0
lnDispHandle = ClrCreateInstanceFrom(Addbs(This.cStartdir) + "tvplancom.dll", "OWS.TvPlan.TvPlanCom.Calculator", @lcError, @lnSize)
If lnDispHandle < 1
Throw Left(lcError, lnSize)
Endif
oMvCalculator = Sys(3096, lnDispHandle)
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl January 16, 2008 @ 12:51pm
Allard you shouldn't use ClrCreateInstanceFrom on your types. Preferrably it's better to use the wwDotNetBridge methods to instantiate new types because that guarantees these types are created in .NET and simply passed back to FoxPro.

I use that approach for a number of Web Service implementations and it works very well.

wwDotNetBridge has a bunch of changes that are in Web Connection and West Wind Client Tools since this post was made, but the basic ideas apply - you use .NET to create the instance rather than using the DLL call to do it. That way you only end up with a singleton instance of wwDotNetBridge (or whatever else you end up building to make the proxy calls on your behalf).

You can get a good idea of the full feature set of wwDotNetBridge here:

http://www.west-wind.com/webconnection/docs?page=_24n1cfw3a.htm
# re: Hosting the .NET Runtime in Visual FoxPro
by Allard January 17, 2008 @ 9:16am
Hi Rick,

At first glance it looks like this solves the problem. Thanks so far. By the way, the reg-free com solution is also working, so we made a switch in our applicatio object which method to use. If you are interested in the reg-free com scenario: I posted it on graig boyds blog: http://www.sweetpotatosoftware.com/SPSBlog/CommentView,guid,A7469C56-D058-41CD-B345-82A8121E1CFD.aspx#6773f32b-32d9-48f5-8187-cb61a90a345e

Allard
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl January 17, 2008 @ 1:00pm
Thanks for posting that Allard - very useful.

However I think that this is more hassle than its worth and possibly error prone. Too many things that can go wrong especially if you have a bunch of components that need registration - you'd bascially have to ensure each assembly you want to access is exposed this way. For one thing you'll need another separate manifest for your EXE that running out side of VPF9.exe... <shrug>

FWIW, I've been using wwDotNetBridge (the current versions) in a number of projects that makes extensive use of COM interop without any issues. The other advantage of this approach is that you can create non-ComVisible objects in .NET. One disadvantage is that you can't access COM Event interfaces if they are published because those interfaces won't be registered in the registry.
# re: Hosting the .NET Runtime in Visual FoxPro
by Allard January 18, 2008 @ 1:39am
Well in fact we did manage to access the com event interface using wwDotNetBridge, but you need the .tlb file generated by .NET. You need to make sure the .NET com component being used is in the same directory as the foxpro-built exe. Then you can use the same method as with com objects registred in the registry (creating an "implements" class).

I think one of the advantages of using the manifest is that other (non .NET) com components can be used this way. Especially in applications used by groups of users not having rights to access the registry (lika all of our customers) this can make deployment much more easy.

One more question. Did you manage to use visual objects using the wwDotNetBridge way? I.e. using a .NET created menu in foxpro?
# re: Hosting the .NET Runtime in Visual FoxPro
by Michael Drozdov June 03, 2008 @ 12:50am
Hello, Rick!

Thank you for your code and comments at http://www.west-wind.com/WebLog/posts/104449.aspx ("Hosting the .NET Runtime in Visual FoxPro" article)! But I have a little problem with using your sample due to overlapped constructor/method with fixed number of parameters with explicitly specified data types. In order to solve this problem I have extended your sample code. Probably, it would be interesting for you.

- I suppose you will need online translation from Russian – you can try this useful webpage http://www.google.com/language_tools?hl=en
- Here is description of my solution: http://vfpdev.narod.ru/docs/xlstbl_r.html - "How to receive html/xml-tables from xlsx-file of MS Excel 2007 by use XSLT-transformation." (rus)
- You can download my more extended code here: http://vfpdev.narod.ru/util_e.html - xlsxtbl.zip (http://vfpdev.narod.ru/download/xlsxtbl.zip (853KB))

Best regards.
Michael Drozdov, ICS Soft, Perm, Russia
# re: Hosting the .NET Runtime in Visual FoxPro
by Michael Drozdov June 03, 2008 @ 4:02am
Hello, Rick!

Another problem is with parameters of System.Byte[*] type
For example
https://forums.microsoft.com/msdn/ShowPost.aspx?PostID=292587&SiteID=1
http://gps678.com/1/8693271f65327068.html


Problem occurs if you try to receive object (object)System.IO.MemoryStream.GetBuffer()
As a possible solution, code in Utils_Reflection.cs from
        public static object CallMethod(object Object, string Method, params object[] Params)
        {
            // *** Pick up parameter types so we can match the method properly
            Type[] ParmTypes = null;
            if (Params != null)
            {
                ParmTypes = new Type[Params.Length];
                for (int x = 0; x < Params.Length; x++)
                {
                    ParmTypes[x] = Params[x].GetType();
                }
            }
            return Object.GetType().GetMethod(Method, Utils.MemberAccess | BindingFlags.InvokeMethod, null, ParmTypes, null).Invoke(Object, Params);
            // *** More reliable but slower
            //return Object.GetType().InvokeMember(Method,Utils.MemberAccess | BindingFlags.InvokeMethod,null,Object,Params);
            //return Object.GetType().GetMethod(Method,Utils.MemberAccess | BindingFlags.InvokeMethod).Invoke(Object,Params);
        }


could be modified as follows:

        public static object CallMethod(object Object, string Method, params object[] Params)
        {
            // *** Pick up parameter types so we can match the method properly
            Type[] ParmTypes = null;
            if (Params != null)
            {
                ParmTypes = new Type[Params.Length];
                for (int x = 0; x < Params.Length; x++)
                {
                    ParmTypes[x] = Params[x].GetType();
                             if (ParmTypes[x].ToString() == "System.Byte[*]")
                             {
                                   ParmTypes[x] = typeof(System.Byte[]);
                                   int nLen = ((Array)Params[x]).Length;
                                   System.Byte[] baTmp = new Byte[nLen];
                                   Array.Copy((Array)Params[x], baTmp, nLen);
                                   Params[x] = (object)new Byte[nLen];
                                   Array.Copy((Array)baTmp, (Array)((object)Params[x]), nLen);
                             }
                }
            }
            return Object.GetType().GetMethod(Method, Utils.MemberAccess | BindingFlags.InvokeMethod, null, ParmTypes, null).Invoke(Object, Params);
            // *** More reliable but slower
            //return Object.GetType().InvokeMember(Method,Utils.MemberAccess | BindingFlags.InvokeMethod,null,Object,Params);
            //return Object.GetType().GetMethod(Method,Utils.MemberAccess | BindingFlags.InvokeMethod).Invoke(Object,Params);
        }


Best regards.
Michael Drozdov, ICS Soft, Perm, Russia
# re: Hosting the .NET Runtime in Visual FoxPro
by Gorka July 12, 2011 @ 11:02pm
Hi Rick
This text has been created with the Google translator, so I apologize if it is not well understood.
If I use clrHost.dll for System.Net.Mail.MailAddress(lcDir) is not working.
I Using Visual Foxpro 8
and the code is this:

lcSysDll = "System, Version = 2.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089"
lcError =''
loSmtp = CreateInstance (lcSysDLL "System.Net.Mail.SmtpClient" @lcError)
loMsg = CreateInstance (lcSysDLL "System.Net.Mail.MailMessage" @lcError)
lcDir = "micorreo@micorreo.es"
loMailAd = CreateInstance (lcSysDLL "System.Net.Mail.MailAddress (lcDir)", @lcError)


loSmtp and loMsg are correct however loMailAd is NULL
Can you help?
regards
Gorka
# re: Hosting the .NET Runtime in Visual FoxPro
by Jesus September 13, 2011 @ 12:27pm
Hi Rick, I was watching the issue of sending mail, but no way of instantiating the System.Net.Mail.Attachment is that it requires attaching a file. The code is as follows:

*********************************************
CLEAR
DO wwDotNetBridge

LOCAL loBridge as wwDotNetBridge

loBridge = CREATEOBJECT("wwDotNetBridge")

loMsg = loBridge.CreateInstance("System.Net.Mail.MailMessage","Mail","Mail","Hello")
loAtt = loBridge.CreateInstance("System.Net.Mail.Attachment","C:\FoxTabsArticle.pdf")
loSmtp = loBridge.CreateInstance("System.Net.Mail.SmtpClient")
? loBridge.cErrorMsg
loMsg.Attachments.Add(loAtt)
loSmtp.Host = "IP"
loSmtp.Port = 25
loSmtp.Send(loMsg)
*********************************************************



loAtt is null
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl September 16, 2011 @ 12:52pm
@Jesus, yes there's some odd complication with System.Net.Mail.Attachment. In my wwSmtp class I added an explicit .NET method called AddAttachment() that would proxy for this. I did manage to get this to work with Attachment but the code was ugly and the proxy method was easier to work with.

If you're running Web Connection/Client tools etc. you can use the wwSmtp class which acts as a front end for the .NET class.
# re: Hosting the .NET Runtime in Visual FoxPro
by Dennis H November 23, 2011 @ 10:56am
Hi--

Thanks for this article.

I ran into a problem where if I load a .net assembly with ClrCreateInstanceFrom as per this article, run a form with a .NET control that created with the Interop Form Toolkit, then reference a property or call a method of the object returned by ClrCreateInstanceFrom, I get an "OLE Exception error: Exception code c0000005. OLE object may be corrupt"

This happens with any Interop Form Toolkit controls.

Any ideas?

Thanks
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl November 23, 2011 @ 12:04pm
Hi Dennis - yes, that's because you're mixing crossing .NET runtimes when you do that.

If you're using 'regular' COM interop via CREATEOBJECT() or using other COM objects to interact with .NET it will create its own .NET runtime. You can participate in that by just loading up the wwDotnetBridge object into that same runtime using CREATEOBJECT("Westwind.WebConnection.wwDotnetBridge"). Everything else will work the same after that. Of course this requires that at least wwDotnetBridge is registered for COM interop.
# re: Hosting the .NET Runtime in Visual FoxPro
by Dennis H December 14, 2011 @ 6:04am
Thanks for the explanation -- I understand now. It works great.

My eventhandler class libraries referenced the ProgID of my .Net DLLs and that wouldn't work, so I referenced the .TLB file. It can be a relative path.

Now I'm trying to get RegFree to work on wwDotNetBridge. If I get it to go I'll post here.

Thanks again!
# re: Hosting the .NET Runtime in Visual FoxPro
by Dennis H December 18, 2012 @ 10:34am
I continue to make frequent use of this utility.

I ran into a problem where if the .Net DLL threw an error upon being loaded, it is not returned to wwDotNetBridge's errorMessage property. You get some thing to the effect of "target of an invocation threw an error" instead.

To get the underlying error, change

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


to

catch(Exception ex)
{
this.ErrorMessage = ex.Message + "\n" + ex.InnerException.Message;
return null;
}

Thanks,

DH
# re: Hosting the .NET Runtime in Visual FoxPro
by Rick Strahl December 18, 2012 @ 12:21pm
@Dennis - you'll want to check out the much more recent open source version of wwDotnetBridge which can be found on Github here:

https://github.com/RickStrahl/wwDotnetBridge

It includes many, many updates to what was posted here in the years since. I can't recall offhand, but most errors do return the base exception I believe.
 


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