博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
android导入本地图书_美丽的本地图书馆
阅读量:2517 次
发布时间:2019-05-11

本文共 38892 字,大约阅读时间需要 129 分钟。

android导入本地图书

I’m obsessed with nice APIs. Not just APIs however, also in making the overall experience of using a library as good as possible. For Python there are quite a few best practices around by now but it feels like there is not really a lot of information available about how to properly structure a native library. What do I mean by native library? Essentially a dylib/DLL/so.

我迷上了不错的API。 但是,不仅要使用API​​,还应尽可能提高使用库的整体体验。 对于Python来说,目前有很多最佳实践,但是似乎没有足够的有关如何正确构建本机库的信息。 我的原生库是什么意思? 本质上是dylib / DLL / so。

Since I’m currently spending more time on C and C++ than Python at work I figured I might take the opportunity and collect my thoughts on how to write proper shared libraries that do not annoy your users.

由于我目前在C和C ++上花费的时间比在工作上花费的时间多,所以我想我可以借此机会并收集有关如何编写适当的共享库的想法,这些共享库不会惹恼用户。

共享还是静态? (Shared or Static?)

This post almost entirely assumes that you are building a DLL or shared library and not something you link statically. While it sounds like a statically and dynamically linked library are essentially the same thing where the only difference is how you link against it, there is much more to it.

这篇文章几乎完全假设您正在构建DLL或共享库,而不是静态链接的东西。 听起来静态和动态链接的库本质上是相同的东西,唯一的区别是链接的方式不同,但还有更多。

With a dynamically linked library you have much better control over your symbols. Dynamically linked libraries also work much better between different programming languages. Nothing stops you from writing a library in C++ and then using it in Python. In fact, that’s exactly how I recommend doing unittests against such libraries. More about that later.

使用动态链接库,您可以更好地控制符号。 动态链接库在不同的编程语言之间也可以更好地工作。 没有什么可以阻止您使用C ++编写库,然后在Python中使用它。 实际上,这正是我建议对此类库进行单元测试的方式。 以后再说。

哪种语言? (Which Language?)

So you want to write a library that compiles into a DLL or something of that sort and it should be somewhat platform independent. Which languages can you actually use there? Right now you can pick between C and C++ and soon you might also be able to add Rust to that list. Why not others? C is easy: because that’s the only language that actually defines a somewhat stable ABI. Strictly speaking it’s not the language that defines it, it’s the operating system, but in one way or another, C is the language of choice for libraries and the C calling conventions is the lingua franca of shared libraries.

因此,您想编写一个可编译为DLL或类似文件的库,并且该库应在某种程度上与平台无关。 您在那里实际上可以使用哪些语言? 现在,您可以在C和C ++之间进行选择,并且很快您也可以将Rust添加到该列表中。 为什么不别人呢? C很容易:因为这是唯一定义某种程度上稳定的ABI的语言。 严格说来,它不是定义它的语言,它是操作系统,但是从某种程度上来说,C是库选择的语言,而C调用约定是共享库的通用语言。

“The greatest trick that C ever pulled was convince the world that it does not have a runtime”. I’m not sure where I heard the quote first, but it’s incredibly appropriate when talking about libraries. Essentially C is so commonplace that everything can assume that some basic functionality is provided by the C standard library. That’s the one thing that everybody agreed on that exists. For C++ the situation is more complicated. C++ needs a bunch of extra functionality that is not provided by the C standard library. Primarily it needs support for exception handling. C++ however degrades otherwise nicely to C calling conventions so it’s very easy to still write libraries in it, that completely hide the fact that there is C++ behind the scenes.

“ C曾经拉过的最大诀窍就是让世界相信它没有运行时”。 我不确定首先在哪里听到报价,但这在谈论库时非常合适。 本质上,C非常普遍,以至于所有东西都可以假定C标准库提供了一些基本功能。 那是所有人都同意存在的一件事。 对于C ++,情况更加复杂。 C ++需要一堆C标准库未提供的额外功能。 首先,它需要支持异常处理。 但是,C ++可以很好地降级为C调用约定,因此仍然很容易在其中编写库,这完全掩盖了C ++在幕后的事实。

For other languages that’s not so easy however. Why for instance is it not a good idea to write a library in Go? The reason for this is that Go for needs quite a heavy runtime that does garbage collection and provides a scheduler for it’s coroutines. Rust is getting closer to not having any runtime requirements besides the C standard library which will make it possible to write libraries in it.

对于其他语言,这并不是那么容易。 例如,为什么在Go中编写一个库不是一个好主意? 原因是Go for需要大量的运行时来执行垃圾回收并为其协同程序提供调度程序。 除了C标准库之外,Rust几乎没有任何运行时要求,这使得可以在其中编写库。

Right now however, C++ is most likely the language you want to use. Why not C? The reason for this is that Microsoft’s C compiler is notoriously bad at receiving language updates and you would otherwise be stuck with C89. Obviously you could just use a different compiler on Windows but that causes a whole bunch of problems for the users of your library if they want to compile it themselves. Requiring a tool chain that is not native to the operating system is an easy way to alienate your developer audience.

但是,现在,C ++很可能是您要使用的语言。 为什么不C? 原因是众所周知,Microsoft的C编译器在接收语言更新方面很差,否则您将被C89所困扰。 显然,您可以在Windows上使用其他编译器,但是如果库用户希望自己编译它,则会给您带来很多问题。 要求不是操作系统本身的工具链是疏远开发人员受众的一种简便方法。

I would however generally recommend to a very C like subset of C++: don’t use exceptions, don’t use RTTI, don’t build crazy constructors. The rest of the post assumes that C++ is indeed the language of choice.

