如何设计一个C/C库以在许多客户端语言中可用?

我打算编写一个库,应该可以由大量的人在广泛的平台上使用。我应该考虑什么设计呢?为了使这个问题更具体,最后有四个“子问题”。

语言选择

考虑到所有已知的要求和细节,我得出结论,用C或C编写的库是要走的路。我认为我的库的主要用途是在C,C和Java SE编写的程序,但我也可以想到的原因来使用它从Java ME,PHP,.NET,Objective C,Python,Ruby,bash scrips,等等…也许我不能瞄准所有的人,但如果可能,我会做到。

要求

这将是很多来描述我的图书馆的全部目的,但有一些方面可能是重要的这个问题:

>库本身将从小开始,但肯定会增长到巨大的复杂性,因此它不是一个并行维护几个版本的选项。
>大多数的复杂性将隐藏在库内,虽然
>该库将构造一个在内部使用的对象图。图书馆的一些客户端只对特定对象的特定属性感兴趣,而其他客户端必须以某种方式遍历对象图
>客户端可以更改对象,并且必须通知库
>库可能会更改对象,并且如果客户端已经具有该对象的句柄,则必须通知它
>库必须是多线程的,因为它将维护与其他主机的网络连接
>虽然可以同步处理对库的一些请求,但是它们中的许多将花费太长时间并且必须在后台处理,并且在成功(或失败)时通知客户端

当然,无论他们是否满足我的具体要求,或者如果他们以一般的方式回答问题对更广泛的受众!

我的假设,到目前为止

所以这里有一些我的假设和结论,我在过去几个月收集:

>在内部我可以使用任何我想要的,例如。 C与操作符重载,多继承,模板元编程…只要有一个可移植编译器处理它(想想gcc / g)
>但是我的接口必须是一个干净的C接口,不涉及名称调整
>另外,我认为我的接口应该只包括函数,基本/原始数据类型(也许指针)作为参数和返回值
>如果我使用指针,我想我应该只使用它们将它们传回库,而不是直接操作引用的内存
>对于在C应用程序中的使用,我也可能提供一个面向对象的接口(这也倾向于名称调整,所以应用程序必须使用相同的编译器,或包括库的源代码形式)
>这也是真的在C#中使用?
>对于在Java SE / Java EE中的使用,Java本机接口(JNI)适用。我有一些基本的知识,但我一定要仔细检查一下。
>不是所有的客户端语言都处理多线程,所以应该有一个线程与客户端通信
>对于Java ME使用,没有JNI这样的东西,但我可能去与Nested VM
>对于在Bash脚本中的使用,必须有一个带有命令行界面的可执行文件
>对于其他客户端语言,我不知道
>对于大多数客户端语言,有一种用该语言编写的适配器接口是很好的。我认为有工具自动生成这个Java和一些其他
>对于面向对象的语言,可能创建一个面向对象的适配器隐藏的事实,即库的接口是基于功能的 – 但我不知道是否值得付出

可能的子问题

>这是可能的可管理的努力,还是它只是太多的可移植性?
>有没有关于这种设计标准的好书/网站?
>是我的任何假设错了吗?
>哪些开源库值得学习从他们的设计/接口/ souce学习?
> meta:这个问题很长,你看到任何方法把它分成几个较小的? (如果你回复这个,做它作为一个评论,而不是一个答案)

最佳答案
大部分是正确的。直线程序界面是最好的。 (这不完全相同的C btw(**),但足够接近)

我接口DLL很多(*),开源和商业,所以这里有一些点,我记得从日常做法,注意这些是更多的推荐领域研究,而不是基数真理:

>注意装修和类似的“次要”修改方案,特别是如果你使用MS编译器。最引人注目的是stdcall约定有时会导致装饰生成VB的缘故(装饰是像@ 6后的函数符号名称之类的东西)
>不是所有的编译器实际上可以布局各种结构:

>因此避免过度使用工会。
>避免bitpacking
>并优选打包记录。虽然速度较慢,至少所有编译器都可以访问打包的记录

>在Windows上使用stdcall。这是Windows DLL的默认值。避免fastcall,它不是完全标准化(特别是如何小记录传递)
>一些提示,使自动标题翻译更容易:

