locked
Returning an instance of a visual studio 2010 addin to a client application RRS feed

  • Question

  • All,

    I have created a simple addin in vs 2010 called MyAddIn using the addin wizard. I want to return an instance of MyAddIn to a client application so the client application can call the addin's methods directly outside of the vs 2010 IDE. I've attempted numerous ways with no success. Here is what I have so far...

    public class Connect : IDTExtensibility2
    {
    ...
    }

    I have a simple method in it called DisplayMessage like so...

    public class Connect : IDTExtensibility2
    {
    ...
    public void DisplayMessage()
    {
    MessageBox.Show("Method successfully called by the client application outside of the IDE.");
    }
    ...
    }


    I have ComVisible set to tru on the class like so:

    [ComVisible(true)]
        [ClassInterface(ClassInterfaceType.None)]
        public class Connect : IDTExtensibility2
    {
    ...
    }

    In the project properties, on the compilation tab, I have register for COM interop selected so the .tlb file is created when I compile the addin solution.


    I then go to tools, addin manager, and select my addin to load. It loads fine because I have a message box show...

    public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
            {
                _applicationObject = (DTE2)application;
                _addInInstance = (AddIn)addInInst;
                MessageBox.Show("Add-In has been Initialized.");
            }


    I then alt+tab to my client application and use syntax like the following to try to get the instance of MyAddIn:


    // DTE 10 is found ok
     let DTE = GetObject("VisualStudio.DTE.10.0")

    // the addin cannot be found and is null
    let addInInstance = DTE.Addins.Item("MyAddIn").Object

    the call to .Object is returning null and not the instance of my addin.

    The call to DTE.Addins.Count returns 2 since I have 2 addins loaded - one of which is mine. So the client application can use the DTE 10 object and get a count of addins, however, when it tries to get an actual reference to my addin it cannot.

    Why? How can I get an instance of my addin returned to the client application so the client application can directly access the methods within my addin?

    I have searched high and low for a solution to this and the only thing I have found that is close is an article on an office addin found here:

    http://msdn.microsoft.com/en-us/library/bb608621.aspx

    Some of that information seems to apply, however, the addin in vs 2010 does not have a  "RequestComAddInAutomationService" to override.

    Help!

    /Frekster

     

    Thursday, December 16, 2010 9:09 AM

Answers

  • Hi Ryan,

    I see my last post was marked as an answer by Victor to the problem. Will this be a fix to visual studio 2010 in the near future? Should I open this as a bug? I just need to know what the next step should be for me, if anything.

    Honestly, I am still a bit foggy to what the problem is in general and what my work around should be.

    Can you please offer me a suggestion on how I can get this to work?

    thanks for the feedback!


    /Frekster


    Well I opened a bug on this, I can repro the crash when I run your script against 2010 and not when run against 2008. It appears that the code that is calculating the address of the IDispatch function to call is getting a very random value and then executing the random memory garbage it finds there.  That code is all in ATL and is (as far as I know) unchanged in 2010, so it is a mystery to me (at the moment) WHAT is going wrong exactly.

    As for getting this to work, it isn't clear yet.  You could, as you said, try registering your AddIn object directly in the ROT, and having your script expect that / retrieve it.  That seems less than ideal, but until we could figure out WHY this is crashing I can't think of any other way to go about it. 

    I am on vacation right now so I won't have a ton of time to look into this in the next few days, but I did file a bug and I can make sure someone looks into it, it seems kind of nasty that VS is crashing like this on what seems to be a fairly straight forward automation scenario.

    Ryan

    • Marked as answer by Frekster12 Thursday, December 30, 2010 12:50 PM
    Tuesday, December 28, 2010 7:54 PM

