C ++ Dynamic Shared Library บน Linux


167

นี่คือการติดตามเพื่อรวบรวมห้องสมุดที่ใช้ร่วมกันแบบไดนามิกที่มีกรัม ++

ฉันกำลังพยายามสร้างไลบรารีคลาสที่แบ่งใช้ใน C ++ บน Linux ฉันสามารถที่จะได้รับห้องสมุดเพื่อรวบรวมและผมสามารถเรียกบางส่วนของ (Non-class) ฟังก์ชั่นการใช้บทเรียนที่ผมพบว่าที่นี่และที่นี่ ปัญหาของฉันเริ่มต้นเมื่อฉันพยายามใช้คลาสที่กำหนดไว้ในไลบรารี บทช่วยสอนที่สองที่ฉันเชื่อมโยงเพื่อแสดงวิธีโหลดสัญลักษณ์สำหรับการสร้างวัตถุของคลาสที่กำหนดไว้ในไลบรารี แต่หยุดสั้น ๆ ในการใช้วัตถุเหล่านั้นเพื่อทำงานให้เสร็จ

ไม่มีใครทราบบทช่วยสอนที่สมบูรณ์ยิ่งขึ้นสำหรับการสร้างไลบรารี่ C ++ ที่แชร์กันซึ่งแสดงวิธีใช้คลาสเหล่านั้นในการปฏิบัติการแยกต่างหาก บทช่วยสอนที่เรียบง่ายมากที่แสดงการสร้างวัตถุใช้งาน (getters และ setters แบบธรรมดาจะใช้ได้) และการลบจะยอดเยี่ยม ลิงก์หรือการอ้างอิงถึงโค้ดโอเพนซอร์สบางตัวที่แสดงให้เห็นถึงการใช้ไลบรารีคลาสที่ใช้ร่วมกันจะดีพอ ๆ กัน


แม้ว่าคำตอบจากcodelogicและnimrodmทำงานได้ฉันแค่อยากจะเพิ่มว่าฉันหยิบสำเนาของBeginning Linux Programmingตั้งแต่ถามคำถามนี้และบทแรกของมันมีตัวอย่างรหัส C และคำอธิบายที่ดีสำหรับการสร้างและใช้ทั้งห้องสมุดแบบคงที่และใช้ร่วมกัน . ตัวอย่างเหล่านี้สามารถผ่าน Google Book Search ในรุ่นเก่าของหนังสือเล่มนั้น


ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณหมายถึงโดย "ใช้" มันเมื่อตัวชี้ไปที่วัตถุจะถูกส่งกลับคุณสามารถใช้มันเหมือนคุณใช้ตัวชี้อื่น ๆ กับวัตถุ
codelogic

บทความที่ฉันเชื่อมโยงเพื่อแสดงวิธีสร้างตัวชี้ฟังก์ชันไปยังฟังก์ชัน Factory Object โดยใช้ dlsym มันไม่แสดงไวยากรณ์สำหรับการสร้างและการใช้วัตถุจากห้องสมุด
บิล Lizard

1
คุณจะต้องใช้ไฟล์ส่วนหัวอธิบายชั้นเรียน ทำไมคุณถึงคิดว่าคุณต้องใช้ "dlsym" แทนที่จะปล่อยให้ระบบปฏิบัติการค้นหาและเชื่อมโยงไลบรารี่ในเวลาโหลด? แจ้งให้เราทราบหากคุณต้องการตัวอย่างง่ายๆ
nimrodm

3
@nimrodm: อะไรคือทางเลือกในการใช้ "dlsym" ฉัน (ควรจะ) เขียนโปรแกรม 3 C ++ ที่ทุกคนจะใช้คลาสที่กำหนดไว้ในไลบรารีที่ใช้ร่วมกัน ฉันยังมีสคริปต์ Perl 1 ตัวที่จะใช้งาน แต่นั่นเป็นปัญหาอื่นทั้งหมดในสัปดาห์หน้า
Bill the Lizard

คำตอบ:


154

MyClass.h

#ifndef __MYCLASS_H__
#define __MYCLASS_H__

class MyClass
{
public:
  MyClass();

  /* use virtual otherwise linker will try to perform static linkage */
  virtual void DoSomething();

private:
  int x;
};

#endif

myclass.cc

#include "myclass.h"
#include <iostream>

using namespace std;

extern "C" MyClass* create_object()
{
  return new MyClass;
}

