我正计划编写一个库,该库应该可以在各种平台上供大量用户使用.我需要考虑什么来设计它呢?为了使这个问题更具体,最后有四个"子问题".

语言的 Select

考虑到所有已知的要求和细节,我得出结论,用C或C++编写的库是可行的.我认为我的库的主要用途将是在用C, C++ and Java SE编写的程序中,但我也可以从Java ME, PHP, .NET, Objective C, Python, Ruby, bash scrips, etc...开始想出使用它的理由也许我不能针对所有的程序,但如果可能的话,我会这样做的.

要求

在这里描述我的图书馆的全部用途是非常重要的,但有一些方面可能对这个问题很重要:

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

Of course, answers are welcome no matter if they address my specific requirements, or if they answer the question in a general way that matters to a wider audience!

我的假设,到目前为止

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

  • 在内部,我可以使用我想要的任何东西,例如C++与运算符重载、多重继承、模板元编程…只要有一个可移植的编译器来处理它(想想gcc/g++)
  • 但我的接口必须是干净的C接口,不涉及名称损坏
  • 此外,我认为我的接口应该只由函数组成,将基本/原始数据类型(可能还有指针)作为参数和返回值传递
  • 如果我使用指针,我认为我应该只使用它们将它们传递回库,而不是直接对引用的内存进行操作
  • 为了在C++应用程序中使用,我可能还会提供面向对象的接口(这也容易出现名称损坏,因此应用程序必须使用相同的编译器,或者以源代码形式包含库)
  • 在C#中的使用也是这样吗?
  • 对于在Java SE/Java EE中的使用,应用Java本机接口(JNI).我对此有一些基本知识,但我一定要再判断一遍.
  • 并非所有客户端语言都能很好地处理多线程,因此应该有一个线程与客户端对话
  • 对于JavaMe的用法,没有JNI这样的东西,但我可能会 Select Nested VM
  • 要在Bash脚本中使用,必须有一个带有命令行界面的可执行文件
  • 对于其他客户端语言,我不知道
  • 对于大多数客户端语言,最好有一个用该语言编写的适配器接口.我认为有一些工具可以为Java和其他一些工具自动生成此代码
  • 对于面向对象的语言,可以创建一个面向对象的适配器来隐藏库的接口是基于函数的这一事实-但我不知道是否值得这样做

可能的子问题

  • 通过可管理的努力,这是可能的吗,或者只是可移植性太强了吗?
  • 有没有关于这类设计标准的好书/好网站?
  • 我的假设有错吗?
  • 哪些开放源代码库值得学习它们的设计/界面/资源?
  • 梅塔:这个问题很长,你有没有办法把它分成几个小问题?(If you reply to this, do it as a comment, not as an answer)

推荐答案

基本正确.直接的程序界面是最好的.(与C btw(**)不完全相同,但足够接近)

我经常与DLL接口(*),既有开源的,也有商业的,所以这里是我在日常实践中记住的一些要点,请注意,这些是更推荐研究的领域,而不是基本事实:

  • 注意装饰和类似的"小"损坏方案,特别是如果你使用MS编译器.最值得注意的是,stdcall约定有时会为了VB而产生装饰(装饰是在函数符号名称后面加上@6之类的东西)
  • 并非所有的编译器都能实际布局各种 struct :
  • 在Windows上使用stdcall.这是Windows DLL的默认设置.避免fastcall,它不是完全标准化的(特别是小记录的传递方式)
  • 使自动标题转换更容易的一些提示:
    • 宏由于非典型性,很难自动转换.避免它们,使用函数
    • 为每种指针类型分别定义类型,函数声明中不要使用复合类型(xtype**).
    • 尽量遵循"先定义后使用"的口头禅,这样可以避免用户在语言一般需要使用前定义的情况下翻译头来重新排列,让一遍解析器更容易翻译.或者他们是否需要上下文信息来自动翻译.
  • 不要在必要的时候曝光更多的东西.如果可能,请保留手柄类型为OPAGY.这只会在以后造成版本问题.
  • 不要将记录/ struct 或数组等 struct 化类型作为returntype函数返回.
  • 始终具有版本判断功能(更容易区分).
  • 注意枚举和布尔值.其他语言可能会有稍微不同的假设.您可以使用它们,但是要很好地记录它们的行为和大小.也要提前考虑,如果您添加几个字段,打破接口,请确保枚举不会变得更大.(例如,在Delphi/Pascal上,默认情况下布尔值为0或1,其他值未定义.类似C的布尔值有一些特殊类型(字节、16位或32位字长,尽管它们最初是为COM而不是C接口引入的).
  • 我更喜欢将指向char+length的指针作为单独字段的字符串类型(COM也可以做到这一点).优选地,不必依赖于零终止.这不仅仅是因为安全(溢出)原因,还因为以这种方式将它们连接到Delphi原生类型更容易/更便宜.
  • 内存请始终以鼓励完全分离内存管理的方式创建API.IOW对内存管理不做任何假设.这意味着lib中的所有 struct 都是通过您自己的内存管理器分配的,如果某个函数向您传递了一个 struct ,请复制该 struct ,而不是存储使用"客户端"内存管理创建的指针.因为您迟早会意外地对其调用free或realloc:-)
  • (实现语言,而不是接口),不愿更改协处理器异常掩码.一些语言将此更改为符合其标准的浮点错误(异常)处理的一部分.
  • 始终将回调与用户可配置的上下文配对.用户可以使用它来给出回调状态,而无需定义全局变量.(例如,对象实例)
  • 请注意协处理器状态字.它可能会被其他人更改并 destruct 您的代码,如果您更改它,其他代码可能会停止工作.状态字通常不作为调用约定的一部分保存/恢复.至少在实践中不是这样.
  • 不要使用C风格的varargs参数.并非所有语言都以不安全的方式允许可变数量的参数

