添加属性时,如何保留 .NET 程序集的 COM 二进制兼容性?
我们开发了一个 .NET 程序集来存储语言翻译信息,并且需要由 VB6 应用程序使用。
我们希望能够更改翻译信息而无需重新编译应用程序。
翻译由名为 LanguageServices 的两个文件部分类提供。
一个文件是不变的库方法,另一个文件是从 resx 文件自动生成的所有属性,而 regx 是从语言翻译信息数据库生成的。
这一切都源于对一个中央翻译数据库的需求,该数据库可以通过编程方式“扁平化”为每个不同应用程序都可以使用的格式。
现在,我可以通过绕过它并以不同的方式来解决这个问题。 事实上,我可以去掉自动生成的属性列表,问题就会消失。
我感兴趣的是如何解决这个问题,即:
如果我们向数据库添加新的翻译标签(这个词中的这个词变成那个词),它会向类添加新的属性,这又会添加新的暴露的属性COM 接口的属性。
这些属性被添加到 COM 接口的中间,从而破坏了二进制兼容性。它们被添加在中间,因为 C# 编译器将分部类的动态部分添加到分部类的静态部分后缀。我需要它做的是要么以相反的方式连接它们,要么在 C# 文件本身中明确说明顺序。我认为在类的静态部分显式设置 DispID 可以做到这一点,但事实并非如此。
以下是构建过程生成的一对 IDL 文件:
这是添加新属性之前的 IDL。
这是添加新属性并且兼容性之后的 IDL损坏:
确切的区别是这一点被推到了中间:
[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。
这是 C# 代码写入/生成: ILanguageServices.cs:自动生成的接口。
[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:界面中不变的部分
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 以包含静态部分。 把它分开真是太好了。
不管怎样,有人能告诉我为什么它不起作用以及如何对 COM 接口保持更严格的控制吗? 严格排序方法似乎是这样...... 布鲁夫。
谢谢,
J1M。
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.
The translation is provided by a two-file partial class called LanguageServices.
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:
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.
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.
Here are the pair of IDL files generated by the build process:
Here is the IDL before I add a new property.
And here is the IDL after a new property has been added and compatibility is broken:
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);
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.
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: 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);
}
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.
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.
Thanks,
J1M.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
来自 C# 语言规范版本 4 第 10.2.6 节:
因此,除了声明顺序之外,C# 语言中没有任何规定来控制类型成员的顺序。在部分声明的类型中,顺序是完全未定义的。
因此,这里的结论是不要对要向 COM 公开的接口使用部分声明。无法控制接口成员顺序,并且由于其在语言中未定义,因此生成的成员顺序可能随时更改。
From the C# Language Specification Version 4 Section 10.2.6:
So there are no provisions in the C# language 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 completely undefined.
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.