Linux'ta C ++ Dinamik Paylaşılan Kitaplık


167

Bu, g ++ ile Dinamik Paylaşılan Kitaplık derlemesinin bir devamıdır .

Linux üzerinde C ++ ile paylaşılan bir sınıf kütüphanesi oluşturmaya çalışıyorum. Ben derleme kütüphaneyi elde edebilir, ve ben buldum o öğreticiler kullanarak fonksiyonları (sınıf-dışı) bazı çağırabilir burada ve burada . Kütüphanede tanımlanan sınıfları kullanmaya çalıştığımda sorunlarım başlıyor. Bağlandığım ikinci öğretici, kütüphanede tanımlanan sınıfların nesnelerini oluşturmak için sembollerin nasıl yükleneceğini gösterir, ancak herhangi bir işi yapmak için bu nesneleri kullanmanın kısa sürmesini durdurur .

Herkes bu sınıfları ayrı bir yürütülebilir dosyada nasıl kullanılacağını gösteren paylaşılan C ++ sınıf kitaplıkları oluşturmak için daha eksiksiz bir öğretici biliyor mu ? Nesne oluşturma, kullanım (basit alıcılar ve ayarlayıcılar iyi olur) ve silme işlemini gösteren çok basit bir öğretici harika olurdu. Paylaşılan bir sınıf kitaplığının kullanımını gösteren bir bağlantı veya bazı açık kaynak koduna yapılan başvuru eşit derecede iyi olacaktır.


Her ne kadar codelogic ve nimrodm'dan gelen cevaplar işe yarıyor olsa da , sadece bu soruyu sorduğundan beri Linux Programlama'nın bir kopyasını aldığımı eklemek istedim ve ilk bölümünde örnek kod ve hem statik hem de paylaşılan kütüphaneleri oluşturmak ve kullanmak için iyi açıklamalar var. . Bu örneklere, kitabın eski bir sürümünde Google Kitap Arama aracılığıyla ulaşılabilir .


"Kullanarak" ne demek istediğinizi anladığımdan emin değilim, nesneye bir işaretçi döndürüldüğünde, bir nesneye başka bir işaretçi gibi kullanabilirsiniz.
09:41

Bağlantı verdiğim makale, dlsym kullanarak bir nesne fabrikası işlevine nasıl bir işlev işaretçisi oluşturulacağını gösterir. Kitaplıktan nesne oluşturma ve kullanma sözdizimini göstermez.
Bill the Lizard

1
Sınıfı tanımlayan başlık dosyasına ihtiyacınız olacak. Neden işletim sisteminin yükleme zamanında kütüphaneyi bulmasına ve bağlantılandırmasına izin vermek yerine "dlsym" kullanmak zorundasınız? Basit bir örneğe ihtiyacınız varsa bana bildirin.
nimrodm

3
@nimrodm: "dlsym" kullanmanın alternatifi nedir? Ben tüm paylaşılan kütüphanede tanımlanan sınıfları kullanacak 3 C ++ programları yazıyorum (olması gerekiyordu). Ayrıca kullanacak 1 Perl betiği var, ama bu önümüzdeki hafta için başka bir sorun.
Kertenkele Bill

Yanıtlar:


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'te aşağıdakilerle derleyin:

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

Linux'ta aşağıdakilerle derleyin:

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

Bu bir eklenti sistemi için olsaydı, MyClass'ı temel sınıf olarak kullanır ve gerekli tüm işlevleri sanal olarak tanımlarsınız. Olur o zaman Sınıfım kaynaklanıyor eklentisi yazar, virtuals geçersiz kılmak ve uygulamak create_objectve destroy_object. Ana uygulamanızın hiçbir şekilde değiştirilmesi gerekmez.


6
Bunu denemek içindeyim, ama sadece bir sorum var. Void * kullanmak kesinlikle gerekli mi yoksa create_object işlevi bunun yerine MyClass * 'ı döndürür mü? Sizden bunu benim için değiştirmenizi istemiyorum, sadece birini diğerinin üzerinde kullanmanın bir nedeni olup olmadığını bilmek istiyorum.
Kertenkele Bill

1
Teşekkürler, bunu denedim ve komut satırından Linux'ta olduğu gibi çalıştı (kod yorumlarında önerdiğiniz değişikliği yaptıktan sonra). Vaktin için minnettarım.
Kertenkele Bill

1
Bunları extern "C" ile bildirmenizin bir nedeni var mı? Bu bir g ++ derleyicisi kullanılarak derlenir. Neden c adlandırma kuralını kullanmak istersiniz? C, c ++ 'ı çağıramaz. C ++ ile yazılmış bir sarmalayıcı arayüzü bunu c'den çağırmanın tek yoludur.
ant2009

6
@ ant2009 gerekir extern "C"çünkü dlsymfonksiyon bir C fonksiyonudur. Ve create_objectişlevi dinamik olarak yüklemek için C tarzı bağlantı kullanacaktır. Eğer kullanmazsanız , C ++ derleyicisindeki ad yönetimi nedeniyle .so dosyasındaki işlevin extern "C"adını bilmenin bir yolu olmazdı create_object.
kokx

1
Güzel bir yöntem, bir Microsoft derleyicisinde ne yapacağına çok benzer. biraz #ifse çalışma ile, güzel bir platform bağımsız sistem alabilirsiniz
Ha11owed

52