extern "C" void destroy_object( MyClass* object )
{
  delete object;
}

MyClass::MyClass()
{
  x = 20;
}

void MyClass::DoSomething()
{
  cout<<x<<endl;
}

class_user.cc

#include <dlfcn.h>
#include <iostream>
#include "myclass.h"

using namespace std;

int main(int argc, char **argv)
{
  /* on Linux, use "./myclass.so" */
  void* handle = dlopen("myclass.so", RTLD_LAZY);

  MyClass* (*create)();
  void (*destroy)(MyClass*);

  create = (MyClass* (*)())dlsym(handle, "create_object");
  destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");

  MyClass* myClass = (MyClass*)create();
  myClass->DoSomething();
  destroy( myClass );
}

บน Mac OS X ให้คอมไพล์ด้วย:

g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user

บน Linux ให้คอมไพล์ด้วย:

g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user

หากนี่เป็นระบบปลั๊กอินคุณจะใช้ MyClass เป็นคลาสพื้นฐานและกำหนดฟังก์ชันที่จำเป็นทั้งหมดเสมือน ผู้เขียนปลั๊กอินจากนั้นก็จะเป็นผลมาจาก MyClass แทนที่ Virtuals และใช้และcreate_object destroy_objectแอปพลิเคชันหลักของคุณไม่จำเป็นต้องเปลี่ยนแปลง แต่อย่างใด


6
ฉันกำลังพยายามทำสิ่งนี้ แต่มีคำถามหนึ่งข้อ จำเป็นอย่างยิ่งที่จะต้องใช้ void * หรือฟังก์ชัน create_object สามารถส่งคืน MyClass * แทนได้หรือไม่? ฉันไม่ได้ขอให้คุณเปลี่ยนสิ่งนี้ให้ฉันฉันแค่อยากจะรู้ว่ามีเหตุผลที่จะใช้อีกคนหนึ่ง
Bill the Lizard

1
ขอบคุณฉันลองและใช้งานได้บน Linux จากบรรทัดคำสั่ง (เมื่อฉันทำการเปลี่ยนแปลงที่คุณแนะนำในการแสดงความคิดเห็นรหัส) ฉันขอขอบคุณเวลาของคุณ
Bill the Lizard

1
มีเหตุผลใดบ้างที่คุณจะประกาศสิ่งเหล่านี้ด้วย "C" ภายนอก? เช่นนี้ถูกรวบรวมโดยใช้คอมไพเลอร์ g ++ ทำไมคุณต้องการที่จะใช้การประชุมการตั้งชื่อค? C ไม่สามารถเรียก c ++ ส่วนต่อประสาน wrapper ที่เขียนด้วย c ++ เป็นวิธีเดียวที่จะเรียกสิ่งนี้จาก c
ant2009

6
@ ant2009 คุณต้องการextern "C"เพราะdlsymฟังก์ชั่นเป็นฟังก์ชั่น C และเพื่อโหลดcreate_objectฟังก์ชั่นแบบไดนามิกมันจะใช้การเชื่อมโยงแบบ C หากคุณไม่ต้องการใช้extern "C"จะไม่มีทางรู้ชื่อของcreate_objectฟังก์ชั่นในไฟล์. so เนื่องจากชื่อ -mangling ในคอมไพเลอร์ C ++
kokx

1
วิธีการที่ดีมันคล้ายกับสิ่งที่คนจะทำในคอมไพเลอร์ของ Microsoft กับบิตของ #if ทำงาน # อื่นคุณจะได้รับแพลตฟอร์มที่ดีระบบอิสระ
Ha11owed

52

ต่อไปนี้แสดงตัวอย่างของไลบรารีคลาสที่แบ่งใช้ที่แบ่งใช้ [h, cpp] และโมดูล main.cpp โดยใช้ไลบรารี มันเป็นตัวอย่างที่ง่ายมากและ makefile สามารถทำได้ดีกว่ามาก แต่มันใช้งานได้และอาจช่วยคุณได้:

shared.h กำหนดคลาส:

class myclass {
   int myx;

  public:

    myclass() { myx=0; }
    void setx(int newx);
    int  getx();
};

shared.cpp กำหนดฟังก์ชั่น getx / setx:

#include "shared.h"

void myclass::setx(int newx) { myx = newx; }
int  myclass::getx() { return myx; }

main.cpp ใช้คลาส

#include <iostream>
#include "shared.h"

using namespace std;

