¿Cómo diseñar una biblioteca de C / C++ para ser utilizable en muchos lenguajes de cliente?


Estoy planeando codificar una biblioteca que debería ser utilizable por un gran número de personas en un amplio espectro de plataformas. ¿Qué tengo que considerar para diseñarlo bien? Para hacer preguntas más específicas, hay cuatro "preguntas" al final.

Elección del idioma

Considerando todos los requisitos y detalles conocidos, llegué a la conclusión de que una biblioteca escrita en C o C++ era el camino a seguir. Creo que el uso principal de mi biblioteca será en programas escritos en C, C++ y Java SE , pero también puedo pensar en razones para usarlo desde Java ME, PHP,. NET, Objective C, Python, Ruby, bash scrips, etc... Tal vez no pueda apuntar a todos ellos, pero si es posible, lo haré.

Requisitos

Sería mucho describir el propósito completo de mi biblioteca aquí, pero hay algunos aspectos que podrían ser importantes para esta pregunta:

  • La biblioteca en sí comenzará pequeña, pero definitivamente crecerá a una enorme complejidad, por lo que es no es una opción para mantener varias versiones en paralelo.
  • La mayor parte de la complejidad estará oculta dentro de la biblioteca, aunque
  • La biblioteca construirá un gráfico de objetos que se usa mucho en su interior. Algunos clientes de la biblioteca solo estarán interesados en atributos específicos de objetos específicos, mientras que otros clientes deben recorrer el gráfico de objetos de alguna manera
  • Los clientes pueden cambiar los objetos, y la biblioteca debe ser notificada de ello
  • La biblioteca puede cambiar los objetos, y el cliente debe ser notificado de ello, si ya tiene un handle para ese objeto
  • La biblioteca debe ser multi-threaded, porque mantendrá conexiones de red a varios otros hosts
  • Si bien algunas solicitudes a la biblioteca pueden manejarse de forma sincrónica, muchas de ellas tardarán demasiado tiempo y deben procesarse en segundo plano, y notificar al cliente sobre el éxito (o el fracaso)

Por supuesto, las respuestas son bienvenidas sin importar si se refieren a mi requisitos específicos, o si responden a la pregunta de una manera general que importa a un público más amplio!

Mis suposiciones, hasta ahora

Así que aquí están algunas de mis suposiciones y conclusiones, que he reunido en los últimos meses:

  • Internamente puedo usar lo que quiera, por ejemplo, C++ con sobrecarga de operadores, herencia múltiple, programación meta de plantillas... mientras haya un compilador portátil que lo maneje (piense en gcc / g++)
  • Pero mi interfaz tiene que ser una interfaz C limpia que no implique el mangling del nombre
  • Además, creo que mi interfaz solo debería consistir en funciones, con tipos de datos básicos/primitivos (y tal vez punteros) pasados como parámetros y valores de retorno
  • Si uso punteros, creo que solo debería usarlos para pasarlos de vuelta a la biblioteca, no para operar directamente en la memoria referenciada
  • Para el uso en una aplicación C++, también podría ofrecer una interfaz orientada a objetos (Que también es propensa a nombrar mangling, por lo que la Aplicación debe usar el mismo compilador, o incluir la biblioteca en forma de código fuente)
  • ¿Es esto también cierto para el uso en C# ?
  • Para el uso en Java SE / Java EE, se aplica la interfaz nativa de Java (JNI). Tengo algunos conocimientos básicos al respecto, pero definitivamente debería comprobarlo dos veces.
  • No todos los lenguajes cliente manejan bien el multihilo, por lo que debería haber un solo hilo que hable con el cliente
  • Para el uso en Java ME, no hay tal cosa como JNI, pero Podría ir con VM anidada
  • Para el uso en scripts Bash, debe haber un ejecutable con una interfaz de línea de comandos
  • Para los otros lenguajes de cliente, no tengo idea
  • Para la mayoría de los lenguajes de cliente, sería bueno tener una especie de interfaz de adaptador escrita en ese lenguaje. Creo que hay herramientas para generar automáticamente esto para Java y algunos otros
  • Para lenguajes orientados a objetos, podría ser posible crear un adaptador orientado a objetos lo que oculta el hecho de que la interfaz de la biblioteca está basada en funciones, pero no se si vale la pena el esfuerzo

