DLL'den dllexport ile işlevleri dışa aktarma


105

C ++ Windows DLL'den bir işlevi dışa aktarmanın basit bir örneğini istiyorum.

Başlığı, .cppdosyayı ve .defdosyayı (kesinlikle gerekliyse) görmek istiyorum.

Dışa aktarılan adın bezemesiz olmasını istiyorum . En standart arama kuralını ( __stdcall?) Kullanmak istiyorum . Kullanmak istiyorum __declspec(dllexport)ve .defdosya kullanmak zorunda değilim .

Örneğin:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

Bağlayıcının ada alt çizgi ve / veya sayı (bayt sayısı?) Eklemesini önlemeye çalışıyorum.

Aynı başlığı desteklememek dllimportve dllexportkullanmakta sorun yok. C ++ sınıfı yöntemleri dışa aktarma hakkında herhangi bir bilgi istemiyorum, sadece c-stili global işlevler.

GÜNCELLEME

Çağrı kuralı dahil etmemek (ve kullanmak extern "C") bana istediğim gibi dışa aktarma adlarını veriyor, ama bu ne anlama geliyor? Pinvoke (.NET), bildirme (vb6) ve GetProcAddressbeklediğim hangi varsayılan çağrı kuralı mı? (Sanırım GetProcAddressarayanın oluşturduğu işlev işaretçisine bağlı olacaktır).

Bu DLL'nin bir başlık dosyası olmadan kullanılmasını istiyorum, bu yüzden #definesbaşlığı bir arayan tarafından kullanılabilir hale getirmek için çok fazla şeye ihtiyacım yok .

Bir *.defdosya kullanmak zorunda olduğum cevabında sorun yok .


Yanlış hatırlıyor olabilirim ama şunu düşünüyorum: a) extern Cişlevin parametre türlerini tanımlayan dekorasyonu kaldıracak, ancak işlevin çağırma kuralını tanımlayan dekorasyonu kaldırmayacaktır; b) tüm dekorasyonu kaldırmak için bir DEF dosyasında (bezemesiz) adı belirtmeniz gerekir.
ChrisW

Ben de öyle görüyordum. Belki de bunu tam teşekküllü bir cevap olarak eklemelisin?
Aardvark

Yanıtlar:


134

Düz C dışa aktarımı istiyorsanız, C ++ değil bir C projesi kullanın. C ++ DLL'ler, tüm C ++ isms (ad alanları vb.) İçin adların karıştırılmasına dayanır. C / C ++ -> Advanced altındaki proje ayarlarınıza giderek kodunuzu C olarak derleyebilirsiniz, derleyici anahtarları / TP ve / TC'ye karşılık gelen bir "Compile As" seçeneği vardır.

Hala kütüphanenizin iç kısımlarını yazmak için C ++ kullanmak, ancak bazı işlevleri yönetilmeyen C ++ dışında kullanmak istiyorsanız, aşağıdaki ikinci bölüme bakın.

VC ++ 'da DLL Kitaplıklarını Dışa / İçe Aktarma

Gerçekten yapmak istediğiniz şey, DLL projenizdeki tüm kaynak dosyalara dahil edilecek bir başlıkta koşullu bir makro tanımlamaktır:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

Ardından dışa aktarılmasını istediğiniz bir işlevde kullanırsınız LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Kitaplık oluşturma projenizde bir tanım oluşturun, LIBRARY_EXPORTSbu, işlevlerinizin DLL yapınız için dışa aktarılmasına neden olacaktır.

Yana LIBRARY_EXPORTSbu proje fonksiyonları yerine ithal edilecek tüm kitaplığınızın başlık dosyası içerir DLL tüketen bir proje, tanımlanan edilmeyecektir.

Kitaplığınız çapraz platform olacaksa, LIBRARY_API'yi Windows'ta değilken hiçbir şey olarak tanımlayabilirsiniz:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

Dllexport / dllimport kullanırken DEF dosyalarını kullanmanıza gerek yoktur, eğer DEF dosyalarını kullanırsanız dllexport / dllimport kullanmanıza gerek yoktur. İki yöntem aynı görevi farklı şekillerde yerine getirir, dllexport / dllimport'un ikisinden önerilen yöntem olduğuna inanıyorum.

LoadLibrary / PInvoke için bir C ++ DLL dosyasından yönetilmeyen işlevleri dışa aktarma

LoadLibrary ve GetProcAddress'i kullanmak için buna ihtiyacınız varsa veya belki başka bir dilden (örn. .NET'ten PInvoke veya Python / R'de FFI vb.) İçe aktarmaya ihtiyacınız varsa extern "C", C ++ derleyicisine adları karıştırmamasını söylemek için dllexport'unuzla satır içi kullanabilirsiniz . Ve dllimport yerine GetProcAddress kullandığımız için, yukarıdan ifdef dansını yapmamıza gerek yok, sadece basit bir dllexport:

Kod:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

Ve işte Dumpbin / dışa aktarımla ihracatın nasıl göründüğü:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

Yani bu kod iyi çalışıyor:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern "C", c ++ stil adının bozulmasını kaldırıyor gibiydi. Tüm ithalat ve ihracat meselesi (soruya dahil etmemeyi önermeye çalıştım) gerçekten sorduğum şey değil (ama iyi bilgi). Sorunu bulanıklaştıracağını düşündüm.
Aardvark

