【问题标题】:Can we use an executable file as shared library on all platforms(Windows, Mac, Linux)?我们可以在所有平台(Windows、Mac、Linux)上使用可执行文件作为共享库吗?
【发布时间】:2022-01-19 01:05:25
【问题描述】:

在某些 linux 系统上,这是可行的。我通常可以设计基于插件的应用程序,这样没有库,只有头文件和可执行文件吗?

如果接口类是只包含纯虚函数的接口,这总是有效的。但是我是否也可以在接口中定义包含符号的类,这些符号必须通过链接到包含它们的可执行文件来绑定?

用例:一个可执行的 foo 应用程序通过共享库 libfoo 为插件提供接口。插件(共享库)在运行时加载。应用程序和插件都链接到 libfoo 以解析它们都使用的类中的符号。这是必要的还是可以将类放在可执行目标中并让插件链接可执行文件?

【问题讨论】:

  • 您可能需要缩小“所有平台”的范围,例如我怀疑它是否适用于 Arduino
  • @Dúthomhas 我不确定该链接是否在谈论同一件事。该链接讨论了为什么共享库在其文件权限中需要“执行”位(因此您可以在.so 文件的mmap 中拥有PROT_EXEC)。在这里,AFAICT,OP 想知道他是否可以设计一个不使用共享库的可执行程序。 (例如,使用静态 .a 库构建,但 exe 导出插件可以链接到的一些符号)。 OP可能希望澄清这一点。
  • Linux Mac Windows
  • @ZsigmondLőrinczy:我理解它的方式,从概念上从定义 foo 及其插件之间共享的所有内容的 foo.exe 和 foopluginprotocol.dll 开始,以及单独构建的 plugin-for-foo.dll这取决于 foopluginprotocol.dll。然后通过让其所有导出都来自 foo.exe 本身来消除额外的 foopluginprotocol.dll。

标签: c++ c++11


【解决方案1】:

我最熟悉 Windows,对你提出的严格问题的答案是否定的,但你仍然可以做你想做的事。

在 Windows 上,EXE 和 DLL 文件都使用相同的文件格式,“便携式可执行文件”。但它们仍然有非常重要的区别:

  • EXE 文件中经常缺少重定位表
  • 入口点不同

由于这些差异,尝试通过LoadLibrary() 加载本机 EXE 文件将失败(LoadLibraryEx(LOAD_LIBRARY_AS_DATAFILE) 很好,可以与 Windows 资源 API 一起使用)。 (.NET 程序集 EXE 文件是一个例外——它们不包含任何实际代码,仅包含中间语言,并且代码地址始终是动态确定的,因此不需要重定位修复)

但是,您的方案“让插件链接可执行文件”仍然有效,因为 EXE 文件确实支持导出表,并且加载程序可以将 DLL 的导入绑定到 EXE 的导出。

不幸的是,C++ ABI 在 Windows 上没有标准化,因此导出 C++ 类非常脆弱,并导致锁定到特定的编译器。要保持松耦合,您需要导出普通的 C 函数或 COM 接口。

使用接口可以避免整个问题——你可以在可执行文件中定义一个类来实现头文件中描述的接口,将接口指针传递给插件,插件可以保存该指针用于all 回调到可执行文件,而没有任何导入条目。例如,COM 的前身“对象链接和嵌入”定义了IObjectWithSiteIOleClientSite 接口。

【讨论】:

  • OLE 不是 COM 的前身:OLE 是在 COM 上构建以提供强大而复杂的文档嵌入和操作工具。 COM 是低级对象模型,将(本质上)接口定义为虚拟表,并具有查询工具来获取特定实例的特定接口。 IIRC。
  • @davidbak:你现在知道的 COM started out as part of OLE。是的,它是构建 OLE 其余部分的基础。您可能会将 OLE 与 OLE 自动化混淆,后者是基于 COM 构建的下一代。
  • 感谢指正!我相信那是我的困惑。
【解决方案2】:

我最熟悉 Linux(或其他基于 ELF 的系统)。

如果您使用 PIE 可执行文件并使用 --export-symbols 构建,您可以跳过使用(例如)libfoo.so

可执行文件将加载插件并向插件提供任何 API 所需的符号