但是,我通常会向非常像C的C ++子集推荐:不要使用异常,不要使用RTTI,不要构建疯狂的构造函数。 文章的其余部分假定C ++确实是首选语言。

公共标题 (Public Headers)

The library you’re building should ideally have exactly one public header file. Internally go nuts and create as many headers as you want. You want that one public header file to exist, even if you think your library is only ever going to be linked against something that is not C. For instance Python’s CFFI library can parse header files and build bindings out of that. People of all languages know how headers work, they will have a look at them to build their own bindings.

理想情况下,您正在构建的库应该只有一个公共头文件。 内部发疯,并根据需要创建任意数量的标题。 您希望存在一个公共头文件,即使您认为您的库只会与非C的东西链接。例如,Python的CFFI库可以解析头文件并从中建立绑定。 各种语言的人都知道标题是如何工作的,他们将研究标题以建立自己的绑定。

What rules are there to follow in headers?

标头中应遵循哪些规则?

标头护卫 (Header Guards)

Each public header that other people use should have sufficiently unique header guards to make sure they can be included multiple times safely. Don’t get too creative with the guards, but also don’t be too generic with them. It’s no fun including a header that has a super generic include guard at the top (like UTILS_H and nothing else). You also want to make sure that there are extern "C" markers for C++.

他人使用的每个公共标头应具有足够独特的标头防护,以确保可以安全地多次包含它们。 不要对守卫太有创造力,但也不要对他们太宽泛。 包含在顶部具有超级通用包含保护的标头(例如UTILS_H等)没什么好玩的 。 您还想确保C ++有外部“ C”标记。

This would be your minimal header:

这将是您的最小标头:

