Exportando funções de uma DLL com dllexport

105

Gostaria de um exemplo simples de exportação de uma função de uma DLL do Windows C ++.

Eu gostaria de ver o cabeçalho, o .cpparquivo e o .defarquivo (se for absolutamente necessário).

Gostaria que o nome exportado não fosse decorado . Eu gostaria de usar a convenção de chamada mais padrão ( __stdcall?). Gostaria de usar __declspec(dllexport)e não preciso usar um .defarquivo.

Por exemplo:

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

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

Estou tentando evitar que o vinculador tenha adicionado sublinhados e / ou números (contagens de bytes?) Ao nome.

Aceito não suportar dllimporte dllexportusar o mesmo cabeçalho. Não quero nenhuma informação sobre como exportar métodos de classe C ++, apenas funções globais de estilo c.

ATUALIZAR

Não incluir a convenção de chamada (e usar extern "C") me dá os nomes de exportação como eu gosto, mas o que isso significa? Qualquer convenção de chamada padrão que estou obtendo é o que pinvoke (.NET), declare (vb6) e GetProcAddressesperava? (Acho GetProcAddressque dependeria do ponteiro de função criado pelo chamador).

Eu quero que essa DLL seja usada sem um arquivo de cabeçalho, então eu realmente não preciso de muita fantasia #definespara tornar o cabeçalho utilizável por um chamador.

Aceito responder que preciso usar um *.defarquivo.

Aardvark
fonte
Posso não me lembrar, mas acho que: a) extern Cirá remover a decoração que descreve os tipos de parâmetros da função, mas não a decoração que descreve a convenção de chamada da função; b) para remover toda a decoração, você precisa especificar o nome (não decorado) em um arquivo DEF.
ChrisW
Isso é o que eu estava vendo também. Talvez você deva adicionar isso como uma resposta completa?
Aardvark

Respostas:

134

Se você deseja exportações simples de C, use um projeto C, não C ++. DLLs C ++ contam com a modificação de nomes para todos os isms C ++ (namespaces etc ...). Você pode compilar seu código como C acessando as configurações do projeto em C / C ++ -> Avançado, há uma opção "Compilar como" que corresponde às opções do compilador / TP e / TC.

Se você ainda deseja usar C ++ para escrever o interno de sua biblioteca, mas exportar algumas funções não fragmentadas para uso fora de C ++, consulte a segunda seção abaixo.

Exportando / importando bibliotecas DLL em VC ++

O que você realmente deseja fazer é definir uma macro condicional em um cabeçalho que será incluído em todos os arquivos de origem em seu projeto DLL:

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

Então, em uma função que você deseja exportar, use LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Em seu projeto de construção de biblioteca, crie um define que LIBRARY_EXPORTSfará com que suas funções sejam exportadas para sua construção de DLL.

Desde a LIBRARY_EXPORTS não será definido em um projeto que consuma a DLL, quando esse projeto incluir o arquivo de cabeçalho de sua biblioteca, todas as funções serão importadas.

Se sua biblioteca deve ser multiplataforma, você pode definir LIBRARY_API como nada quando não estiver no Windows:

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

Ao usar dllexport / dllimport, você não precisa usar arquivos DEF; se usar arquivos DEF, não precisa usar dllexport / dllimport. Os dois métodos realizam a mesma tarefa de maneiras diferentes, acredito que dllexport / dllimport é o método recomendado dos dois.

Exportar funções não fragmentadas de uma DLL C ++ para LoadLibrary / PInvoke

Se você precisar disso para usar LoadLibrary e GetProcAddress, ou talvez importar de outra linguagem (ou seja, PInvoke de .NET ou FFI em Python / R etc), você pode usar extern "C" inline com seu dllexport para dizer ao compilador C ++ para não destruir os nomes. E como estamos usando GetProcAddress em vez de dllimport, não precisamos fazer a dança ifdef de cima, apenas um dllexport simples:

O código:

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

E aqui está a aparência das exportações com Dumpbin / exports:

  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)

Portanto, este código funciona bem:

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
fonte
1
extern "C" parecia remover o nome de estilo c ++ mutilado. Toda a coisa de importação versus exportação (que tentei sugerir não incluir na pergunta) não é realmente o que estou perguntando (mas é uma boa informação). Achei que isso obscureceria o problema.
Aardvark
A única razão pela qual posso pensar que você precisa disso é para LoadLibrary e GetProcAddress ... Isso já está
resolvido
É EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Isso está no SDK?
Aardvark
3
Não se esqueça de adicionar o arquivo de definição de módulo nas configurações do vinculador do projeto - apenas "adicionar um item existente ao projeto" não é suficiente!
Jimmy
1
Usei isso para compilar uma DLL com VS e depois chamá-la de R usando .C. Ótimo!
Juancentro
33

