我正在使用openCV库进行计算机视觉研究,并且在编译时遇到了一些问题,这些问题使我试图了解Operative System如何将库与源代码链接。在互联网上寻找了一段时间后,获得了很好的概述并阅读了有关g++ / gcc,ld的手册...我得出一些结论,我希望有更多经验的人向我解释。
首先是我使用的编译行。这是:
-输入:g++
pkg-config --cflags --libs opencv image-conversion.cpp -o image-conversion
pkg-config --cflags --libs opencv
-I/opt/local/include/opencv -I/opt/local/include -L/opt/local/lib -lopencv_shape -lopencv_stitching -lopencv_objdetect -lopencv_superres -lopencv_videostab -lopencv_calib3d -lopencv_features2d -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_video -lopencv_photo -lopencv_ml -lopencv_imgproc -lopencv_flann -lopencv_core
我的代码所需的库是
-lopenhighgui
,但我更喜欢以这种方式进行编译,因为该库依赖于其他库。问题是,当我转到/opt/local/lib
来查看库时,我有三个文件:-libopencv_highgui.3.1.0.dylib
-libopencv_highgui.3.1.dylib
-libopencv_highgui.dylib
我不知道
-lopenhighgui
指向哪个库。我在g++手册中发现-l
标志指定了库名,避免了lib
前缀和*.a
*.so
(linux)/ *.dylib
(mac)后缀。执行otool -L
可执行文件后,我得到输出:/opt/local/lib/libopencv_highgui.3.1.dylib (compatibility version 3.1.0, current version 3.1.0)
那么,为什么要使用它而不是其他方法呢?它的使用方式又是什么呢?这三个库有什么区别?
另一个问题是关于链接和执行过程。我已经了解使用静态库时的链接过程。我的问题是编译动态库时。在下一个示例中:
输入:
g++ -I/opt/local/include/opencv -I/opt/local/include -L/opt/local/lib -lopencv_highgui image-conversion.cpp -o image-conversion
我发现从编译到执行程序的过程可以分为三个部分。
-I
标志传递的目录进行代码解析。 -lflag
指定的库。这是通过链接器(ld)问题是下一个问题:我在互联网上发现一些人说,如果他们不使用库的非标准目录设置
$LD_LIBRARY_PATH
(在linux中)或$DYLD_LIBRARY_PATH
(在mac OSx中)(在我的情况下是export LD_LIBRARY_PATH="/opt/local/lib"
),则动态链接程序找不到该库,并且程序执行失败。我发现我的程序不会崩溃,如果我执行otool(用于查看链接的内容),我会得到这个(这是所有链接的库的摘要):/opt/local/lib/libopencv_shape.3.1.dylib (compatibility version 3.1.0, current version 3.1.0) /opt/local/lib/libopencv_imgproc.3.1.dylib (compatibility version 3.1.0, current version 3.1.0) ... /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
怎么了?我发现LD_LIBRARY_PATH可用于测试新库,但是为什么这些人说他们只需要为执行程序设置此变量?最后一部分如何工作?我没有设置它,并且效果很好。
感谢任何人。
最佳答案
实际上有两个问题(顺便说一句,这不是很好的SO实践),所以我将独立回答它们。
另外,我将专注于Linux-这是我熟悉的 Realm 。从问题的文本来看,我认为Mac世界中的情况非常相似。
图书馆命名约定
在Linux环境中使用.so linraries时,经常会看到动态库通常以三元组的形式呈现。例如,库foo可能存在于3个文件中:libfoo.so,libfoo.so.6,libfoo.so.6.5.4。如果仔细看,您会发现它们都是相同的文件-通常,其中两个只是指向第三个文件的符号链接(symbolic link)。为了进一步讨论,将libfoo.so称为未版本化的库,将ibfoo.so.6称为major-versioned,将libfoo.so.6.5.4 * full-versioned。你为什么需要那些?为了更好的版本控制。
链接应用程序时,始终对未版本化的Livbary使用链接器规则-考虑到链接器在规则中添加了lib
和.so
的事实,
g++ ... -lfoo ...
链接应用程序后,链接器将打开libfoo.so并检查其中的几件事。它所检查的是所谓的
SONAME
header 。链接.so库时,将创建此头,并且该头可以具有与当前正在查看的一个链接器不同的文件名。例如,可能其中有一个主版本文件,链接器将看到该文件:SONAME = libfoo.so.6。当链接器看到该SONAME时,它将标记生成的应用程序文件为
libfoo.so.6
-甚至当您实际要求libfoo.so
时也是如此。通过这样做链接程序保留了该库的特定版本。您的应用程序最初是编译并与版本6链接的,因此,只要运行该应用程序,就需要版本6。
如果以后升级系统(或应用程序在不同的系统上运行),而
foo
具有不同的版本(例如7),则.so文件将不同:libfoo.so,libfoo.so.7,libfoo.so。 7.6.5。由于您的应用需要libfoo.so.6
,它将无法启动-这是一件好事,因为谁知道版本7仍然兼容?没有这种保护,应用程序将启动并使用不同的库版本,其后果可能是灾难性的。LD_LIBRARY_PATH搜索
您的第二个问题是LD_LIBRARY_PATH。的确如此,运行时链接程序在查找动态库时会查询此变量。但是,这不是它唯一要咨询的内容。除此之外,还有默认的搜索路径,还有一个针对每个应用程序的动态库路径,该路径在链接时记录在应用程序中,通常由链接器的rpath参数控制,例如:
g++ ... -Wl,rpath,/path/to/so/library
当这样记录路径时,运行时链接程序将在加载应用程序时将这些路径添加到搜索路径列表中。
可以在没有LD_LIBRARY_PATH的情况下为您的应用程序找到库的事实意味着两件事之一:rpath是在链接应用程序时记录的,或者
/opt/local/lib
实际上包含在平台上的默认搜索路径中。