Posibles subcuestiones

  • ¿es esto posible con un esfuerzo manejable, o es simplemente demasiada portabilidad?
  • ¿hay buenos libros / sitios web sobre este tipo de criterios de diseño?
  • ¿alguna de mis suposiciones es incorrecta?
  • qué bibliotecas de código abierto vale la pena estudiar para aprender de su diseño / interfaz / ¿souce?
  • meta: Esta pregunta es bastante larga, ¿ves alguna manera de dividirla en varias más pequeñas? (Si responde a esto, hágalo como un comentario, no como una respuesta)
Author: Lena Schimmel, 2009-12-19

7 answers

Mayormente correcto. La interfaz de procedimiento directo es la mejor. (que no es del todo lo mismo que C btw (**), pero lo suficientemente cerca)

I interfaz DLL mucho ( * ), tanto de código abierto y comercial, así que aquí hay algunos puntos que recuerdo de la práctica diaria, tenga en cuenta que estas son las áreas más recomendadas para la investigación, y no verdades cardinales:

  • Tenga cuidado con la decoración y esquemas similares "menores" de manipulación, especialmente si se utiliza un compilador de MS. En particular, la convención stdcall a veces conduce a la generación de decoración por el bien de VB (decoración es cosas como @6 después del nombre del símbolo de función)
  • No todos los compiladores pueden realmente diseñar todo tipo de estructuras:
    • así que evita abusar de los sindicatos.
    • evite el bitpacking
    • y preferiblemente empacar los registros. Aunque más lento, al menos todos los compiladores pueden acceder a los registros empaquetados afaik
  • En Windows use stdcall. Este es el valor predeterminado para las DLL de Windows. Evite fastcall, no está completamente estandarizado (especialmente cómo se pasan los registros pequeños)
  • Algunos consejos para facilitar la traducción automática de encabezados:
    • las macros son difíciles de autoconvertir debido a su falta de tipo. Evitarlos, utilizar funciones
    • Defina tipos separados para cada tipo de puntero, y no utilice tipos compuestos (xtype **) en declaraciones de funciones.
    • siga el mantra" definir antes de usar " tanto como sea posible, esto evitará que los usuarios que traducen encabezados los reorganicen si su idioma en general requiere definir antes de su uso, y hace que sea más fácil para los analizadores de una pasada para traducirlos. O si necesitan información de contexto para traducir automáticamente.
  • No exponga más de lo necesario. Deje los tipos de manija opague si es posible. Solo causará problemas de versiones más adelante.
  • No devuelve tipos estructurados como registros/estructuras o arrays como tipo de retorno de funciones.
  • siempre tener una función de comprobación de versiones (más fácil de hacer una distinción).
  • tenga cuidado con enum y booleano. Otros idiomas pueden tener suposiciones ligeramente diferentes. Puede usarlos, pero documente bien cómo se comportan y cuán grandes son. También piense en el futuro, y asegúrese de que las enumeraciones no se hacen más grandes si agrega algunos campos, rompa la interfaz. (por ejemplo, en Delphi/pascal por defecto los booleanos son 0 o 1, y otros valores no están definidos. Hay tipos especiales para booleanos similares a C (byte,tamaño de palabra de 16 bits o 32 bits, aunque se introdujeron originalmente para COM, no para la interfaz de C))
  • prefiero stringtypes que son puntero a char + length como campo separado (COM también hace esto). Preferiblemente no tener que depender de cero terminado. Esto no es solo por razones de seguridad (desbordamiento), sino también porque es más fácil/más barato interactuar con los tipos nativos de Delphi de esa manera.
  • Memoria siempre cree la API de manera que fomente una separación total de la administración de memoria. IOW no asume nada sobre la gestión de la memoria. Esto significa que todas las estructuras en su lib asignado a través de su propio administrador de memoria, y si una función le pasa una estructura, cópiela en lugar de almacenar un puntero hecho con la administración de memoria "clientes". Porque tarde o temprano llamarás accidentalmente gratis o realloc en él: -)
  • (lenguaje de implementación, no interfaz), sea reacio a cambiar la máscara de excepción del coprocesador. Algunos lenguajes cambian esto como parte de la conformidad con sus estándares de manejo de errores de coma flotante(excepción).
  • Siempre emparejar un callbacks con un contexto configurable por el usuario. Esto puede ser utilizado por el usuario para dar el estado de devolución de llamada sin definir variables globales. (como, por ejemplo, una instancia de objeto)
  • tenga cuidado con la palabra de estado del coprocesador. Puede ser cambiado por otros y romper su código, y si lo cambia, otro código podría dejar de funcionar. La palabra de estado generalmente no se guarda / restaura como parte de las convenciones de llamada. Al menos no en la práctica.
  • no utilice parámetros varargs de estilo C. No todos los idiomas permiten variable número de parámetros de forma insegura ( * ) Delphi programmer by day, un trabajo que implica interconectar una gran cantidad de hardware y, por lo tanto, traducir encabezados SDK de proveedores. Por la noche Free Pascal desarrollador, a cargo de, entre otros, los encabezados de Windows.

