FoxPro and .NET Interop
wwDotnetBridge and big foxpro app
Gravatar is a globally recognized avatar based on your email address. wwDotnetBridge and big foxpro app
  Aurelien Dellieux
  All
  Mar 23, 2020 @ 06:43am

Hi,

i will try to be as concise as possible.

we have a big vfp 9 application, and we try to move parts of vfp 9 code to .net code. our goal is to migrate from a vfp 9 application to a front end client in a web browser and several web apis in .net we started with some zip compression, rtf to text conversion, in a single .net solution. clrhost.dll, wwdotnetbridge.dll are located in the same directory as our application. everything worked really well?

but since one year or so, we started to use .netstandard, client classes (for web api access from vfp 9). we then started having troubles with dependencies, newtonsoft json, log4net, and some others. it becomes problematic to handle versioning of these dependencies. one solution would be to have several solutions, one for each technical module (zip compression, rtf conversions, web api client, and so on...), instead of just one.

i tried using LoadAssembly() with a relative path, and that worked pretty well. nevertheless, i encountered a drawback : dependencies are not found by .net (nuget packages assemblies which are added to the projects, like log4net). as soon as i move those dll files near clrhost.dll and others, everything works again.

is there a way to still use several sub-directories in which i would store each solutions binaries ? i tried to use SET PATH, without success, and SET DIRECTORY does not seem to handle several path. is there something i can do in my .net classes ?

i would be very grateful if you could provide me some tips. i can provide you some test cases files if you desire.

many thanks for your time

Aurélien

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Rick Strahl
  Aurelien Dellieux
  Mar 23, 2020 @ 12:40pm

I don't understand what you're doing based on this description. You say you use WebAPI yet you also are using what wwDotnetBridge?

You're going to have to be more specific about what you're doing here.

As to assembly versioning you can use Assembly Redirects to pin to the particular version the host application is using.

For wwDotnetBridge here's the info in the docs:

This is specific to JSON.nET but applies really to any assembly.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Aurelien Dellieux
  Rick Strahl
  Mar 24, 2020 @ 03:06am

Hi Rick,

Thanks for your answer. It's difficult to describe our application's architecture, so forgive me if things are not so clear. Below, I will try to explain our VFP 9 / .Net / wwDotnetBridge experience.

Our main application is 25 year old, and was initially under FoxPro 2.6 We migrated from FoxPro 2.6 to VFP 9, with several steps. It is based on a main executable, which manages approximately 60 other VFP 9 executables We have numerous COM servers, mainly coded in VFP 9, and lately some coded in .Net Framework. We use some dll, activex components coded in Delphi 6, and internet explorer 11 activex webbrowser. Some COM servers and VFP 9 executables are used by other teams, in different projects.

So our goal today is to migrate to web apis, and web browser clients, using .Net : that is a long term goal. We discovered wwDotnetBridge some years ago, and recently, after giving it a try, we started to port some functions to .Net Framework. We began with some simple functions, like an old proprietary zip compression, and an rtf to text/html converter. This allowed us to greatly optimize code, and get execution speed faster than using old Delphi 6.

We put those functions calls in an interop .Net dll, that we called vfp interop dll. This allowed us to implement interop classes that fox can use via wwDotnetBridge. For example, this interop dll allow us to map List and other specific .Net objects to arrays, as described in your wwDotnetBridges examples. In this dll, we use webapi clients or web services, because those webapis are used by other projects with web browsers, outside our team. Those clients are .Net assemblies implementing httpwebrequest, we name them requesters. This allow us to manage those requesters in .Net, and not in VFP 9.

By the time, this interop dll allow our application to access to the following :

  • two compression algorithms (native Deflate in .Net, and custom zip)
  • one rtf <-> html / text converter
  • 7 web apis / web services for various tasks (log, trace, notifications, drugs databases services, etc...)
  • updater (our database is on a sql server, and some updates are still done in VFP 9, but recent updates are coded using Transact SQL queries, and some specific .Net methods)

This interop dll is a single solution, and containing one project. Each functionality is coded in a class. As you can imagine, those classes use various dependencies in the form of nuget packages, maybe in different versions. We have some custom common packages, used by different projects (and those clients used by the interop dll). Some use log4net, newtonsoft json, for example. Furthermore, with time, teams decided to code mainly using .NetStandard 2.0, or .Net Core 2.0, and not .Net Framework anymore.

In the end, our interop dll may use a .Net Framework client which, through several dependencies, use newtonsoft json 9, and another .Net Standard client with use newtonsoft json 12. This is just an example describing our problem : those two versions of newtonsoft are incompatible, because there are breaking changes between those two versions. This can occur in our own dependencies, as we produce a lot of internal nuget packages, sometimes depending on other nuget packages of our own. Visual Studio compiles the solution well, does not warn about compatibility problems. Problems then occur at execution time, because we have exceptions, for example telling us that a dll in a specific version could not be loaded because of compatibility reasons (newtonsoft json as an example). The interop dll solution's bin output dir mix all dependencies, and in the case of newtonsoft json, we don't know which version will remain when the soluion ends compiling.

That's why i study a way to have one different interop dll (.net solution) for each functionality, each in a specific directory, to avoid those assemblies conflicts.

I tried to use relative paths, but was not entirely successfull. .Net tries to find dependencies in our main application's directory, and not in sub directories specified by relative paths. Yesterday, i tried PrivateBinPath and derivatives, but failed.

I carefully read the assembly redirects link you submitted me, but i don't know if it could apply to our use case.

Aurélien

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Aurelien Dellieux
  Rick Strahl
  Mar 24, 2020 @ 06:36am

I may have found something interesting here : how-to-add-folder-to-assembly-search-path-at-runtime-in-net

