【问题标题】:How to use shared object libraries for enabling/disabling features?如何使用共享对象库来启用/禁用功能?
【发布时间】:2015-03-03 16:53:34
【问题描述】:
  • 语言:C
  • 操作系统:红帽 EL

以“例如”开头:

  • 假设我有两个库:libJUMP.solibSIT.so
  • JUMP 包含函数 jump(),同样 SIT 包含函数 sit()
  • 我有一个应用程序想提供给不同的人;他们可以获得jump() 功能、sit() 功能,或两者兼而有之。但是,如果可能的话,我想使用#ifdef

libJUMP.so 的标题:

#ifndef JUMP_H_
#define JUMP_H_

#define JUMP_ENABLED
void jump();

#endif /* JUMP_H_ */

libSIT.so 的标题:

#ifndef SIT_H_
#define SIT_H_

#define SIT_ENABLED
void sit();

#endif /* SIT_H_ */

我有一个应用程序:

#include "jump.h"
#include "sit.h"

int main()
{
    // #ifdef JUMP_ENABLED
    jump();
    // #endif /* JUMP_ENABLED */

    // #ifdef SIT_ENABLED
    sit();
    // #endif /* SIT_ENABLED */
}

所以:

  • 有没有办法在不使用#ifdef 的情况下做到这一点?有没有更好的方法?
    • 我听说我们可以通过编译两个 SO 库来做到这一点,如果在目标系统上运行应用程序时缺少一个,它可以自动排除该功能(使用 dlopen() 和 @987654336 的某种组合@?) 任何简单的例子,如果这确实是正确的?如果可能的话,我上面的代码的一个例子:D?

如果这是一个愚蠢的问题,或者根本不可能,请随时告诉我。如果有类似的问题会被视为重复,请告诉我,我将删除此帖子。

【问题讨论】:

  • 我会创建一个新的#define,例如JUMP_OR_SIT,它可以是jumpsit
  • this 有帮助吗?
  • 首先,您给出的示例过于模糊,无法推荐可靠的解决方案。如果编译时检查没问题,您的答案可能是 autoconf。它可以检查现有库并将适当的defines 放入配置头文件中。 Linux Kernel Development book 建议尽可能避免ifdef 内部函数。在您的情况下,如果未定义 SIT_ENABLED,本书建议定义一个空的坐函数,并且您仍然可以在任何地方调用坐。
  • @meaning-matters 很遗憾没有。它谈到了darwin,这很好,因为我认为苹果 gcc 和标准 gcc 之间会有一些相似之处。然而,他所说的只是剥离符号,而不是在运行时启用或禁用某些功能。
  • @holgac 这是我的目标——在函数中避免#ifdef。我会调查autoconf。我试图让这个例子简短——如果它含糊不清,对不起。我可以添加更多所需的详细信息。我的最终目标是让某些功能在运行时可用(或不可用),如果这样更有意义?

标签: c linux shared-libraries dynamic-linking


【解决方案1】:

考虑这三个文件。一、jump.c

#include <stdio.h>

int jump(const double height)
{
    fflush(stdout);
    fprintf(stderr, "Jumping %.3g meters.\n", height);
    fflush(stderr);
    return 0;
}

第二,sit.c

#include <stdio.h>

int sit(void)
{
    fflush(stdout);
    fprintf(stderr, "Sitting down.\n");
    fflush(stderr);
    return 0;
}

第三,example.c 使用上述一种或两种,取决于它们(分别为libjump.solibsit.so)是否存在于当前工作目录中:

#include <stdio.h>
#include <dlfcn.h>

static const char *jump_lib_path = "./libjump.so";
static int (*jump)(const double) = NULL;

static const char *sit_lib_path = "./libsit.so";
static int (*sit)(void) = NULL;

static void load_dynamic_libraries(void)
{
    void *handle;

    handle = dlopen(jump_lib_path, RTLD_NOW | RTLD_LOCAL);
    if (handle) {
        jump = dlsym(handle, "jump");
        /* If no jump symbol, we don't need the library at all. */
        if (!jump)
            dlclose(handle);
    }

    handle = dlopen(sit_lib_path, RTLD_NOW | RTLD_LOCAL);
    if (handle) {
        sit = dlsym(handle, "sit");
        /* If no sit symbol, the library is useless. */
        if (!sit)
            dlclose(handle);
    }
}

int main(void)
{
    int retval;

    load_dynamic_libraries();

    if (jump) {
        printf("Calling 'jump(2.0)':\n");
        retval = jump(2.0);
        printf("Returned %d.\n\n", retval);
    } else
        printf("'jump()' is not available.\n\n");

    if (sit) {
        printf("Calling 'sit()':\n");
        retval = sit();
        printf("Returned %d.\n\n", retval);
    } else
        printf("'sit()' is not available.\n\n");

    return 0;
}

让我们首先编译并运行示例程序:

gcc -Wall -O2 example.c -ldl -o example
./example

程序输出 jump() 或 sit() 都不可用。让我们将 jump.c 编译成动态库 libjump.so,然后再次运行示例:

gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libjump.so -o libjump.so
./example

现在,jump() 函数起作用了。让我们也编译 sat.c,最后一次运行该示例:

gcc -Wall -O2 -fPIC -shared jump.c -Wl,-soname,libsit.so -o libsit.so
./example

在这里,两个函数都被调用,一切正常。


example.cjumpsit 中是函数指针。我们将它们初始化为NULL,以便我们可以使用if (jump) 来检查jump 是否指向一个有效的函数。

load_dynamic_libraries()函数使用dlopen()dlsym()获取函数指针。请注意,如果动态库打开成功,并且找到了必要的符号,我们就不要dlclose()它,因为我们希望将动态库保留在内存中。 (如果它看起来不是我们想要的那种库,我们只会dlclose()它。)

如果你想避免 if (jump)if (sit) 子句,你可以使用 stubs like

int unsupported_jump(const double height)
{
    return ENOTSUP;
}

int unsupported_sit(void)
{
    return ENOTSUP;
}

load_dynamic_libraries() 的末尾,将函数转移到存根而不是空指针,即

if (!jump)
    jump = unsupported_jump;
if (!sit)
    sit = unsupported_sit;

请注意,类函数接口最容易使用,因为函数指针充当有效原型。如果您需要对象,我建议使用 getter 函数。只要您记得dlsym() 返回一个指向该对象的指针,对象就可以正常工作;使用 getter 函数,这在 getter 函数指针类型中是显式的。

插件接口通常只有一个函数(比如int properties(struct plugin *const props, const int version)),用于填充函数和对象指针的结构。应用程序提供它使用的结构的版本,插件函数返回成功或失败,这取决于它是否可以填充结构以适应该版本。

由于插件通常存储在单个目录中(/usr/lib/yourapp/plugins/ 很常见),您可以通过使用opendir()readdir() 扫描插件目录中的文件名来轻松加载所有插件一,dlopen()每一个,获取properties()函数指针,调用它看插件提供了哪些服务;通常创建插件结构的数组或链表。

正如您所见,所有这些在 Linux 中都非常非常简单明了。如果您想要一个特定的插件功能示例,我建议您将其作为一个单独的问题提出,并详细说明接口应该公开什么样的功能——确切的数据结构和函数原型在很大程度上取决于什么样的我们手头的应用程序。

问题?评论?

【讨论】:

  • 非常感谢!这是迄今为止我见过的最简单的解释,这正是我所寻找的。如果可以的话,我会请你喝啤酒,但是因为我不能——另外谢谢你!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-08
  • 1970-01-01
  • 2018-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多