Buna ihtiyacınız olduğunu düşünmemin tek nedeni LoadLibrary ve GetProcAddress için ... Bu zaten halledildi, yanıt
metnimde açıklayacağım

EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport) mı? Bu SDK'da mı?
Aardvark

3
Modül tanımlama dosyasını projenin bağlayıcı ayarlarına eklemeyi unutmayın - sadece "projeye mevcut bir öğeyi eklemek" yeterli değildir!
Jimmy

1
Bunu VS ile bir DLL derlemek için kullandım ve sonra bunu .C kullanarak R'den çağırdım. Harika!
Juancentro

33

C ++ için:

Ben de aynı sorunla karşı karşıya ve bunun tek kullandığınızda bir sorun çıkageldi kayda değer olduğunu düşünüyorum, hem __stdcall(veya WINAPI) ve extern "C" :

Bildiğiniz gibi extern "C"dekorasyonu kaldırır, böylece bunun yerine:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

bezemesiz bir sembol adı elde edersiniz:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

Bununla birlikte, _stdcall(= arama kuralını değiştiren WINAPI makro) aynı zamanda isimleri de dekore eder, böylece ikisini de kullanırsak şunu elde ederiz:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

ve extern "C"sembol süslendiği için (_ @bayt ile)

Bunun yalnızca x86 mimarisi için gerçekleştiğine dikkat edin, çünkü __stdcallkural x64'te yok sayılır ( msdn : x64 mimarilerinde, kural olarak, argümanlar mümkün olduğunda kayıtlara aktarılır ve sonraki argümanlar yığın üzerinde iletilir .).

Hem x86 hem de x64 platformlarını hedefliyorsanız bu özellikle zordur.


İki çözüm

  1. Bir tanım dosyası kullanın. Ancak bu sizi def dosyasının durumunu korumaya zorlar.

  2. en basit yol: makroyu tanımlayın ( msdn'ye bakın ):

#define EXPORT yorumu (linker, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

ve sonra aşağıdaki pragmayı işlev gövdesine dahil edin:

#pragma EXPORT

Tam Örnek:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

Bu, x86 __stdcallkuralını korurken, hem x86 hem de x64 hedefleri için dekore edilmemiş işlevi dışa aktarır . __declspec(dllexport) Değil bu durumda gerekli.


5
Bu önemli ipucu için teşekkür ederim. 64Bit DLL'min neden 32 bit DLL'den farklı olduğunu merak etmiştim. Cevabınızı, cevap olarak kabul edilenden çok daha faydalı buluyorum.
Elmue

1
Bu yaklaşımı gerçekten seviyorum. Tek tavsiyem makroyu EXPORT_FUNCTION olarak yeniden adlandırmaktır çünkü __FUNCTION__makro yalnızca işlevlerde çalışır.
Luis

3

Tam olarak aynı sorunu yaşadım, çözümüm __declspec(dllexport)dışa aktarımı tanımlamak yerine modül tanımlama dosyasını (.def) kullanmaktı ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Bunun neden işe yaradığına dair hiçbir fikrim yok ama işe yarıyor


Bir kullanarak: herkese Not başka bu işe çalışan .defdosya modülü ihracatını yapar işi, ancak tedarik edememek pahasına externörn için başlık dosyasına tanımları küresel veri içinde bu durumda, sağladığınız zorunda externelle tanımı kullanımları bu veriler. (Evet, buna ihtiyaç duyduğunuz zamanlar vardır.) Hem genel olarak hem de özellikle çapraz platform kodu için __declspec(), verileri normal şekilde dağıtabilmeniz için bir makro ile kullanmak daha iyidir .
Chris Krycho

2
Nedeni kullanıyorsanız muhtemelen çünkü __stdcall, o zaman __declspec(dllexport)olacak değil dekorasyonları kaldırın. .defAncak işlevi bir iradeye eklemek .
Björn Lindqvist

1
@ BjörnLindqvist +1, bunun sadece x86 için geçerli olduğuna dikkat edin. Cevabımı gör.
Malick

-1

Sanırım _naked istediğinizi alabilir, ancak aynı zamanda derleyicinin işlev için yığın yönetim kodunu oluşturmasını da engeller. extern "C", C stili isim dekorasyonuna neden olur. Bunu kaldırın ve bu sizin _'lerinizden kurtulmalıdır. Bağlayıcı alt çizgileri eklemez, derleyici ekler. stdcall, bağımsız değişken yığın boyutunun eklenmesine neden olur.

Daha fazla bilgi için bkz: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

Daha büyük soru, bunu neden yapmak istiyorsun? Karışık isimlerin nesi var?


Karıştırılmış adlar, LoadLibrary / GetProcAddress veya ac / c ++ başlığına sahip olmaya dayanmayan diğer yöntemleri kullanın çağrıldığında çirkin.
Aardvark

4
Bu yardımcı olmayacaktır - yalnızca çok özel durumlarda derleyici tarafından oluşturulan yığın yönetimi kodunu kaldırmak istersiniz. (Sadece __cdecl kullanmak, dekorasyonları kaybetmenin daha az zararlı bir yolu olacaktır - varsayılan olarak __declspec (dllexport), __cdecl yöntemleriyle olağan _ önekini içermiyor gibi görünüyor.)
Ian Griffiths

Gerçekten yardımcı olacağını söylemiyordum, bu nedenle diğer etkilerle ilgili uyarılarım ve neden yapmak istediğini bile sorguladım.
Rob K
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.