Overview:
The VSTA v 2 ProxyGen tool creates proxy files designed to work with the updated C# compiler in Visual Studio 2008. It is possible to modify this file to compile with Visual Studio 2005 and we offer a Visual Studio macro to automate these changes.
Attached macro:
The zip file attached includes all the macro code necessary to perform the modifications so that the VSTA v 2 proxy will compile in Visual Studio 2005. Either copy the macro code into the local recording module and call the VS05_VSTA2_Fix method from the temporary macro, or load it as a macro project and run the VS05_VSTA2_Fix method. This macro is intended to be run on an unmodified proxy and may not work if run on a proxy with certain modifications. It is important to have the proxy file selected in Visual Studio because this macro works on the active document. If you do not have SP1 for Visual Studio 2005 you may not be able to run macros.
Details:
What in the proxy is dependent on the Visual Studio 2008 compiler?
Lambda expressions. Lambda expressions are new to Visual Studio 2008 and offer a more concise syntax to use anonymous methods (Vipul Patel’s article on Lambda Expressions). They are an improvement on syntax that is available in C# (.Net ) 2.0 which Visual Studio 2005 is based on. These expressions are used in the VSTA v 2 proxy for delayed event hookups.
Why are delayed event hook ups needed?
Delayed event hook ups are needed to deal with Visual Basic add-ins. Visual Basic can add events in the constructor prior to initialization. C#, which the proxy is written in, does not allow events to be hooked up to the remoteObject prior to it being initialized. The VSTA v 2 proxy solves this by delaying the event hookup with lambda expressions. Below is an example of Visual Basic hooking up an event to an uninitialized object (Me). VSTA v 1 had a different solution which will not work in VSTA v 2. To see exactly how and when Visual Basic add-ins hook up events step through the loading process of a visual basic add-in that uses the Handles keyword to attach to an entry point class event (like CreateDrawing not Startup).
Private Sub AppAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup
End Sub
Private Sub do_something(ByVal sender As Object, ByVal e As CreatedDrawingEventArgs) Handles Me.CreatedDrawing
End Sub
Where is the offensive code?
The lambda expressions are found in the event hookups of the entry point classes. These expressions are used to add a hook up to a list of delegates. The delegates are invoked once the remoteObject for the entry point class is initialized and the entry point class events are hooked up then.
For example, in the ShapeApp samples the Created Drawing event in the ApplicationEntryPoint class is shown below (the namespace has been abbreviated). This event adds the event hook up to the delayedEventList. The delegates in this list are invoked, there by executing the hookups, once the remote object has been initialized. An exert from the Microsoft.VisualStudio.Tools.Applicaitons.Runtime.IEntryPoint.Initialize method, which is included in all entry point classes, shows that the method which invokes the delegates is called once the remoteObject has been set. The region which holds the delegate list and the methods which work with it is also included below.
public partial class ApplicationEntryPoint
{
void Initialize(global::System.IServiceProvider serviceProvider)
{
//Initialize the remote object
this.remoteObject = this.providerHost.GetHostObject(this.PrimaryType, this.PrimaryCookie) as Application;
//Hook up the delayed events
this.InitializeDelayedEvents();
}
public event global::ShapeApp.CreatedDrawingEventHandler CreatedDrawing
{
add
{
if (this.remoteObject != null)
((ShapeApp.Application)(this.remoteObject)).CreatedDrawing += value;
else //add the hook up to the delayed event list
GetDelayedEventsList().Add(
() => ((ShapeApp.Application)(this.remoteObject)).CreatedDrawing += value);
}
remove
{
if (this.remoteObject != null)
((ShapeApp.Application)(this.remoteObject)).CreatedDrawing -= value;
else //add the hook up to the delayed event list
GetDelayedEventsList().Add(
() => ((ShapeApp.Application)(this.remoteObject)).CreatedDrawing -= value);
}
}
#region Delay adding event handlers
private delegate void MethodInvokerNoArg(); //delegate for list
// Because VB will add event handlers, if there are any, in constructor, we need to delay that until remoteObject has been initialized.
private global::System.Collections.Generic.List<MethodInvokerNoArg> delayedEventsList; //list of delegates
private global::System.Collections.Generic.List<MethodInvokerNoArg> GetDelayedEventsList() //returns list of delegates
{
if ((this.delayedEventsList == null))
{
this.delayedEventsList = new global::System.Collections.Generic.List<MethodInvokerNoArg>();
}
return this.delayedEventsList;
}
private void InitializeDelayedEvents() //invokes delegates
{
if (this.GetDelayedEventsList() != null)
{
foreach (MethodInvokerNoArg invoker in this.GetDelayedEventsList())
{invoker();}
this.GetDelayedEventsList().Clear();
}
}
#endregion
}
Workaround for Visual Studio 2005:
The workaround uses an event, InitDelayedEvents, which is raised after the remoteObject is initialized to hook up the events. The InitDelayedEvents is hooked up to by the events in the entry point class when the entry point class event would be added to the un-initialized remote object.
In the example below, the InitDelayedEvents event is declared within the entry point class and replaces the “Delay adding event handlers” region. The entry point class event, CreatedDrawing, hooks up this event instead of adding the hookup to a list. The event is raised in the Initialize method after the remoteObject is initialized, instead of calling the InitializeDelayedEvents method.
public partial class ApplicationEntryPoint
{
private event System.EventHandler InitDelayedEvents;
void Initialize(global::System.IServiceProvider serviceProvider)
{
//Initialize the remote object
this.remoteObject = this.providerHost.GetHostObject(this.PrimaryType, this.PrimaryCookie) as Application;
//Hook up the delayed events if there are any
if (InitDelayedEvents != null)
InitDelayedEvents(this, null);
}
public event global:: ShapeApp.CreatedDrawingEventHandler CreatedDrawing
{
add
{
if (this.remoteObject != null)
((ShapeApp.Application)(this.remoteObject)).CreatedDrawing += value;
else //add the hook up to the InitDelayedEvents event
{
InitDelayedEvents += delegate
{ ((ShapeApp.Application)(this.remoteObject)).CreatedDrawing += value; };
}
}
remove
{
if (this.remoteObject != null)
((ShapeApp.Application)(this.remoteObject)).CreatedDrawing -= value;
Else //add the hook up to the InitDelayedEvents event
{
InitDelayedEvents += delegate
{ ((ShapeApp.Application)(this.remoteObject)).CreatedDrawing -= value; };
}
}
}
}