and here : 'probing' Element

i created a file with the name of my main executable, and the path i needed .Net to look into, and it seems to work. i need to make more tests, as i fear that .net stops at the first file with the required name. i don't know if .Net checks both the assembly version and the file name. fingers crossed...

[Edit] : you already made an article about probing here : Loading .NET Assemblies out of Seperate Folders

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Rick Strahl
  Aurelien Dellieux
  Mar 24, 2020 @ 12:26pm

Ok... I see the .NET Interop. I still don't see how the WebAPI stuff fits into that.

If you're doing COM interop with FoxPro from WebAPI be aware that that's likely to cause you big problems because of the MTA threaded nature of Web API and all newer Microsoft technologies. Under load this will break FoxPro servers unless you have a mechanism in place to isolate threads (custom thread pool) or some way to force WebAPI to run as STA (which if I remember right can't be done via any built-in mechanism).

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Aurelien Dellieux
  Rick Strahl
  Mar 25, 2020 @ 01:17am

Hi Rick,

I will try to clarify the webapi aspect. Our main application is really in Foxpro. It makes calls via our interop dll (dll is in .Net). The interop dll uses several clients (clients are in .Net) which in turn makes calls to the webapis using httpwebrequest. The webapi are in .Net. Our FoxPro application is our client.

Some forms in this application use a full window internet explorer activex webbrowser. In these specific forms, there are no Foxpro objects, just the activex, and foxpro code that reacts to the activex events. In those activex, we load a front end ui from a web server, coded in angular 7 and typescript. Those front end ui make calls to the webapis by themselves.

In other forms, we have foxpro visual components, and foxpro business classes. Those classes may need results from webapis too. But we don't call webapis directly from foxpro, not because it doesn't work, but because in .net we have all the power of the language. That's why we make calls through the interop dll. We don't have any problems with single or multi thread, because all this is handled in .net clients, used by the interop dll. The webapis are used by our foxpro application, and by other teams client applications (coded in delphi xe, front end ui, windev, etc...) Those webapis are really backend servers hosted by iis servers.

I hope i managed to clarify this somewhat complex architecture. It's a first step to a long term goal : drop all foxpro applications. We have several reasons :

  • maybe in a near future Microsoft will move to a 64/128 bits os
  • SQL server support in VFP 9 is limited (we use ADO and SQL native client) : we fear each sql server new release
  • VFP 9 is not maintained anymore, and we already have a really huge number of bugs and ugly workarounds. Sometimes we don't have workarounds, like graphic glitches with containes when having more than two level of container and not visible bitmap objects.

Fortunately, you have created wwDotnetBridge and that allowed us this first step. We try to move foxpro non visual parts in .net. We even managed to use Chromium, using CefSharp, making an ActiveX object usable by VFP 9, in order to replace most critical forms that access web apis (because things get really slow with internet explorer 11 activex, without mentioning html 5 compatibility).

Thanks for taking the time to read all this. Aurélien

Gravatar is a globally recognized avatar based on your email address. re: wwDotnetBridge and big foxpro app
  Rick Strahl
  Aurelien Dellieux
  Mar 25, 2020 @ 01:39pm

All assemblies need to live in the startup folder except for assemblies that are in the GAC. This means any dependencies you have have to be available in one of those two places. For .NET code you're calling - and since it sounds like you're in control of the .NET code - you should make sure that everything is set to Copy Local in your assembly references in the .NET projects. That will ensure everything gets pulled to local files.

You can't really using probing paths with because the path has to be set before startup. wwDotnetBridge doesn't set a probing path. If I recall correctly, the .NET LoadAssembly() API that wwDotnetBridge uses pulls assemblies from the application startup folder and in any folder that you load an assembly from.

Frankly I've never had problems with this - if you do then most likely you have projects with assemblies being pulled from odd locations and not using Copy Local.

The way to do this if you have problems or if your application is complex enough to require a ton of external dependencies I would recommend you create a wrapping .NET assembly that internally references that you're trying to use. You can then create a custom assembly and or type loader and passes back the objects you need without having to futz around with all the dependency resolution in FoxPro. .NET should be able to resolve all child dependencies of a loaded assembly automatically unless the path of the assembly is hardcoded to a non-default path.

Additionally if you create a custom loader or assembly you can also implement the AppDomain.AssemblyResolve event which fires whenever an assembly cannot be resolved. You can look at the failed request and then look for the assembly yourself and return it explicitly. I do this for addins in Markdown Monster for example where assemblies are loaded out of arbitrary folders.

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    // missing resources are... missing
    if (args.Name.Contains(".resources"))
        return null;

    // check for assemblies already loaded
    Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
    if (assembly != null)
        return assembly;

    // Try to load by filename - split out the filename of the full assembly name
    // and append the base path of the original assembly (ie. look in the same dir)
    // NOTE: this doesn't account for special search paths but then that never
    //           worked before either.
    string filename = args.Name.Split(',')[0] + ".dll".ToLower();


    // try load the assembly from install path (this should always be automatic)
    try
    {
        // this allows addins to load before form has loaded
        return Assembly.LoadFrom(filename);
    }
    catch
    {
    }

    // try load from install addins folder
    string asmFile = FindFileInPath(filename, ".\\Addins");
    if (!string.IsNullOrEmpty(asmFile))
    {
        try
        {
            return Assembly.LoadFrom(asmFile);
        }
        catch
        {
        }
    }

    return null;
}

Assembly loading in .NET is a complex topic, but it's rarely a problem unless a component is doing whacky stuff with their own assembly loading. I think CefSharp is one of those, but the problem there isn't the .NET bits but the native Chromium dependencies.

+++ Rick ---

© 1996-2020