我想要一个从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风格的全局函数。
更新
不包括调用约定(并使用extern“C”)给出了我喜欢的导出名称,但这意味着什么?是什么默认调用约定我得到什么pinvoke(.NET),声明(vb6)和GetProcAddress会期望? (我猜对于GetProcAddress,它将取决于调用者创建的函数指针。)
我希望在没有头文件的情况下使用这个DLL,因此我并不需要很多花哨的#defines来使调用者可以使用该头文件。
我很满意答案是我必须使用DEF文件。
答案 0 :(得分:119)
如果您想要普通的C导出,请使用C项目而不是C ++。 C ++ DLL依赖于所有C ++主题(命名空间等)的名称修改。您可以通过进入C / C ++ - > Advanced下的项目设置将代码编译为C,有一个选项“Compile As”,它与编译器开关/ TP和/ TC协调。
您真正想要做的是在标头中定义条件宏,该标头将包含在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和GetProcAddress,或者从.NET执行PInvoke,您可以使用extern "C"
与dllexport内联。因为我们使用的是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")
);
答案 1 :(得分:21)
对于C ++:
我刚刚面临同样的问题,我认为值得一提的是,如果同时使用__stdcall
(或WINAPI
)和 extern "C"
:
如您所知extern "C"
删除了装饰,而不是:
__declspec(dllexport) int Test(void) --> dumpbin : ?Test@@YaHXZ
您获得了未修饰的符号名称:
extern "C" __declspec(dllexport) int Test(void) --> dumpbin : Test
然而_stdcall
(=更改调用约定的宏WINAPI)也会修饰名称,这样如果我们同时使用它们,我们就会得到:
extern "C" __declspec(dllexport) int WINAPI Test(void) --> dumpbin : _Test@0
并且extern "C"
的好处丢失了,因为符号已装饰(使用_ @bytes)
请注意,此仅适用于x86架构,因为 在x64架构上的x64(msdn:)上忽略
__stdcall
约定,按照惯例,参数在可能的情况下在寄存器中传递,后续参数在堆栈上传递。)
如果您同时定位x86和x64平台,这一点尤其棘手。
两个解决方案
使用定义文件。但这会强制您维护def文件的状态。
最简单的方法:定义宏(参见msdn):
#define EXPORT注释(链接器,“/ EXPORT:”__ FUNCTION__“=” __FUNCDNAME __)
然后在函数体中包含以下pragma:
#pragma EXPORT
完整示例:
int WINAPI Test(void)
{
#pragma EXPORT
return 1;
}
这将为x86和x64目标导出未修饰的函数,同时保留x86的__stdcall
约定。在这种情况下,__declspec(dllexport)
不是。
答案 2 :(得分:3)
我有完全相同的问题,我的解决方案是使用模块定义文件(.def)而不是__declspec(dllexport)
来定义导出(http://msdn.microsoft.com/en-us/library/d91k01sh.aspx)。我不知道为什么会这样,但确实如此
答案 3 :(得分:-1)
我认为_naked可能会得到你想要的东西,但它也会阻止编译器为函数生成堆栈管理代码。 extern“C”导致C样式名称装饰。删除那个,那应该摆脱你的_。链接器不会添加下划线,编译器会这样做。 stdcall导致附加参数堆栈大小。
更多信息,请参阅: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx
更大的问题是你为什么要那样做?被破坏的名字有什么问题?