Aşağıda paylaşılan bir sınıf kitaplığı örneği gösterilmiştir. [H, cpp] ve kitaplığı kullanan bir main.cpp modülü. Bu çok basit bir örnek ve makefile çok daha iyi hale getirilebilir. Ama işe yarıyor ve size yardımcı olabilir:

shared.h sınıfı tanımlar:

class myclass {
   int myx;

  public:

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

shared.cpp, getx / setx işlevlerini tanımlar:

#include "shared.h"

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

main.cpp sınıfı kullanır,

#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;
}

ve libshared.so üreten ve ana dosyayı paylaşılan kitaplıkla bağlayan makefile:

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

Gerçek 'run' komutunu çalıştırmak ve libshared ile bağlantı kurmak için muhtemelen yükleme yolunu belirtmeniz (veya / usr / local / lib veya benzeri bir yere koymanız) gerekecektir.

Aşağıdaki, geçerli dizini kitaplıklar için arama yolu olarak belirtir ve main (bash sözdizimi) çalıştırır:

export LD_LIBRARY_PATH=.
./main

Programın libshared. ile bağlantılı olduğunu görmek için ldd'yi deneyebilirsiniz:

LD_LIBRARY_PATH=. ldd main

Makineme yazdırır:

  ~/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
Bu, çalışma zamanında dinamik bağlantı kullanmak yerine, libshared.so dosyasını yürütülebilir programınıza statik olarak bağlıyor gibi görünüyor (çok eğitimsiz gözüme). Doğrumuyum?
Bill the Lizard

10
Hayır. Bu standart Unix (Linux) dinamik bağlantıdır. Dinamik bir kütüphane ".so" (Paylaşılan Nesne) uzantısına sahiptir ve yükleme zamanında çalıştırılabilir dosyayla (bu durumda ana) bağlantılıdır - main her yüklendiğinde. Statik bağlantı, bağlantı zamanında gerçekleşir ve ".a" (arşiv) uzantılı kütüphaneleri kullanır.
nimrodm

9
Bu, derleme zamanında dinamik olarak bağlıdır . Başka bir deyişle, bağlandığınız kitaplık hakkında önceden bilgi sahibi olmanız gerekir (örn. Dlopen için 'dl'ye bağlanmak). Bu, ön bilginin gerekli olmadığı kullanıcı tanımlı bir dosya adına dayalı olarak bir kütüphaneyi dinamik olarak yüklemekten farklıdır .
09:12

10
(Kötü) açıklamaya çalıştığım şey, bu durumda, kütüphanenin adını derleme zamanında bilmeniz gerektiğidir (gcc'ye -lshared iletmeniz gerekir). Genellikle, bu bilgi mevcut olmadığında dlopen () kullanılır, yani kütüphane adı çalışma zamanında keşfedilir (örn: eklenti numaralandırma).
Codelogic

3
-L. -lshared -Wl,-rpath=$$(ORIGIN)Bağlarken kullanın ve bırakın LD_LIBRARY_PATH=..
Maxim Egorushkin

9

Temel olarak, sınıfın başlık dosyasını, sınıfı paylaşılan kitaplıkta kullanmak istediğiniz koda eklemeniz gerekir. Ardından, bağlantı oluşturduğunuzda, kodunuzu paylaşılan kitaplığa bağlamak için '-l' bayrağını kullanın. Tabii ki, bu OS'nin bulabileceği yerde .so olmasını gerektirir. Görmek 3.5. Paylaşılan Kitaplığı Yükleme ve Kullanma

Dlsym kullanmak, hangi kütüphaneyi kullanmak istediğinizi derlemediğiniz zaman içindir. Buradaki gibi görünmüyor. Belki de karışıklık, bağlantıyı derleme veya çalışma zamanında (benzer yöntemlerle) yapsanız da Windows'un dinamik olarak yüklenen kütüphaneleri çağırmasıdır? Eğer öyleyse, dlsym'i LoadLibrary'ye eşdeğer olarak düşünebilirsiniz.

Gerçekten kütüphaneleri dinamik olarak yüklemeniz gerekiyorsa (yani, eklentilerdir), bu SSS yardımcı olacaktır.


1
Dinamik bir paylaşılan kitaplığa ihtiyacımın nedeni, aynı zamanda Perl kodundan da çağırıyorum. Kendi parçamda da geliştirdiğim diğer C ++ programlarından dinamik olarak çağırmam gereken tam bir yanlış anlama olabilir.
Bill the Lizard

Entegre perl ve C ++'ı hiç denemedim, ancak XS'yi
Matt Lewis

5

Önceki cevapların üstüne, RAII (Kaynak Edinimi Başlatma) deyimini kullanmanız gerektiği konusunda farkındalık yaratmak istiyorum işleyicinin imhası konusunda güvende olmak .

İşte tam bir çalışma örneği:

Arayüz bildirimi Interface.hpp::

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

using Base_creator_t = Base *(*)();

Paylaşılan kitaplık içeriği:

#include "Interface.hpp"

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

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

Dinamik paylaşılan kütüphane işleyici 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);
        }
    }
};

Müşteri kodu:

#include "Derived_factory.hpp"

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

Not:

  • Özlü olması için her şeyi başlık dosyalarına koydum. Gerçek hayatta elbette kodunuzu .hppve.cpp dosyalar bölmelisiniz.
  • Basitleştirmek için, new/ deleteaşırı yükü işlemek istediğiniz durumu görmezden geldim .

Daha fazla ayrıntı için iki net makale:


Bu mükemmel bir örnek. RAII kesinlikle gitmenin yolu.
David Steinhauer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.