Çözüm

extern "C"

C++, C bağlamaları (bindings) ile işlev bildirimi için özel bir anahtar kelimeye sahiptir: extern "C". extern "C" olarak bildirilen bir işlev, C işlevinde olduğu gibi, işlev ismini sembol ismi olarak kullanır. Bu yüzden sadece üye olmayan işlevler extern "C" olarak bildirilebilir ve aşırı yüklenemez.

Pek çok kısıtlamanın olmasının yanında extern "C" işlevleri çok kullanışlıdır çünkü bir C işlevi gibi dlopen kullanarak çalışma anında yüklenebilir.

Bu, extern "C" olarak sınıflandırılan işlevlerin C++ kodu içermeyecekleri anlamına gelmez.

İşlevlerin Yüklenmesi

C++'da işlevler C'deki gibi dlsym ile yüklenir. Sembol isimlerinin cenderelenmesini engellemek için yüklemek istediğiniz işlevler extern "C" olarak sınıflandırılmalıdır.

Örnek 7.34. Bir işlevin yüklenmesi

main.cpp:

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

int main() {
    using std::cout;
    using std::cerr;

    cout << "C++ dlopen demo\n\n";

    // open the library
    cout << "Opening hello.so...\n";
    void* handle = dlopen("./hello.so", RTLD_LAZY);

    if (!handle) {
        cerr << "Cannot open library: " << dlerror() << '\n';
        return 1;
    }

    // sembolü yükle
    cout << "Loading symbol hello...\n";
    typedef void (*hello_t)();

    // hataları resetle
    dlerror();
    hello_t hello = (hello_t) dlsym(handle, "hello");
    const char *dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol 'hello': " << dlsym_error <<
            '\n';
        dlclose(handle);
        return 1;
    }

    // hesaplama yapmak için kullan
    cout << "Calling hello...\n";
    hello();

    // kütüphaneyi kapat
    cout << "Closing library...\n";
    dlclose(handle);
}

hello.cpp:

#include <iostream>

extern "C" void hello() {
    std::cout << "hello" << '\n';
}

hello işlevi hello.cpp içinde extern "C" tanımlanmıştır; main.cpp içinde dlsym'in çağırılması ile yüklenmiştir. İşlev extern "C" olarak sınıflandırılmalıdır çünkü aksi taktirde sembol ismini bilemeyecektik.

Uyarı

extern "C" tanımlamanın iki değişik biçimi vardır: yukarıdaki gibi extern "C" kullanımı ve küme parantezleri arasında extern "C" { … } biçiminde. İlki (satıriçi biçimi) harici ilintilemeli (extern linkage) ve C dili ilintilemeli bildirimdir; ikincisi sadece dil ilintilemesini etkiler. Bu yüzden aşağıdaki iki bildirim eşdeğerdir:

extern "C" int foo;
extern "C" void bar();

ve

extern "C" {
     extern int foo;
     extern void bar();
}

extern ve extern olmayan işlev bildirimi arasında fark olmadığından değişken bildirimi yapmadığınız sürece sorun yoktur. Değişken tanımlarsanız şunu aklınızdan çıkarmayın

extern "C" int foo;

ile

extern "C" {
    int foo;
}

aynı şey değildir.

Daha ileri açıklamalar için 7. bölüme özellikle dikkat ederek [ISO14882] 7.5 bölümüne veya [STR2000], 9.2.4'e bakınız.

Harici (extern) değişkenlerle daha ileri işlemler için Ayrıca bakınız bölümündeki belgeleri inceleyiniz.

Sınıfların Yüklenmesi

Bir sınıfı yüklemek biraz daha karmaşıktır çünkü sadece bir işlev göstericisi değil, sınıfın bir örneği gereklidir.

new kullanarak bir sınıfın örneğini yaratamayız çünkü sınıf çalıştırılabilir kod içinde tanımlı değildir ve (bazı durumlarda) ismini bile bilmeyiz.

Çözüm çokbiçimlilik ile gerçekleştirilir. Bir baz -çalıştırılabilir kod içinde sanal üyeler ile arayüz sınıfı- ve bir türemiş -modül içinde gerçekleştirim sınıfı- tanımlarız. Genel olarak arayüz sınıfı soyuttur (abstract - bir sınıf eğer saf sanal işlevi varsa soyuttur).

