6

I have a C# application which interfaces with some hardware (USB device) as follows: C# application -> intermediate DLL -> hardware DLL -> hardware. The intermediate DLL and hardware DLL are supplied with the USB device so I have no control over these.

The intermediate DLL is the only which which I need to include in the VS project as this is what I call. The hardware DLL is then in the same directory so must be found automatically.

A new version of hardware device is now released with a different hardware DLL. The old DLL is not compatible with the new hardware and the new DLL is not compatible with the old hardware.

How can I make my application work with both pieces of hardware? I guess that I need to load and unload each DLL as required?

Mark
  • 1,759
  • 4
  • 32
  • 44
  • 2
    You first need to be able to determine which hardware you're targeting. Have you solved that yet? – Grant Thomas Jun 03 '13 at 07:48
  • I can either do that by a dropdown selection - or, ideally, by loading one DLL and scanning for hardware, then loading the other DLL and scanning for hardware.. if possible.. – Mark Jun 03 '13 at 08:01
  • Is an intermediate DLL a native DLL? – Dennis Jun 03 '13 at 08:03
  • Intermediate - so I can just add as a reference like I would any other C# library. – Mark Jun 03 '13 at 12:14
  • You need to speak to the vendors of the intermediate / hardware DLLs. There are various ways of making this work however it will depend on how those DLLs are built. If you can't do that then it would be helpful if you could identify what the hardware DLL is (native, managed or mixed-mode) and what mechanism the intermediate dll uses to load and call into that dll. – Justin Jun 03 '13 at 15:05
  • This sounds like an install issue. Determine the hw version at install time and place the appropriate version of the hw dlls in the install folder. Then you won't have to deal with at runtime. Error message to reinstall if h/w mismatch detected if h/w upgraded after install. – Jay Walker Jun 03 '13 at 19:22
  • The amount of pain and suffering you'll endure from having to support multiple versions of incompatible DLLs, plus the enormous cost of the support you'll have to provide when there's a mis-match, is very rarely competitive with just forcing the customer to update their hardware. It needs to cost *many* hundreds of dollars before you're close to break-even. They'll have to update sooner or later. Sooner is *always* better. Send your customers a nice letter to explain that. Don't hesitate to blame the hardware manufacturer, ought to give your customer a nice discount. – Hans Passant Jun 04 '13 at 01:06
  • @HansPassant: Yes, that would be the easy option! However, not ideal and this is also a good chance for me to learn a bit! :) – Mark Jun 04 '13 at 13:18
  • @JayWalker: Same as for above, that would certainly be easier - although it would not notify me of the wrong DLL - just fail to detect any hardware so it might not be so transparent to the user. I may have to fall back on that solution, but I like a challenge :) – Mark Jun 04 '13 at 13:19
  • @Justin: Cheers, I will look into contacting them for more details. Is there a solution either way? – Mark Jun 04 '13 at 13:20
  • Hmm, a learning experience is always attractive, but you are seriously messing with the customer's business interest in staying productive and running their operation smooth and trouble-free. An hour of downtime in a production environment adds up awfully quickly to serious amounts of money. You need to discuss this with your supervisor, you can get in deep doggy-doo over this. – Hans Passant Jun 04 '13 at 14:06

3 Answers3

4

Here's what I do for a similar problem. I have a chunk of code that I want to work with, but I have to load the dll at runtime. So I refer to it in my project, but I don't put it in the same directory as the rest of my assemblies. Instead, in the consuming code, I have some code that looks like this:

// constructor called from a static constructor elsewhere
MyDllLoader(string hardwareFolder) {
    _hardwareFolder = hardwareFolder;
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    SeeIfAlreadyLoaded();
}


private void SeeIfAlreadyLoaded() {
    // if the assembly is still in the current app domain then the AssemblyResolve event will
    // never fire.
    // Since we need to know where the assembly is, we have to look for it
    // here.
    Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies();
    foreach (Assembly am in assems)
    {
        // if it matches, just mark the local _loaded as true and get as much
        // other information as you need
    }
}

System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
    string name = args.Name;
    if (name.StartsWith("Intermediate.dll,"))
    {
        string candidatePath = Path.Combine(_hardwareFolder, "Intermediate.dll");
        try {
            Assembly assem = Assembly.LoadFrom(candidatePath);
            if (assem != null) {
                _location = candidateFolder;
                _fullPath = candidatePath;
                _loaded = true;
                return assem;
            }
        }
        catch (Exception err) {
            sb.Append(err.Message);
        }
    }
    return null;
}

