如何devise一个C / C ++库在许多客户端语言中可用?

我正在计划编写一个图书馆,这个图书馆应该可以在众多平台上被大量的人使用。 我有什么需要考虑devise它的权利? 为了使这个问题更具体,最后有四个“子问题”。

语言的select

考虑到所有已知的要求和细节,我总结出用C或C ++编写的库是最好的select。 我认为我的库的主要用法是用C,C ++和Java SE编写的程序,但我也可以考虑从Java ME,PHP,.NET,Objective C,Python,Ruby,bash脚本,等等…也许我不能瞄准所有的人,但是如果可能的话,我会去做的。

要求

在这里描述我的图书馆的全部目的是很有意义的,但是对于这个问题可能有一些重要的方面:

  • 图书馆本身从小开始,但肯定会变得非常复杂,所以不能同时维护多个版本。
  • 虽然大部分的复杂性都隐藏在图书馆里面
  • 该库将构build一个内部使用的对象图。 图书馆的一些客户只会对特定对象的特定属性感兴趣,而其他客户则必须以某种方式遍历对象图
  • 客户可能会更改对象,并且必须通知图书馆
  • 该库可能会更改对象,并且必须通知客户端,如果它已经有该对象的句柄
  • 该库必须是multithreading的,因为它将维护与其他几个主机的networking连接
  • 尽pipe对图书馆的一些要求可能会同步处理,但是其中很多要求过长,必须在后台处理,并通知客户成功(或失败)

当然,不pipe答案是否符合我的具体要求,或者如果他们以一般的方式回答这个问题,对更广泛的观众来说,答案是可以接受的!

我的假设,迄今

以下是我在过去几个月收集的一些假设和结论:

  • 在内部,我可以使用任何我想要的,例如C + +操作符重载,多重inheritance,模板元编程…只要有一个可移植的编译器来处理它(想想gcc / g ++)
  • 但是我的界面必须是一个干净的C界面,不涉及名称修改
  • 此外,我认为我的接口应该只包含函数,基本/原始数据types(也许指针)作为parameter passing和返回值
  • 如果我使用指针,我想我应该只用它们将它们传回给库,而不是直接在引用的内存上操作
  • 对于在C ++应用程序中的使用,我可能还会提供一个面向对象的接口(它也容易出现名称混乱,所以应用程序必须使用相同的编译器,或者以源代码forms包含库)
  • 这在C#中的用法也是这样吗?
  • 对于Java SE / Java EE中的使用,Java本地接口(JNI)适用。 我有一些关于它的基本知识,但我一定要仔细检查一下。
  • 并不是所有的客户端语言都能很好地处理multithreading,所以应该有一个单线程与客户端交谈
  • 对于在Java ME上的使用,没有像JNI那样的东西,但是我可能会使用嵌套的VM
  • 对于在Bash脚本中的使用,必须有一个带有命令行界面的可执行文件
  • 对于其他客户端语言,我不知道
  • 对于大多数客户端语言来说,使用该语言编写适配器接口将会很不错。 我认为有一些工具可以为Java和其他一些自动生成
  • 对于面向对象的语言,可以创build一个面向对象的适配器来隐藏这个库的接口是基于函数的,但是我不知道它是否值得

可能的子问题

  • 这是可能的,可pipe理的努力,还是只是太多的可移植性?
  • 有没有关于这种devise标准的好书/网站?
  • 我的假设是否有错?
  • 哪些开源的图书馆值得研究,从他们的devise/界面/源中学习?
  • meta:这个问题很长,你有什么办法把它分成几个小的吗? (如果您回复此问题,请将其作为评论,而不是回答)

大部分是正确的。 直观的程序界面是最好的。 (这是不完全一样的C BTW(**),但足够接近)

