-1

I'm trying to follow this answer https://stackoverflow.com/a/47295752/1237135, in order to get a list of IIS Express websites, which involves referencing Microsoft.Web.dll (this is a .NET assembly, but presumably it uses COM calls) and calling this code

using (var runtimeStatusClient = new RuntimeStatusClient())
{
  var workerProcess = runtimeStatusClient.GetWorkerProcess(19464);
  //there's more but this is all that is needed for failure
}

It actually works, the code runs and has meaningful data, however a few seconds after it completes I get this error

System.InvalidCastException: 
'Unable to cast COM object of type 'System.__ComObject' 
to interface type 'Microsoft.Web.RuntimeStatus.IRsca2_WorkerProcess'. 
This operation failed because the QueryInterface call on the COM component
for the interface with IID '{B1341209-7F09-4ECD-AE5F-3EE40D921870}' failed 
due to the following error: No such interface supported (Exception from 
HRESULT: 0x80004002 (E_NOINTERFACE)).'

E_NOINTERFACE is often associated with not using an STAThread model, but I've verified that the thread is STA.

The code works without error in a Console app environment, but not WPF.

The answer above mentions

I looked into RegisteredUrlsInfo (in Microsoft.Web.dll) as well and found that it's using two COM interfaces,

IRsca2_Core (F90F62AB-EE00-4E4F-8EA6-3805B6B25CDD) IRsca2_WorkerProcess (B1341209-7F09-4ECD-AE5F-3EE40D921870)

And I saw another answer https://stackoverflow.com/a/1058978/1237135 that talks about

Try adding this to your App.exe.manifest:

iid="{C677308A-AC0F-427D-889A-47E5DC990138}"
proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"
baseInterface="{00000000-0000-0000-C000-000000000046}" tlbid = "{PUT-YOUR-TLB-GUID-HERE}" /> Where TLBID can be found from your Visual Studio generated Native.Namespace.Assembly.Name.manifest, looking like this:

but I'm unclear if this applies here.

I also wondered if it's DLL Hell but that wouldn't explain why it works from a Console, would it?

EDIT: minimal reproduction.

Create a WPF project (I used 4.6.1 runtime) and in the codebehind for the MainWindow I used

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();


            using (var runtimeStatusClient = new Microsoft.Web.RuntimeStatus.RuntimeStatusClient())
            {
                var workerProcess = runtimeStatusClient.GetAllWorkerProcesses();

            }
        }
    }
}

The only hard part is you need to find and reference Microsoft.Web.DLL (which if you have IIS Express on your machine should be in your GAC). You can do a Windows search on your C:\ drive and it will probably be somewhere like C:\Windows\assembly\GAC_MSIL\Microsoft.Web\7.1.0.0__31bf3856ad364e35

Do that and you'll see the problem.

Jim W
  • 4,866
  • 1
  • 27
  • 43

1 Answers1

1

Apparently I made 2 fatal assumptions:

  1. Console Apps use STA. However this isn't true, it seems by default they are MTA. That figures I suppose as desktop apps have to explicitly state STA in the Main method.

  2. To do COM interop you have to use STA. I assumed this because using STA is the go-to solution to E_NOINTERFACE problems on the web. However as I understand it now, some COM can use MTA. It appears for Microsoft.Web.DLL, you need MTA.

So my solution is to create a new thread (which will use MTA by default), eg.

    public MainWindow()
    {
        InitializeComponent();

        //Do use ThreadPool instead of this...
        Thread thread = new Thread(new ThreadStart(() => { GetWebsites(); }));
        thread.Start();

    }

    void GetWebsites()
    {
Jim W
  • 4,866
  • 1
  • 27
  • 43
  • 1
    FWIW: Your use of the [`[MTAThread]`](https://msdn.microsoft.com/en-us/library/system.mtathreadattribute(v=vs.110).aspx) attribute is not correct. `[STAThread]` and `[MTAThread]` only do anything on the entry point of your application. By default, all new threads in .NET are MTA threads. To change the apartment type, you have to use [`SetApartmentState`](https://msdn.microsoft.com/en-us/library/system.threading.thread.setapartmentstate(v=vs.110).aspx). See the Remarks section of each of the referenced documentation pages for confirmation. – Michael Gunter Jun 04 '18 at 16:10
  • @MichaelGunter thanks, I appreciate you mentioning that. Oddly it made it work anyway - however I'll change it to `SetApartmentState` because you are correct. – Jim W Jun 04 '18 at 16:12
  • It's not odd. The code change you really made was to start a new thread to call `GetWebsites` instead of calling it on your main thread. That new thread is MTA by default. You don't need to use `[MTAThread]` or `SetApartmentState` to create an MTA thread. Of course, if you feel it helps document your code to have these things in there, feel free. But be aware that none of that is really necessary. – Michael Gunter Jun 04 '18 at 16:14