int main(int argc, char *argv[])
{
  myclass m;

  cout << m.getx() << endl;
  m.setx(10);
  cout << m.getx() << endl;
}

และ makefile ที่สร้าง libshared.so และลิงก์หลักกับไลบรารีที่แชร์:

main: libshared.so main.o
    $(CXX) -o main  main.o -L. -lshared

libshared.so: shared.cpp
    $(CXX) -fPIC -c shared.cpp -o shared.o
    $(CXX) -shared  -Wl,-soname,libshared.so -o libshared.so shared.o

clean:
    $rm *.o *.so

หากต้องการเรียกใช้ 'main' และเชื่อมโยงกับ libshared.so จริงคุณอาจต้องระบุพา ธ โหลด (หรือใส่ไว้ใน / usr / local / lib หรือคล้ายกัน)

ต่อไปนี้ระบุไดเร็กทอรีปัจจุบันเป็นพา ธ การค้นหาสำหรับไลบรารีและรัน main (bash syntax):

export LD_LIBRARY_PATH=.
./main

หากต้องการดูว่าโปรแกรมนั้นเชื่อมโยงกับ libshared.so คุณสามารถลอง ldd:

LD_LIBRARY_PATH=. ldd main

พิมพ์บนเครื่องของฉัน:

  ~/prj/test/shared$ LD_LIBRARY_PATH=. ldd main
    linux-gate.so.1 =>  (0xb7f88000)
    libshared.so => ./libshared.so (0xb7f85000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e74000)
    libm.so.6 => /lib/libm.so.6 (0xb7e4e000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb7e41000)
    libc.so.6 => /lib/libc.so.6 (0xb7cfa000)
    /lib/ld-linux.so.2 (0xb7f89000)

1
สิ่งนี้จะปรากฏขึ้น (ต่อตาที่ไม่ได้รับการฝึกฝนอย่างมากของฉัน) เพื่อเชื่อมโยง libshared.so ไปยังปฏิบัติการของคุณแทนที่จะใช้การเชื่อมโยงแบบไดนามิกในเวลาทำงาน ฉันถูกไหม?
Bill the Lizard

10
ไม่นี่คือการเชื่อมโยงแบบไดนามิกมาตรฐาน Unix (Linux) ไลบรารีแบบไดนามิกมีส่วนขยาย ".so" (วัตถุที่ใช้ร่วมกัน) และเชื่อมโยงกับปฏิบัติการ (หลักในกรณีนี้) ณ เวลาโหลด - ทุกครั้งที่โหลดหลัก การลิงก์แบบสแตติกเกิดขึ้นในเวลาลิงก์และใช้ไลบรารีที่มีนามสกุล ".a" (ไฟล์เก็บถาวร)
nimrodm

9
นี่คือการเชื่อมโยงแบบไดนามิกในเวลาที่สร้าง ในคำอื่น ๆ ที่คุณต้องการความรู้ก่อนหน้าของห้องสมุดที่คุณกำลังเชื่อมโยงกับ (เช่นการเชื่อมโยงกับ 'dl' สำหรับ dlopen) สิ่งนี้แตกต่างจากการโหลดไลบรารีแบบไดนามิกโดยอ้างอิงจากชื่อไฟล์ที่ผู้ใช้ระบุซึ่งไม่จำเป็นต้องมีความรู้มาก่อน
codelogic

10
สิ่งที่ฉันพยายามอธิบาย (ไม่ดี) คือในกรณีนี้คุณต้องรู้ชื่อของห้องสมุดในเวลาที่สร้าง (คุณต้องผ่าน - แบ่งปันไปยัง gcc) โดยปกติแล้วหนึ่งใช้ dlopen () เมื่อข้อมูลนั้นไม่สามารถใช้ได้เช่นชื่อห้องสมุดที่ค้นพบที่รันไทม์ (เช่น: การแจงนับปลั๊กอิน)
codelogic

3
ใช้เมื่อเชื่อมโยงและวางที่-L. -lshared -Wl,-rpath=$$(ORIGIN) LD_LIBRARY_PATH=.
Maxim Egorushkin

9

