c# - ¿Cómo preservo la compatibilidad binaria para un ensamblado.NET que presente una interfaz COM?
interop binary-compatibility (1)
Hemos desarrollado un .NET Assembly que almacena información de traducción de idiomas y necesita ser consumido por una aplicación VB6.
Nos gustaría poder cambiar la información de traducción sin tener que volver a compilar la aplicación.
La traducción es proporcionada por una clase parcial de dos archivos llamada LanguageServices.
Un archivo es métodos de biblioteca no cambiantes, el otro son todas las propiedades autogeneradas de un archivo resx y el regx se genera a partir de una base de datos de información de traducción de idiomas.
Todo esto surgió de la necesidad de tener una base de datos central de traducciones que pudiera ser programada "aplanada" a un formato que pueda ser consumido por cada una de nuestras dispares aplicaciones.
Ahora puedo resolver este problema evitándolo y haciéndolo de una manera diferente. De hecho, podría deshacerme de la lista de propiedades generadas automáticamente y el problema desaparecería.
Lo que me interesa es cómo puedo resolver este problema, que es así:
Si agregamos nuevas etiquetas de traducción a la base de datos (THIS WORD en THIS WORD se convierte en THAT WORD) agrega nuevas propiedades a la clase, que a su vez agrega nuevas propiedades expuestas a la interfaz COM.
Las propiedades se agregan en el medio de la interfaz COM, rompiendo así la compatibilidad binaria. Se agregan en el medio porque el compilador de C # tiene el sufijo de la parte dinámica de la clase parcial con la parte estática de la clase parcial. Lo que necesito hacer es concatenarlos al revés o indicar explícitamente el orden en los archivos C #. Pensé que establecer los DispIDs explícitamente en la parte estática de la clase lo haría, pero no fue así.
Aquí están el par de archivos IDL generados por el proceso de compilación:
Aquí está el IDL antes de agregar una nueva propiedad.
Y aquí está el IDL después de que se ha agregado una nueva propiedad y se rompe la compatibilidad:
La diferencia exacta es que este pedacito se empuja en el medio:
[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);
Y creo que ese es el problema, es cambiar el orden de los métodos. Pensé que el orden podría ser anulado definiendo explícitamente el DispID (se puede ver que todo desde HRESULT Culture([in] ICultureInfo* pRetVal);
adelante tiene un id empezando desde 0.
Aquí está el código de C # que está escrito / generado: ILanguageServices.cs: interfaz generada automáticamente.
[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: la parte no cambiante de la interfaz
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);
}
Pensando en eso, probablemente podría simplemente no convertirlo en una clase parcial. Simplemente cambie el xslt que generó la parte generada automáticamente para incluir la parte estática. Fue genial mantenerlo separado.
Independientemente, ¿alguien puede decirme por qué no está funcionando y cómo mantener un control más estricto sobre la interfaz COM? Estrictamente ordenar los métodos simplemente parece tan ... bleugh.
Gracias,
J1M.
De la C # Langauge Specification Version 4 Section 10.2.6
El orden de los miembros dentro de un tipo rara vez es significativo para el código C #, pero puede ser significativo al interactuar con otros lenguajes y entornos. En estos casos, el orden de los miembros dentro de un tipo declarado en varias partes no está definido.
Por lo tanto, no hay disposiciones en el lenguaje c # para controlar el orden de los miembros de un tipo, que no sea el orden en que se declaran. En un tipo que se declara parcialmente, el orden está completamente indefinido.
Entonces la conclusión aquí es que no use declaraciones parciales para las interfaces que va a exponer a COM. No hay forma de controlar el orden de los miembros de la interfaz, y dado que no está definido en el idioma, el orden de los miembros resultante podría cambiar en cualquier momento.