可供Office多种版本使用的无PIA或_VtblGap的加载外接程序

原文作者:Andrew Whitechapel

原文链接: http://blogs.msdn.com/andreww/archive/2008/06/24/add-ins-for-multiple-office-versions-without-pias-pt2-or-vtblgap.aspx 

发布时间: 2008624

 

      在我的上一篇文章中讨论了如何避免依赖Office PIAs,方法是使用ComImport重新定义主程序的OM接口。一个开发人员指出我实际上省略了trailing两个成员的IRibbonControl接口,我提到这不会阻止代码的运行。我同时也提到了,省略接口成员在技术上是非法的,所以今天我就解释一下我的意思吧。

      我会给这篇文章附上sample solution(样例解决方案),你可以和上次文章所附的sample solution进行比较,你会发现他们是相同的,除了一点:这次的解决方案中,声明ComImport时我省略了选择接口成员。原因是我不想包含我没有在使用的接口成员的定义。你可能认为我省略全部的成员的话,对于trailing成员的确可以,但是对于非trailing成员来说,就不确定。严格来说,就是trailing成员,这样做也不太好。

     原因是这些COM接口,以及一个接口中定义的方法的编号和位置,都是极为重要的。在运行时,这些方法以vtable(虚拟函数表)的形式出现,这个vtable能安排与方法的通信,顺序已经在接口中定义好了。因为访问COM接口,是通过访问vtable从表头开始的偏移(或者说是实现接口的对象的vtable)来实现的,这意味着成员的顺序/位置和编号是非常重要的。

     当你使用ComImport重新定义一个COM接口时,顺序和编号必须保持一致,.NET Framework提供了成员语法来支持这个操作。为了保留vtable顺序,可以使用一个特殊的special _VtblGapXX_YY方法来表示丢失的成员,XX代表vtable中的位置,YY代表了可以从这个位置开始忽略的vtable成员的的编号。 例如,使用如下的声明,可以在_CustomTaskPane这个接口,从位置1,忽略vtable的2个成员:

    void _VtblGap1_2();

     这个语法允许我保留我没有定义的成员的vtable,并且使接下来的成员处于正确的位置。如下是custom task pane的COM接口导入的设置。注意,借助省略DockPosition和DockPositionRestrict成员,我可以避免声明MsoCTPDockPosition和MsoCTPDockPositionRestrict枚举。 

//public enum MsoCTPDockPosition

//{

//    msoCTPDockPositionLeft,

//    msoCTPDockPositionTop,

//    msoCTPDockPositionRight,

//    msoCTPDockPositionBottom,

//    msoCTPDockPositionFloating

//}

 

//public enum MsoCTPDockPositionRestrict

//{

//    msoCTPDockPositionRestrictNone,

//    msoCTPDockPositionRestrictNoChange,

//    msoCTPDockPositionRestrictNoHorizontal,

//    msoCTPDockPositionRestrictNoVertical

//}

 

[ComImport, Guid("000C033B-0000-0000-C000-000000000046"), TypeLibType((short)0x10c0), DefaultMember("Title")]

public interface _CustomTaskPane

{

    [DispId(0)]

    string Title { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(0)] get; }

 

    // From position 1, we omit 2 vtbl members.

    void _VtblGap1_2();

    //[DispId(1)]

    //object Application { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(1)] get; }

    //[DispId(2)]

    //object Window { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(2)] get; }

 

    [DispId(3)]

    bool Visible { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(3)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(3)] set; }

    [DispId(4)]

    object ContentControl { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(4)] get; }

 

    // From position 5, we omit 1 vtbl member.

    //void _VtblGap5_1();

    void _VtblGap_1();

    //[DispId(5)]

    //int Height { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(5)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(5)] set; }

 

    [DispId(6)]

    int Width { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(6)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(6)] set; }

 

    // From position 7, we omit 3 vtbl members.

    void _VtblGap7_3();

    //[DispId(7)]

    //MsoCTPDockPosition DockPosition { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(7)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(7)] set; }

    //[DispId(8)]

    //MsoCTPDockPositionRestrict DockPositionRestrict { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(8)] get; [param: In] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(8)] set; }

    //[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(9)]

    //void Delete();

}

 

