Judging from questions on the message board and private support issues that I've been debugging with customers, there's some confusion on how wwDotnetBridge works with various .NET versions. In this post I discuss how wwDotnetBridge loads the .NET Runtime and how .NET versions are managed by the hosted runtime.
In case you haven't heard about wwDotnetBridge, it is an open source library to make it easier to interact with .NET components by providing much richer access to .NET functionality than is possible through regular COM Interop.
You can find out more about wwDotnetBridge here:
How wwDotnetBridge loads the .NET Runtime
One of the main features of wwDotnetBridge is that it hosts the .NET Runtime directly inside of Visual FoxPro. By doing so it's possible to bypass the COM registration requirements for .NET COM Interop, which is both cumbersome and seriously limits of what .NET components you can access with standard COM Interop. wwDotnetBridge hosts the .NET Runtime explicitly and loads wwDotnetBridge into it, which allows loading of arbitrary .NET components directly and offering access to a much richer feature set and most .NET components.
In more detail here is how it works:
wwDotnetBridge hosts the .NET Runtime manually inside your Visual FoxPro IDE or compiled FoxPro EXE process. It does this by using some Windows APIs - CorBindRuntimeEx() specifically - to create a Runtime host instance and loading an AppDomain into it. The Windows API returns an instance to the .NET Runtime Host via COM and passes back a reference to the host and AppDomain. wwDotnetBridge's loader then loads the wwDotnetBridge .NET component into the appdomain and retrieves the reference to this .NET object reference over COM and passes it back to FoxPro.
This instance is then stored on the wwDotnetBridge::oDotnetBridge property, which hangs on to that instance. The instance can then be used to load assemblies, creating .NET object instances (without COM registration) and perform all the other rich features that are available on the wwDotnetBridge object and most of these tasks are wrapped by the FoxPro wwDotnetBridge instance.
Although, the .NET component was launched using COM and acts basically like any other .NET COM object once returned to Visual FoxPro, the component did not have to be registered in the registry. Rather wwDotnetBridge loads components from within the .NET Runtime based on the component's name. This makes it possible to launch just about any .NET component directly, regardless of whether it's registered for COM interop or not.
Here's a graph that shows the general flow of of the loader process:
The end result of this process is that you end up with the wwDotnetBridge FoxPro class that lets you instantiate any .NET component - including those that aren't registered through COM - directly from Visual FoxPro:
do wwDotNetBridge && Load library loBridge = CreateObject("wwDotNetBridge","V4") loBridge.LoadAssembly("bin\InteropExamples.dll")
*** Create a .NET object instance
loFox = loBridge.CreateInstance("InteropExamples.Examples") *** Call a method on the .NET object and return another .NET object loPerson = loFox.GetNewPerson() ? loPerson.FirstName ...
You can find out more about what you can do with this functionality in the white paper. In this post the focus is the .NET Runtime loading and how to manage the .NET version loaded.
Understanding .NET Runtime Loading
One key aspect to about .NET Runtime loading to understand is that only one instance of the .NET Runtime can be active in a process at any given point in time. Although wwDotnetBridge allows you to explicitly specify the runtime version to load, only the first load of the .NET actually loads an instance of the Runtime. All subsequent loads simply use the already loaded instance of the .NET Runtime that exists in memory.
This means if you do something like the following:
loBridge = CreateObject("wwDotNetBridge","V4") ? loBridge.GetDotnetVersion() loBridge2 = CreateObject("wwDotNetBridge","V2") ? loBridge2.GetDotnetVersion()
both instances actually get Version 4.0 instances of the .NET Runtime. The results from GetDotnetVersion() in both cases returns:
.NET Version: 4.0.30319.18033
This behavior has caused some confusion to some developers, as they expect to get different versions of the runtime for each of those commands.
It bears repeating: Only one instance of the .NET Runtime will be loaded - the first instance loaded is the instance that all wwDotnetBridge code will run under.
It's important to understand that .NET 2.0 components (that is .NET 2, 3 and 3.5 components all of which use the .NET 2.0 Runtime) are forward compatible and can run in .NET 4.0. You can easily load .NET 1.x and 2.0 components in a .NET 4.0 Runtime instance. The reverse however is not true even if the .NET 4.0 compiled component only uses .NET 2.0 code. .NET 4.0 compiled assemblies will not load in .NET 2.0 Runtimes.
As you might expect, this behavior can potentially be confusing or cause some problems if you're not careful about which Runtime gets loaded first. Essentially you need to ensure that the highest version of .NET that you expect to use gets loaded first, so that all other components will then also use this highest version.
If you have a component that expects Version 4.0, but somewhere along the line the .NET 2.0 Runtime was loaded (with the "V2" switch explicitly set), the version 4.0 component will not be able to load.
This can be especially tricky if you use .NET as part of reusable components that internally load up instances of wwDotnetBridge. Examples of this in Web Connection and Client Tools are wwSmtp and wwJsonSerializer, both of which load up wwDotnetBridge internally where you can't control the .NET runtime version.
Internally these components use GetwwDotnetBridge() - which is a helper function in wwdotnetbridge.prg - that provides a cached instance of wwdotnetbridge and tries to load the highest version of .NET installed on the machine. This function scours the the .NET install folder and tries to find the latest version of .NET installed and then uses that to load the .NET runtime. Using GetwwDotnetBridge() is a good idea for your own applications as it is a simple way to minimize load time for wwdotnetbridge and share a single instance of wwdotnetbridge.
In most cases GetwwDotnetBridge() does the right thing by finding the highest version installed and using it. For this reason we recommend that you use this function - especially if you expose wwDotnetBridge in other components where the calling application may not be able to explicitly set the .NET version. By using GetwwDotnetBridge everything uses the same logic to find the same runtime version which should ensure there's no confusion over which version is used.
But using this helper does not by itself guarantee that you get the right runtime - it only guarantees you get the latest version. But it won't prevent problems if somewhere in your application another component or your own code explicitly creates wwDotnetBridge with another explicit .NET Runtime version, before your call to load the runtime.
Making sure you load the right .NET Runtime
Ok - so getting the right version can potentially suck, especially when you might have multiple components that also load wwdotnetbridge!
However, there's a simple trick you can use to make sure your application always gets the right version of the runtime and under your control and not some random component's:
In your applications startup code force to load wwDotnetBridge. Do it as part of the Application's initialization code and simply do:
loBridge = CreateObject("wwDotNetBridge","V4")
to force the .NET 4 runtime, or:
loBridge = CreateObject("wwDotNetBridge","V2")
to force use of the .NET 2 runtime.
You should specify the highest runtime that any of your application's .NET Interop requires. So if you'll have any .NET 4 components in your app, make sure you specify "V4". Otherwise specify "V2".
This works because the .NET Runtime loads only once - any other component that explicitly requests to load with V2 after you've specified V4 still gets the V4 .NET Runtime.
wwDotnetBridge can only load a single version of the .NET Runtime and the first load wins - all subsequent loads will use the same runtime. To make sure you don't get a version too low because one component explicitly loads a lower version, take proactive steps and explicitly create an instance of wwDotnetBridge right at application startup with the highest version of .NET that you expect to use. Since .NET is backward compatible old components built for an older version will run, while your latest and greatest components can take advantage of the newest .NET version.
Get to it!