Para C ++:

Acabei de enfrentar o mesmo problema e acho que vale a pena mencionar que surge um problema quando se usa ambos __stdcall(ou WINAPI) e extern "C" :

Como você sabe, extern "C"remove a decoração para que, em vez de:

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

você obtém um nome de símbolo não decorado:

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

No entanto, a _stdcall(= macro WINAPI, que muda a convenção de chamada) também decora os nomes de forma que, se usarmos ambos, obteremos:

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

e o benefício de extern "C"é perdido porque o símbolo é decorado (com _ @bytes)

Observe que isso ocorre apenas para a arquitetura x86 porque a __stdcallconvenção é ignorada no x64 ( msdn : em arquiteturas x64, por convenção, os argumentos são passados ​​em registradores quando possível, e os argumentos subsequentes são passados ​​na pilha .).

Isso é particularmente complicado se você tiver como alvo as plataformas x86 e x64.


Duas soluções

  1. Use um arquivo de definição. Mas isso força você a manter o estado do arquivo def.

  2. a maneira mais simples: definir a macro (ver msdn ):

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

e inclua o seguinte pragma no corpo da função:

#pragma EXPORT

Exemplo completo:

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

Isso exportará a função não decorada para destinos x86 e x64, preservando a __stdcallconvenção para x86. O __declspec(dllexport) não é necessário neste caso.

Malick
fonte
5
Obrigado por esta dica importante. Já me perguntei por que minha DLL de 64 bits é diferente da de 32 bits. Acho sua resposta muito mais útil do que a aceita como resposta.
Elmue
1
Eu realmente gosto dessa abordagem. Minha única recomendação seria renomear a macro para EXPORT_FUNCTION porque a __FUNCTION__macro só funciona em funções.
Luis
3

Eu tive exatamente o mesmo problema, minha solução foi usar o arquivo de definição de módulo (.def) em vez de __declspec(dllexport)definir exportações ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Não tenho ideia de por que isso funciona, mas funciona

Rytis I
fonte
Nota para qualquer outra pessoa correr para isso: usando um .defmódulo de exportação de arquivo faz o trabalho, mas à custa de ser capaz de fornecer externdefinições no arquivo de cabeçalho para, por exemplo dados no globais caso, você tem que fornecer a externdefinição manualmente no internos usos de esses dados. (Sim, há momentos em que você precisa disso.) É melhor geralmente e especialmente para código de plataforma cruzada simplesmente usar __declspec()com uma macro para que você possa manipular os dados normalmente.
Chris Krycho
2
A razão é provavelmente porque se você estiver usando __stdcall, então __declspec(dllexport)será não remover as decorações. .defNo entanto, adicionar a função a um testamento.
Björn Lindqvist
1
@ BjörnLindqvist +1, observe que é apenas o caso para x86. Veja minha resposta.
Malick
-1

Acho que _naked pode conseguir o que você deseja, mas também impede que o compilador gere o código de gerenciamento de pilha para a função. extern "C" causa a decoração do nome do estilo C. Remova isso e isso deve se livrar do seu _. O vinculador não adiciona sublinhados, o compilador faz. stdcall faz com que o tamanho da pilha de argumentos seja anexado.

Para obter mais informações, consulte: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

A grande questão é por que você quer fazer isso? O que há de errado com os nomes mutilados?

Rob K
fonte
Os nomes mutilados são feios quando chamados usam LoadLibrary / GetProcAddress ou outros métodos que não dependem de ter um cabeçalho ac / c ++.
Aardvark
4
Isso não seria útil - você só deseja remover o código de gerenciamento de pilha gerado pelo compilador em circunstâncias muito especializadas. (Apenas usar __cdecl seria uma maneira menos prejudicial de perder as decorações - por padrão, __declspec (dllexport) não parece incluir o prefixo _ usual com métodos __cdecl.)
Ian Griffiths
Eu não estava realmente dizendo que seria útil, por isso minhas ressalvas sobre os outros efeitos e questionando por que ele queria fazer isso.
Rob K