(**)

  • c在某些二进制格式(a.out,Coff?)上添加下划线前缀
  • 有时,不同的C编译器对如何处理通过值传递的小 struct 有不同的看法.从官方来看,他们根本不应该支持AFAIK,但大多数人都支持.
  • struct 打包有时会有所不同,调用约定(如跳过)的细节也会有所不同

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

虽然我对SWIG不太了解,但我知道并使用了一些特定于delphi的头工具(h2pas、Darth/headconv等).

然而,我从不在全自动模式下使用它们,因为更多的时候不是输出糟糕.注释更改行或被剥离,并且不保留格式.

我通常会制作一个小脚本(用Pascal编写,但你可以使用任何有良好字符串支持的脚本),将标题拆分,然后在相对同质的部分(例如,仅 struct 或仅定义等)上try 一个工具.

然后我判断我是否喜欢自动转换输出,或者使用它,或者try 自己制作一个特定的转换器.因为它是针对子集的(就像只针对 struct ),所以通常比制作一个完整的头转换器容易得多.当然,这要看我的目标是什么.(漂亮的、可读的标题或快速而肮脏的标题).在每一步,我可能会做一些替换(使用sed或编辑器).

我为Winapi commctrl和ActiveX/comctl头做的最复杂的方案.在那里,我组合了idl和C头(接口的idl,这是C语言中一堆无法解析的宏,睡觉的C头),并设法让宏输入大约80%的类型(通过将sendmessage宏中的类型转换传播回宏声明,并使用合理的(wparam,lparam,lresult)缺省值)

半自动方式的缺点是声明的顺序不同(例如,先是常量,然后是 struct ,然后是函数声明),这有时会使维护变得很痛苦.因此,我总是保留原始头/SDK以进行比较.

绝地温纳皮转化项目可能有更多的信息,他们将大约一半的视窗标题翻译成德尔菲,因此有丰富的经验.

C++相关问答推荐

为什么在传输 Big Data 时共享内存段的运行时间比管道更长?

错误:在.h程序中重新定义 struct

如何在C中从函数返回指向数组的指针?

模拟shell并运行.sh文件

为什么即使在强制转换时,此代码也会溢出?

正确的TCP/IP数据包 struct

如何将字符**传递给需要常量字符指针的常量数组的函数

将数据移动到寄存器时出现分段故障

在CLANG中调试预处理器宏

Setenv在c编程中的用法?

为什么函数是按照定义的顺序执行的,而不是按照从avr-c中的int main()调用的顺序执行的?

关于";*(++p)->;t";、&++p->;t";和&q;++*p->;t";的问题

在C语言中,指针指向一个数组

如何确保我将使用C标准库函数的函数版本,如&getc";,而不是类似函数的宏版本?

表达式x&;&;(~x)应该返回1还是0?它依赖于编译器吗?

计算SIZE_MAX元素的长数组的大小

变量值不正确的问题

将size_t分配给off_t会产生符号转换错误

定义 int a = 0, b = a++, c = a++;在 C 中定义了行为吗?

为什么在许多开源代码中如此流行对 C 中内置的函数或变量使用 #define 或 typedef 别名?