>宏很难自动转换,因为它们不正确。避免他们,使用功能
>为每个指针类型定义单独的类型,并且不要在函数声明中使用复合类型(xtype **)。
>尽可能地遵循“使用前定义”咒语,这将避免用户翻译头文件以重新排列它们,如果他们的语言通常需要在使用之前定义,并且使单向解析器更容易翻译它们。或者如果他们需要上下文信息来自动翻译。

>不要暴露超过必要的。如果可能,请保持句柄类型不一致。它只会导致版本问题后,。
>不要将诸如记录/结构体或数组之类的结构化类型作为returntype函数返回。
>总是有一个版本检查功能(更容易做出区分)。
>注意枚举和布尔。其他语言可能有略微不同的假设。你可以使用它们,但记录他们的行为和它们有多大。还要提前考虑,并确保枚举不会变大,如果你添加几个字段,打破界面。 (例如,在Delphi / pascal上,默认布尔值为0或1,其他值未定义。有类似于C的布尔值(字节,16位或32位字大小,尽管它们最初是为COM引入的,不是C接口))
>我喜欢stringtypes是指向char长度作为单独的字段(COM也这样做)。优选地不必依赖于零终止。这不仅仅是因为安全(溢出)的原因,也因为它更容易/更便宜的接口到德尔福本机类型的方式。
>内存总是以鼓励内存管理完全分离的方式创建API。 IOW不承担任何关于内存管理的事情。这意味着你的lib中的所有结构都通过你自己的内存管理器分配,如果一个函数传递一个结构到你,复制它,而不是存储一个指针与“客户端”内存管理。因为你迟早会不小心调用free或realloc 🙂
>(实现语言,而不是接口),不愿意改变协处理器异常掩码。一些语言将其作为符合其标准浮点错误(异常)处理的一部分进行更改。
>始终将回调与用户可配置上下文配对。这可以由用户使用来给出回调状态,而不定义全局变量。 (例如对象实例)
>小心协处理器状态字。它可能会被其他人改变并破坏你的代码,如果你改变它,其他代码可能会停止工作。状态字通常不作为调用约定的一部分保存/恢复。至少不是在实践中。
>不要使用C风格的varargs参数。并非所有语言都以不安全的方式允许可变数量的参数
(*)Delphi程序员,一天工作,涉及接口许多硬件,从而转换供应商SDK头。到晚上免费Pascal开发人员,负责,其中,Windows标题。

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

> C在一些二进制格式上添加下划线前缀(a.out,Coff?)
>有时不同的C编译器对于通过价值传递的小结构有什么样的意见。官方上他们不应该支持它在所有afaik,但大多数。
>结构打包有时不同,调用约定的细节(如跳过
 整数寄存器,如果参数在FPU寄存器中是可注册的)

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

虽然我不知道SWIG,我知道和使用一些delphi的特定标题工具(h2pas,Darth / headconv等)。

然而我从来没有使用他们在全自动模式,因为更经常的时候,不是输出吸。注释更改行或被删除,并且不保留格式。

我通常做一个小脚本(在Pascal,但你可以使用任何具有体面的字符串支持)分割标题,然后尝试一个工具在相对同质的部分(例如只有结构,或只定义等)。

然后我检查我是否喜欢自动转换输出,并使用它,或尝试自己做一个特定的转换器。由于它是一个子集(只有结构),它通常比做一个完整的头转换器更容易。当然,这取决于我的目标是一点点。 (漂亮,可读的标题或快速和脏的)。在每个步骤,我可以做一些替代(用sed或编辑器)。

我对Winapi commctrl和ActiveX / comctl头文做的最复杂的方案。在那里我结合了IDL和C头(接口的IDL,C中的一些不可解释的宏,其余的C头),并设法获得大约80%的宏类型(通过传播sendmessage中的类型转换宏返回宏声明,带有合理的(wparam,lparam,lresult)默认值)

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

Jedi winapi转换项目可能有更多的信息,他们将大约一半的窗口标题转换为Delphi,因此有巨大的经验。

转载注明原文:如何设计一个C/C库以在许多客户端语言中可用? - 代码日志