添加属性时,如何保持 .NET 程序集的 COM 二进制兼容性? [英] How do I preserve COM binary compatibility for a .NET Assembly when properties are added?

查看:14
本文介绍了添加属性时,如何保持 .NET 程序集的 COM 二进制兼容性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们开发了一个存储语言翻译信息的 .NET 程序集,它需要被 VB6 应用程序使用.

We have developed a .NET Assembly that stores language translation information and it needs to be consumed by a VB6 application.

我们希望能够更改翻译信息而无需重新编译应用程序.

We would like to be able to change the translation information without having to recompile the application.

翻译由名为 LanguageServices 的两文件分部类提供.

The translation is provided by a two-file partial class called LanguageServices.

一个文件是不变的库方法,另一个是所有从 resx 文件自动生成的属性,而 regx 是从语言翻译信息数据库生成的.

One file is non-changing library methods, the other is all auto generated properties from a resx file and the regx is generated from a database of language translation information.

这一切都源于需要有一个中央翻译数据库,该数据库可以通过编程方式扁平化"为我们每个不同的应用程序都可以使用的格式.

This all arose from a need to have a central database of translations that could be programatically "flattened" to a format that can be consumed by each of our disparate applications.

现在,我可以绕过它并以不同的方式解决这个问题.事实上,我可以摆脱自动生成的属性列表,问题就会消失.

Now, I can solve this problem by bypassing it and doing it in a different manner. In fact I could just get rid of the auto-generated list of properties and the problem would go away.

我感兴趣的是如何解决这个问题,因此:

What I'm interested is how I can solve this problem, which is thus:

如果我们向数据库添加新的翻译标签(THIS WORD 中的 THIS WORD 变成 THAT WORD),它会向类添加新属性,进而向 COM 接口添加新的公开属性.

If we add new translation labels to the database (THIS WORD in THIS WORD becomes THAT WORD) it adds new properties to the class, which in turn adds new exposed properties to the COM interface.

属性被添加到 COM 接口的中间,从而破坏了二进制兼容性.它们被添加到中间是因为 C# 编译器在部分类的动态部分后缀为部分类的静态部分.我需要它做的是将它们以另一种方式连接起来,或者在 C# 文件本身中明确说明顺序.我认为在类的静态部分显式设置 DispID 可以做到这一点,但事实并非如此.

The properties get added in the middle of the COM interface, thus breaking binary compatibility. They get added in the middle because the C# compiler suffixes the dynamic part of the partial class with the static part of the partial class. What I need it to do is either concatenate them the other way around or explicitly state the order in the C# files themselves. I thought setting the DispIDs explicitly on the static part of the class would do it, but it has not.

以下是构建过程生成的一对 IDL 文件:

Here are the pair of IDL files generated by the build process:

这是我添加新属性之前的 IDL.

Here is the IDL before I add a new property.

http://pastebin.com/qPvcUV9z

这是添加新属性并破坏兼容性后的 IDL:

And here is the IDL after a new property has been added and compatibility is broken:

http://pastebin.com/K2MuqtYV

确切的区别是这个位被推到了中间:

The exact difference is this bit gets shoved in the middle:

[id(0x60020039), propget]
HRESULT Jn_ExactCaseMatch([out, retval] VARIANT_BOOL* pRetVal);
[id(0x6002003a), propget]
HRESULT Jn_Regex([out, retval] VARIANT_BOOL* pRetVal);
[id(0x6002003b), propget]
HRESULT Jn([out, retval] BSTR* pRetVal);

