locked
IVsLaunchPad output to window pane is really choppy. RRS feed

  • Question

  • I am using IVsLaunchPad to run a deployment executable, and piping the output to an IVsOutputWindowPane.  The issue I'm having is that I don't get any output until the executable has finished running, and then I get all of the output at once.  In a different situation, I use the IVsLaunchPad, and I get output once every 15-20 seconds, and it gives me big chunks of the output all at once.

    What is the bottleneck here?  Is there a way to get the IVsLaunchPad to output more regularly?

    Thanks,

    Jonathan

    Tuesday, February 28, 2012 7:25 PM

All replies

  • Are you running this task (the deployment executable) on the UI thread?  The UI can't update until it gets back to the main message loop, which can't happen if you are synchronously running code on the UI thread.

    Ryan

    Tuesday, February 28, 2012 7:35 PM
  • Hmmm.  I was under the impression that using IVsLaunchPad.ExecCommand would spawn its own thread to run the executable in, and pipe that output to the output window pane.  If this is not the case, then that is definitely a problem...

    Jonathan

    Tuesday, February 28, 2012 8:04 PM
  • No, it looks like it runs it synchronously if you are redirecting output (i.e. it has a loop where it reads the spawned processes output pipe and writes it to the output window).  However, I do believe the launch pad factory is free-threaded, so you can use it on a background thread to spawn the launchpad, which will then run synchronously on the bg thread piping its input to the output window there.  Of course if you need to block the UI thread until the remote process finishes that complicates things a bit, but I think you can do it and still have the UI repainting via the IVsCommonMessagePump.

    Ryan

    Tuesday, February 28, 2012 8:42 PM
  • Thanks for the insight.  I will try it out and post on this thread whether or not it fixed my issue.

    Jonathan

    Tuesday, February 28, 2012 8:49 PM
  • Hi xavier19255,

    Have you solve this issue?

    If yes, please mark the useful reply as answer.

    If not, please let us know.

    Thank you for your understanding!


    Lucy Liu [MSFT]
    MSDN Community Support | Feedback to us

    Monday, March 5, 2012 9:42 AM
  • Unfortunately, I haven't had a chance to try this solution.  I had to shift my priorities for the last week.  I should be able to get back to this this week, and I will update the thread accordingly.

    Thank you for understanding,

    Jonathan

    Monday, March 5, 2012 3:26 PM
  • I try to use the IVsLaunchPadFactory in the background thread, but it fails when I try to create a LaunchPad.  I get the following error:

    "Unable to cast COM object of type 'System.__ComObject' to interface type 'Microsoft.VisualStudio.Shell.Interop.IVsLaunchPadFactory'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{C21C16A2-1612-4995-B445-F7B1C1657878}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))."

     I'm probably not marshalling it appropriately, but I can't find a straight answer on how to do this in managed code.

    Jonathan

    Friday, March 9, 2012 2:38 AM
  • The CLR should do the marshalling for you.  Try this

    1:  Get the IVsLaunchPadFactory on the UI thread, pass the reference from here to your background code.

    2:  In your background code use the IVsLaunchPadFactory to create an IVsLaunchPad and invoke it from the BG thread.

    Ryan

    Friday, March 9, 2012 4:19 AM
  • Here's what I've done (this fails):

    public static void LaunchPadTest ()

    {

        IVsLaunchPadFactory launchPadFactory = (IVsLaunchPadFactory)Package.GetGlobalService(typeof(SVsLaunchPadFactory));

        Thread execThread = new Thread(TestClass.LaunchPadTestThread);
        execThread.Start(launchPadFactory);

        return result;
    }


    private static void LaunchPadTestThread(object data) {
        try
        {
            IVsLaunchPadFactory factory = (IVsLaunchPadFactory)data;
            IVsLaunchPad launchPad;
            factory.CreateLaunchPad(out launchPad);
        }
        catch (Exception e)
        {
            string message = e.Message;
        }
    }

        
    Friday, March 9, 2012 8:48 PM
  • In a related thread, someone points out that vsshell.idl contains the following information:

    "....
            // It is specifically possible to create a LaunchPad object on a 
            // background builder thread. In order to do this, the project system should QueryService for the 
            // SVsLaunchPadFactory service from the main UI thread. It should pass the IVsLaunchPadFactory interface 
            // unmarshalled to their builder thread and then call pVsLaunchPadFactory->CreateLaunchPad from the 
            // builder thread in order to create the LaunchPad object on the builder thread. 
            // NOTE: this does slightly bend the marshalling rules of COM but is necessary to get the LaunchPad object 
            // created on the proper thread. The IVsLaunchPad object returned is an Apartment Model object.
    "

    Can you pass something unmarshalled?

    Jonathan

    Friday, March 9, 2012 11:17 PM
  • That comment is non-sensical.  I think what it really means is that technically you don't HAVE to marshal the pointer. The launch pad factory aggregates the free-threaded marshaller, so marshalling is a NOP, but it isn't safe in general to avoid that step under the assumption that it is a NOP.

    What version of VS are you using here? I tried similar code in 2010 and it works fine, I don't get any sort of QI failure.

    edit:  Ahhh, it appears it only started aggregating the free-threaded marshaller in 2010, so prior to that you had to simply not marhsal it.  You can't really get the CLR to do something like that since it breaks COM rules.  You can do something hacktastic like this

    private static T GetDelegateFor<T>(IntPtr thisPtr, int vtblOffset) where T : class
    {
        // Read the vtable pointer from the first DWORD at the this pointer.
        IntPtr vtable = Marshal.ReadIntPtr(thisPtr);
    
        // Compute the offset to the function pointer
        IntPtr vptrFn;
    
        if (Marshal.SizeOf(typeof(IntPtr)) == 4)
        {
            vptrFn = new IntPtr(vtable.ToInt32() + vtblOffset);
        }
        else
        {
            vptrFn = new IntPtr(vtable.ToInt64() + vtblOffset);
        }
    
        // Read the function pointer
        IntPtr fn = Marshal.ReadIntPtr(vptrFn);
    
        // Turn it into a delegate
        Delegate del = Marshal.GetDelegateForFunctionPointer(fn, typeof(T));
    
        return del as T;
    }
    
    private delegate int CreateLaunchPad(IntPtr thisPtr, out IntPtr launchPadRaw);
    
    
    //In your method that will spawn the worker thread
    IVsLaunchPadFactory launchPadFactory = (IVsLaunchPadFactory)GetService(typeof(SVsLaunchPadFactory));
    
    IntPtr punk = Marshal.GetIUnknownForObject(launchPadFactory);
    Thread t = new Thread(delegate(object state)
                          {
                              IntPtr lpRaw = IntPtr.Zero;
                              try
                              {
                                  //CreateLaunchPad is at offset 3 as IVsLaunchPadFactory derives from IUnknown
                                  //and thus the first three v-table slots are AddRef/Release/QI, meaning the first
                                  //IVsLaunchPadFactory method is in v-table slot 3.
                                  int offset = (3 * Marshal.SizeOf(typeof(IntPtr)));
                                  CreateLaunchPad lpDel = GetDelegateFor<CreateLaunchPad>(punk, offset);
    
                                  int res2 = lpDel(punk, out lpRaw);
                                  IVsLaunchPad lp = (IVsLaunchPad)Marshal.GetObjectForIUnknown(lpRaw);
    
                                  //Use IVsLaunchPad here
                              }
                              finally
                              {
                                  if(punk != IntPtr.Zero)
                                  {
                                      Marshal.Release(punk);
                                  }
     
                                  if(lpRaw != IntPtr.Zero)
                                  {
                                      Marshal.Release(lpRaw);
                                  }
                              }
                          });
    t.Start();

    Ryan


    Saturday, March 10, 2012 6:31 PM
  • We have to support VS2005, 2008, and 2010, and would like a solution that works with the CLR, and not hack our way around it, so we decided to find an alternative solution to IVsLaunchPad.  I had no idea starting out that it would be such a pain.  I really appreciate all of your help.

    Jonathan

    Monday, March 12, 2012 6:32 PM
  • Yeah, it is unfortuante that the original IDL/object was 'free-threaded' by telling people to simply not marshal it :( 

    The hack above technically is valid based on that advice (i.e. that marshalling is unecessary), and only relies on:

    1:  The call to GetIUnknownForObject calling AddRef on the underlying COM object (it has to).

    2:  The comment being accurate that it is safe for cross-thread usage without marshalling (it is, I checked, but this is the step that is technically 'against the rules' in COM (i.e. going cross-thread with an object pointer without marshalling it)).

    3:  The v-table layout of the object (this is guaranteed by COM).

    That said it is kind of gross to have to do all of that :(

    Ryan

    Monday, March 12, 2012 8:48 PM