(**) Esto se debe a que lo que "C" significa binario todavía depende del compilador de C usado, especialmente si no hay un sistema universal ABI real. Piensa en cosas como:

  • C añadiendo un prefijo de subrayado en algunos formatos binarios (a. out, ¿Coff?)
  • a veces diferentes compiladores de C tienen diferentes opiniones sobre qué hacer con estructuras pequeñas pasadas por valor. Oficialmente no deberían apoyarlo en absoluto afaik, pero la mayoría lo hace.
  • el embalaje de la estructura a veces varía, al igual que los detalles de las convenciones de llamada (como saltar registros enteros o no si un parámetro es registrable en un registro FPU)

===== conversiones automatizadas de encabezado = = = =

Aunque no sé beber tan bien, sé y uso algunos delphi herramientas de cabecera específicas (h2pas, Darth / headconv, etc.).

Sin embargo, nunca los uso en modo completamente automático, ya que más a menudo que no la salida apesta. Los comentarios cambian de línea o se eliminan, y el formato no se conserva.

Normalmente hago un pequeño script (en Pascal, pero puedes usar cualquier cosa con soporte de cadena decente) que divide un encabezado, y luego pruebo una herramienta en partes relativamente homogéneas (por ejemplo, solo estructuras, o solo define, etc.).

Entonces compruebo si me gusta el salida de conversión automática, y ya sea usarlo, o tratar de hacer un convertidor específico yo mismo. Dado que es para un subconjunto (como solo estructuras) a menudo es mucho más fácil que hacer un convertidor de encabezado completo. Por supuesto que depende un poco de cuál sea mi objetivo. (encabezados agradables y legibles o rápidos y sucios). En cada paso podría hacer algunas sustituciones (con sed o un editor).

El esquema más complicado que hice para las cabeceras Winapi commctrl y ActiveX/comctl. Allí combiné IDL y el encabezado C (IDL para las interfaces, que son un montón de macros no compatibles en C, el encabezado C para el resto), y logró obtener las macros escritas por alrededor del 80% (propogando las tipografías en las macros de sendmessage a la declaración de macro,con valores predeterminados razonables (wparam,lparam, lresult))

La forma semiautomática tiene la desventaja de que el orden de las declaraciones es diferente (por ejemplo, primero constantes, luego estructuras y luego declaraciones de funciones), lo que a veces hace que el mantenimiento sea un problema. Me por lo tanto, mantenga siempre los encabezados/sdk originales para comparar.

El proyecto de conversión Jedi winapi podría tener más información, tradujeron aproximadamente la mitad de los encabezados de windows a Delphi, y por lo tanto tienen una enorme experiencia.

 29
Author: Marco van de Voort,
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
2015-03-08 20:00:29

No lo sé, pero si es para Windows, entonces puede probar una API tipo C (similar a la WINAPI), o empaquetar su código como un componente COM: porque supongo que los lenguajes de programación podrían querer ser capaces de invocar la API de Windows, y/o usar objetos COM.

 4
Author: ChrisW,
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
2009-12-19 13:27:29

Con respecto a la generación automática de envoltorios, considere usar SWIG. Para Java, hará todo el trabajo de JNI. Además, es capaz de traducir correctamente interfaces complejas de OO-C++(siempre que siga algunas pautas básicas, es decir, sin clases anidadas, sin uso excesivo de plantillas, además de las mencionadas por Marco van de Voort).

 3
Author: Alexander Gessler,
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
2009-12-19 13:38:14

Piensa en C, nada más. C es uno de los lenguajes de programación más populares. Es ampliamente utilizado en muchas plataformas de software diferentes, y hay pocas arquitecturas de computadora para las que no existe un compilador de C. Todos los lenguajes populares de alto nivel proporcionan una interfaz a C. Que hace que su biblioteca sea accesible desde casi todas las plataformas existentes. No se preocupe demasiado por proporcionar una interfaz orientada a Objetos. Una vez que tenga la biblioteca hecha en C, OOP, funcional o cualquier otro estilo la interfaz se puede crear en los idiomas de cliente apropiados. Ningún otro lenguaje de programación de sistemas le dará la flexibilidad y potabilidad de C.

 3
