¿Cómo puedo usar clases de Biblioteca Estándar (STL) en mi interfaz dll o ABI?


Ha habido algunas preguntas antes sobre la exportación de una clase que contiene clases stl en relación con visual studio warning C4251: Por ejemplo, esta pregunta o esta pregunta. Ya he leído la excelente explicación en UnknownRoad.

Desactivar ciegamente la advertencia parece un poco peligroso, aunque puede ser una opción. Empaquetar todas esas clases std y exportarlas tampoco es realmente una opción. Después de todo, se llama la Biblioteca de Plantillas Estándar... Es decir, uno quiere proporcionar una interfaz con estas clases estándar.

¿Cómo puedo usar las clases stl en mi interfaz dll? ¿Cuáles son las prácticas comunes?

Author: John Dibling, 2011-04-14

1 answers

Tenga en cuenta una cosa antes de leer más: Mi respuesta viene desde el punto de vista de escribir código portable que se puede utilizar en aplicaciones compuestas por módulos compilados bajo diferentes compiladores. Esto puede incluir diferentes versiones o incluso diferentes niveles de parche del mismo compilador.

¿Cómo puedo usar stl-classes en mi dll-interfaz?

Respuesta: A menudo no puedes1.

Razón: El STL es un código biblioteca, no una biblioteca binaria como una DLL. No tiene un solo ABI que esté garantizado para ser el mismo donde quiera que lo use. De hecho, STL significa " Standard Template Library", pero una palabra clave aquí además de Standard es Template .

El Estándar define los métodos y miembros de datos que cada clase STL debe proporcionar, y define lo que esos métodos deben hacer; pero no más. En particular, el Estándar no especifica cómo compilador los escritores deben implementar la funcionalidad definida por el estándar. Los escritores del compilador son libres de proporcionar una implementación de una clase STL que agrega funciones miembro y variables miembro no enumeradas en el Estándar, siempre y cuando los miembros que están definidos en el Estándar sigan ahí y hagan lo que dice el Estándar.

Tal vez un ejemplo esté en orden. La clase basic_string se define en el Estándar como teniendo ciertas funciones y variables miembro. La definición real es casi 4 páginas en el Estándar, pero aquí hay solo un fragmento de él:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
  public:
    // 21.3.3 capacity:
    size_type size() const;
    size_type length() const;
    size_type max_size() const;
    void resize(size_type n, charT c);
    void resize(size_type n);
    size_type capacity() const;
    void reserve(size_type res_arg = 0);
    void clear();
    bool empty() const;
[snip]
};

Considere las funciones miembro size() y length(). No hay nada en el Estándar que especifique variables miembro para mantener esta información. De hecho, no hay variables miembro definidas en absoluto, ni siquiera para contener la cadena en sí. Entonces, ¿cómo se implementa esto?

La respuesta es, muchas maneras diferentes. Algunos compiladores pueden usar una variable miembro size_t para contener el tamaño y una char* para contener la cadena. Otro podría usar un puntero a algún otro almacén de datos que contenga esos datos (este podría ser el caso en una implementación contada por referencia). De hecho, diferentes versiones o incluso niveles de parche del mismo compilador pueden cambiar estos detalles de implementación. No puedes confiar en ellos. Por lo tanto, la implementación de MSVC 10 podría verse así:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
char* m_pTheString;
};

size_t basic_string::size() const { return strlen(m_pTheString;) }

...Mientras que MSVC 10 con SP1 podría verse así:

namespace std {
  template<class charT, class traits = char_traits<charT>,
    class Allocator = allocator<charT> >
  class basic_string {
[snip]
vector<char> m_TheString;
};

size_t basic_string::size() const { return m_TheString.size(); }

No estoy diciendo que se vean así, estoy diciendo podrían. El punto aquí es que la implementación real depende de la plataforma, y realmente no tiene forma de saber qué será en ningún otro lugar.

Ahora digamos que usas MSVC10 para escribir una DLL que exporta esta clase:

class MyGizmo
{
public:
  std::string name_;
};

¿Qué es el sizeof(MyGizmo)?

Asumiendo mis implementaciones propuestas anteriormente, bajo MSVC10 va a ser sizeof(char*), pero bajo SP1 será sizeof(vector<char>). Si escribe una aplicación en VC10 SP1 que utiliza la DLL, el tamaño del objeto se verá diferente que en realidad lo es. Se cambia la interfaz binaria.


Para otro tratamiento de esto, consulte C++ Coding Standards (Amazon link) issue # 63.


1: "A menudo no puede " En realidad puede exportar componentes de biblioteca estándar o cualquier otro componente de biblioteca de código (como Boost) con una buena cantidad de confiabilidad cuando tiene un control completo sobre las cadenas de herramientas y las bibliotecas.

El problema fundamental es que con las bibliotecas de código fuente los tamaños y definiciones de las cosas pueden ser diferentes entre diferentes compiladores y diferentes versiones de la biblioteca. Si está trabajando en un entorno donde controla ambas cosas en todas partes donde se usa su código, entonces probablemente no tendrá un problema. Por ejemplo, en una empresa comercial donde todos los sistemas se escriben internamente y se utilizan solo internamente, podría ser posible hacer esto.

 84
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-13 20:54:50