[ComImport, Guid("000C033B-0000-0000-C000-000000000046")]

public interface CustomTaskPane : _CustomTaskPane

{

}

 

[ComImport, Guid("000C033D-0000-0000-C000-000000000046"), TypeLibType((short)0x10c0)]

public interface ICTPFactory

{

    [return: MarshalAs(UnmanagedType.Interface)]

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(1)]

    CustomTaskPane CreateCTP([In, MarshalAs(UnmanagedType.BStr)] string CTPAxID, [In, MarshalAs(UnmanagedType.BStr)] string CTPTitle, [In, Optional, MarshalAs(UnmanagedType.Struct)] object CTPParentWindow);

}

 

[ComImport, Guid("000C033E-0000-0000-C000-000000000046"), TypeLibType((short)0x10c0)]

public interface ICustomTaskPaneConsumer

{

    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(1)]

    void CTPFactoryAvailable([In, MarshalAs(UnmanagedType.Interface)] ICTPFactory CTPFactoryInst);

}


     正如你可以从如上列表中看到的,从位置5,我使用了一个更简单形式的_VtblGap语法来省略一个vtable成员。注意,详细说明需要保留的vtable的编号就足够了,没有必要说明位置,因为它的位置是从_VtblGap_YY自身声明的位置中得到的。
    //void _VtblGap5_1();

    void _VtblGap_1();

 

     同时也要注意,正是由于我可以在XX组件里详细说明位置,_VtblGap声明本身不允许我重新对位置进行排序。说到这,你可能想知道,为什么我对_VtblGapXX_YY中的XX很困扰,因为出于保留正确顺序的目的,XX是没有必要使用的。答案就是,_VtblGap登记了所有的计数作为方法声明,如果我仅仅使用YY组件,我将会有很大的机率使用一个冲突的名字,代码将会编译错误。使用XX组件只是一个很方便的方法来避免这个问题。_VtblGap语法的文档请参见 《Common Language Infrastructure Annotated Standard》http://www.amazon.com/Language-Infrastructure-Annotated-Microsoft-Development/dp/0321154932/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1214293899&sr=8-1


     Ribbon接口的形式也是类似的。注意(也是我的上一篇文章中的一个例子),我可以简单的省略trailing成员,但在这个例子中,我已经明确的选择保留vtable空间,尽管我没有用到这些成员,并且接口中这些成员之后也没有其他成员了。鼓励保留全部vtable大小的原因是,理论上说是一个使用这种方法写接口的用户,完全依赖于全部vtable的大小。目前这种说法非常牵强,在这个特定的例子里,我在自己的Project里给COM导入了这些接口,并没有设计它可以被任何其他用户重用。尽管如此,它也是一个可行的方法,无伤大雅。

 

[ComImport, Guid("000C0396-0000-0000-C000-000000000046"), TypeLibType((short)0x1040)]
public interface
 IRibbonExtensibility
{
    [
return
: MarshalAs(UnmanagedType.BStr)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType 
= MethodCodeType.Runtime), DispId(1
)]
    
string GetCustomUI([In, MarshalAs(UnmanagedType.BStr)] string
 RibbonID);
}

 

[ComImport, Guid(
"000C0395-0000-0000-C000-000000000046"), TypeLibType((short)0x1040
)]
public interface
 IRibbonControl
{

    [DispId(
1
)]
    
string Id { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(1)] get
; }

    
// From position 2, we omit 2 vtbl members.


    
void _VtblGap2_2();

    
//
[DispId(2)]

    
//
object Context { [return: MarshalAs(UnmanagedType.IDispatch)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(2)] get; }

    
//
[DispId(3)]

    
//string Tag { [return: MarshalAs(UnmanagedType.BStr)] [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), DispId(3)] get; }


}
 

     省略所有这些成员的最终结果是代码量减少,性能提高。也同时意味着导致bug的机会减少,将来需要维护的代码也降低。

posted @ 2008-11-03 14:44  Cathy Yan  阅读(524)  评论(0编辑  收藏  举报