There's another solution too - it's complicated, but I've done it and done the work for you. You declare an abstract class, say MyHardwareAbstraction, that has the signatures of the methods that you want and you code against that interface. Then you write some code which given a path to an assembly, loads it and dynamically defines a new class that matches MyHardwareAbstraction and makes it map onto an instance of the actual object that you want. I wrote a blog several years ago on how to do this.

The nice thing about doing this is that you use the abstract type in your code and work against that and then the adapter compiler will, at run time, compile a new class that will complete that abstract type using some other type as the target type. It's fairly efficient too.

plinth
  • 48,267
  • 11
  • 78
  • 120
0

Edit:

If the intermediate DLL is a .Net Assembly, you can use the method mentioned here to specify where to look for your intermediate DLL before you call any method that uses the intermediate DLL, without having to change your existing code.

then you must not directly reference the DLL in your C# project, because .Net Assemblies are discovered and loaded before your Main method is even called. Instead, you must dynamically load the intermediate DLL using AppDomain or other methods, then use the library via reflection, or by using dynamic objects.

Apparently, this would make programming very cumbersome. However, there is an alternative method. You can write a launcher program, that loads your original application (you can load .exe files as libraries), and invokes the Main method of your original program reflectively. To make sure that the correct intermediate DLL is loaded, you can use the method mentioned here, while your launcher program is loading your original application.

The following discussion still applies to the hardware DLL.


The following is valid if:

  1. You need only one version of the dll at a time (during the entire period your application runs), and
  2. The two versions of the intermediate DLLs have exactly the same API.

According to MSDN, the DLL Search Path includes directories specified under the PATH environment variable. (http://msdn.microsoft.com/en-us/library/7d83bc18%28v=vs.80%29.aspx). Hence, you may put the two versions of the intermediate DLLs under seperate sub-directories under your application directory, but with exactly the same name under each directory, for example:

bin\
   hardware-intermediate-v1\
       intermediate.dll
   hardware-intermediate-v2\
       intermediate.dll

Then, at start up, after your application has determined which version to use, you may add one of the above directories to your PATH environment variable,

using System;
using System.Reflection;
using System.IO;
...
Environment.SetEnvironmentVariable(
    "PATH", 
    Environment.GetEnvironmentVariable("PATH") + ";" + 
        Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + 
        "\\hardware-intermediate-v1"
);

Then, calls to P-Invoke methods (DLLImport) will result in the corresponding version of the DLL to be loaded. To immediately load all DLLs, you may refer to DllImport, how to check if the DLL is loaded?.

However, if you wish to use the two version of the DLLs together without restarting your application, or if there are any API difference between the two DLLs on the level of method name and/or parameter count/type, you must create two seperate sets of P-Invoke methods, each binding to its corresponding version of the intermediate DLL.

Community
  • 1
  • 1
Interarticle
  • 385
  • 4
  • 9
  • This does not appear to work in my project. If I call `Marshal.PrelinkAll(Type)`, as suggested in the link, it does not help either. There is also no return value to know if it has actually done anything. – Mark Jun 03 '13 at 12:54
  • RE the update, that sounds like what I have already done but no luck :( – Mark Jun 04 '13 at 13:21
0

I you want both dll to coexist in the program, you'll have to use AppDomains, as explained here.

Else, you can simply use LoadLibrary after the user has made a clear choice about what version he needs ?

Community
  • 1
  • 1
C4stor
  • 8,355
  • 6
  • 29
  • 47
  • I can see how this could be used to load the intermediate DLL, but if this calls the hardware DLL, is there any way of specifying that it should use a given AppDomain? – Mark Jun 03 '13 at 12:41
  • I'm not sure. If the two intermediates dll are in separate folders, and the corresponding hardware are in the same folder, I think (hope ?) that the intermediate will simply load the dll from their respective folder. – C4stor Jun 03 '13 at 14:07
  • 1
    Is the intermediate DLL a .Net assembly? – Interarticle Jun 03 '13 at 14:45
  • @Interarticle: Intermediate appears to be. Hardware DLL I'm not sure about. – Mark Jun 04 '13 at 13:12
  • @C4stor: That is how I have it setup but it doesn't appear to be loading them correctly. – Mark Jun 04 '13 at 13:13