#ifndef YOURLIB_H_INCLUDED#ifndef YOURLIB_H_INCLUDED#define YOURLIB_H_INCLUDED#define YOURLIB_H_INCLUDED#ifdef __cplusplus#ifdef __cplusplusextern extern "C" "C" {
{
#endif#endif/* code goes here *//* code goes here */#ifdef __cplusplus#ifdef __cplusplus}}#endif#endif#endif#endif

出口标记 (Export Markers)

Because you yourself will probably include your header file as well you will need to make sure that there are macros defined that export your functions. This is necessary on Windows and it’s a really good idea on other platforms as well. Essentially it can be used to change the visibility of symbols. I will go into that later, for the time being just add something that looks like this:

因为您自己也可能包含头文件,所以需要确保定义了一些宏来导出您的函数。 在Windows上这是必需的,在其他平台上也是一个好主意。 本质上,它可用于更改符号的可见性。 我将在稍后讨论,暂时添加如下所示的内容:

On Windows it will set YL_API (I used YL as short version for “Your Library” here, pick a prefix that fits you) for DLLs appropriately depending on what flag is set. Whoever includes the header without doing anything fancy before will automatically get __declspec(dllimport) in its place. This is a really good default behavior on Windows. For other platforms nothing is set unless a somewhat recent GCC/clang version is used in which case the default visibility marker is added. As you can see some macros can be defined to change which branch is taken. For instance when you build the library you would tell the compiler to also defined YL_BUILD_SHARED.

在Windows上,它将根据设置的标志适当地为DLL设置YL_API (我在这里将YL用作“ Your Library”的简称,选择适合您的前缀)。 谁在不做任何花哨的情况下包含标头的人都会自动获得__declspec(dllimport)的位置。 这是Windows上非常好的默认行为。 对于其他平台,除非使用了较新的GCC / clang版本,否则不会进行任何设置,在这种情况下,将添加默认可见性标记。 如您所见,可以定义一些宏来更改采用哪个分支。 例如,当您构建库时,您将告诉编译器也定义了YL_BUILD_SHARED

On Windows the default behavior for DLLs has always been: all symbols are not exported default unless marked with __declspec(dllexport). On other platforms unfortunately the behavior has always been to export everything. There are multiple ways to fix that, one is the visibility control of GCC 4. This works okay, but there are some extra things that need to be considered.

在Windows上,DLL的默认行为始终是:默认情况下不会导出所有符号,除非标记为__declspec(dllexport) 。 不幸的是,在其他平台上,行为始终是导出所有内容。 有多种解决方法,一种是GCC 4的可见性控制。这可以正常工作,但是还需要考虑一些其他事项。

The first is that the in-source visibility control is not the silver bullet. For a start the marker will do nothing unless the library is compiled with -fvisibility=hidden. More important than that however is that this will only affect your own library. If you statically link anything against your library, that library might expose symbols you do not want to expose. Imagine for instance you write a library that depends on another library you want to statically link in. This library’s symbols will also be exported from your library unless you prevent that.

首先是源内可见性控件不是灵丹妙药。 首先,除非使用-fvisibility = hidden编译库,否则标记将不执行任何操作。 比这更重要的是,这只会影响您自己的库。 如果您将任何内容静态链接到您的库,则该库可能会公开您不想公开的符号。 例如,假设您编写了一个库,该库依赖于要静态链接的另一个库。该库的符号也将从库中导出,除非您禁止这样做。

This works differently on different platforms. On Linux you can pass --exclude-libs ALL to ld and the linker will remove those symbols automatically. On OS X it’s tricker because there is no such functionality in the linker. The easiest solution is to have a common prefix for all functions. For instance if all your functions start with yl_ it’s easy to tell the linker to hide everything else. You do this by creating a symbols file and then pointing the linker to it with -exported_symbols_list symbols.txt. The contents of this file can be the single line _yl_*. Windows we can ignore as DLLs need explicit export markers.

这在不同平台上的工作方式有所不同。 在Linux上,您可以将--exclude-libs ALL传递给ld ,链接器将自动删除这些符号。 在OS X上,这很麻烦,因为链接器中没有这样的功能。 最简单的解决方案是为所有功能使用通用前缀。 例如,如果您所有的函数都以yl_开头,则很容易告诉链接器隐藏其他所有内容。 为此,您可以创建一个符号文件,然后使用-exported_symbols_list symbol.txt将链接器指向该文件。 该文件的内容可以是_yl_ *单行 。 由于DLL需要明确的导出标记,因此我们可以忽略Windows。

小心包含和定义 (Careful with Includes and Defines)

One thing to be careful about is that your headers should not include too many things. Generally I believe it’s fine for a header to include things like stdint.h to get some common integer types. However what you should not do is being clever and defining types yourself. For instance msgpack had the brilliant idea to define int32_t and a few other types for Visual Studio 2008 because it lacks the stdint.h header. This is problematic as only one library can define those types then. Instead the better solution is to ask the user to provide a replacement stdint.h header for older Visual Studio versions.

要注意的一件事是标题不应该包含太多内容。 通常,我相信标头包含stdint.h之类的东西来获取一些常见的整数类型就可以了。 但是,您不应该做的是聪明并自己定义类型。 例如,msgpack有一个绝妙的主意,因为它缺少stdint.h标头,所以为Visual Studio 2008定义了int32_t和其他一些类型。 这是有问题的,因为那时只有一个库可以定义这些类型。 相反,更好的解决方案是要求用户为旧版本的Visual Studio提供替换的stdint.h标头。

Especially do not ever include windows.h in a library header. That header pulls in so much stuff that Microsoft added extra defines to make it leaner (WINDOWS_LEAN_AND_MEAN, WINDOWS_EXTRA_LEAN and NOMINMAX). If you need windows.h included, have a private header file that’s only included for your .cpp files.

特别是永远不要在库头文件中包含windows.h 。 该标头引入了很多东西,Microsoft添加了额外的定义以使其更精简( WINDOWS_LEAN_AND_MEANWINDOWS_EXTRA_LEANNOMINMAX )。 如果需要包含windows.h ,请使用仅包含.cpp文件的专用头文件。

稳定的ABI (Stable ABI)

Do not put any structs into public headers unless you are 100% sure that you will never change them. If you do want to expose structs and you do want to add extra members later, make sure that the user does not have to allocate that header. If the user does have to allocate that header, add a version or size information as first member into the struct.

除非您100%确保不会更改它们,否则请勿将任何结构放入公共头文件中。 如果确实要公开结构,并且以后确实要添加额外的成员,请确保用户不必分配该标头。 如果用户必须分配该标头,则将版本或大小信息作为第一个成员添加到结构中。

Microsoft generally puts the size of structs into the structs to allow adding members later, but this leads to APIs that are just not fun to use. If you can try to avoid having too many structs in the headers, if you can’t at least try to come up with alternative methods to make the API suck less.

Microsoft通常将结构的大小放入结构中,以允许以后添加成员,但这会导致API使用起来很不好玩。 如果可以避免在标头中包含太多结构,则至少不能尝试提出其他方法来减少API的负担。

With structs you also run into the issue that alignments might differ between different compilers. Unfortunately there are cases where you are dealing with a project that forces the alignment to be different for the whole project and that will obviously also affect the structs in your header file. The fewer structs the better 🙂

使用结构时,您还会遇到以下问题:不同的编译器之间的对齐方式可能会有所不同。 不幸的是,在某些情况下,您正在处理的项目会迫使整个项目的对齐方式不同,这显然也会影响头文件中的结构。 结构越少越好

Something that should go without saying: do not make macros part of your API. A macro is not a symbol and users of languages not based on C will hate you for having macros there.

不用说的事情是:不要将宏作为API的一部分。 宏不是符号,使用基于C的语言的用户会讨厌您在那里使用宏。

One more note on the ABI stability: it’s a very good idea to include the version of the library both in the header as well as compiled into the binary. That way you can easily verify that the header matches the binary which can save you lots of headaches.

关于ABI稳定性的另一点说明:在头文件中以及编译为二进制文件时都包含库的版本是一个很好的主意。 这样,您可以轻松地验证标题是否与二进制文件匹配,从而可以省去很多麻烦。

Something like this in the header:

标头中的内容如下:

#define YL_VERSION_MAJOR 1#define YL_VERSION_MAJOR 1#define YL_VERSION_MINOR 0#define YL_VERSION_MINOR 0#define YL_VERSION ((YL_VERSION_MAJOR << 16) | YL_VERSION_MINOR)#define YL_VERSION ((YL_VERSION_MAJOR << 16) | YL_VERSION_MINOR)unsigned unsigned int int yl_get_versionyl_get_version (( voidvoid ););int int yl_is_compatible_dllyl_is_compatible_dll (( voidvoid ););

And this in the implementation file:

而这在实现文件中:

导出C API (Exporting a C API)

When exposing a C++ API to C there is not much that needs to be considered. Generally for each internal class you have, you would have an external opaque struct without any fields. Then provide functions that call into your internal functions. Picture a class like this:

将C ++ API暴露给C时,不需要考虑太多。 通常,对于您拥有的每个内部类,您将拥有一个外部不透明结构,没有任何字段。 然后提供调用内部函数的函数。 画一个像这样的课程:

namespace namespace yourlibrary yourlibrary {
{
class class Task Task {
{
publicpublic : : TaskTask (); (); ~~ TaskTask (); (); bool bool is_pendingis_pending () () constconst ; ; void void ticktick (); (); const const char char ** result_stringresult_string () () constconst ; ; };};}}

The internal C++ API is quite obvious, but how do you expose that via C? Because the external ABI now no longer knows how large the structs are you will need to allocate memory for the external caller or give it a method to figure out how much memory to allocate. I generally prefer to allocate for the external user and provide a free function as well. For how to make the memory allocation system still flexible, have a look at the next part.

内部的C ++ API很明显,但是如何通过C公开呢? 由于外部ABI现在不再知道结构有多大,因此您需要为外部调用方分配内存,或者为其提供一种方法来确定要分配多少内存。 我通常更喜欢分配给外部用户并提供免费功能。 有关如何使内存分配系统仍具有灵活性,请看下一部分。

For now this is the external header (this has to be in extern "C" braces):

现在,这是外部标头(必须在外部“ C”大括号中):

And this is how the shim layer would look like in the implementation:

这就是填充层在实现中的样子:

#define AS_TYPE(Type, Obj) reinterpret_cast
(Obj)#define AS_TYPE(Type, Obj) reinterpret_cast
(Obj)#define AS_CTYPE(Type, Obj) reinterpret_cast
(Obj)#define AS_CTYPE(Type, Obj) reinterpret_cast
(Obj)yl_task_t yl_task_t ** yl_task_newyl_task_new ()(){
{
return return AS_TYPEAS_TYPE (( yl_task_tyl_task_t , , new new yourlibraryyourlibrary :::: TaskTask ());());}}void void yl_task_freeyl_task_free (( yl_task_t yl_task_t ** tasktask )){
{
if if (( !! tasktask ) ) returnreturn ; ; delete delete AS_TYPEAS_TYPE (( yourlibraryyourlibrary :::: TaskTask , , tasktask ););}}int int yl_task_is_pendingyl_task_is_pending (( const const yl_task_t yl_task_t ** tasktask )){
{
return return AS_CTYPEAS_CTYPE (( yourlibraryyourlibrary :::: TaskTask , , tasktask )) ->-> is_pendingis_pending () () ? ? 1 1 : : 00 ;;}}void void yl_task_tickyl_task_tick (( yl_task_t yl_task_t ** tasktask )){
{
AS_TYPEAS_TYPE (( yourlibraryyourlibrary :::: TaskTask , , tasktask )) ->-> ticktick ();();}}const const char char ** yl_task_get_result_stringyl_task_get_result_string (( const const yl_task_t yl_task_t ** tasktask )){
{
return return AS_CTYPEAS_CTYPE (( yourlibraryyourlibrary :::: TaskTask , , tasktask )) ->-> result_stringresult_string ();();}}

Notice how the constructor and destructor is fully wrapped. Now there is one problem with standard C++: it raises exceptions. Because constructors have no return value to signal to the outside that something went wrong it will raise exceptions if the allocation fails. That’s however not the only problem. How do we customize how the library allocates memory now? C++ is pretty ugly in that regard. But it’s largely fixable.

注意构造函数和析构函数是如何完全包装的。 现在,标准C ++存在一个问题:它引发了异常。 由于构造函数没有返回值来向外界发出信号,指出出了问题,如果分配失败,它将引发异常。 但是,这不是唯一的问题。 我们如何自定义库现在如何分配内存? 在这方面,C ++非常难看。 但这在很大程度上是可以修复的。

Before we go on: please under no circumstances, make a library, that pollutes the namespace with generic names. Always put a common prefix before all your symbols (like yl_) to lower the risk of namespace clashes.

在继续之前:请在任何情况下都不要创建一个库,该库会使用通用名称污染名称空间。 始终在所有符号(例如yl_ )之前放置一个公共前缀,以降低名称空间冲突的风险。

上下文对象 (Context Objects)

Global state is terrible, so what’s the solution? Generally the solution is to have what I would call “context” objects that hold the state instead. These objects would have all the important stuff on that you would otherwise put into a global variable. That way the user of your library can have multiple of those. Then make each API function take that context as first parameter.

全局状态很糟糕,那么解决方案是什么? 通常,解决方案是使用我称之为状态的“上下文”对象代替。 这些对象将包含所有其他重要内容,否则您会将它们放到全局变量中。 这样,您的图书馆的用户可以拥有多个。 然后,使每个API函数都将该上下文作为第一个参数。

This is especially useful if your library is not threadsafe. That way you can have one per thread at least, which might already be enough to get some parallelism out of your code.

如果您的库不是线程安全的,这特别有用。 这样,每个线程至少可以有一个线程,这可能已经足够从代码中获得一些并行性。

Ideally each of those context objects could also use a different allocator, but given the complexities of doing that in C++ I would not be super disappointed if you did not make that work.

理想情况下,这些上下文对象中的每一个也可以使用不同的分配器,但是鉴于在C ++中执行此操作的复杂性,如果您不做这些工作,我不会感到非常失望。

内存分配定制 (Memory Allocation Customization)

As mentioned before, constructors can fail and we want to customize memory allocations, so how do we do this? In C++ there are two systems responsible for memory allocations: the allocation operators operator new and operator new[] as well as the allocators for containers. If you want to customize the allocator you will need to deal with both. First you need a way to let others override the allocator functions. The simplest is to provide something like this in the public header:

如前所述,构造函数可能会失败,我们想自定义内存分配,那么我们该如何做呢? 在C ++中,有两个系统负责内存分配:分配运算符operator newoperator new []以及容器的分配器。 如果要自定义分配器,则需要同时处理两者。 首先,您需要一种让其他人覆盖分配器功能的方法。 最简单的方法是在public标头中提供以下内容:

And then in your internal header you can add a bunch of inline functions that redirect to the function pointers set to an internal struct. Because we do not let users provide calloc and strdup you probably also want to reimplement those functions:

然后,可以在内部标头中添加一堆内联函数,这些内联函数重定向到设置为内部结构的函数指针。 因为我们不允许用户提供callocstrdup,所以您可能还想重新实现这些功能:

struct struct yl_allocators_s yl_allocators_s {
{
void void ** (( ** f_mallocf_malloc )()( size_tsize_t ); ); void void ** (( ** f_reallocf_realloc )()( void void ** , , size_tsize_t ); ); void void (( ** f_freef_free )()( void void ** ););};};extern extern struct struct yl_allocators_s yl_allocators_s _yl_allocators_yl_allocators ;;inline inline void void ** yl_mallocyl_malloc (( size_t size_t sizesize )){
{
return return _yl_allocators_yl_allocators .. f_mallocf_malloc (( sizesize ););}}inline inline void void ** yl_reallocyl_realloc (( void void ** ptrptr , , size_t size_t sizesize )){
{
return return _yl_allocators_yl_allocators .. f_reallocf_realloc (( ptrptr , , sizesize ););}}inline inline void void yl_freeyl_free (( void void ** ptrptr )){
{
_yl_allocators_yl_allocators .. f_freef_free (( ptrptr ););}}inline inline void void ** yl_callocyl_calloc (( size_t size_t countcount , , size_t size_t sizesize )){
{
void void ** ptr ptr = = _yl_allocators_yl_allocators .. f_mallocf_malloc (( count count * * sizesize ); ); memsetmemset (( ptrptr , , 00 , , count count * * sizesize ); ); return return ptrptr ;;}}inline inline char char ** yl_strdupyl_strdup (( const const char char ** strstr )){
{
size_t size_t length length = = strlenstrlen (( strstr ) ) + + 11 ; ; char char ** rv rv = = (( char char ** )) yl_mallocyl_malloc (( lengthlength ); ); memcpymemcpy (( rvrv , , strstr , , lengthlength ); ); return return rvrv ;;}}

For the setting of the allocators themselves you probably want to put that into a separate source file:

对于分配器本身的设置,您可能希望将其放入单独的源文件中:

内存分配器和C ++ (Memory Allocators and C++)

Now that we have those functions set, how do we make C++ use them? This part is tricky and annoying. To get your custom classes allocated through your yl_malloc you need to implement the allocation operators in all your classes. Because that’s quite a repetitive process I recommend writing a macro for it that can be placed in the private section of the class. I chose to pick by convention that it has to go into private, even though the function it implements are public. Primarily I did that so that it lives close to where the data is defined, which in my case is usually private. You will need to make sure you don’t forget adding that macro to all your classes private sections:

现在我们已经设置了这些功能,如何使C ++使用它们呢? 这部分是棘手的和令人讨厌的。 要通过yl_malloc分配自定义类,您需要在所有类中实现分配运算符。 因为这是一个重复的过程,所以我建议为其编写一个宏,该宏可以放在类的私有部分中。 我选择按照惯例选择它必须私有化,即使它实现的功能是公共的。 首先,我这样做是为了使它位于定义数据的位置附近,在我的情况下,该位置通常是私有的。 您需要确保不要忘记将该宏添加到所有类的私有部分中:

#define YL_IMPLEMENTS_ALLOCATORS #define YL_IMPLEMENTS_ALLOCATORS public: public:     void *operator new(size_t size) { return yl_malloc(size); }     void *operator new(size_t size) { return yl_malloc(size); }     void operator delete(void *ptr) { yl_free(ptr); }     void operator delete(void *ptr) { yl_free(ptr); }     void *operator new[](size_t size) { return yl_malloc(size); }     void *operator new[](size_t size) { return yl_malloc(size); }     void operator delete[](void *ptr) { yl_free(ptr); }     void operator delete[](void *ptr) { yl_free(ptr); }     void *operator new(size_t, void *ptr) { return ptr; }     void *operator new(size_t, void *ptr) { return ptr; }     void operator delete(void *, void *) {}     void operator delete(void *, void *) {}     void *operator new[](size_t, void *ptr) { return ptr; }     void *operator new[](size_t, void *ptr) { return ptr; }     void operator delete[](void *, void *) {}     void operator delete[](void *, void *) {} private:private:

Here is how an example usage would look like:

用法示例如下所示:

Now with that all your classes will be allocated through your allocator functions. But what if you want to use STL containers? Those containers will not be allocated through your functions yet. To fix that particular issue you need to write an STL proxy allocator. That’s an enormously annoying process because of how complex the interface is, for essentially doing nothing.

现在,所有类都将通过分配器函数分配。 但是,如果您想使用STL容器怎么办? 这些容器尚未通过您的功能进行分配。 要解决该特定问题,您需要编写一个STL代理分配器。 这是一个非常烦人的过程,因为界面是如此复杂,实际上什么都不做。

#include 
#include
template template << class class TT >>struct struct proxy_allocator proxy_allocator {
{
typedef typedef size_t size_t size_typesize_type ; ; typedef typedef ptrdiff_t ptrdiff_t difference_typedifference_type ; ; typedef typedef T T ** pointerpointer ; ; typedef typedef const const T T ** const_pointerconst_pointer ; ; typedef typedef TT & & referencereference ; ; typedef typedef const const T T && const_referenceconst_reference ; ; typedef typedef T T value_typevalue_type ; ; template template << class class UU > > struct struct rebind rebind {
{
typedef typedef proxy_allocatorproxy_allocator << UU > > otherother ; ; }; }; proxy_allocatorproxy_allocator () () throwthrow () () {} {} proxy_allocatorproxy_allocator (( const const proxy_allocator proxy_allocator && ) ) throwthrow () () {} {} template template << class class UU > > proxy_allocatorproxy_allocator (( const const proxy_allocatorproxy_allocator << UU > > && ) ) throwthrow () () {} {} ~~ proxy_allocatorproxy_allocator () () throwthrow () () {} {} pointer pointer addressaddress (( reference reference xx ) ) const const {
{
return return && xx ; ; } } const_pointer const_pointer addressaddress (( const_reference const_reference xx ) ) const const {
{
return return && xx ; ; } } pointer pointer allocateallocate (( size_type size_type ss , , void void const const * * = = 00 ) ) {
{
return return s s ? ? reinterpret_castreinterpret_cast << pointerpointer >> (( yl_mallocyl_malloc (( s s * * sizeofsizeof (( TT ))) ))) : : 00 ; ; } } void void deallocatedeallocate (( pointer pointer pp , , size_typesize_type ) ) {
{
yl_freeyl_free (( pp ); ); } } size_type size_type max_sizemax_size () () const const throwthrow () () {
{
return return stdstd :::: numeric_limitsnumeric_limits << size_tsize_t >::>:: maxmax () () / / sizeofsizeof (( TT ); ); } } void void constructconstruct (( pointer pointer pp , , const const TT & & valval ) ) {
{
new new (( reinterpret_castreinterpret_cast << void void *>*> (( pp )) )) TT (( valval ); ); } } void void destroydestroy (( pointer pointer pp ) ) {
{
pp ->~->~ TT (); (); } } bool bool operatoroperator ==== (( const const proxy_allocatorproxy_allocator << TT > > && otherother ) ) const const {
{
return return truetrue ; ; } } bool bool operatoroperator !=!= (( const const proxy_allocatorproxy_allocator << TT > > && otherother ) ) const const {
{
return return falsefalse ; ; }}};};

So before we go on, how does one use this abomination? Like this:

因此,在我们继续之前,如何使用这种可憎性? 像这样:

I would recommend making a header somewhere that defines all the containers you want to use and then force yourself not to use anything else from the STL without typedefing it to use the right allocator. Careful: do not new TaskQueue() those things as you would invoke the global new operator. Place them instead as members in your own structs so that the allocation happens as part of your object which has a custom allocator. Alternatively just put them on the stack.

我建议在某处制作一个标头,该标头定义要使用的所有容器,然后强制自己不要使用STL中的其他任何东西,而不必对它进行类型定义以使用正确的分配器。 注意:不要像调用全局new操作符那样对TaskQueue()进行新操作。 而是将它们作为成员放置在您自己的结构中,以便分配发生在具有自定义分配器的对象的一部分中。 另外,也可以将它们放在堆栈中。

内存分配失败 (Memory Allocation Failures)

In my mind the best way to deal with memory allocation failures is to not deal with them. Just don’t cause any allocation to fail. For a library that’s easy to accomplish, just be aware of how much memory you will allocate in the worst case scenario and if you are unbounded, provide the user of the library with a way to get an idea of how bad things are. The reason for this is that nobody deals with allocation failures either.

在我看来,处理内存分配失败的最佳方法是不处理它们。 只是不要使任何分配失败。 对于易于完成的库,只需知道在最坏的情况下将分配多少内存,如果您不受限制,请为库的用户提供一种了解坏事的方法。 这样做的原因是,也没有人处理分配失败。

For a start the STL entirely depends on std::bad_alloc being thrown from operator new (which we’re not doing above, hehe) and will just bubble up the error for you to deal with it. When you compile your library without exception handling then the library will just terminate the process. That’s pretty terrible, but that’s what’s going to happen anyways if you’re not careful. I have seen more code that ignores the return value of malloc than code that deals with it properly.

首先,STL完全依赖于从运算符new抛出的std :: bad_alloc (我们在上面没有这样做,呵呵),并且只会冒出错误供您处理。 当您在没有异常处理的情况下编译库时,该库将终止该过程。 那真是太糟糕了,但是如果您不小心的话,那将是会发生的事情。 与正确处理malloc的代码相比,我看到更多的代码忽略了malloc的返回值。

Aside from that: on some systems malloc will totally lie to you about how much memory is available anyways. Linux will gladly give you pointers to memory it can’t back up with real physical memory. This fiat memory behavior is quite useful but also will mean that you generally already have to assume that allocation failure might not happen. So instead of reporting allocation errors, if you use C++ and you also want to stick to the STL, then give up on that and just don’t run out of memory.

除此之外,在某些系统上,malloc仍然会欺骗您到底有多少可用内存。 Linux会很乐意为您提供无法使用实际物理内存备份的内存的指针。 此法定内存行为非常有用,但也意味着您通常已经不得不假设可能不会发生分配失败。 因此,如果您使用C ++并且还想坚持使用STL,则不要报告分配错误,而是放弃该操作,并且不要耗尽内存。

In computer games the general concept there is to give subsystems their own allocator and just make sure they never allocate more than what they are given. EA seems to recommend the allocator to handle allocation failures. For instance when it fails to load more memory it would check if it can free up some resources that are not needed (like caches) instead of letting the caller know there is a memory failure. This works even with the limited design that the C++ standard gives with allocators.

在计算机游戏中,通常的概念是为子系统分配自己的分配器,并确保子系统分配的数量永远不会超过分配的数量。 EA似乎建议分配器处理分配失败。 例如,当无法加载更多内存时,它将检查是否可以释放一些不需要的资源(例如缓存),而不是让调用者知道内存出现故障。 即使C ++标准使用分配器提供的有限设计也可以使用。

建造 (Building)

Now that you have written the code, how do you build your library without making your users unhappy? If you’re like me you come from a Unix background where makefiles are what builds software. However that’s not what everybody wants. Autotools/autoconf are terrible, terrible pieces of software and if you give that to a windows guy they will call you all kinds of names. Instead make sure there are Visual Studio solutions sitting around.

现在,您已经编写了代码,如何在不使用户感到不满意的情况下构建库? 如果您像我一样,那么您来自Unix背景,makefile是构建软件的背景。 但这不是每个人都想要的。 Autotools / autoconf是非常糟糕的软件,如果您将它授予Windows操作系统的家伙,他们会叫您各种名字。 相反,请确保周围有Visual Studio解决方案。

What if you don’t want to deal with Visual Studio because it’s not your toolchain of choice? What if you want to keep solutions and makefiles in sync? The answer to that question is or . Which of the two you use depends largely on you. Both can generate Makefiles, XCode or Visual Studio solutions out of a simple definition script.

如果您不想使用Visual Studio,因为它不是您的首选工具链,该怎么办? 如果要保持解决方案和Makefile同步怎么办? 这个问题的答案是或 。 您使用两者中的哪一个在很大程度上取决于您。 两者都可以通过简单的定义脚本生成Makefile,XCode或Visual Studio解决方案。

I used to be a huge fan of cmake but I now switched to premake. The reason for this is that cmake has some stuff hardcoded which I need to customize (for instance building a Visual Studio solution for Xbox 360 is something you cannot do with stock cmake). Premake has many of the same problems as cmake but it’s written almost entirely in lua and can be easily customized. Premake is essentially one executable that includes a lua interpreter and a bunch of lua scripts. It’s easy to recompile and if you don’t want to, your premake file can override everything if you just know how.

我曾经是cmake的忠实拥护者,但现在我改用预制棒。 原因是cmake有一些我需要自定义的硬编码的东西(例如,使用股票cmake无法完成针对Xbox 360的Visual Studio解决方案的构建)。 Premake与cmake存在许多相同的问题,但是它几乎完全用lua编写,并且可以轻松定制。 Premake本质上是一个可执行文件,其中包括lua解释器和一堆lua脚本。 它很容易重新编译,如果您不愿意,只要您知道怎么做,您的premake文件就可以覆盖所有内容。

测试中 (Testing)

Lastly: how do you test your library? Now obviously there are tons of testing tools written in C and C++ you can use, but I think the best tools are actually somewhere else. Shared libraries are not just for C and C++ to enjoy, you can use them in a variety of languages. What better way is there to test your API by using it from a language that is not C++?

最后:您如何测试您的图书馆? 现在显然可以使用大量用C和C ++编写的测试工具,但是我认为最好的工具实际上在其他地方。 共享库不仅供C和C ++使用,还可以在多种语言中使用它们。 通过非C ++语言使用API​​来测试API有什么更好的方法?

In my case I am using Python to test my libraries. More to the point: I’m using and to test my library. This has a couple of big advantages over directly doing it in C/C++.

就我而言,我正在使用Python测试我的库。 更重要的是:我正在使用和来测试我的库。 与直接在C / C ++中进行比较,这有很多优点。

The biggest advantage is the increased iteration speed. I do not have to compile my tests at all, they just run. Not only does the compilation step fall away, I can also take advantage of Python’s dynamic typing and py.test’s good assert statement. I write myself helpers to print out information and to convert data between my library and Python and I get all the benefit of good error reporting.

最大的优点是提高了迭代速度。 我根本不必编译我的测试,它们可以运行。 不仅编译步骤失败了,我还可以利用Python的动态类型和py.test的良好assert语句。 我写了自己的助手来打印信息,并在我的库和Python之间转换数据,并且获得了良好的错误报告的所有好处。

The second advantage is good isolation. is a plugin for py.test that adds the --boxed flag to py.test which runs each test in a separate process. That’s amazingly useful if you have tests that might crash due to a segfault. If you enable coredumps on your system you can then afterwards load up the segfault in gdb and figure out what’s wrong. This also works really well because you don’t need to deal with memory leaks that happen because an assertion failed and the code skips the cleanup. The OS will clean up for each test separately. Unfortunately that’s implemented through the fork() system call so it does not work well on windows right now.

第二个优点是隔离性好。 是的插件,它在py.test中添加了--boxed标志,可在单独的进程中运行每个测试。 如果您的测试可能因段错误而崩溃,那么这将非常有用。 如果在系统上启用了核心转储,则可以随后在gdb中加载segfault并找出问题所在。 这也非常有效,因为您无需处理由于断言失败并且代码跳过清除而发生的内存泄漏。 操作系统将分别针对每个测试进行清理。 不幸的是,这是通过fork()系统调用实现的,因此目前在Windows上无法正常运行。

So how do you use your library with CFFI? You will need to do two things: you need to make sure your public header file does not include any other headers. If you can’t do that, just add a define that disables the includes (like YL_NOINCLUDE).

那么如何将您的库与CFFI一起使用? 您将需要做两件事:确保公共头文件不包含任何其他头。 如果您不能这样做,则只需添加一个定义即可禁用包含(如YL_NOINCLUDE )。

This is all that’s needed to make CFFI work:

这是使CFFI工作所需的全部:

import import ososimport import subprocesssubprocessfrom from cffi cffi import import FFIFFIhere here = = osos .. pathpath .. abspathabspath (( osos .. pathpath .. dirnamedirname (( __file____file__ ))))header header = = osos .. pathpath .. joinjoin (( herehere , , 'include''include' , , 'yourlibrary.h''yourlibrary.h' ))ffiffi .. cdefcdef (( subprocesssubprocess .. PopenPopen ([    ([    'cc''cc' , , '-E''-E' , , '-DYL_API=''-DYL_API=' , , '-DYL_NOINCLUDE''-DYL_NOINCLUDE' ,    ,    headerheader ], ], stdoutstdout == subprocesssubprocess .. PIPEPIPE )) .. communicatecommunicate ()[()[ 00 ])])lib lib = = ffiffi .. dlopendlopen (( osos .. pathpath .. joinjoin (( herehere , , 'build''build' , , 'libyourlibrary.dylib''libyourlibrary.dylib' ))))

Place it in a file called testhelpers.py next to your tests.

将其放在测试旁边的文件testhelpers.py中

Now obviously that is the simple version that only works on OS X but it’s simple to extend for different operating systems. In essence this invokes the C preprocessor and adds some extra defines, then feeds the return value of that to the CFFI parser. Afterwards you have a beautiful wrapped library to work with.

现在显然是仅适用于OS X的简单版本,但可以轻松扩展到不同的操作系统。 本质上,这会调用C预处理器并添加一些额外的定义,然后将其返回值馈送到CFFI解析器。 之后,您将拥有一个漂亮的包装库。

Here an example of how such a test could look like. Just place it in a file called test_something.py and let py.test execute it:

这是一个这样的测试的示例。 只需将其放在一个名为test_something.py的文件中,然后让py.test执行该文件即可

py.test has other advantages too. For instance it supports fixtures which allow you to set up common resources that can be reused between tests. This is super useful for instance, if using your library requires creating some sort of context object, setting up common configuration on it, and later destroying it.

py.test也有其他优点。 例如,它支持固定装置,使您可以设置可在测试之间重用的通用资源。 例如,如果使用您的库需要创建某种上下文对象,在其上设置通用配置,然后销毁它,则这非常有用。

To do that, just create a conftest.py file with the following content:

为此,只需创建一个具有以下内容的conftest.py文件:

import import pytestpytestfrom from testhelpers testhelpers import import liblib , , ffiffi@pytest.fixture@pytest.fixture (( scopescope == 'function''function' ))def def contextcontext (( requestrequest ):    ):    ctx ctx = = liblib .. yl_context_newyl_context_new ()    ()    liblib .. yl_context_set_api_keyyl_context_set_api_key (( ctxctx , , "my api key""my api key" )    )    liblib .. yl_context_set_debug_modeyl_context_set_debug_mode (( ctxctx , , 11 )    )    def def cleanupcleanup ():        ():        liblib .. yl_context_freeyl_context_free (( ctxctx )    )    requestrequest .. addfinalizeraddfinalizer (( cleanupcleanup )    )    return return ctxctx

To use this now, all you need to do is to add a parameter called context to your test function:

要立即使用此功能,您需要做的就是在测试函数中添加一个名为context的参数:

摘要 (Summary)

Since this is longer than usual, here a quick summary of the most important things to keep in mind when building a native shared library:

由于这比通常的时间长,因此在此简要概述了构建本机共享库时要记住的最重要的事情:

  • Write it in C or C++, don’t get crazy with building it in a language that pulls in a whole runtime that takes up CPU and memory.
  • No global state if you can avoid it!
  • Do not define common types in your public headers
  • Do not include crazy headers like windows.h in your public headers.
  • Be light on includes in your headers altogether. Consider adding a way to disable all includes through a define.
  • take good care about your namespace. Don’t expose symbols you do not want to be exposed.
  • Create a macro like YL_API that prefixes each symbol you want to expose.
  • Try to build a stable ABI
  • Don’t go crazy with structs
  • let people customize the memory allocators. If you can’t do it per “context” object, at least do it per library.
  • Be careful when using the STL, always only through a typedef that adds your allocator.
  • Don’t force your users to use your favourite build tool, always make sure that the user of a library finds a Visual Studio solution and makefile in place.
  • 用C或C ++编写它,不要以用会占用整个CPU和内存的整个运行时的语言来构建它而发疯。
  • 如果可以避免,则没有全局状态!
  • 不要在公共标题中定义常见类型
  • 不要在公共头文件中包含疯狂的头文件,例如Windows.h
  • 轻松将标头中的所有内容包括在内。 考虑添加一种通过定义禁用所有包含的方法。
  • 照顾好您的命名空间。 不要公开您不想公开的符号。
  • 创建一个像YL_API这样的宏,为要公开的每个符号添加前缀。
  • 尝试建立稳定的ABI
  • 不要为结构疯狂
  • 人们可以自定义内存分配器。 如果无法针对每个“上下文”对象执行此操作,则至少针对每个库执行此操作。
  • 使用STL时要特别小心,始终只能通过添加分配器的typedef进行。
  • 不要强迫您的用户使用您喜欢的构建工具,请始终确保库的用户可以找到Visual Studio解决方案和makefile。

That’s it! Happy library building!

而已! 图书馆建设愉快!

翻译自:

android导入本地图书

转载地址:http://bnqwd.baihongyu.com/

你可能感兴趣的文章
IOS网络方面(异步请求)
查看>>
day6 python学习
查看>>
事务分类
查看>>
《程序是怎样跑起来的》第四章读后感
查看>>
遍历datatable的几种方法(C# )
查看>>
Oracle记录(三) Scott用户的表结构
查看>>
centos静默式安装Oracle11g
查看>>
软件评测师下午题笔记
查看>>
性能测试的概念
查看>>
JavaScript中的函数上下文和apply,call
查看>>
中文排序
查看>>
少数股东损益
查看>>
SecureCRT的安装
查看>>
POJ2635-The Embarrassed Cryptographer
查看>>
css中font-family的中文字体
查看>>
学习笔记:CentOS 7学习之十二:查找命令
查看>>
delphi回调函数
查看>>
收到了TUSC寄来的V$ View For Oracle Database 11g
查看>>
gc buffer busy/gcs log flush sync与log file sync
查看>>
Java String.intern的深入研究
查看>>