我接口DLLs很多(*),无论是开源和商业,所以这里有一些点,我记得从日常实践中,请注意,这些是更多的推荐领域进行研究,而不是基本事实:

  • 小心装饰和类似的“小”的mangling scheme,特别是如果你使用MS编译器。 最值得注意的是,stdcall约定有时会导致装饰生成为VB(装饰是像@ 6之类的东西在函数符号名称之后)
  • 并不是所有的编译器都能实际布置各种结构:
    • 所以避免过度使用工会。
    • 避免bitpacking
    • 并最好打包logging。 尽pipe速度较慢,但​​至less所有编译器都可以访问打包的loggingafaik
  • 在Windows上使用stdcall。 这是Windows DLL的默认设置。 避免快速通话,它不是完全标准化(特别是如何通过小logging)
  • 一些提示可以使自动化标题翻译变得更容
    • macros由于它们的无types性而难以自动转换。 避免使用function
    • 为每个指针types定义单独的types,并且在函数声明中不使用复合types(xtype **)。
    • 尽可能遵循“定义使用前”的口头禅,这将避免翻译标题的用户重新排列它们,如果他们的语言一般需要在使用前定义它们,并且使得一遍parsing器更容易翻译它们。 或者,如果他们需要上下文信息来自动翻译。
  • 不要超过必要的暴露。 如果可能,留下句柄types。 以后只会造成版本问题。
  • 不要返回logging/结构体或数组等结构化types作为函数的返回types。
  • 总是有一个版本检查function(更容易区分)。
  • 要小心枚举和布尔值。 其他语言的假设可能略有不同。 你可以使用它们,但要logging它们的行为和它们有多大。 另外想一想,并确保枚举不会变大,如果你添加几个字段,打破界面。 (例如在Delphi / pascal中默认的布尔值是0或1,其他的值是不确定的)C类布尔types有特殊types(字节,16位或32位字长,尽pipe它们最初是为COM引入的,不是C接口))
  • 我更喜欢指向char +长度的string作为单独的字段(COM也这样做)。 最好不要依靠零终止。 这不仅仅是因为安全(溢出)的原因,而且因为以这种方式将它们连接到Delphi本地types更容易/更便宜。
  • 内存总是以鼓励完全分离内存pipe理的方式创buildAPI。 IOW不要假设内存pipe理。 这意味着你的lib中的所有结构都是通过你自己的内存pipe理器来分配的,如果一个函数传递一个结构给你,就拷贝它而不是存储用“客户”内存pipe理器所做的指针。 因为你迟早会不小心打电话给free或者realloc 🙂
  • (实现语言,而不是接口),不愿意改变协处理器的exception掩码。 一些语言将其作为符合其标准浮点错误(exception)处理的一部分进行更改。
  • 始终将callback与用户可configuration上下文配对。 这可以被用户用来给定callback状态而不用定义全局variables。 (比如例如对象实例)
  • 请注意协处理器状态字。 它可能会被别人改变,并破坏你的代码,如果你改变它,其他代码可能会停止工作。 状态字一般不作为调用约定的一部分保存/恢复。 至less不是在实践中。
  • 不要使用C风格可变参数。 并非所有的语言都以不安全的方式允许可变数量的参数(*)Delphi程序员白天,这涉及到连接大量的硬件,因此翻译供应商的SDK头。 到了晚上,免费的Pascal开发人员负责Windows头文件。

(**)这是因为“C”意味着二进制仍然依赖于所使用的C编译器,特别是如果没有真正的通用系统ABI。 想想像这样的东西:

  • C在某些二进制格式(a.out,Coff?)上添加下划线前缀
  • 有时候,不同的C编译器会对如何处理小型结构有所不同。 正式的,他们不应该支持它,但大部分都是。
  • 结构打包有时会变化,调用约定的细节(如跳过整数寄存器,或者如果参数可在FPU寄存器中注册)

=====自动化标题转换====

虽然我不知道SWIG,但我知道并使用了一些delphi特定的头文件(h2pas,Darth / headconv等)。

但是,我从来没有在全自动模式下使用它们,因为更多的时候不是输出。 注释更改行或被删除,格式不被保留。

我通常会制作一个小脚本(在Pascal中,但是您可以使用任何支持体面string的任何东西),将头部分开,然后在相对均匀的部分(例如只有结构或只定义等)尝试工具。

然后检查我是否喜欢自动转换输出,并使用它,或者尝试自己制作一个特定的转换器。 由于它是一个子集(就像只有结构),它往往比做一个完整的标题转换器更容易。 当然,这取决于我的目标是什么。 (很好,可读的头文件或快速和肮脏)。 在每一步我都可以做一些replace(使用sed或编辑器)。

我为Winapi commctrl和ActiveX / comctl头文件做的最复杂的scheme。 在那里,我将IDL和C头结合在一起(接口的IDL,C是一堆不可parsing的macros,剩余的C头),并设法获得大约80%的macros(通过在sendmessagemacros返回macros的声明,合理的(wparam,lparam,lresult)默认)