All replies

  • Is there anyone that can help with this problem?
    /Frekster
    Friday, December 17, 2010 11:44 AM
  •  

    All,

    Just wanted to reply to see if anyone can help with this one. I've searched high and low for an answer but cannot find anything on the internet to help me. If anyone has a suggestion, can you please post it.

    Thanks!


    /Frekster
    Sunday, December 19, 2010 5:19 AM
  • Have you created a proxy dll or registered your typelib to allow COM proxies to be generated?  See here and here.

    Ryan

    Sunday, December 19, 2010 4:13 PM
  • Thanks so much for the reply! I have tried the following with no success but I think I am getting closer. My vs 2010 vs add in project I have this setup:

    [ProgId("MyAddIn.Connect")]
        [Guid("85BD2D94-DE82-4633-9005-4CC40C8A5C4C")]
        public class Connect : IDTExtensibility2
    ...

    So I have the class exposed to comm and the option to generate the tlb file checked in the compilation tab in the project properties.

    Also have AssemblyInfo.cs ComVisible(true).

    I copy MyAddIn.dll and MyAddIn.tlb to c:\windows for testing.

    I use the .net framework v4 regtlibv12.exe tool and it states it succeeds in registerring MyAddIn.tlb.

    regtlibv12.exe  "c:\windows\MyAddIn.tlb"

    Now following this article you posted...

    http://blogs.msdn.com/b/larryosterman/archive/2006/01/09/510856.aspx

    I confirm the registry keys.  most of the registry keys were created by the regtlibv12.exe  tool and they look ok.


    I open vs 2010 and go to the add in manager and choose my add in which points to c:\windows\MyAddIn.dll and it loads fine.

    I go to my client application, now assuming it should be able to get an instance of the add in due to the tlb registration and it still cannot. For example:

    // fails
    let addIn = DTE.Addins.Item("MyAddIn").Object


    or even vb script syntax like so...

    // fails
    set addIn = CreateObject("MyAddIn")

    vb script, my client application, nor vb 6 can get a reference to the MyAddIn.dll add in.

    I think I am about 90% there and I am missing one or two more things. Can you or anyone point something out that I am or may be missing?

    btw I'm trying to not use a proxy dll since I seen this same scenario work under vs 2003. That is, an addin was exposed to com and external clients outside of vs 2003 could get an instance of the addin and use it.

    At this point, what am I doing wrong?

    Thanks man for the help and to anyone else who can help me too!


    /Frekster
    Tuesday, December 21, 2010 11:37 AM
  • >btw I'm trying to not use a proxy dll since I seen this same scenario work under vs 2003.

    There is a huge number of possible changes between 2003 and 2010, 2003 was 7 years ago (it was 3 releases ago).

    I tried reproing the issue though I used C# instead of VB since I didn't have VB installed on my box.  I can't seem to repro it, here are the steps I took

    1:  Create a new C# AddIn via the New Project wizard.

    2:  Make it COM visible and exposing ClassInterface.None as you did above

    3:  Make it load on startup + put a message box into its OnConnection method to ensure it was loaded.

    4:  Create a seperate C# console application that fetched DTE from the ROT

    5:  Ensure I could see all AddIns and fetch them by prog id.

    NOTE: I didn't register the TLB as the previous links I gave you talk about, I simply marked the project as registering for COM interop so the TLB was generated, then I ran the console application (from step 4) from a command prompt and made sure VS was running with the AddIn loaded.  One thing to note your out of proc process needs to be running as admin to access the ROT, at least on my Win7 box it appears to need that, running as non-admin gets me an exception in trying to fetch the DTE instance.

    The console app I talk about above works.  I couldn't call DisplayMessage because even when I declared the fetched item as dynamic I got an error at runtime saying there was no DisplayMessage method, so perhaps more is needed to expose it properly via IDispatch so it can be called at runtime, but it seems I got further along than you since my AddIn instance wasn't null.

    Are you sure the NULL isn't just VB masking some kind of failure? For instance when I tried to fetch the AddIn from Item via its 'user friendly name' instead of its ProgId I would get an exception because the COM call would return DISP_E_BADINDEX.

    The C# code I am using to fetch the AddIn out of proc and display information about it is shown below

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using EnvDTE;
    
    namespace OutOfProcDTEAccess
    {
      class Program
      {
        static void Main(string[] args)
        {
          DTE dteObj = (DTE)Marshal.GetActiveObject("VisualStudio.DTE.10.0");
          if (dteObj != null)
          {
            Console.WriteLine(String.Format("Fetched the DTE object from the ROT.{0}", Environment.NewLine));
    
            Console.WriteLine(String.Format("{0} AddIns reported.{1}", dteObj.AddIns.Count.ToString(), Environment.NewLine));
            List<string> addInProgIds = new List<string>();
    
            foreach (object o in dteObj.AddIns)
            {
              AddIn addin = o as AddIn;
              if (addin != null)
              {
                Console.WriteLine(String.Format("AddIn Name: {0}{1}AddIn ProgId: {2}{1}", addin.Name, Environment.NewLine, addin.ProgID));
                addInProgIds.Add(addin.ProgID);
              }
              else
              {
                Console.WriteLine("AddIn was null.");
              }
            }
    
            Console.WriteLine();
    
            foreach (string progId in addInProgIds)
            {
              AddIn fetchedAddIn = dteObj.AddIns.Item(progId);
              if (fetchedAddIn != null)
              {
                Console.WriteLine("Successfully fetched \"{0}\" by progid \"{1}\".", fetchedAddIn.Name, fetchedAddIn.ProgID);
              }
              else
              {
                Console.WriteLine("Failed to fetch add in with progid \"{0}\".", progId);
              }
            }
          }
        }
      }
    }
    

    And the console output it generates is

    c:\Users\rmolden\Documents\Visual Studio 2010\Projects\OutOfProcDTEAccess\OutOfProcDTEAccess\bin\Debug>OutOfProcDTEAccess.exe
    Fetched the DTE object from the ROT.

    2 AddIns reported.

    AddIn Name: TestAddIn - No Name provided.
    AddIn ProgId: TestAddIn.Connect

    AddIn Name: .NET Reflector
    AddIn ProgId: RedGate.Reflector.Addin.Connect9


    Successfully fetched "TestAddIn - No Name provided." by progid "TestAddIn.Connect".
    Successfully fetched ".NET Reflector" by progid "RedGate.Reflector.Addin.Connect9".

    Ryan

    Tuesday, December 21, 2010 5:55 PM
  • Doh, I see I didn't do the whole 'access the Object' property that you were doing that was resulting in null :(  Unforunately when I tried to do that in my console app it crashed VS :(  I guess I will be looking more deeply into that now.

    Ryan

    Tuesday, December 21, 2010 6:16 PM
  • Well, making some progress :)  Accessing the Object property on an AddIn whose Connected property is returning false crashes VS.  This seems like a bug to me, but it is avoidable via checking the Connected property before accessing Object.  When I do this (with the above example, modified to actually access the Object property) I get an non-null return value for my Object property, but it is a System.__ComObject and using dynamic in conjunction with DisplayMessage results in an exception complaining that System.__ComObject doesn't have a DisplayMessage method, so now the problem just seems to be that it either isn't understanding IDispatch properly or I need to do 'something else'. 

    Ryan

    Tuesday, December 21, 2010 8:40 PM
  • Thanks man for the help! I am actually writing the addin in c#.net so we are on the same page. So now you have been able to reproduce my exact problem. That is, getting null back when calling .Object. My client application is not a .NET application, rather it is a client application that uses a scripting language. This is why I think I need to register the tlb to let the external application see it/use it. It has syntax like this:

    // DTE 10 is found ok
     let DTE = GetObject("VisualStudio.DTE.10.0")

    // the addin cannot be found and is null
    let addInInstance = DTE.Addins.Item("MyAddIn").Object
    // even trying this it is null
    let addInInstance = DTE.Addins.Item("MyAddIn.Connect").Object

    You could probably reproduce using an external app that is not a .net app by using vb 6 or vc++ 6 or perhaps even a classic asp page.

    If you or anyone else can figure out a work around for this, please let me know.

    We are close...

    Thanks again!


    /Frekster
    Tuesday, December 21, 2010 9:27 PM
  • Well, I wasn't getting back null, I was only getting back null if the AddIn hadn't been loaded, if it was loaded I was getting a non-null System.__ComObject back from the Object property.  The problem was that trying to invoke DisplayMessage was always throwing an exception at runtime because there was no DisplayMessage available (according to the runtime binder).  I think what is happening is that .NET doesn't expose non-interface methods to COM.  You may be able to force it to do so somehow, but an easy fix for me was to take your DisplayMessage method, put it in an interface like so

    [ComVisible(true)]
    public interface IMyInterface 
    {
      void DisplayMessage();
    }
    

    Implement IMyInterface on my Connect class and then it all worked (i.e. I was able to get the Object property and call DisplayMessage on it and it caused a message box to popup at runtime, as expected).  I didn't have to register any proxies to get this to work, but my scenario was C# AddIn + C# Console App, not sure if you would need the typlib registered + proxies registered to get your scripting language example to work.  One thing I noticed is that when I ran regtlibv12 is that it DIDN'T produce the proxy enteries, they are the ones talked about in the previously linked article that look like this

    Key: HKEY_CLASSES_ROOT\Interface\<IID>\
        Default Value: <friendly name for the interface> Again, not really required, but nice for oleview
    Key: HKEY_CLASSES_ROOT\Interface\<IID>\ProxyStubClsid32\
        Default Value: {00020424-0000-0000-C000-000000000046}
    Key: HKEY_CLASSES_ROOT\Interface\<IID>\TypeLib\
        Default Value: <LibID>

    I had to manually insert those enteries myself even after runnin regtlibv12.  Though that didn't make it work, the bit that made it work was making the methods on an interface, marking it COMVisible and implementing it on my Connect class.  However, your scripting language may require the proxies be registered, hard to say.

    Ryan

    Tuesday, December 21, 2010 9:54 PM
  • Sweet... you had success on your end! Is there any way you could get me the exact syntax to enter the .reg keys since I am not following the  syntax. My interface is called IProxy and my add in is called SuperAddIn (not really but... ) :) I do not know how to use the namespace, if even required, in the reg keys. Is it namespace.interface name? namespace.class name? assembly name.namespace.interface name? Confused on that part and where to put it in the reg keys that I must manually enter.

    Here is what I have on the interface:

    namespace ApplicationAddIn
    {
        [Guid("DAE15DD9-F089-4FEE-AD56-B338A4F9D55D")]
        [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
        [ComVisible(true)]
        public interface IProxy
        {
            [DispId(1)]
            void DisplayMessage();
        }
    }


    I do not know if any of the attributes are required but it was from some other tutorial I was looking over for exposing a c#.net dll to a com client application.

    My connect class is setup like this with the following attributes...


    namespace ApplicationAddIn
    {
        using System;
        using System.Runtime.InteropServices;
        using System.Windows.Forms;

        using EnvDTE;

        using EnvDTE80;

        using Extensibility;


        /// <summary>The object for implementing an Add-in.</summary>
        /// <seealso class='IDTExtensibility2' />
        [ProgId("SuperAddIn.Connect")]
        [ClassInterface(ClassInterfaceType.None)]
        [Guid("85BD2D94-DE82-4633-9005-4CC40C8A5C4C")]
        public class Connect : IDTExtensibility2, IProxy

        {
           
           

            #region Fields

            private AddIn _addInInstance;
            private DTE2 _applicationObject;

            #endregion Fields

            #region Constructors

            /// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
            public Connect()
            {
            }

            #endregion Constructors

            #region Methods

            /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
            /// <param term='custom'>Array of parameters that are host application specific.</param>
            /// <seealso class='IDTExtensibility2' />  
            public void OnAddInsUpdate(ref Array custom)
            {
            }

            /// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
            /// <param term='custom'>Array of parameters that are host application specific.</param>
            /// <seealso class='IDTExtensibility2' />
            public void OnBeginShutdown(ref Array custom)
            {
            }

            /// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
            /// <param term='application'>Root object of the host application.</param>
            /// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
            /// <param term='addInInst'>Object representing this Add-in.</param>
            /// <seealso class='IDTExtensibility2' />
            public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
            {
                _applicationObject = (DTE2)application;
                _addInInstance = (AddIn)addInInst;

                // For now, let us know that we launched the Add-in.
                // We'll remove this once we are comfortable.
                MessageBox.Show("super add in has been Initialized.");
            }

            /// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
            /// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
            /// <param term='custom'>Array of parameters that are host application specific.</param>
            /// <seealso class='IDTExtensibility2' />
            public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
            {
                MessageBox.Show("super addin unloaded");
            }

            /// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
            /// <param term='custom'>Array of parameters that are host application specific.</param>
            /// <seealso class='IDTExtensibility2' />
            public void OnStartupComplete(ref Array custom)
            {
            }

            #endregion Methods

            #region IProxy Members

            public void DisplayMessage()
            {
                MessageBox.Show("DisplayMessageBox has been called.");
            }

            #endregion
        }
    }

    thanks Ryan for any help! I think if I can get the exact syntax for manually adding the keys I should be ok. Also, if you think any of the com attributes are not required any feedback on those would be appreciated too. I am running windows 7 64 bit edition - I think you said you were running that too.

    Thanks again!


    /Frekster
    Tuesday, December 21, 2010 11:56 PM
  • I don't think you need the GUID on the interface (though having it won't hurt), it isn't exposed directly to users but rather the CLR exposes all of the interface members via its implementation of IDispatch, so the end caller (the scripting language) probably only ever deals with IDispatch. 

    You also shouldn't need the DispId attribute on the method, that just allows you to re-number the methods in case you needed to match say some previously published version of the object which had a specific IDispatch method id -> method mapping. 

    The reg enteries I had are show below, though the GUIDs will be different for your stuff depending on the generated GUID for the COM stuff.  I found the right GUIDs previously using OleView, going down to the TypeLibs node and expanding it then starting to type TestAddin.Connect, it took me to the right tree node which had the typelib info (GUID) there (shown below):

    Windows Registry Editor Version 5.00

    [HKEY_CLASSES_ROOT\Interface\{C1FF68A5-BA15-31B9-A378-53B90AD43671}]
    @="TestAddIn.Connect"

    [HKEY_CLASSES_ROOT\Interface\{C1FF68A5-BA15-31B9-A378-53B90AD43671}\ProxyStubClsid32]
    @=" {00020424-0000-0000-C000-000000000046}"

    [HKEY_CLASSES_ROOT\Interface\{C1FF68A5-BA15-31B9-A378-53B90AD43671}\Typelib]
    @="{24464CCA-FF02-30C1-A468-AFDD659466FF}"
    "Version"="1.0"

    Like I said I deleted this from my registry after things were working and they kept working in the C# console app case, so the thing that got that scenario working seemed to be the move to an actual interface in my connect class, you could try pasting what I have above into a new C# console app and seeing if it is working for your AddIn without needing the above registry goop. 

    If it does you can try moving to your scripting language, if that doesn't work then it likely has to do with the proxies or somethin similar (though the scripting language itself may just be treating the item as IDispatch as well, in which case it shouldn't need any of this info).

    One other thing I did notice is that in my VS + C# console app case that both VS had to be running as admin (likely to place things into the ROT) and the console app also had to be running as admin (likely because the item was placed in teh ROT by an admin process, so a non-admin process couldn't access it as that would lead to a possile escalation of privledges).

    Ryan

    Wednesday, December 22, 2010 12:15 AM
  • Oh yeah, you probably don't need the InterfaceType attribute on the IProxy interface either, I didn't have it, and as far as I know that just influences whether the interop marshaller will marshal the managed object as IDispatch (which is default iirc) or as IUnknown.

    Ryan

    Wednesday, December 22, 2010 12:16 AM
  • Ok, I got the console application to work fine, however, the scripting language cannot get a reference to the com exposed  object.

     

    Strangely enough, the scripting language can get an instance to the DTE 10 object and use it fine.

     

    hmmmmm. I am at a loss as to what I am missing here. Obviously the c#.net console and vs 2010 works fine, the scripting language getting DTE 10 works fine, but the scripting language getting my exposed com class is not working.

     

    The scripting language is not clr and I believe is written in c++.

     

    Here is some documentation from the scripting language on using GetObject below:

     

    // sample syntax that is failing
    let addIn = GetObject("Superaddin.Connect")

     

    // documentation for GetObject

     

    object GetObject(string ApplicationName)

     

    Category:  Object Model And MSAA

     

    Return Type:  object

     

    string ApplicationName
    Program to find.

     

    Description:  Looks in the Windows Running Object Table for an instance of an application with the specified COM class name.

     


    With this information in mind, what am I missing in order to get the object to return?

     

    Thanks for the help!


    /Frekster

    Thursday, December 23, 2010 3:27 PM
  • Another function in the scripting language is CreateObject. I wanted to post documentation on it to maybe help define how the scripting language is getting or creating the com exposed objects.

    With this information, maybe we can figure out what needs to be done in .NET to get this to work?

    // sample syntax that fails
    let addIn = CreateObject("Superaddin.Connect")

    // documentation for CreateObject

    object CreateObject(string ApplicationName, string manifestFile)

    Category:  Object Model And MSAA

    Return Type:  object

    Return Description:  The automation object associated with the program.

    string ApplicationName
    Name of the COM class that can return an automation object.

    string manifestFile
    name of the manifest file that contains the Registration Free COM information or the name of a .dll with a manifest embedded as a resource. Providing this parameter allows creation of an object that hasn't been registered. More information can be found  at http://msdn.microsoft.com by searching for "Registration Free COM"

    Description:  In applications such as Internet Explorer or those found in Microsoft Office, launches an application under the control of the screen reader application, which is the automation object. The difference between CreateObject and GetObject is that GetObject creates a pointer to an automation object that already exists, whereas CreateObject creates the automation object for the application.


    /Frekster
    Thursday, December 23, 2010 3:51 PM
  • 1:  What is the scripting language in question?  You say 'we can figure out what needs to be done in .NET to get this to work', but if a normal C# console app works I don't think the problem is 'in .NET', it sounds like the problem is in the scripting language or our use of it.  It sounds like VS and the CLR are doing everything properly here from what I can tell.  If a normal C# app doesn't need anything 'extra' to access the Object property then the scripting language shouldn't either.

    2:  Have you tried accessing other properties/methods on the AddIn object?  Do they all work (i.e. is it JUST the Object property?)


    3:  There really are no such things as properties in COM, the .Object is really (behind the scenes) a call to get_Object(IDispatch **ppDisp).  Perhaps the scripting language isn't translating the property -> accessor correctly and thus is trying to look up a method called Object on the IDispatch and that fails.  You could try .get_Object but I am not sure how you would represent the out parameter in the scripting language calling convention.

     

    Ryan

    Friday, December 24, 2010 5:27 PM
  • The scripting language I am using is freedom scientific's jaws for windows scripting language found at www.freedomscientific.com. Jaws is a screen reader for blind/visually impaired programmers that comes with it's own scripting language. Jaws, the screen reader, is able to get the addin under both vs 2003 and vs 2005 and use its properties and methods fine such as DisplayMessage(). However, under vs 2010 Jaws, the screen reader, cannot get a working copy of the addin and crashes vs 2010. Under vs 2010, when I try to use other properties on the addin such as .Name or .ProgID it crashes vs 2010. At this point I am stumped. This routine worked fine under vs 2003, vs 2005, I've not tried under vs 2008, but crashes and does not work under vs 2010.

    Could this be a bug in vs 2010? How can I tell if it is vs 2010 causing the issue or the screen reader?

    thanks for the help!


    /Frekster
    Monday, December 27, 2010 11:59 AM
  • Accessing the Object property on an unloaded AddIn crashes 2010, but from what I can tell the code that was having a problem was also present in 2008 and would likely also crash 2008, and maybe even earlier.  I have since fixed that code to just return null for Object on an unloaded AddIn and not crash VS.  

    I haven't seen 2010 crash in the above scenario.  As for working in 2003 and 2005, lots has changed since then including the CLR, so it is hard to say what is 'not working' at this moment.  

    If you have crashes can you get callstacks from them?  That would be very helpful.  You would need to attach a debugger to VS (VS or another debugger such as Windbg), if you are in VS you would need to uncheck the 'Enable Just My Code' in the Tools->Options->Debugging page, and also set up the symbol server to point at the public Microsoft symbol server.  Also if you attach VS to VS you need to change the 'debug' type to include both native and managed code.  If you then repro the crash we should have a callstack to work with, which might give more visibility into what is happening exactly.

     

    Ryan

    Monday, December 27, 2010 5:09 PM
  • Thanks Ryan for the update. I will try to get you a callstack. Stay tuned!
    /Frekster
    Monday, December 27, 2010 9:19 PM
  • Ryan,

    Here is some information that I have discovered. I think it will be helpful.

    JAWS uses IDispatch for all COM related calls.
    In this case, everything works fine until JAWS obtains an IDispatch pointer to an addin.
    Any calls on that IDispatch to retrieve things like the Addin's name, the AddIn itself by means of the .object member, etc. results in Visual Studio throwing an exception.
    You can confirm this by Debugging Visual studio itself and noticing that a first-chance Access Violation occurs in code that isn't considered to be part of a module.
    This might be JHIT compiled code, but the WinDbg SOSEx extension can't find any sort of managed stack.

    The easiest tool to repro with is a small sample written in JScript and which can be run by calling
    cscript /nologo addins.js
     
    Notice that when you run this with Visual Studio 2005 or 2003 and I expect 2008 as well, you actually get the name of the addin.
    When run with 2010 (even with the ServicePack beta installed) you get the aforementioned error.
     
    The jscript code is:
     
    // Only one GetObject line should be  uncommented
    //7.1 is Visual Studio 2003, 8.0 is Visual Studio 2005, 9.0 is 2008, etc.
    //var vs = GetObject("", "VisualStudio.DTE.8.0");
     var vs = GetObject("", "VisualStudio.DTE.10.0");
    var addins= vs.addins;
    WScript.Echo(addins.count);
    for (i = 1;i <= addins.count;i++)
    {
     var one =addins.item(i);
     WScript.Echo(one.name);
    }

    In the short run, should I  try registering my AddIn in the RunningObjectTable and getting a pointer to it directly?
    This isn't nearly as nice because I'll need to make it an autoload addin, where in the other case I could set it's connected field to true and cause it to load when the JAWS scripts first want to use it.

    Please let me know your thoughts.

    Thanks man!


    /Frekster
    • Marked as answer by Victor_Chen Tuesday, December 28, 2010 7:30 AM
    • Unmarked as answer by Victor_Chen Wednesday, December 29, 2010 2:08 AM
    Tuesday, December 28, 2010 6:19 AM
  • Hi Frekster,

     

    Thanks for sharing.

     

    Best Regards,

    Ziwei Chen

     


    Ziwei Chen [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, December 28, 2010 7:31 AM
  • Hi Ryan,

    I see my last post was marked as an answer by Victor to the problem. Will this be a fix to visual studio 2010 in the near future? Should I open this as a bug? I just need to know what the next step should be for me, if anything.

    Honestly, I am still a bit foggy to what the problem is in general and what my work around should be.

    Can you please offer me a suggestion on how I can get this to work?

    thanks for the feedback!


    /Frekster
    Tuesday, December 28, 2010 10:25 AM
  • Victor shouldn't have marked that as the answer, as it isn't really an answer :)  I will try and take a look at your repro today.  Access Violations generally are either someone using a bad COM pointer (i.e. they treat an IFoo as an IBar either via a direct cast or by passing in an IFoo ** as a void ** param and the callee expects to write an IBar ** through the void **) or a ref counting issue (i.e. someone is trying to use an already deleted object).


    Ryan

    Tuesday, December 28, 2010 5:15 PM
  • Hi Ryan,

    I see my last post was marked as an answer by Victor to the problem. Will this be a fix to visual studio 2010 in the near future? Should I open this as a bug? I just need to know what the next step should be for me, if anything.

    Honestly, I am still a bit foggy to what the problem is in general and what my work around should be.

    Can you please offer me a suggestion on how I can get this to work?

    thanks for the feedback!


    /Frekster


    Well I opened a bug on this, I can repro the crash when I run your script against 2010 and not when run against 2008. It appears that the code that is calculating the address of the IDispatch function to call is getting a very random value and then executing the random memory garbage it finds there.  That code is all in ATL and is (as far as I know) unchanged in 2010, so it is a mystery to me (at the moment) WHAT is going wrong exactly.

    As for getting this to work, it isn't clear yet.  You could, as you said, try registering your AddIn object directly in the ROT, and having your script expect that / retrieve it.  That seems less than ideal, but until we could figure out WHY this is crashing I can't think of any other way to go about it. 

    I am on vacation right now so I won't have a ton of time to look into this in the next few days, but I did file a bug and I can make sure someone looks into it, it seems kind of nasty that VS is crashing like this on what seems to be a fairly straight forward automation scenario.

    Ryan

    • Marked as answer by Frekster12 Thursday, December 30, 2010 12:50 PM
    Tuesday, December 28, 2010 7:54 PM
  • lucky you... vacation! Wooh could I use one too :)

    Thanks for filing this as a bug. Should I just watch this post for an update on the status of the bug or look somewhere else to follow the status of it?

    Have a good vacation and don't think of work at all!

    thanks!


    /Frekster
    Tuesday, December 28, 2010 9:55 PM
  • The bug isn't exposed publically (it wasn't a Connect bug, I just opened one in our internal database), but I can post here if we make any near term progress. It is probably too late for it to make it into SP1 since the Beta is already out and I think they are quickly finalizing any 'last minute' changes before releasing it.  So that means it would either be a hotfix (probably not likely as we haven't seen this effecting a lot of users) or fixed in Dev11.  However, knowing the actual root cause and what caused the regression may point out other, less terrible work-arounds than registering your AddIn in the ROT.  Also feel free to follow up if you want to know the status, my mail is rmolden AT microsoft DOT com.

    Ryan

    Tuesday, December 28, 2010 10:49 PM
  • A co-worker much smarter than myself made quick work of this bug (I think, I haven't verified his explanation yet but it makes sense and would explain the crash). 

    Long story short there was a re-ordering in the base class list of a type (in C++) which invalidated an assumption that one of the base classes was making about a pointer that was being passed to it. Or in other words, we found some really unmaintainable code :)

    I think the fix is understood and I will try one possible fix out tomorrow and see if it fixes the crash, I suspect it will. If it does I will push for inclusion in SP1 since it is a crashing bug, but as I said the deadline is VERY soon (maybe already passed) so I can't guarantee it will go in SP1, just that if my co-worker is correct the fix is trivial and pretty much 0 risk, so I think we can make a strong argument for including it.

    That said, it also means there is no work around on your side as the bug is in the type itself and there is no way to avoid it as it occurs in the constructor :(

    Ryan

    Wednesday, December 29, 2010 12:28 AM
  • Hi Frekster and Ryan,

     

    Thanks for correcting, I'm going to unmark it.

     Best Regards,

    Ziwei Chen

     


    Ziwei Chen [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, December 29, 2010 2:10 AM
  • Thanks so much Ryan for the update on this man. Please, push man push for it in SP 1! :) We are upgrading to vs 2010 in jan and having this in place means I can have a little more job security as jaws  could stand to use an addin in vs 2010 to give us blind programmers a more accessible experience.

    Also, I am just trying to follow, are you saying that manually registerring my addin in the ROT is not going to work based on the bug?

    Thanks for the information!


    /Frekster
    Wednesday, December 29, 2010 7:39 AM
  • If you register your AddIn it should work fine.  The bug is in the class that implements the DTE::AddIn interface itself, so if you manually register the item that the AddIn object would return from its Object property that should be fine.

    Ryan

    Wednesday, December 29, 2010 4:43 PM
  • Thanks Ryan for the response. I have a question on registerring the addin into the rot. This is actually a new topic, but hopefully I can just toss it out here for now :)

    In the addin project, I have this code:

    public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
            {
                _applicationObject = (DTE2)application;
                _addInInstance = (AddIn)addInInst;
               
                 // For now, let us know that we launched the Add-in.
               
                MessageBox.Show("JAWS VS 2010 Add-In has been Initialized.");
                this.rotID = ROTHook.AddToROT(this, "JawsVS2010AddIn.Connect");
                MessageBox.Show(Convert.ToString(this.rotID));
            }

    This registration is working fine. The message box showing the rotID is typically in the 32000 range and is different each time.

    The problem is that jaws, the screen reader, still cannot get an instance of it from the ROT and I think it is from how I am registerring it.

    Below I'll paste in my ROTHook.cs class for a quick skim. I think I need to use the following flag when registerring my class into the ROT:

    private const int ROTFLAGS_ALLOWANYCLIENT = 2;

    when I set it to allow any client though, I get the following error:

    Error Message: The class is configured to run as a security id
    different from the caller
    Error number: 80004015

    If I use the flag:

    private const int ROTFLAGS_REGISTRATIONKEEPSALIVE = 1;

    the addin loads fine but my client - the screen reader - cannot get an instance from the ROT. Here is my C#.NET class. Can you please let me know if something stands out that may not be correct or perhaps how I can fix this error so I can use the ROT as a work around?

    Thanks for the help!
    namespace JawsVS2010AddIn
    {
        using System;
        using System.Collections;
        using System.Collections.Generic;
        using System.Data;
        using System.Diagnostics;
        using System.Runtime.InteropServices;
        using System.Runtime.InteropServices.ComTypes;

       

        public class ROTHook
        {
            #region Fields

            private const int ROTFLAGS_ALLOWANYCLIENT = 2;
            private const int ROTFLAGS_REGISTRATIONKEEPSALIVE = 1;

            #endregion Fields

            #region Methods

            public static int AddToROT(object obj, string classID)
            {
                //----------------------------------------------------------------------------------
                // AddToROT
                //
                // Abstract - Adds a Reference to This Object to the Runtime Object Table
                //
                // Parameters
                //               obj Object to register
                //               classID Class ID of object to register
                //
                // Return Value  cookie to revoke ROT registration
                //----------------------------------------------------------------------------------
                int cookieValue = 0;
                IRunningObjectTable rot = null;
                IMoniker moniker = null;

                try
                {
                    //---- Get the ROT -------------------------------------------------------------
                    rot = GetRunningObjectTable(0);
                    //--- Create a moniker ---------------------------------------------------------
                   
                    moniker = CreateItemMoniker("!",  classID);
                    //--- Registers the object in the running object table -------------------------
                    cookieValue = rot.Register(ROTFLAGS_ALLOWANYCLIENT, (IJawsVS2010AddIn)obj, moniker);

                   
                    return cookieValue;
                }
                finally
                {
                    // Releases the COM objects
                    if (moniker != null)
                        while (Marshal.ReleaseComObject(moniker) > 0) ;
                    if (rot != null) while (Marshal.ReleaseComObject(rot) > 0) ;
                }
            }

            public static object GetActiveObject(string objName)
            {
                //----------------------------------------------------------------------------------
                // GetActiveObject
                //
                // Abstract - Gets an Instance of this Object from the Runtime Object Table
                //
                // Parameters
                //
                // Return Value  Object found or Nothing if Not Found
                //----------------------------------------------------------------------------------
                try
                {

                    object ROTObject = null;
                    IRunningObjectTable runningObjectTable = null;
                    IEnumMoniker monikerEnumerator = null;
                    IMoniker[] monikers = new IMoniker[2];

                    runningObjectTable = GetRunningObjectTable(0);
                    runningObjectTable.EnumRunning(out monikerEnumerator);
                    monikerEnumerator.Reset();
                    IntPtr numFetched = new IntPtr();
                    while ((monikerEnumerator.Next(1, monikers, numFetched) == 0))
                    {
                        IBindCtx ctx = null;
                        ctx = CreateBindCtx(0);
                        string runningObjectName = "";
                        monikers[0].GetDisplayName(ctx, null, out runningObjectName);
                        runningObjectName = runningObjectName.ToUpper();
                        if ((runningObjectName.StartsWith(objName.ToUpper())))
                        {
                            object runningObjectVal = null;
                            runningObjectTable.GetObject(monikers[0], out runningObjectVal);
                            ROTObject = (object)runningObjectVal;
                            return ROTObject;
                        }
                    }

                    return ROTObject;
                }
                catch (Exception Exc)
                {
                    throw Exc;
                }
            }

            public static object GetObjectByClassID(string objClassID)
            {
                return GetActiveObject("!{" + objClassID + "}");
            }

            public static object GetObjectByClassName(string objClassName)
            {
                return GetActiveObject("!" + objClassName);
            }

            public static void RemoveFromROT(int myCookie)
            {
                if (myCookie != 0)
                {
                    IRunningObjectTable  rot = null;
                    try
                    {
                        // Get the running object table and revoke the cookie
                        rot = GetRunningObjectTable(0);
                        rot.Revoke(myCookie);
                        myCookie = 0;
                    }
                    finally
                    {
                        if (rot != null) while (Marshal.ReleaseComObject(rot) > 0) ;
                    }
                }
            }

            [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
            private static extern IBindCtx CreateBindCtx(int reserved);

            [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
            private static extern IMoniker CreateItemMoniker(string lpszDelim, string lpszItem);

            [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
            private static extern IRunningObjectTable GetRunningObjectTable(Int32 reserved);

            #endregion Methods
        }
    }


    /Frekster
    Wednesday, December 29, 2010 5:08 PM
  • I forgot to mention that I am not using any of the GetXXXX methods in the ROTHook.cs class. I.E the GetActiveObject etc. The screen reader has its own GetObject method in the scripting language that I am using outside of vs 2010 to look at the rot for the class id. I am only useing the AddToROT and RemoveFromROT methods in the ROTHook.cs class. Just wanted to  clarify that point.
    /Frekster
    Wednesday, December 29, 2010 5:12 PM
  • I am far from a ROT expert (in fact I have never used it :)), but this part in AddToROT seems wrong

    finally
    {
      // Releases the COM objects
      if (moniker != null)
        while (Marshal.ReleaseComObject(moniker) > 0) ;
      if (rot != null) while (Marshal.ReleaseComObject(rot) > 0) ;
    }
    
    
    

    You normally NEVER want to spin on ReleaseComObject (really you should never call it, Chris Brumme wrote a good blog post as to why that is: http://blogs.msdn.com/b/cbrumme/archive/2003/04/16/51355.aspx) as it will KILL the RCW which means if anyone else has a reference to it, the next time they try to use it it will throw an exception.  In this case, since the COM objects are coming back to you as RCWs (i.e. they look like normal .NET interfaces not IntPtrs or anything) you don't need to call ReleaseComObject (or any Release) on them, treat them like any other garbage collected object. When the last reference dies the CLR will call Release on the underlying COM object, which is proper.

    I also wasn't aware you could transform Windows API calls that had out params and turn them into returning the RCW directly, that is nifty :)

    Apart from that it seems kosher, the only question is are both VS and your script code running under the same permissions?  I.e. if one running as Admin then both probably have to be.

    Looking on Bing it looks like there may be more fun on Vista+ (see here: http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.ole/2007-06/msg00039.html), it refers to an article here: http://msdn.microsoft.com/en-us/library/ms679687.aspx  At the bottom that article talks about 'COM Servers and ROT', not sure if it applies here, but it may.

    In other news I confirmed my co-worker's hunch and his simple fix of re-ordering the base classes prevents the crash and causes the script to output the correct count and name of each AddIn.  I added a note to the bug to our triage team strongly suggesting we try and get it in SP1 as the fix is trivial, but that decision rests with 'the powers that be'.

    Ryan

    Thursday, December 30, 2010 1:35 AM
  • Any word on this bug yet? I will try creating a test addon and seeing if I can get JAWS to see it somehow (Now that I have SP1 rtm).
    Sunday, March 20, 2011 12:26 AM
  • I have fixed the bug, I don't know if the fix was included in SP1, I would have to look on Monday.

    Ryan

    Sunday, March 20, 2011 4:56 AM
  • It does not look like this fix was put into SP1.

    Ryan

    Monday, March 21, 2011 5:01 PM
  • Hi Guys,

    Great interaction here.

    And Ryan, great that you were able to have the bug fixed.

    I am a blind vs C#.Net developer.  I also use the Jaws screen reader.

    I am currently on VS 2010 and Jaws 12.

    I am experiencing this crash in VS when using Jaws while coding business applications for my job.

    Our team has migrated to VS 2010, and Now I have to stay in 2008 due to the problem.

    Also, I cannot work on vs 2010 projects with them, since the solution files etc are all different.

    Do we have any scripts or solutions that can address this issue between Jaws and VS 2010 at this point?

    I'll sincerely appreciate any pointers.


    I  Currently have the VS 2005 and 2008 Jaws scripts installed, but they obviously do not get the job done to enable Jaws to interact with VS 2010, like Flexter rightfully mentioned.

    Flexter, where are you on creating your addin for Jaws and VS 2010?

    You'll save my life if you can help us get to that point.

     

    Thanks again.

    Friday, March 25, 2011 9:43 PM
  • Hi,

    I have  a start for a plugin over at http://jawsvs2010.codeplex.com

    You can get the code since it is open source.

    Compile it and set the compiled dll to be an add in that loads when vs 2010 loads.

    It gives code editor support but no designer support.

    If you'd like to join the project to contribute, please let me know... we are always looking for helpers!

    thanks,
    Justin


    /Frekster
    Friday, March 25, 2011 10:46 PM
  • Is there a hotfix or planned release for this issue?  

     

    We have a VS addin that is normally in the not connected state, but is turned on programmatically.  When trying to find our addin from JavaScript, VS 2010 SP1 crashes.  The failure mode seems to match the issue being discussed in this thread.  The JavaScript which reproduces the problem is below.

        var Addin;

        var DTE = new ActiveXObject("VisualStudio.DTE.10.0");

        DTE.MainWindow.Visible = true;

        DTE.MainWindow.WindowState = 0; 

        for (var i = 1; i <= DTE.AddIns.Count; i++ ) {

            if (DTE.AddIns.Item(i).Name == "Desired Add-in") {  <=== Crashes when trying to access DTE.AddIns.Item(i).  

                Addin = DTE.DTE.AddIns.Item(i);

            }

        }

    This is also repeatable from within the Visual Studio 2010 debugger while trying to debug this script.  Expand the DTE -> AddIns -> [0] object and the Visual Studio programmatically started will crash.  This worked fine with Visual Studio 2008 and earlier.

     

    Wednesday, August 24, 2011 9:07 PM
  • There is no hotfix planned that I am aware of.  Yes, it was an unnoticed regression in 2010 (as you pointed out it worked in 2008) and it has since been fixed (internally), but I don't believe that fix has 'left the building' in any form.

    Ryan

    Wednesday, August 24, 2011 9:39 PM
  • Ryan,

     

    Thanks for the quick response.  Is there an issue number that we can reference that would allow us to track when this actually releases?

     

    Stephen

    Thursday, August 25, 2011 1:07 PM
  • I don't know what an issue number would be if one exists as I believe those are created by CSS (customer support folks).  The internal bug # was 111219, if you contact CSS they may be able to query if there is an issue number associated with it, or create one.  I don't believe this fix would be released outside a normal servicing / product release cycle unless there was a customer case for it, I can't create such a case myself.

    Ryan

    Sunday, August 28, 2011 9:48 PM
  • Ryan,

    Can you please confirm if this fix has been deployed to visual studio 2012 rc that was just released?

    /Frekster


    /Frekster

    Friday, June 8, 2012 1:57 PM
  • I haven't had a time to test this yet (maybe later today I will), but I am 99.9% sure it would be in 2012.  The reason it wasn't in SP1 was due to our code orginization, basically we have a branching structure that looks roughly like this:

    RTM ---------------------------> SP1

         \                                    /

           vNext ------------------/-------->

    So as my amazing ascii art shows there is a fork of RTM after we ship (internally this is generally refered to as MQ or vNext).  In this branch bug fixes/early work for the next version begins.  SP1 work also happened in this branch afaik.  At some point the code changes from vNext that are going into SP1 are ported to a special release branch just for SP1. For whatever reasons the fix for this particulat bug did not go over to SP1 (there was at least one other I feel should have been in SP1 but wasn't :(). I am not sure who made the porting decisions as to what would be in SP1, I wasn't involved in that process.

    That said, since vNext continues forward after that break line and becomes the base for the next version (2012 in this case) then ALL changes (regardless of if they were in SP1 or not) will be included in vNext.

    Ryan

    Monday, June 11, 2012 6:05 PM