views:

245

answers:

2

I have an interesting situation where I need to do something like this:

[Export[typeof(ICandy1)]
[Export[typeof(ICandy2)]
public class Candy : ICandy2 { ... }

where

public interface ICandy1 { ... }
public interface ICandy2 : ICandy1 { ... }

I couldn't find any posts anywhere regarding using multiple [Export] attributes, so I figured, what the hell, might as well try it.

At first glance, it actually seemed to work. I have a couple of methods that call into both interfaces of a Candy instance, and it was fine.

However, as I started to test the app, I saw that the behavior wasn't right, and when looking at the Output window, I saw that I was getting tons of COMExceptions. I couldn't track down where they were all coming from, but they always occurred when a worker thread was sleeping. I figured that it had to be from the main thread, then, but didn't know how to debug this at all. Nothing should have been going on in the GUI, and I disabled my DispatchTimers just in case -- same thing.

Even more strange than the COMExceptions was the really, really erratic behavior when stepping through code. About 30% of the time, when I single stepped, it would pop out of the method, or it would single step over two lines of code! Totally weird stuff that I am not used to seeing.

The only thing that changed between working and non-working code was the introduction of MEF through my plugin loading code. So as a test, I changed my plugin assembly to only export one interface, and I hardcoded everything in the app that relied on the other (now not-implemented) interface. And now the COMExceptions are gone, and the weird debugging behavior is gone.

Is this something people here have seen before? If MEF is not expected to allow a class to Export multiple interfaces, then shouldn't a CompositionException get raised when composing the parts? Can anyone explain why MEF would cause these weird problems???

Here's a sample of the main thread's call stack around the time of the COMException. Not sure if it means anything to anyone, but if you can suggest any ways to debug this, that would be great.

> UIAutomationProvider.dll!MS.Internal.Automation.UiaCoreProviderApi.UiaHostProviderFromHwnd(System.IntPtr hwnd) + 0x38 bytes 
  UIAutomationProvider.dll!System.Windows.Automation.Provider.AutomationInteropProvider.HostProviderFromHandle(System.IntPtr hwnd) + 0x2d bytes 
  PresentationCore.dll!MS.Internal.Automation.ElementProxy.HostRawElementProvider.get() + 0x65 bytes 
  [Native to Managed Transition] 
  [Managed to Native Transition] 
  UIAutomationProvider.dll!System.Windows.Automation.Provider.AutomationInteropProvider.RaiseAutomationPropertyChangedEvent(System.Windows.Automation.Provider.IRawElementProviderSimple element, System.Windows.Automation.AutomationPropertyChangedEventArgs e) + 0x2a bytes 
  PresentationCore.dll!System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree() + 0x2c9 bytes 
  PresentationCore.dll!System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree() + 0x2f8 bytes 
  PresentationCore.dll!System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree() + 0x2f8 bytes 
  PresentationCore.dll!System.Windows.Automation.Peers.AutomationPeer.UpdateSubtree() + 0x2f8 bytes 
  PresentationCore.dll!System.Windows.ContextLayoutManager.fireAutomationEvents() + 0x98 bytes 
  PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayout() + 0x65b bytes 
  PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayoutCallback(object arg) + 0x19 bytes 
  PresentationCore.dll!System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork() + 0x10 bytes 
  PresentationCore.dll!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() + 0x97 bytes 
  PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget = null) + 0x80 bytes 
  PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandler(object resizedCompositionTarget) + 0x2b bytes 
  WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback = {Method = Cannot evaluate expression because the code of the current method is optimized.}, object args = null, bool isSingleParameter = true) + 0x8a bytes 
  WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, bool isSingleParameter, System.Delegate catchHandler = null) + 0x4a bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.WrappedInvoke(System.Delegate callback, object args, bool isSingleParameter, System.Delegate catchHandler) + 0x44 bytes 
  WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0x5d bytes 
  WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x38 bytes 
  mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x51 bytes 
  [Native to Managed Transition] 
  [Managed to Native Transition] 
  mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x67 bytes 
  mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x45 bytes 
  WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x63 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x127 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x63 bytes 
  WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd = 464158, int msg = 49869, System.IntPtr wParam = 0, System.IntPtr lParam = 0, ref bool handled = false) + 0xbe bytes 
  WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7a bytes 
  WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback = {Method = Cannot evaluate expression because the code of the current method is optimized.}, object args = {MS.Win32.HwndSubclass.DispatcherOperationCallbackParameter}, bool isSingleParameter = true) + 0x8a bytes 
  WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, bool isSingleParameter, System.Delegate catchHandler = null) + 0x4a bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.WrappedInvoke(System.Delegate callback, object args, bool isSingleParameter, System.Delegate catchHandler) + 0x44 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, bool isSingleParameter) + 0x91 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority priority, System.Delegate method, object arg) + 0x40 bytes 
  WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd = 464158, int msg = 49869, System.IntPtr wParam = 0, System.IntPtr lParam = 0) + 0xdc bytes 
  [Native to Managed Transition] 
  [Managed to Native Transition] 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0xc7 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes 
  WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes 
  PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x1e bytes 
  PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
  PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
  PresentationFramework.dll!System.Windows.Application.Run() + 0x19 bytes 

