0
votes

I'm working on a WPF project where we are using MVVM Light Messenger and SimpleIOC. The solution has several projects that provide implementations for working with Serial / USB devices, and those implementations use a common interface to abstract away the details of the various devices.

At application startup, the solution registers our dependencies in the IOC:

Kernel.Container.Register<IMainWindow, MainWindow>(Lifestyle.Transient);
//more dependency registrations.

When MainWindow loads, we detect which device the user wants to use and load it up. However, because we don't want to tightly couple the application to the device implementations, there are no direct references in the WPF application to any of the Device implementations. When loading a device, we use Reflection to pull in the DLL and construct the object, and pass back the common interface IDevice.

Code for loading the DLL and constructing the device:

public static Assembly GetAssembly(string dll)
{
            var codeBase = Assembly.GetExecutingAssembly().CodeBase;
            var uri = new UriBuilder(codeBase);
            var path = Uri.UnescapeDataString(uri.Path);
            //Load the assembly from the specified path.
            var theDirectory = Path.GetDirectoryName(path);
            var strTempAssmbPath = string.Format(@"{0}\Devices\{1}", theDirectory, dll);
            var theDll = Assembly.LoadFile(strTempAssmbPath);
            return theDll;
}

//Actual code that gets the assembly and loads the IDevice
var assembly = GetAssembly("MyDevice.dll");
(IDevice)Activator.CreateInstance(assembly.GetType(type), ConstructorConstraint);

Each device uses a static MessageBus (direct project reference to our Messaging library) to send messages back to the application when events on the device happen (again, allowing device implementations to be abstracted away from the WPF application). Here is our MessageBus implementation:

public static class MessageBus
    {
        public static void Send<T>(T message)
        {
            Messenger.Default.Send(message);
        }

        public static void Receive<T>(object subscriber, Action<T> action) where T : MessageBase
        {
            Messenger.Default.Register(subscriber, action);
        }
    }

This is all well and good when running the application through the debugger, and everything works as expected. However, after building our application through our build process and packaging the installer, then installing on our QA machines, the WPF application will run like normal, load the device appropriately, but the messages that the device implementation sends are not being received by the MainWindow in the running application.

The running theory we have right now is that because the devices are being loaded at runtime, they are not getting the same reference to the Messaging library (either the Messaging DLL is missing or the reference in the device dll is not the same as the reference in the WPF application). We are experimenting with copying out the Messaging DLL into the Devices folder and getting some mixed results.

Why aren't the messages that the devices send being received by our application after we build and package the application for release, but it works in the debugger?

1

1 Answers

0
votes

The solution is pretty elusive and doesn't really explain the behavior, but we were able to solve the problem.

Basically, our build process uses an open source version of ILMerge that merges the dependencies into the output executable. As a part of that process, the version of ILMerge has issues with merging assemblies into WPF executables that aren't embedded resources in the WPF project itself. To solve that issue, we have to have an MSBuild task on our WPF project that embeds all references so that the ILMerge process can properly merge them. Example of that here: https://gist.github.com/thoemmi/3724333

Here's where things start to get a little weird. The Messaging project that houses our MessageBus is a direct reference in the WPF project and is being embedded and merged by our build process. Our device DLLs are not able to resolve those references from the merged in assembly from the build output.

In order to get things working, we have modified our build process to output the Messaging DLL into the same directory as the application executable, and everything works as expected.

It's not the elegant solution that we were hoping for, but it definitely solved our problem.

If someone comes up with a better answer that solves the problem, I'd be happy to test it out and take that as the answer instead.