โดยทั่วไปคุณควรรวมไฟล์ส่วนหัวของชั้นเรียนในรหัสที่คุณต้องการใช้ชั้นเรียนในห้องสมุดสาธารณะที่ใช้ร่วมกัน จากนั้นเมื่อคุณลิงก์ให้ใช้การตั้งค่าสถานะ '-l'เพื่อเชื่อมโยงรหัสของคุณกับไลบรารีที่แชร์ แน่นอนว่าต้องใช้. ดังนั้นจึงเป็นที่ที่ระบบปฏิบัติการสามารถค้นหาได้ ดู3.5 การติดตั้งและการใช้ Shared Library

การใช้ dlsym นั้นใช้สำหรับเมื่อคุณไม่รู้เวลารวบรวมไลบรารีที่คุณต้องการใช้ ไม่ฟังดูเหมือนเป็นกรณีที่นี่ อาจสับสนว่า Windows เรียกห้องสมุดที่โหลดแบบไดนามิกไม่ว่าคุณจะทำการเชื่อมโยงที่รวบรวมหรือใช้เวลา (ด้วยวิธีการแบบอะนาล็อก)? ถ้าเป็นเช่นนั้นคุณสามารถคิดว่า dlsym เทียบเท่ากับ LoadLibrary

หากคุณต้องการโหลดไลบรารีแบบไดนามิกจริงๆ (เช่นเป็นปลั๊กอิน) ดังนั้นคำถามที่พบบ่อยนี้จะช่วยได้


1
เหตุผลที่ฉันต้องการไลบรารี่ที่แชร์แบบไดนามิกคือฉันจะโทรหามันจากโค้ด Perl มันอาจเป็นความเข้าใจผิดอย่างสมบูรณ์ในส่วนของตัวเองที่ฉันต้องเรียกมันแบบไดนามิกจากโปรแกรม C ++ อื่น ๆ ที่ฉันกำลังพัฒนา
Bill Lizard

ฉันไม่เคยพยายาม Perl แบบบูรณาการและ C ++ แต่ฉันคิดว่าคุณจำเป็นต้องใช้ XS: johnkeiser.com/perl-xs-c++.html
แมตต์ลูอิส

5

นอกเหนือจากคำตอบก่อนหน้านี้ฉันต้องการสร้างความตระหนักเกี่ยวกับความจริงที่ว่าคุณควรใช้RAII (การรับทรัพยากรเป็นการเริ่มต้น)ที่ปลอดภัยสำหรับการทำลายผู้ดูแล

นี่คือตัวอย่างการทำงานที่สมบูรณ์:

ประกาศอินเตอร์เฟซInterface.hpp::

class Base {
public:
    virtual ~Base() {}
    virtual void foo() const = 0;
};

using Base_creator_t = Base *(*)();

เนื้อหาไลบรารีที่แชร์:

#include "Interface.hpp"

class Derived: public Base {
public:
    void foo() const override {}
};

extern "C" {
Base * create() {
    return new Derived;
}
}

ตัวจัดการไลบรารีที่แชร์แบบไดนามิกDerived_factory.hpp::

#include "Interface.hpp"
#include <dlfcn.h>

class Derived_factory {
public:
    Derived_factory() {
        handler = dlopen("libderived.so", RTLD_NOW);
        if (! handler) {
            throw std::runtime_error(dlerror());
        }
        Reset_dlerror();
        creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
        Check_dlerror();
    }

    std::unique_ptr<Base> create() const {
        return std::unique_ptr<Base>(creator());
    }

    ~Derived_factory() {
        if (handler) {
            dlclose(handler);
        }
    }

private:
    void * handler = nullptr;
    Base_creator_t creator = nullptr;

    static void Reset_dlerror() {
        dlerror();
    }

    static void Check_dlerror() {
        const char * dlsym_error = dlerror();
        if (dlsym_error) {
            throw std::runtime_error(dlsym_error);
        }
    }
};

รหัสลูกค้า:

#include "Derived_factory.hpp"

{
    Derived_factory factory;
    std::unique_ptr<Base> base = factory.create();
    base->foo();
}

บันทึก:

  • ฉันใส่ทุกอย่างลงในไฟล์ส่วนหัวเพื่อความกระชับ ในชีวิตจริงคุณควรแยกรหัสของคุณระหว่าง.hppและ.cppไฟล์
  • เพื่อลดความซับซ้อนฉันไม่สนใจกรณีที่คุณต้องการจัดการnew/ deleteเกิน

สองบทความที่ชัดเจนเพื่อรับรายละเอียดเพิ่มเติม:


นี่เป็นตัวอย่างที่ยอดเยี่ยม RAII เป็นวิธีที่จะไปแน่นอน
David Steinhauer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.