Sınıfların dinamik yüklenmesi genellikle uyumlu-eklentiler (plug-in) için kullanıldığından -ki açıkça tanımlanmış arayüzleri ortaya çıkarması gerekir- bir arayüz ve türemiş gerçekleştirim sınıfı tanımlamak zorunda oluruz.

Sonra, hala modül içindeyken, sınıf üretim işlevleri (class factory functions) olarak bilinen iki ek yardımcı işlev tanımlarız. Bu işlevlerden bir tanesi sınıfın bir örneğini yaratır ve bunun göstericisini geri döndürür. Diğer işlev üretim tarafından oluşturulmuş bir sınıf göstericisi alır ve onu yokeder. Bu iki işlev extern "C" olarak sınıflandırılır.

Sınıfı modülden kullanmak için dlsym kullanarak hello işlevini yüklediğimiz gibi iki üretim işlevini yükleriz; sonra, istediğimiz kadar örnek oluşturur ve yokederiz.

Örnek 7.35. Bir Sınıfın Yüklenmesi

Burada arayüz olarak çok bilinen polygon sınıfını ve gerçekleştirim olarak türemiş triangle sınıfını kullanıyoruz.

main.cpp:

#include "polygon.hpp"
#include <iostream>
#include <dlfcn.h>

int main() {
    using std::cout;
    using std::cerr;

    // load the triangle library
    void* triangle = dlopen("./triangle.so", RTLD_LAZY);
    if (!triangle) {
        cerr << "Cannot load library: " << dlerror() << '\n';
        return 1;
    }

    // hataları resetle
    dlerror();

    // sembolleri yükle
    create_t* create_triangle = (create_t*) dlsym(triangle, "create");
    const char* dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol create: " << dlsym_error << '\n';
        return 1;
    }

    destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
    dlsym_error = dlerror();
    if (dlsym_error) {
        cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
        return 1;
    }

    // sınıfın bir örneğini yarat
    polygon* poly = create_triangle();

    // sınıfı kullan
    poly->set_side_length(7);
        cout << "The area is: " << poly->area() << '\n';

    // sınıfı yoket
    destroy_triangle(poly);

    // triangle kütüphanesini boşalt (unload)
    dlclose(triangle);
}

polygon.hpp:

#ifndef POLYGON_HPP
#define POLYGON_HPP

class polygon {
protected:
    double side_length_;

public:
    polygon()
        : side_length_(0) {}

    virtual ~polygon() {}

    void set_side_length(double side_length) {
        side_length_ = side_length;
    }

    virtual double area() const = 0;
};

// sınıf üretiminin türleri
typedef polygon* create_t();
typedef void destroy_t(polygon*);

#endif

triangle.cpp:

#include "polygon.hpp"
#include <cmath>

class triangle : public polygon {
public:
    virtual double area() const {
        return side_length_ * side_length_ * sqrt(3) / 2;
    }
};


// sınıf üretimi

extern "C" polygon* create() {
    return new triangle;
}

extern "C" void destroy(polygon* p) {
    delete p;
}

Sınıfları yüklerken dikkat edilmesi gereken bir kaç şey vardır:

  • Hem oluşturma hem de yoketme işlevi yapmalısınız; örneği çalıştırılabilir kodun içinden delete kullanarak yoketmemeli, herzaman onu modüle geri göndermelisiniz. Bunun sebebi C++'da new ve delete operasyonlarının aşırı yüklü olabilmesidir; bu, bellek sızıntısı ve parçalama arızasına (segmentation fault) yolaçan eşleşmeyen new ve delete çağırımına sebep olabilecektir. Modül ve çalıştırılabilir kodun bağlanması için farklı standart kütüphaneleri kullanılırsa yine aynısı geçerlidir.

  • Arayüz sınıfının yıkıcısı her durumda sanal olmalıdır. Bunun zorunlu olmadığı çok nadir durumlar olabilir, fakat risk almaya değmez, çünkü ek yük genellikle ihmal edilebilir.

    Eğer baz sınıfın hiçbir yıkıcıya ihtiyacı yoksa yine de boş (ve sanal) bir yıkıcı tanımlayın; aksi taktirde er ya da geç sorunlarla karşılaşırsınız; bunu garanti ederim. Bu sorunlar hakkında daha fazla bilgi için comp.lang.c++ SSS http://www.parashift.com/c++-faq-lite/, bölüm 20'yi okuyabilirsiniz.