Author: Vijay Mathew,
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
2009-12-19 16:12:11

NestedVM creo que va a ser más lento que Java puro debido a los límites de la matriz de comprobación en el int [] [] que representa la memoria de la máquina virtual MIPS. Es un buen concepto pero podría no funcionar lo suficientemente bien en este momento (hasta que los fabricantes de teléfonos agreguen soporte NestedVM (si lo hacen!), la mayoría de las cosas va a ser LENTO por ahora, n'est-ce pas)? Si bien puede ser capaz de desempaquetar JPEGs sin error, la velocidad no es de poca preocupación! :)

Nada más en lo que has escrito sobresale, ¡lo que no quiere decir que esté bien o mal! Los principios suenan (principalmente solo escuchar la elección de palabras y lenguaje para ser honesto) como una mejor práctica estándar, pero no he pensado en los detalles de todo lo que has dicho. Como usted mismo ha dicho, esto realmente debería ser varias preguntas. Pero, por supuesto, hacer este tipo de cosas no es automáticamente fácil solo porque estás fijo en quizás una arquitectura ligeramente diferente a la última base de código que has trabajado en...! ;)

Mis pensamientos:

Todos sus comentarios sobre la compatibilidad de la interfaz de C suenan sensatos para mí, más o menos las mejores prácticas, excepto que no parece abordar correctamente la política de administración de memoria - algunas oraciones un poco ambiguas/vagas/que suenan mal. El diseño de la gestión de memoria estará en gran medida determinado por los patrones de acceso realizados en su aplicación, en lugar de la funcionalidad per se. Yo suiggest usted estudia los intentos de otros de hacer interfaces portátiles como el estándar ANSI C API, Unix API, Win32 API, Cocoa, J2SE, etc cuidadosamente.

Si fuera yo, escribiría la biblioteca en un subconjunto cuidadosamente elegido de los elementos comunes de Java regular y Davlik virtual machine Java y también escribiría mi propio analizador sintáctico personalizado que traduce el código a C para plataformas que soportan C, que por supuesto serían la mayoría de ellas. Yo sugeriría que si se restringe a los tipos de datos de varios tamaños ints, bools, Cadenas, Diccionarios y matrices y tenga cuidado uso de ellos que ayudarán en problemas multiplataforma sin afectar el rendimiento la mayor parte del tiempo.

 2
Author: martinr,
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
2009-12-19 13:43:14

Sus suposiciones parecen estar bien, pero veo problemas por delante, muchos de los cuales ya han visto en sus suposiciones. Como ha dicho, realmente no puede exportar clases y métodos de c++, necesitará proporcionar una interfaz de c basada en funciones. Cualquiera que sea la fachada que construyas alrededor de eso, seguirá siendo una interfaz basada en funciones en el corazón.

El problema básico que veo con eso es que las personas eligen un lenguaje específico y su tiempo de ejecución debido a su forma de pensar (funcional u orientada a objetos) o la problema que abordan (programación web, base de datos,...) corresponde a ese lenguaje de una manera u otra. Una biblioteca implementada en c probablemente nunca se sentirá como las bibliotecas a las que están acostumbradas, a menos que programen en c ellos mismos. Personalmente, siempre preferiría una biblioteca que "se sienta como python" cuando uso python, y una que se sienta como java cuando hago Java EE, a pesar de que conozco c y c++.

Así que su esfuerzo podría ser de poca utilidad real (aparte de su ganancia en experiencia), porque la gente probablemente querrá seguir con su mentalidad, y más bien volver a implementar la funcionalidad que utilizar una biblioteca que hace el trabajo, pero no encaja.

También me temo que la portabilidad deseada obstaculizará seriamente el desarrollo. Solo piensa en la configuración de compilación infinita necesaria, y las pruebas para eso. He trabajado en un proyecto que trataba de mantener la compatibilidad para 5 sistemas operativos (todos similares a posix, pero aún así) y alrededor de 10 compiladores, las compilaciones fueron una pesadilla para probar y mantener.

 2
Author: tabdamage,
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
2009-12-19 13:47:15

Darle una interfaz XML, ya sea pasado como un parámetro y valor de retorno o como archivos a través de una invocación de línea de comandos. Esto puede no parecer tan directo como una interfaz de función normal, pero es la forma más práctica de acceder a un ejecutable desde, por ejemplo, Java.

 0
Author: Joshua Fox,
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
2009-12-19 16:19:41