半自动化方式的缺点是声明的顺序是不同的(例如,第一个常量,然后是结构,然后是函数声明),这有时会使维护变得很痛苦。 因此,我总是保持原来的标题/ SDK比较。

绝地winapi转换项目可能有更多的信息,他们翻译了大约一半的窗口标题delphi,因此有巨大的经验。

我不知道,但如果是Windows,那么你可以尝试一个类似于C的API(类似于WINAPI),或者把你的代码打包成一个COM组件:因为我猜想编程语言可能希望能够调用Windows API,和/或使用COM对象。

关于自动包装生成,请考虑使用SWIG。 对于Java,它将完成所有的JNI工作。 此外,它能够正确地翻译复杂的OO-C ++接口(只要遵循一些基本的指导原则,即没有嵌套类,不过度使用模板,再加上Marco van de Voort提到的那些)。

想想C,没有别的。 C是最stream行的编程语言之一。 它在很多不同的软件平台上被广泛使用,并且没有C编译器不存在的计算机体系结构。 所有stream行的高级语言都为C提供了一个接口。这使得你的库几乎可以在所有的平台上访问。 不要太担心提供面向对象的接口。 一旦你在C中完成了库,OOP,可以用适当的客户语言创buildfunction或任何其他样式的接口。 没有其他的系统编程语言会给你C的灵活性和可用性。

我认为NestedVM会比纯Java慢,因为在代表MIPS虚拟机内存的int [] []上进行数组边界检查。 这是一个很好的概念,但是现在可能不够好(直到手机制造商增加了NestedVM支持(如果他们这样做的话),大多数的东西现在会变得很慢,而且还没有完成)。 虽然它可能能够无误地解压缩JPEG,速度是不小的担心! 🙂

没有别的东西写在你写的东西上,这并不是说是对还是错! 原则听起来(主要是听取文字和语言的select是诚实的)像大致标准的最佳做法,但我没有想过你所说的一切的细节。 如你所说,这确实应该是几个问题。 但是,当然,做这种事情并不是一件容易的事情,因为你已经修复了一个稍微不同的体系结构,以至于你所做的最后一个代码库。 ;)

我的想法:

所有关于C接口兼容性的意见听起来都很明智,除了你似乎没有正确处理内存pipe理策略 – 一些句子有点含糊/模糊/错误 – 听起来很不错。 内存pipe理的devise在很大程度上取决于应用程序中的访问模式,而不是function本身。 我希望能够仔细研究其他人制作便携式界面的尝试,比如标准的ANSI C API,Unix API,Win32 API,Cocoa,J2SE等。

如果是我,我会在常规Java和Davlik虚拟机Java的通用元素的精心挑选的子集中编写库,并编写自己的定制parsing器,将代码翻译为支持C的平台的C语言,当然是其中大部分。 我会build议,如果你限制自己的数据types的各种大小整数,布尔,string,字典和数组,并认真使用它们,这将有助于跨平台的问题,而不会影响性能在很大程度上。

你的假设看起来不错,但是我看到了前面的麻烦,其中很多你已经在你的假设中发现了。 正如你所说,你不能真正导出c + +类和方法,你将需要提供一个基于函数的c接口。 围绕这一点构build的任何门面,它将始终是一个基于function的界面。

我所看到的基本问题是,人们select一种特定的语言及其运行时间,因为他们的思维方式(function或面向对象)或他们解决的问题(networking编程,数据库…)以某种方式对应于该语言或其他。 用c实现的库可能永远不会像自己习惯的库,除非它们自己编程。 就我个人而言,当我使用python时,我总是更喜欢一个“感觉像python”的库,而当我执行Java EE时,感觉像是java,尽pipe我知道c和c ++。

所以你的努力可能实际上没什么用处(除了你的经验),因为人们可能会想要坚持自己的思维方式,而不是重新实现这个function,而不是使用一个能胜任这个工作但不适合的库。

我也担心预期的便携性会严重阻碍发展。 试想一下所需的无限构build设置,然后进行testing。 我曾经在一个试图维护5个操作系统兼容性的项目上工作(所有类似posix的,但仍然是),还有大约10个编译器,这些构build是一个噩梦来testing和维护。

给它一个XML接口,无论是作为parameter passing还是作为返回值传递,或者作为文件通过命令行调用。 这看起来可能不像普通的函数接口那样直接,而是从例如Java访问可执行文件的最实用的方法。