换句话说,插件需要知道foo 可执行文件如何获取API 符号。它们可以在不引用任何库的情况下链接。

下面是一个完整的测试用例......

请注意,我将 API 调用直接绑定到可执行文件中。但是,可执行文件可以从共享的libfoo.so [如果需要] 加载 API 调用。但是,插件会对这个库一无所知

请注意,您可以添加/减少一些选项。从 Makefile 中的 ### 开始,我做了一些相当大的黑客攻击,直到我找到了一个有效的组合。


文件:生成文件

# pieplugin/Makefile -- make file for pieplugin
#
# SO: can we use an executable file as shared library on all platformswindows
# SO: mac l
# SITE: stackoverflow.com
# SO: 70370572

XFILE = foo
XOBJ += foo.o

PLUGINS += libplug1.so
PLUGINS += libplug2.so

CFLAGS += -Wall -Werror -I.
###CFLAGS += -g

PIEFLAGS += -fpie
PIEFLAGS += -fPIC
###PIEFLAGS += -fpic

PICFLAGS += -fPIC
###PICFLAGS += -fpic
PICFLAGS += -nostdlib
PICFLAGS += -nodefaultlibs

PLUG_CFLAGS += $(CFLAGS)
PLUG_CFLAGS += $(PICFLAGS)

PLUG_LFLAGS += -shared
###PLUG_LFLAGS += $(PICFLAGS)
###PLUG_LFLAGS += -no-pie

CC = gcc
###CC = clang
LDSO = $(CC)
LDSO = ld
XFILE_LFLAGS += -Wl,--export-dynamic

all: $(PLUGINS) $(XFILE)

foo.o: foo.c
    $(CC) $(CFLAGS) $(XFILE_CFLAGS) -c foo.c

$(XFILE): foo.o
    $(CC) -o $(XFILE) $(XFILE_LFLAGS) foo.o -ldl
    file $(XFILE)

plug1.o: plug1.c
    $(CC) $(PLUG_CFLAGS) -c plug1.c

libplug1.so: plug1.o
    $(LDSO) $(PLUG_LFLAGS) -o libplug1.so plug1.o
    file libplug1.so

plug2.o: plug2.c
    $(CC) $(PLUG_CFLAGS) -c plug2.c

libplug2.so: plug2.o
    $(LDSO) $(PLUG_LFLAGS) -o libplug2.so plug2.o
    file libplug2.so

test:
    ./$(XFILE) $(PLUGINS)

xtest: clean all test

clean:
    rm -f $(XFILE) $(PLUGINS) *.o

文件:foo.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dlfcn.h>
#include <foopriv.h>

#define PLUGSYM(_fnc) \
    plug->plug_##_fnc = plugsym(plug,"plugin_" #_fnc)

// plugsym -- load symbol from plugin file
void *
plugsym(plugin_t *plug,const char *sym)
{

    void *fnc = dlsym(plug->plug_so,sym);
    int sverr = errno;

    printf("plugsym: loading %s from %s at %p\n",
        sym,plug->plug_file,fnc);

    if (fnc == NULL) {
        printf("plugsym: failed -- %s\n",strerror(sverr));
        exit(1);
    }

    return fnc;
}

// plugload -- load plugin file
void
plugload(const char *tail)
{
    char file[1000];

    plugin_t *plug = calloc(1,sizeof(*plug));

    strcpy(plug->plug_file,tail);

    sprintf(file,"./%s",tail);
    printf("plugload: dlopen of %s ...\n",file);

    //plug->plug_so = dlopen(file,RTLD_LOCAL);
    //plug->plug_so = dlopen(file,RTLD_GLOBAL);
    plug->plug_so = dlopen(file,RTLD_LAZY);
    int sverr = errno;
    printf("plugload: plug_so=%p\n",plug->plug_so);

#if 1
    if (plug->plug_so == NULL) {
        printf("plugload: failed -- %s\n",strerror(sverr));
        exit(1);
    }
#endif

    PLUGSYM(fncint);
    PLUGSYM(fncflt);

    plug->plug_next = plugin_list;
    plugin_list = plug;
}