认为这就是问题所在,它改变了方法的顺序.我认为可以通过显式定义 DispID 来覆盖顺序(您可以看到从 HRESULT Culture([in] ICultureInfo* pRetVal); 开始的所有内容都有一个从 0 开始的 id.

And I think that's the problem, it's change the order of the methods. I thought the order could be overriden by explicitly defining the DispID (you can see that everything from HRESULT Culture([in] ICultureInfo* pRetVal); onwards has an id starting from 0.

这是编写/生成的 C# 代码:ILanguageServices.cs:自动生成的接口.

Here's the C# code that's written/generated: ILanguageServices.cs: Auto generated interface.

[Guid("547a7f6e-eeda-4f77-94d0-2dd24f38ba58")]
public partial interface ILanguageServices
{
    /// <summary>
    /// 
    /// </summary>
    System.Boolean Offence_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Offence_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string Offence { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Colour_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Colour_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string Colour { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DebtManagementSystem_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DebtManagementSystem_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string DebtManagementSystem { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DateOfContravention_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DateOfContravention_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string DateOfContravention { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean ContraventionDetails_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean ContraventionDetails_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string ContraventionDetails { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Income_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Income_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string Income { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Hold_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Hold_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string Hold { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CivilEnforcementOfficer_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CivilEnforcementOfficer_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string CivilEnforcementOfficer { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean PCNDebt_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean PCNDebt_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string PCNDebt { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean OnHold_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean OnHold_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string OnHold { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DatePutOnHold_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DatePutOnHold_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string DatePutOnHold { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean HoldCode_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean HoldCode_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string HoldCode { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DateHoldExpires_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean DateHoldExpires_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string DateHoldExpires { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean PutOnHoldByUserName_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean PutOnHoldByUserName_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string PutOnHoldByUserName { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CurrentState_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CurrentState_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string CurrentState { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Vrm_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean Vrm_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string Vrm { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean State_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean State_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string State { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CurrentStatechangedd2d2d4_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean CurrentStatechangedd2d2d4_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string CurrentStatechangedd2d2d4 { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean SimonTest_ExactCaseMatch { get; }

    /// <summary>
    /// 
    /// </summary>
    System.Boolean SimonTest_Regex { get; }

    /// <summary>
    /// 
    /// </summary>
    string SimonTest { get; } 
}

ILanguageServices_Static.cs:界面中不变的部分

ILanguageServices_Static.cs: The non-changing part of the interface

public partial interface ILanguageServices
{
    [DispId(0)]
    ICultureInfo Culture { get; set; }
    [DispId(1)]
    IResourceManager ResourceManager { get; }
    [DispId(2)]
    ICultureInfo[] GetCultures(System.Globalization.CultureTypes enCultureTypes);
    [DispId(3)]
    ICultureInfo GetCultureInfo(int LCID);
    [DispId(4)]
    ICultureInfo CurrentCulture { get; }
    [DispId(5)]
    string TranslateString(string rawString, bool searchInsideString);
    [DispId(6)]
    string TranslateString(string rawString);
}

考虑一下,我可能只是让它不是部分类.只需更改生成自动生成部分的 xslt 以包含静态部分.把它分开真是太好了.

Thinking about it, I could probably just make it not a partial class. Just change the xslt that generated the auto-generated part to include the static part. It was just neat to keep it seperate.

无论如何,谁能告诉我为什么它不工作以及如何更严格地控​​制 COM 接口?严格排序方法似乎如此......呜呜呜.

Regardless, can anybody tell me why it's not working and how to keep tighter control over the COM interface? Strictly ordering the methods just seems so... bleugh.

谢谢,

J1M.

推荐答案

来自C# 语言规范版本 4 第 10.2.6 节

From the C# Langauge Specification Version 4 Section 10.2.6

类型中成员的顺序对于 C# 代码来说很少重要,但在与其他语言和环境交互时可能很重要.在这些情况下,在多个部分中声明的类型中成员的顺序是未定义的.

The ordering of members within a type is rarely significant to C# code, but may be significant when interfacing with other languages and environments. In these cases, the ordering of members within a type declared in multiple parts is undefined.

因此,除了声明它们的顺序之外,c# 语言中没有任何规定来控制类型成员的顺序.在部分声明的类型中,顺序是完全未定义的.

So there are no provisions in the c# laguage to control the ordering of members of a type, other than the order they are declared. In a type that is declared partially, then the order is completley undefined.

因此,这里的结论是不要对要公开给 COM 的接口使用部分声明.没有办法控制接口成员顺序,并且由于它在语言中未定义,因此生成的成员顺序可能随时更改.

So the conclusion here is don't use partial declarations for interfaces that you are going to expose to COM. There is no way to control the interface member order, and since its undefined in the language the resulting member order could change at any time.

这篇关于添加属性时,如何保持 .NET 程序集的 COM 二进制兼容性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