winapi - 使用dllexport从DLL导出函数





visual-c++ name-decoration (5)


我认为_naked可能会得到你想要的,但它也会阻止编译器为该函数生成堆栈管理代码。 extern“C”导致C风格的名字装饰。 删除,那应该摆脱你的_。 链接器不会添加下划线,编译器会这样做。 stdcall导致参数堆栈大小被追加。

欲了解更多信息,请参阅: http://en.wikipedia.org/wiki/X86_calling_conventions : http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

更大的问题是你为什么要这样做? 这些重名的名字有什么问题?

我想要一个从C ++ windows DLL导出函数的简单示例。

我希望看到标题,cpp文件和def文件(如果绝对需要)。

我希望导出的名称不需要修饰 。 我想使用最标准的调用约定(__stdcall?)。 我想使用__declspec(dllexport) ,而不必使用DEF文件。

例如:

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

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

我试图避免链接器为名称添加下划线和/或数字(字节数?)。

我确定不支持使用相同头文件的dllimport和dllexport。 我不想要任何有关导出C ++类方法的信息,只需要c样式的全局函数。

UPDATE

不包括调用约定(和使用extern“C”)给我的出口名称,我喜欢,但这是什么意思? 无论默认调用约定我得到什么pinvoke(.NET),声明(VB6)和GetProcAddress会期望? (我猜GetProcAddress取决于调用者创建的函数指针)。

我希望这个DLL可以在没有头文件的情况下使用,所以我并不需要很多花哨的#define来使头部可以被调用者使用。

我确定答案是我必须使用DEF文件。




对于C ++:

我只是面临同样的问题,我认为值得一提的是,当使用__stdcall (或WINAPI extern "C"

正如你所知, extern "C"删除了装饰,而不是:

__declspec(dllexport) int Test(void)                        --> dumpbin : [email protected]@YaHXZ

你得到一个符号名称undecorated:

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

然而, _stdcall (=宏WINAPI,改变了调用约定)也修饰了名字,这样如果我们同时使用,我们可以得到:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : [email protected]

extern "C"的好处是丢失,因为符号是装饰(_ _bytes)

请注意,这适用于x86体系结构,因为在x64 忽略__stdcall约定( msdn在x64体系结构上,按照约定,参数在可能的情况下传递到寄存器中,并且随后的参数在堆栈上传递 )。

如果您针对的是x86和x64平台,这尤其棘手。

两种解决方案

  1. 使用定义文件。 但是这迫使你保持def文件的状态。

  2. 最简单的方法:定义宏(参见msdn ):

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

然后在函数体中包含以下附注:

#pragma EXPORT

完整示例:

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

这将导出x86和x64目标未修饰的函数,同时保留x86的__stdcall约定。 在这种情况下__declspec(dllexport) 不是必需的。







如果你想要简单的C导出,使用C项目而不是C ++。 C ++ DLL依赖于所有C ++主体(命名空间等)的名称重组。 通过进入C / C ++ - > Advanced下的项目设置,您可以将代码编译为C,其中有一个与编译器开关/ TP和/ TC相对应的“编译为”选项。

在VC ++中导出/导入DLL库

你真正想做的是在头文件中定义一个条件宏,它将包含在DLL项目的所有源文件中:

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

然后在你想要导出的函数上使用LIBRARY_API

LIBRARY_API int GetCoolInteger();

在您的库构建项目中创建一个定义LIBRARY_EXPORTS这将导致您的函数被导出用于您的DLL构建。

由于LIBRARY_EXPORTS不会在使用DLL的项目中定义,因此当该项目包含库的头文件时,所有函数都将被导入。

如果您的库是跨平台的,则可以在不在Windows上时将LIBRARY_API定义为“无”:

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

使用dllexport / dllimport时,不需要使用DEF文件,如果使用DEF文件,则不需要使用dllexport / dllimport。 这两种方法以不同的方式完成相同的任务,我相信dllexport / dllimport是两者中推荐的方法。

为LoadLibrary / PInvoke从C ++ DLL中导出未加载的函数

如果你需要这个来使用LoadLibrary和GetProcAddress,或者从.NET中进行PInvoke,你可以在你的dllexport中使用extern "C" 。 由于我们使用GetProcAddress而不是dllimport,所以我们不需要从上面执行ifdef舞蹈,只需简单的dllexport即可:

代码:

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

以下是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)

所以这段代码工作正常:

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")
);



看起来Visual Studio只是在默认参数是初始化列表时调用它所调用的构造函数 。 这段代码:

#include <iostream>

struct test {
  test ()  { std::cout << "test ()" << std::endl ; } 
  test (int)  { std::cout << "test (int)" << std::endl ; }
};

void func( test const &s = {} )
{
}

int main()
{
    test s = {} ;
    func() ;
}

gccclang生成此结果,请在此处查看

test ()
test ()

Visual Studio产生以下结果:

test ()
test (int)

并为此代码:

#include <iostream>
#include <initializer_list>

struct test {
  test ()  { std::cout << "test ()" << std::endl ; };

  test (int)  { std::cout << "test (int)" << std::endl ; };
  test ( std::initializer_list<int>) { std::cout << "test (initializer_list<int>)" << std::endl ; } ;
};

void func( test const &s = {0} )
{
}

int main()
{
    test s = {0} ;
    func() ;
}

gccclang产生这个结果看到它住在这里

 test (initializer_list<int>)
 test (initializer_list<int>)

Visual Studio产生此错误:

 error C2440: 'default argument' : cannot convert from 'initializer-list' to 'const test &'
    Reason: cannot convert from 'initializer-list' to 'const test'
    No constructor could take the source type, or constructor overload resolution was ambiguous

更新

为了进行健全性检查,我回到了标准,以确保在这种差异的根源上没有一些奇怪的规则,或者可能是某些限制导致此代码格式错误。 据我所知,这段代码不是格式错误8.3.5节语法特别允许这样:

parameter-declaration:
  attribute-specifier-seqopt decl-specifier-seq declarator
  attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
  [...]

看起来似乎没有第8.5初始化器8.3.6 默认参数添加任何限制但是此缺陷报告994.braced-init-list作为默认参数和工作文件作为默认参数的大括号初始化程序的措辞明确表示它是预期的并概述了对标准所做的更改以允许它并且查看增量没有明显的限制。







winapi visual-c++ dll name-decoration