Eksportowanie funkcji z biblioteki DLL za pomocą dllexport

105

Chciałbym prosty przykład eksportowania funkcji z biblioteki DLL C ++ systemu Windows.

Chciałbym zobaczyć nagłówek, .cppplik i .defplik (jeśli jest to absolutnie wymagane).

Chciałbym, aby wyeksportowana nazwa była bez ozdób . Chciałbym użyć najbardziej standardowej konwencji wywoływania ( __stdcall?). Chciałbym użyć __declspec(dllexport)i nie muszę używać .defpliku.

Na przykład:

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

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

Próbuję uniknąć dodawania przez konsolidator podkreśleń i / lub liczb (liczba bajtów?) Do nazwy.

Nie mam nic przeciwko temu, że nie obsługuję dllimporti dllexportużywam tego samego nagłówka. Nie chcę żadnych informacji o eksportowaniu metod klasy C ++, tylko funkcje globalne w stylu C.

AKTUALIZACJA

Bez konwencji wywoływania (i używania extern "C") daje mi nazwy eksportu tak, jak lubię, ale co to oznacza? Czy jest jakakolwiek domyślna konwencja wywoływania, którą otrzymuję pinvoke (.NET), deklaruję (vb6) i GetProcAddressczego się spodziewa? (Myślę GetProcAddress, że zależy to od wskaźnika funkcji utworzonego przez wywołującego).

Chcę, aby ta biblioteka DLL była używana bez pliku nagłówkowego, więc naprawdę nie potrzebuję dużo fantazji, #definesaby nagłówek był używany przez dzwoniącego.

Nic mi nie jest z odpowiedzią, że muszę użyć *.defpliku.

Aardvark
źródło
Mogę się pomylić, ale myślę, że: a) extern Cusunie dekorację opisującą typy parametrów funkcji, ale nie dekorację opisującą konwencję wywoływania funkcji; b) aby usunąć całą dekorację, należy określić (niezdekorowaną) nazwę w pliku DEF.
ChrisW,
Właśnie to widziałem. Może powinieneś dodać to jako pełną odpowiedź?
Aardvark

Odpowiedzi:

134

Jeśli chcesz eksportować zwykłe C, użyj projektu C, a nie C ++. Biblioteki DLL C ++ polegają na manipulowaniu nazwami dla wszystkich isms C ++ (przestrzeni nazw itp.). Możesz skompilować swój kod jako C, przechodząc do ustawień projektu w C / C ++ -> Advanced, jest opcja „Kompiluj jako”, która odpowiada przełącznikom kompilatora / TP i / TC.

Jeśli nadal chcesz używać C ++ do pisania wewnętrznych elementów swojej biblioteki, ale eksportujesz niektóre niezarządzane funkcje do użytku poza C ++, zobacz drugą sekcję poniżej.

Eksportowanie / importowanie bibliotek DLL w VC ++

To, co naprawdę chcesz zrobić, to zdefiniować warunkowe makro w nagłówku, które zostanie uwzględnione we wszystkich plikach źródłowych w projekcie DLL:

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

Następnie na funkcji, którą chcesz wyeksportować, używasz LIBRARY_API:

LIBRARY_API int GetCoolInteger();

W projekcie kompilacji biblioteki utwórz definicję, LIBRARY_EXPORTSktóra spowoduje wyeksportowanie funkcji do kompilacji biblioteki DLL.

Ponieważ LIBRARY_EXPORTSnie zostanie zdefiniowany w projekcie korzystającym z biblioteki DLL, gdy ten projekt zawiera plik nagłówkowy biblioteki, zamiast tego zostaną zaimportowane wszystkie funkcje.

Jeśli twoja biblioteka ma być wieloplatformowa, możesz zdefiniować LIBRARY_API jako nic, gdy nie jest w systemie Windows:

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

Podczas korzystania z dllexport / dllimport nie musisz używać plików DEF, jeśli używasz plików DEF, nie musisz używać dllexport / dllimport. Te dwie metody wykonują to samo zadanie na różne sposoby, uważam, że dllexport / dllimport jest zalecaną metodą spośród dwóch.

Eksportowanie niezarządzanych funkcji z biblioteki C ++ DLL dla LoadLibrary / PInvoke

Jeśli potrzebujesz tego do korzystania z LoadLibrary i GetProcAddress, lub może importując z innego języka (np. PInvoke z .NET lub FFI w Python / R itp.), Możesz użyć extern "C"wbudowanego portu dllexport, aby poinformować kompilator C ++, aby nie zmieniał nazw. A ponieważ używamy GetProcAddress zamiast dllimport, nie musimy wykonywać tańca ifdef z góry, wystarczy prosty 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())
  );
}

A oto jak wygląda eksport z Dumpbin / export:

  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)

Więc ten kod działa dobrze:

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")
);
joshperry
źródło
1
extern "C" zdawało się usuwać zniekształcanie nazw w stylu c ++. Cały import kontra eksport (którego próbowałem zasugerować, aby nie uwzględniać go w pytaniu) nie jest tak naprawdę tym, o co pytam (ale jest to dobra informacja). Pomyślałem, że to utrudni problem.
Aardvark
Jedynym powodem, dla którego mogę pomyśleć, że potrzebujesz tego jest LoadLibrary i GetProcAddress ... To już załatwione, wyjaśnię w mojej treści odpowiedzi ...
joshperry
Czy EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Czy to jest w SDK?
Aardvark
3
Nie zapomnij dodać pliku definicji modułu do ustawień linkera projektu - samo „dodanie istniejącego elementu do projektu” nie wystarczy!
Jimmy
1
Użyłem tego do skompilowania biblioteki DLL z VS, a następnie wywołałem ją z R przy użyciu .C. Wspaniały!
Juancentro
33