Dan pointed out that specifying two ExportAttributes would likely create two instances from the different ImportAttributes, but I believe it only created one instance because I put a breakpoint in the constructor for Candy and it was only hit during the lifetime of the app.

A: 

NOTE: The below guess has been confirmed incorrect.


If you have two exports, I'm pretty sure the import for each type of export will create a different instance. This is probably what caused your issues.

Dan Bryant
that's a great point -- I would have expected a null exception earlier on, though. I'll stick some code in there to confirm what you've said, but I bet you're right. Do you know of any magic attribute I can use to force it to return the same instance?
Dave
This is interesting. I put a breakpoint in the constructor of the plugin, and it only got hit once. Yet the code that calls into the other interface is working. So it seems that MEF is actually only constructing one instance, yet there is still something else going awry. The COMExceptions have come back after exporting the 2nd interface again.
Dave
I browsed through the docs a bit, but I don't see anything obvious. You can export to a contract name and use that as your import for either interface rather than the type, but what you're really after is two imports asking for different contracts and getting the same instance. You could hack around it with a proxy object that implements ICandy1, imports ICandy2 and routes ICandy1 methods to the service implementation of ICandy2 : ICandy1.
Dan Bryant
Hmm, if it's only constructed once, then not sure what's causing your issue.
Dan Bryant
+2  A: 

Having multiple exports on a single class is a perfectly normal use case in MEF. We do this all the time without problems.

Dan's remark is not correct. Unless you explicitly set a part creation policy on the import or export, MEF will prefer to reuse instances within a container rather than create multiple ones.

The COM exceptions you are seeing have nothing to do with MEF, as MEF itself is purely managed and doesn't use use any COM interop. You should take a look at the COM exception message and its stack trace. To make the debugger break when such an exception occurs, configure visual studio accordingly (Use the shortcut ctrl+d, e to see the relevant settings).

The erratic debugger behavior which you describe usually means that the source code and the compiled assembly do not match. Try cleaning out your bin folder, and check that the dependencies between your projects are project references rather than direct assembly references. Direct assembly reference do not properly trigger rebuilds if the source code of the dependency has changed.

Wim Coenen
I'm glad to hear that this has nothing to do with MEF. The weird thing is that now I'm at home working on the same code (I tagged the version that threw COMExceptions), and it doesn't exhibit the same behavior *at all*. I will rerun the code at work and try to get a call stack tomorrow morning. Thanks for clearing up everything with the part creation policy.
Dave
@Wim: Yes, I do use CTRL-D,E for when I need to really dig into exceptions. It's exactly what I did at the office when I was getting COMExceptions, but this is the first case where when the exception is thrown, it did **not** switch to the thread at the point where the exception was thrown!!! Really odd. I will do a full clean tomorrow. Hmm.. actually I will just RDP in now and see what's up.
Dave
@Wim: do you have any other suggestions? I've cleaned, rebuilt, tested this on multiple systems, and this COMException **only** happens on one of my machines! I cannot get the code to stop executing at the point the COMException is thrown, regardless of my VS2008 options.
Dave
@Dave: 1) Do you have the exception message of the COMException? 2) Do you use any native code? 3) Is your code multi-threaded?
Wim Coenen
@Wim: I'll have to run this test again, but I recall the COMException not giving me anything useful. Stack trace is also useless. I do have native code because of a 3rd party DLL, but I don't think the offending code (multiple export) is using it. My code is multithreaded, but also not around the point of interest (as far as I can tell). I'll get back to you on this!
Dave