int
main(int argc,char **argv)
{

    --argc;
    ++argv;

    // NOTE: in production code, maybe we use opendir/readdir to find plugins
    for (;  argc > 0;  --argc, ++argv)
        plugload(*argv);

    for (plugin_t *plug = plugin_list;  plug != NULL;  plug = plug->plug_next) {
        printf("main: calling plugin %s fncint ...\n",plug->plug_file);
        plug->plug_fncint(NULL);
    }

    for (plugin_t *plug = plugin_list;  plug != NULL;  plug = plug->plug_next) {
        printf("main: calling plugin %s fncint ...\n",plug->plug_file);
        plug->plug_fncflt(NULL);
    }

    return 0;
}

// functions provided by foo executable to plugins ...

void
foo_fncint(fooint_t *ptr,const char *who)
{

    printf("foo_fncint: called from %s ...\n",who);
}

void
foo_fncflt(fooflt_t *ptr,const char *who)
{

    printf("foo_fncflt: called from %s ...\n",who);
}

文件:plug1.c

// plug1.c -- a plugin

#include <foopub.h>

void ctors
initme(void)
{
}

void
plugin_fncint(fooint_t *ptr)
{

    foo_fncint(ptr,"plug1_fncint");
}

void
plugin_fncflt(fooflt_t *ptr)
{

    foo_fncflt(ptr,"plug1_fncflt");
}

文件:plug2.c

// plug2.c -- a plugin

#include <foopub.h>

void ctors
initme(void)
{
}

void
plugin_fncint(fooint_t *ptr)
{

    foo_fncint(ptr,"plug2_fncint");
}

void
plugin_fncflt(fooflt_t *ptr)
{

    foo_fncflt(ptr,"plug2_fncflt");
}

这是make xtest的输出:

rm -f foo libplug1.so libplug2.so *.o
gcc -Wall -Werror -I. -fPIC -nostdlib -nodefaultlibs -c plug1.c
ld -shared -o libplug1.so plug1.o
file libplug1.so
libplug1.so: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
gcc -Wall -Werror -I. -fPIC -nostdlib -nodefaultlibs -c plug2.c
ld -shared -o libplug2.so plug2.o
file libplug2.so
libplug2.so: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
gcc -Wall -Werror -I.  -c foo.c
gcc -o foo -Wl,--export-dynamic foo.o -ldl
file foo
foo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d136c53c818056fdbec75294ea472ab8c056ca52, not stripped
./foo libplug1.so libplug2.so
plugload: dlopen of ./libplug1.so ...
plugload: plug_so=0x7fd320
plugsym: loading plugin_fncint from libplug1.so at 0x7fad7f93e037
plugsym: loading plugin_fncflt from libplug1.so at 0x7fad7f93e059
plugload: dlopen of ./libplug2.so ...
plugload: plug_so=0x7fd940
plugsym: loading plugin_fncint from libplug2.so at 0x7fad7f939037
plugsym: loading plugin_fncflt from libplug2.so at 0x7fad7f939059
main: calling plugin libplug2.so fncint ...
foo_fncint: called from plug2_fncint ...
main: calling plugin libplug1.so fncint ...
foo_fncint: called from plug1_fncint ...
main: calling plugin libplug2.so fncint ...
foo_fncflt: called from plug2_fncflt ...
main: calling plugin libplug1.so fncint ...
foo_fncflt: called from plug1_fncflt ...

【讨论】:

    【解决方案3】:

    当然,对于某些文件格式。

    Mono、.NET Core 和 .NET 5.0+ 程序集将在 Windows、Linux 和 Mac 上运行。

    同样适用于 Java .class 和 .jar 文件。

    还有其他的。

    【讨论】:

    • 您对这个问题的理解与我完全不同。对我来说,这不是问单个文件是否可以在所有平台上使用,而是在所有平台上,对于每个平台的原生文件,可执行文件和可加载库之间是否可能存在特定关系。
    • 没错,尤其是因为它是关于 C++ 的,无论如何都必须针对每个平台进行编译
    • CLR字节码是CLR字节码,JVM字节码是JVM字节码。使用什么语言来创建两者都无关紧要。基本事实是,在多个平台上运行的任何可执行格式都必须在某种程度上为每个平台解释或编译。
    猜你喜欢
    • 2014-01-14
    • 1970-01-01
    • 2021-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多