Dla C ++:

Właśnie spotkałem się z tym samym problemem i myślę, że warto wspomnieć o problemie, który pojawia się, gdy używa się obu __stdcall(lub WINAPI) i extern "C" :

Jak wiadomo extern "C"usuwa dekorację, dzięki czemu zamiast:

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

otrzymujesz nieozdobioną nazwę symbolu:

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

Jednak _stdcall(= makro WINAPI, które zmienia konwencję wywoływania) również ozdabia nazwy, więc jeśli użyjemy obu, otrzymamy:

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

a korzyści z extern "C"są tracone, ponieważ symbol jest ozdobiony (z _ @ bajtów)

Zauważ, że dzieje się tak tylko w architekturze x86, ponieważ __stdcallkonwencja jest ignorowana na x64 ( msdn : na architekturach x64, zgodnie z konwencją, argumenty są przekazywane do rejestrów, jeśli to możliwe, a kolejne argumenty są przekazywane na stos ).

Jest to szczególnie trudne, jeśli kierujesz reklamy na platformy x86 i x64.


Dwa rozwiązania

  1. Użyj pliku definicji. Ale to zmusza cię do utrzymania stanu pliku def.

  2. najprostszy sposób: zdefiniuj makro (patrz msdn ):

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

a następnie dołącz następującą pragmę do treści funkcji:

#pragma EXPORT

Pełny przykład:

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

Spowoduje to wyeksportowanie funkcji bez dekoracji zarówno dla obiektów docelowych x86, jak i x64, zachowując __stdcallkonwencję dla x86. W tym przypadku __declspec(dllexport) nie jest to wymagane.

Malick
źródło
5
Dziękuję za tę ważną wskazówkę. Już się zastanawiałem, dlaczego moja 64-bitowa biblioteka DLL różni się od 32-bitowej. Twoja odpowiedź jest o wiele bardziej użyteczna niż ta przyjęta jako odpowiedź.
Elmue
1
Bardzo podoba mi się to podejście. Moim jedynym zaleceniem byłaby zmiana nazwy makra na EXPORT_FUNCTION, ponieważ __FUNCTION__makro działa tylko w funkcjach.
Luis
3

Miałem dokładnie ten sam problem, moim rozwiązaniem było użycie pliku definicji modułu (.def) zamiast __declspec(dllexport)definiowania eksportu ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Nie mam pojęcia, dlaczego to działa, ale działa

Rytis I
źródło
Uwaga nikomu działa w ten sposób: przy użyciu .defeksportu modułu plik robi pracę, ale kosztem jest w stanie dostarczyć externdefinicji w pliku nagłówka dla np DANYCH takim przypadku masz globalne dostarczyć externręcznie definicję w wewnętrznych zastosowań te dane. (Tak, są chwile, kiedy tego potrzebujesz.) Lepiej jest zarówno ogólnie, jak i szczególnie w przypadku kodu wieloplatformowego, po prostu używać go __declspec()z makrem, aby można było normalnie przekazywać dane.
Chris Krycho
2
Powodem jest prawdopodobnie dlatego, że jeśli używasz __stdcall, następnie __declspec(dllexport)będzie nie usunąć dekoracje. .defJednak dodanie funkcji do testamentu.
Björn Lindqvist
1
@ BjörnLindqvist +1, zauważ, że dotyczy to tylko x86. Zobacz moją odpowiedź.
Malick
-1

Myślę, że _naked może uzyskać to, czego chcesz, ale uniemożliwia również kompilatorowi wygenerowanie kodu zarządzania stosem dla funkcji. extern "C" powoduje dekorację nazwy w stylu C. Usuń to i to powinno pozbyć się twoich _. Konsolidator nie dodaje podkreślenia, kompilator to robi. stdcall powoduje dołączenie rozmiaru stosu argumentów.

Więcej można znaleźć na stronie : http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

Większe pytanie brzmi: dlaczego chcesz to zrobić? Co jest nie tak z zniekształconymi nazwami?

Rob K
źródło
Zniekształcone nazwy są brzydkie, gdy są wywoływane use LoadLibrary / GetProcAddress lub inne metody, które nie opierają się na nagłówku ac / c ++.
Aardvark
4
Byłoby to nieprzydatne - chcesz usunąć kod zarządzania stosem wygenerowany przez kompilator tylko w bardzo wyspecjalizowanych okolicznościach. (Samo użycie __cdecl byłoby mniej szkodliwym sposobem na utratę dekoracji - domyślnie __declspec (dllexport) nie wydaje się zawierać zwykłego prefiksu _ z metodami __cdecl.)
Ian Griffiths,
Nie mówiłem, że to będzie pomocne, stąd moje zastrzeżenia dotyczące innych efektów i pytanie, dlaczego w ogóle chciał to zrobić.
Rob K,