Linux上位机开发实战(编写API库)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
我们自己编写linux上位机软件的时候,尽量都是通过框架+库的形式来开发。这就是所谓的低耦合,高内聚。相似的功能、模块和算法,都可以做成一个静态库,或者是动态库。实际操作中,客户一般喜欢设计为动态库。因为相比较动态库而言,静态库需要重新编译,比较麻烦。而动态库,即使有bug,直接替换dll、so文件即可。
1、API库的构成
提供给客户的API库,从技术上说一般包含了head头文件、so动态库文件即可。另外,从版权保护的角度来说,通常还包含一个license文件,或者是一个usb key。
2、编程语言
通常来讲,接口都会是c语言开发的,实现部分可以是c,也可以是c++。这样,即使客户是用其他语言开发的,也只需要把库文件简单做一个语言封装,这样就可以比较容易地转到其他编程语言,比如说c#、java、python、js、ruby、perl、go等等。
3、基本原则
对外输出的动态库,最重要的原则,就是只提供必要的信息,隐藏实现的细节。对外提供的函数,一般都是包括在header头文件里面。中间如果涉及到复杂的结构体,最好也是用void*表示。实际开发库的时候,我们自己习惯用cpp开发,但是发布给客户,一定要用extern "C"包装一下。
4、文档部分
除了技术的部分,详细的使用文档也是必不可少的。每一个api是什么意思,对应的参数是什么,都需要详细地告诉用户。用户可能也不一定看,但是交付的时候,还是有必要提供的。和代码版本一样,文档部分,我们也要做好版本管理。
5、demo代码
前面说过,客户拿到API库之后,未必会认真查看文档。这种情况下,把范例做好,就可以极大地降低我们开发的工作量。另外,客户的行业、开发水平、人员构成也是千差万别,所以example需要尽量简单,api的命名也尽量简单一些。功能最好直接一点,比如生成的二维码、条形码、人脸识别的位置和概率、车牌识别位置和内容,这种是最合适的。
6、举例说明
假设我们需要写一个图像处理的库。这个时候就需要把实现和接口分开。这里说的分开,并不是说头文件和cpp文件分开,而是说api接口,和类实现分开。
假设实现是这样的,
// header file
#ifndef IMG_PROGRAME_FRAME_H
#define IMG_PROGRAME_FRAME_H
class ImgProgrameFrame
{
public:
ImgProgrameFrame();
~ImgProgrameFrame();
bool Initialize(bool isRGB, int width, int height);
int GetWidth();
int GetHeight();
bool GetResult(void* p_img, void* p_result);
private:
void InnerProcess(); // other function defined here
};
#endif
// cpp file
ImgProgrameFrame::ImgProgrameFrame() {}
ImgProgrameFrame::~ImgProgrameFrame() {}
bool ImgProgrameFrame::Initialize(bool, int, int) { return false; }
int ImgProgrameFrame::GetWidth() { return 0; }
int ImgProgrameFrame::GetHeight() { return 0; }
bool ImgProgrameFrame::GetResult(void*, void*) { return true; }
void ImgProgrameFrame::InnerProcess() {}; // other function implementation
但是给客户的API接口肯定不是这样,上面的实现只能我们自己看到。不可能把class的细节都给客户,
// header file
#ifndef IMG_PROGRAME_FRAME_API_H
#define IMG_PROGRAME_FRAME_API_H
void Initialize(bool isRGB, int width, int height);
int GetWidth();
int GetHeight();
bool GetResult(void* img,void* param);
#endif
// cpp file
static class ImgProgrameFrame _gFrame;
void Initialize(bool isRGB, int width, int height)
{
_gFrame.Initialize(isRGB, width, height);
}
int GetWidth() { return _gFrame.GetWidth(); }
int GetHight() { return _gFrame.GetHeight(); }
bool GetResult(void* img, void* result) { return _gFrame.GetResult(img, result); }
和具体的实现相比较,对外提供的接口都比较简洁。类中的变量、私有函数、部分public函数都会做一个提取之后,再转交给客户。或者说,我们提供给客户的部分,只是客户愿意付费的那部分。这是比较方便、也比较合理的一个做法。
当然如果需要并发处理,一般就会生成一个动态的ImgProgrameFrame对象,通过void*给客户,每次客户使用的时候再传进来即可。类似于这样,
// header file
#ifndef IMG_PROGRAME_FRAME_API_H
#define IMG_PROGRAME_FRAME_API_H
void* InitializeObject(bool isRGB, int width, int height);
int GetWidth(void* p);
int GetHeight(void* p);
bool GetResult(void*p, void* img, void* result);
#endif
// cpp file
void* InitializeObject(bool isRGB, int width, int height)
{
ImgProgrameFrame* p = new ImgProgrameFrame();
p->Initialize(isRGB, width, height);
return (void*)p;
}
int GetWidth(void* p)
{
return ((ImgProgrameFrame*)(p))->GetWidth();
}
int GetHeight(void* p)
{
return ((ImgProgrameFrame*)(p))->GetHeight();
}
bool GetResult(void*p, void* img, void* result)
{
return ((ImgProgrameFrame*)(p))->GetResult(img, result);
}