The Pain of deploying Primary Interop Assemblies

Alright, first of all, what are Primary Interop Assemblies (PIAs), and why am I devoting an entire post to the pains caused by deploying these things? And secondly, how do they relate with what we’ve been talking about thus far?

The answer to the second question is simple – we’ve been talking about COM Interop in C# lately, and we’ve just gotten through how C# 4.0 makes Indexed Properties accessible in a first class manner. Primary Interop Assemblies fit to the picture because they’re the way that COM interfaces are exposed into the managed world. In essence, they’re the bridge that allows you to use native COM “stuff” from your managed programming models. They are typically large API collections that must be deployed along with client applications.

What’s wrong with deploying PIAs?

Well, nothing’s wrong with it per se, but it certainly isn’t pleasant for developers who just want to write code against a COM API to have to worry about shipping the PIAs onto the client box, worry about whether or not it already exists on the client machine and what version lives there and all that. As I’ve mentioned before, you can think of the overarching theme of C# 4.0 as an interop release, one where we attempt to make interoping with different programming models and paradigms much simpler and much more first class.

How can we ship our apps without PIAs?

Avid readers will recall another interop feature that we’ve discussed in great length – dynamic. Since dynamic pushes dispatches operations at runtime, and is designed to statically use the C# paradigm while dynamically determining the runtime paradigm, wouldn’t it be perfect for this? We could compile everything without the PIAs, and therefore avoid shipping them altogether, and have the DLR deal with dispatching to the COM APIs at runtime.

Genius!

Turns out that is a perfectly viable solution – you can make all your COM calls dispatch dynamically and can avoid mentioning the PIA altogether.

So… we’re done then?

Well, this wouldn’t be much of a post if we were now, would it?

Turns out there are some costs associated with dynamic. The first cost is that you have limited intellisense. Because you’re constantly working with dynamic variables, the IDE can’t give you any guidance and useful information because it frankly doesn’t have any to give you. This means that developers new to your code base and new to using the COM APIs will struggle with them quite a bit, and will take more time to start being productive in your organization.

Secondly, there is a performance cost associated with dynamic. Now, granted we’re working very hard to minimize that impact, you cant get something for nothing. Namely, you can’t push your static binding to runtime, and expect it to be just as performant as a call bound statically.

Lastly, there are certain scenarios that I wont go into here where the dynamic COM binder isn’t able to perform the binding without information from the PIA. My colleague Misha Sneerson’s blog has more info about this.

Because of these limitations, we’ve applied some great minds to the problem, and have come up with a solution which addresses both of these concerns, and helps you get the best (well, most of it) from both worlds.

Type Equivalence

I’ll briefly outline the general idea of the solution that we’ve implemented in the 4.0 runtime right now, and go into more detail later.

Type equivalence is the notion that two different types are considered to be equivalent at runtime – that is, they’re considered interchangeable. Even though the types themselves are not the same managed type, allowing them to be equivalent means that one can use them interchangeably and not notice any difference.

Consider this brief example.

Assembly a.dll contains the following:

 public interface IFoo { }
public class A
{
    public IFoo GetFoo() { ... }
}

Assembly b.exe contains the following:

 public interface IFoo { }
public class B
{
    public void UseFoo(IFoo foo) { ... }
    public static void Main()
    {
        B b = new B();
        A a = new A();
        b.UseFoo(a.GetFoo());
    }
}

Notice that today, this can’t work. Assembly a.dll’s IFoo is a different type than assembly b.dll’s IFoo, so the compiler won’t allow you to pass a result of type A.IFoo to a method that takes a B.IFoo to successfully make the call. However, if we were to provide a special mechanism by which the compiler can know that both IFoo types are intended to be equivalent, then the compiler can treat these types as equivalent, and allow the call to go through.

Here’s where we need some cooperation with the CLR. If we can establish some well-known protocol between the CLR and the .NET compilers, then we can essentially agree on a mechanism to mark and treat types as being equivalent.

This is the core of the cross-team type equivalence feature, and is the foundation of the feature known as “No PIA” on the C# team.

Stay tuned for the details on what the feature does, what that contract is, what compiler writers need to do to opt in to the mechanism, and what compiler writers need to do regardless of whether or not they want to opt-in to support the feature.

kick it on DotNetKicks.com

Comments

  • Anonymous
    February 24, 2010
    I open an MS Project (MPP) file, like such:        Dim projectApp As New ApplicationClass()        projectApp.FileOpen(ofdOpen.FileName, True, Missing.Value, Missing.Value, Missing.Value,        Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value,        PjPoolOpen.pjDoNotOpenPool, Missing.Value, Missing.Value, Missing.Value, Missing.Value) And I read the nodes and populate a list , like such: For Each p As System.Reflection.PropertyInfo In proj.GetType().GetProperties()            If p.CanRead Then                Try                    Console.WriteLine("{0}: {1}", p.Name, p.GetValue(proj, Nothing))                    Dim item As RadListBoxItem = New RadListBoxItem                    item.Name = p.Name                    item.Text = p.Name                    item.Value = p.GetValue(proj, Nothing)                    lbNodes.SelectedIndex = 0                    If blnComboboxItemSelected = False Then                        lbNodes.SelectedItem = p.Name                        blnComboboxItemSelected = True                    End If                    lbNodes.Items.Add(item)                Catch ex As Exception                End Try            End If        Next This works in XP Pro having VS 2010 B2. I am able to see a node called “Tasks”. However, it doesn’t in Windows 7 Pro x64 having VS 2010 RC. Some nodes are missing.

  • Anonymous
    February 24, 2010
    Could this be the problem? http://blogs.msdn.com/brismith/archive/2008/02/15/comexception-on-x64-platforms-when-automating-the-project-client-via-the-primary-interop-assembly-pia.aspx

  • Anonymous
    April 17, 2010
    The comment has been removed

  • Anonymous
    April 10, 2013
    Am I mistaken or is this massive, recurring, industry-wide, utter pain entirely avoidable using type provider from F# ?