用VS2010建的dll项目 定义 def文件可导出函数名 用dllexport却不行 为什么呢?

sdkwjc 2016-02-25 08:44:58
#ifndef __SOCKETTEST_H__
#define __SOCKETTEST_H__
#define ALMONDTEST_API extern "C" _declspec(dllexport)
ALMONDTEST_API int _stdcall SendCmdBySocket(char* addr,int port,int protocal,char* sendcmd,int sendlength,bool ischeckrecv,char* revcmd,int timeout);

#endif




例如上面.h文件定义 生成解决方案后 把 dll 放到depend工具查看 看不到导出函数名


但是把ALMONDTEST_API 去掉增加def文件定义函数名 生成的dll 在depends工具就可以查看到 这是为什么呢????
...全文
58 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
MSVC vs. MinGW 之 (lib,dll,def,obj,exe) vs (a,dll,def,o,exe) 玩转攻略手记 一份粗糙的研究记录,有待补完和整理。 MinGW: c -> o gcc -c a.c c -> exe gcc a.c libs.o -o a.exe (从主程序a.c,附加libs,生成a.exe) o -> exe gcc a.o b.o ... -o main.exe c -> dll,def,a gcc a.c -shared -o a.dll -Wl,--output-def,a.def,--out-implib,liba.a a -> dll a2dll liba.a dll -> a: dlltool --dllname a.dll --def a.def --output-lib liba.a (需要def文件) a -> def: dumpbin /exports lib.a > lib.def (在windows上调用,def需要修改) dll -> def : pexports a.dll -o > a.def (这里的-o是指给函数标序号) lib -> def : reimp -d a.lib lib -> a: (for __cdecl functions in most case) reimp a.lib; (for __stdcall functions) MSVC: c -> lib cl /LD a.c (注意已经定义了export列表) c -> dll cl /LD a.c c -> obj cl /c a.c c -> exe cl a.c /out:a.exe dll ->lib lib /machine:ix86 /def:a.def /out:a.lib (需要def文件) obj ->lib lib a.obj b.obj... /out:mylib.lib dll ->def DUMPBIN a.dll /EXPORTS /OUT:a.def (生成的def需要做修正) lib ->def reimp -d a.lib (这个要在MSYS+MinGW下用) 关于这些工具的适用范围可以很容易的理解和记忆。 dll和exe都是PE文件,所以可以使用pexports. lib和a是静态库文件,都是归档类型,不是PE格式。所以不能使用pexports. dll可以使用dlltool. lib可以使用lib, 和reimp(lib->a工具) 所有的bin文件,包括dll,exe,lib,a都可以使用dumpbin. 参考: http://hi.baidu.com/kaien_space/blog/item/5e77fafa2ba9ff16a8d3110a.html Mingw官网文档: http://www.mingw.org/wiki/MSVC_and_MinGW_DLLs http://oldwiki.mingw.org/index.php/CreateImportLibraries http://www.mingw.org/wiki/FAQ http://hi.baidu.com/opaquefog/blog/item/9b21b6deb324e25dccbf1ab7.html http://qzone.qq.com/blog/8330936-1238659272 http://hi.baidu.com/jzinfo/blog/item/b0aa1d308de99f9da8018e00.html 本篇测试用代码: 1. main.cpp #include #include #include "mylib.h" using namespace std; int main() { char str[]="Hello world!"; printhello(str); return 0; } 2. mylib.cpp #include #include #include "mylib.h" using namespace std; void EXPORT printhello(char *str) { cout << str << endl; } 3. mylib.h #define EXPORT __declspec(
为何DLL 先看看静态库与DLL的不同之处 可执行文件的生成(Link期):前者很慢(因为要将库中的所有符号定义Link到EXE文件中),而后者很快(因为后者被Link的引入库文件无符号定义) 可执行文件的大小:前者很大,后者很小(加上DLL的大小就和前者差不多了) 可执行文件的运行速度:前者快(直接在EXE模块的内存中查找符号),后者慢(需要在DLL模块的内存中查找,在另一个模块的内存中查找自然较慢) 可共享性:前者不可共享,也就是说如果两个EXE使用了同一个静态库,那么实际在内存中存在此库的两份拷贝,而后者是可共享的。 可升级性:前者不可升级(因为静态库符号已经编入EXE中,要升级则EXE也需要重新编译),后者可以升级(只要接口不变,DLL即可被升级为不同的实现) 综合以上,选择静态库还是DLL 1. 静态库适于稳定的代码,而动态库则适于经常更改代码(当然接口要保持不变),当DLL更改(仅实现部分)后,用户不需要重编工程,只需要使用新的Dll即可。 2. 由于静态库很吃可执行文件的生成(Link期)时间,所以如果对可执行文件的Link时间比较敏感,那么就用DLL。 使用DLL 在介绍如何创DLL之前,让我们先了解它是如何被使用的。 1. 显式调用(也叫动态调用) 显 示调用使用API函数LoadLibrary或者MFC提供的AfxLoadLibrary将DLL加载到内存,再用GetProcAddress()在 内存中获取引入函数地址,然后你就可以象使用本地函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的 AfxLoadLibrary释放DLL。 下面是个显示调用的例子,假定你已经有一个Test.dll,并且DLL中有个函数为Test,其声明式是void(); #include < iostream > using namespace std; typedef void(*TEST )(); int main( char argc, char* argv[] ) { const char* dllName = "Test.dll"; const char* funcName = "Test"; HMODULE hDLL = LoadLibrary( dllName ); if ( hDLL != NULL ) { TEST func = TEST( GetProcAddress( hDLL, funcName ) ); if ( func != NULL ) { func(); } else { cout << "Unable to find function \'" << funcName << "\' !" << endl; } FreeLibrary( hDLL ); } else { cout << "Unable to load DLL \'" << dllName << "\' !" << endl; } return 0; } 注意 1. 显示调用使用GetProcAddress,所以只能加载函数,无法加载变量和类。 2. 此外GetProcAddress是直接在.dll文件中寻找同函数,如果DLL中的Test函数是个C++函数,那么由于在.dll文件中的实际文件会被修饰(具体被修饰的规则可参考函数调用约定详解或者使用VC自带的Depends.exe查看),所以直接找Test是找不到的,这时必须把函数修改为正确的被修饰后的称,下面是一种可能(此函数DLL中的调用约定为__cdecl): const char* funcName = "?Test@@YAXXZ"; 2. 隐式调用(也叫静态调用) 隐式调用必须提供DLL的头文件和引入库(可以看作轻量级的静态库(没有符号定义,但是说明了符号处于哪个DLL中))。 有了头文件和引入库,DLL的使用就跟普通静态库的使用没啥区别,只除了DLL要和EXE一起发布。 显示调用与隐式调用的优缺点 显示调用使用复杂,但能更加有效地使用内存,因为DLL是在EXE运行时(run time)加载,必须由用户使用LoadLibrary和FreeLibrary来控制DLL从内存加载或卸载的时机。此外还可以加载其他语言编写的DLL函数。 静态调用使用简单,但不能控制DLL加载时机,EXE加载到内存同时自动加载DLL到内存,EXE退出时DLL也被卸载。 创DLL 下面我们着重讲解如何在VC下创DLL 首先要立一个Win32的DLL工程。再把普通静态库的所有文件转移到DLL工程中,然后: 为所有的类声明加上__declspec(dllexport)关键字,这有这样编译器才能自动为你产生引入库文件(否则你需要自己写.def文件来产生,因为不常用所以在此不再阐述其细节) 对于DLL的用户来讲,类声明就需要用另外一个关键字__declspec(dllimport)(此关键字对于类和函数而言并非必须,但对于变量而言则是必须的)。所以通常我们会定义一个宏来包装之,比如 #ifdef MYDLL_EXPORTS # define MYDLL_API __declspec(dllexport) #else # define MYDLL_API __declspec(dllimport) #endif 这样我们就能写出如下的类 class MYDLL_API MyClass { ... }; 当然在创DLL的工程里需要包含preprocessor(预处理指示器)MYDLL_EXPORTS,而在用户工程里面则不应该包含MYDLL_EXPORTS。 其实上面说的VC早就帮我们做好了。如果你创DLL工程叫做Test,那么此工程自动包含TEST_EXPORTS。如果你在创工程的时候选择了Exprot Symbols, 那么VC还会自动帮你创一个示例文件Test.h,此文件定义出 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif 你自定义文件都应该包含此文件以使用TEST_API。(如果没有选择Exprot Symbols,那么就得自己动手写出这段宏了) 示例文件中还包括了一个类,变量,以及全局函数的写法 class TEST_API CTest { public: CTest(void); // TODO: add your methods here. }; extern TEST_API int nTest; TEST_API int fnTest(void); 通过上面的示例我们也可以看出全局(或者字空间)变量和函数的声明方法 细节讨论 1. DLL的入口函数DllMain并非必须,如果没有它,编译器会帮你自动生成一个不做任何事的DllMain。 2. 如果是可以定义在头文件里面的东西:包括宏,常量,内联函数(包括成员内联函数)以及模板。那么在DLL中的定义中可以不必包含TEST_API,和普通的定义没有区别。 如果一个类完全由内联函数和纯虚函数构成,那么也不需要TEST_API这样的关键字。一个不带成员函数的struct也一样。 3. 所有未经__declspec(dllexport)导出的类都只能作为DLL内部类使用。当然外部仍然可以使用其内联成员函数(前提是该成员函数不应该调用任何未经导出函数或者类成员函数) 发布DLL 1. 程序库的作者应该将三件套:头文件,引入库文件DLL一同发布给用户,其中头文件和引入库是专为静态调用的用户准备,也就是C/C++用户。(此外有些 DLL内部使用的头文件,如果没有被接口头文件直接#include,那么该头文件就不必发布,这和静态库是一样的)。 2. DLL的用户只需将DLL和可执行程序一同发布即可。 C++程序使用C语言DLL(静态库规则一样) C不存在class, 所以由C创DLL必然也不会出现class;对于全局变量的使用C和C++没有什么不同,所以我们把焦点集中在全局函数上(此外全局变量的规则一样)。 我们知道C++程序要使用C语言的函数就必须在函数声明前加上extern "C"关键字,这对于静态库和DLL没有什么不同。 但是这个关键字不能直接出现在头文件函数声明中,否则DLL无法通过编译, 原因很简单,C语言并没有extern "C"这个关键字。 1. 一种作法是把C向C++迁移的责任交给DLL者。定义出一个宏,以使DLL(C工程)中不出现extern "C"或者只是extern,而在用户工程(C++工程)中保持原样。幸运的是windows早已为我们设计好一切,这个宏就是EXTERN_C(存在于 winnt.h中) : #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C extern #endif 注意上面必须是extern而不是空。因为虽然C的函数声明不是必须添加extern,但是变量声明则必须添加extern。 有了EXTERN_C,在头文件中这样定义函数: EXTERN_C TEST_API int fnTest(void); 2. 另外一种做法是把把C向C++迁移的责任交给用户,用户在包含DLL文件的时候以extern "C"来include: extern "C" { #include "mydll.h" #include "mydllcore.h" ... } 可以把上述的extern "C"段放在新的头文件中和DLL一起发布,这样C++用户只需要包含这个头文件就可以了。比如Lua库就自带了一个etc/lua.hpp文件。通常此文件会由DLL作者提供,所以此时迁移的责任仍在DLL者。 注意用户不要试图以extern "C"来重新声明函数,因为重复声明是允许的,但是必须保证和头文件中的声明相同。 3. 这种做法的一个变形就是直接在C头文件中以extern "C"将所有函数和变量声明包含之,这样就无需像上面那样多提供一个额外的头文件了。通常像这样(mydll文件): #include 头文件段 #ifdef __cplusplus extern "C" { #endif 函数和变量声明 ... #ifdef __cplusplus } #endif 创标准的DLL,使其可被其他语言使用 通常我们会希望DLL能够被其他语言使用,因而我们的DLL往往不会提供类定义,而只提供函数定义。(因为许多编程语言是不支持类的)。 此时函数的调用约定必须是__stdcall(在vc下默认是__cdecl,所以你不得不手工添置),因为大部分语言不支持__cdecl,但支持__stdcall(比如VBScript,Delphi等)。 此外我们希望导出DLL中的函数能够更易被识别(用户使用才会更方便),也就是说DLL应该编译出无修饰的C函数。 所以我们可能会 1. 如果你只用C语言,那么必然以C文件DLL(自动编出C符号),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。 2. 如果你使用C++,那么必然以C++文件DLL,这时你应该直接以extern "C"修饰。 结论 所以要创能够为任意编程语言使用之DLL,我们应该 1. 只创函数 2. 声明__stdcall调用约定(或者WINAPI,CALLBACK,前提是你必须包含windows头文件) 3. 以CPP文件 + extern "C" 或者 C文件 + EXTERN_C 4. 当然还需要DLL_API的声明(如果光有dllexport,那么DLL也只能被显示调用)。 更完美一点 不应该使用dllexport和extern "C"而应该使用.def文件。虽然经过上面的4个步骤,我们的DLL里面的C函数已经相当简洁了,但仍不是最完美的:比如一个声明为function的函数,实际在DLL中的字确可能是function@#。而使用.def文件,我们可以让DLL中的函数和声明的函数保持一致。 关于.def的详细用法可参考: 微软DLL专题 使用 DEF 文件DLL 导出DLL与静态库之间切换 前面我曾经提到对于使用DLL的用户__declspec(dllimport)关键字可有可无,当然前提是DLL中不包括变量定义。 所以要把库既能够做成DLL,也能够做成静态库,那么就应该作出类似下面这样的定义: 1. DLL不包括变量的定义 #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API #endif 然 后只要把工程的配置属性(Configuration Type)简单地在Static Library (.lib)或者Dynamic Library (.dll)切换即可(VC会自动地为DLL添加预处理器TEST_EXPORTS,为静态库取消TEST_EXPORTS)。 2. DLL包含变量定义,也是标准的做法 #ifdef TEST_BUILD_AS_DLL #ifdef TEST_EXPORTS # define TEST_API __declspec(dllexport) #else # define TEST_API __declspec(dllimport) #endif #else #define TEST_API #endif 如果要将库做成DLL,那么需要DLL者添加预处理器TEST_BUILD_AS_DLL和TEST_EXPORTS,后者通常由编译器自动添加;如果做成静态库则不需要添加任何预处理器。 用户则可以通过添加或取消TEST_BUILD_AS_DLL来使用动态库或静态库。 对于DLL的用户,TEST_BUILD_AS_DLL这个称似乎起得不好。下面的做法或许会更合理些: #if defined(TEST_API) #error "macro alreday defined!" #endif #if defined(TEST_BUILD_DLL) && defined(TEST_USE_DLL) #error "macro conflict!" #endif #if defined(TEST_BUILD_DLL) // build dll # define TEST_API __declspec(dllexport) #elif defined(TEST_USE_DLL) // use dll # define TEST_API __declspec(dllimport) #else // build or use library, no preprocessor needs # define TEST_API #endif 相关链接 微软DLL专题 Windows API编程之动态链接库(DLL
VS2010中使用C++创和使用DL.docx,文档加代码,全了。工程代码下载: 1.生成动态链接库(_declspec(dllexport)方式导出函数) 2.生成动态链接库(以.def文件(模块定义文件)方式导出函数) 3.以加载时动态链接方式调用DLL 4.以运行时动态链接方式调用DLL 5.以模块定义方式(.def文件立的动态链接库的调用 遇到的问题: 1.库导入的时候目录的问题。对应文中的问题1,后面有解释。 2.字符集的问题(是Unicode字符集还是多字节集),两种方案,一种修改字符集为多字节集,二是将字符串前面加 _T(""),文中问题2 3.不知道怎么通过模块定义文件方式生成DLL,通过看参考博客的代码找到了答案,主要修改头文件,和添加模块定义文件。 4.模块定义文件中的库文件应和工程一致。 DllMain函数 Windows在加载DLL时,需要一个入口函数,就像控制台程序需要main函数一样。有的时候,DLL并没有提供DllMain函数,应用程序也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的默认DllMain函数版本,并不意味着DLL可以抛弃DllMain函数。 根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数,这就说明不能在客户端直接调用DllMain函数DllMain函数是自动被调用的。 DllMain函数DLL被加载和卸载时被调用,在单个线程启动和终止时,DllMain函数也被调用。参数ul_reason_for_call指明了调用DllMain的原因,有以下四种情况: DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLLDllMain函数,传递的ul_reason_for_call参数值为DLL_PROCESS_ATTACH。这种情况只有首次映射DLL时才发生; DLL_THREAD_ATTACH:该通知告诉所有的DLL执行线程的初始化。当进程创一个新的线程时,系统会查看进程地址空间中所有的DLL文件映射,之后用DLL_THREAD_ATTACH来调用DLL中的DllMain函数。要注意的是,系统不会为进程的主线程使用值DLL_THREAD_ATTACH来调用DLL中的DllMain函数DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数ul_reason_for_call参数值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。如果进程的终结是因为系统中有某个线程调用了TerminateProcess来终结的,那么系统就不会用DLL_PROCESS_DETACH来调用DLL中的DllMain函数来执行进程的清理工作。这样就会造成数据丢失; DLL_THREAD_DETACH:该通知告诉所有的DLL执行线程的清理工作。注意的是如果线程的终结是使用TerminateThread来完成的,那么系统将不会使用值DLL_THREAD_DETACH来执行线程的清理工作,这也就是说可能会造成数据丢失,所以不要使用TerminateThread来终结线程。以上所有讲解在工程DLLMainDemo(工程下载)都有体现。 函数导出方式
关于特定情况下的调用,比如DLL函数中使用到了win32 API或者将C++生成的DLL供标准C语言使用,则需要注意以下一些情况:   如果使用到了win32 API,则应该使用调用方式为“__stdcall”。   在将C++生成的DLL供标准C语言使用,输出文件需要用“extern "C"”修饰,否则不能被标准C语言调用。如果使用“__stdcall”调用方式,可能产生C不识别的修饰,所以设置导出函数时要采用.def文件形式,而不是__declspec(dllexport)形式。后者会进行修饰转换,C语言无法识别函数。   下面的代码是一个定义文件的示例。   // SampleDLL.def   //   LIBRARY "sampleDLL"   EXPORTS   HelloWorld示例 DLL 和应用程序XXXXXXXX 在 Microsoft Visual C++ 6.0 中,可以通过选择“Win32 动态链接库”项目类型或“MFC 应用程序向导 (dll)”来创 DLL。下面的代码是一个在 Visual C++ 中通过使用“Win32 动态链接库”项目类型创DLL 的示例。   // SampleDLL.cpp   //#include "stdafx.h"   #define EXPORTING_DLL   #include "sampleDLL.h"   BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)   {   return TRUE;   }   void HelloWorld(){   MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);   }   // File: SampleDLL.h   //#ifndef INDLL_H   #define INDLL_H   #ifdef EXPORTING_DLLextern __declspec(dllexport) void HelloWorld() ;   #elseextern __declspec(dllimport) void HelloWorld() ;   #endif   #endif   下面的代码是一个“Win32 应用程序”项目的示例,该示例调用 SampleDLL DLL 中的导出 DLL 函数。   // SampleApp.cpp   //#include "stdafx.h"   #include "sampleDLL.h"   int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)   {   HelloWorld();   return 0;   }   注意:在加载时动态链接中,您必须链接在生成 SampleDLL 项目时创的 SampleDLL.lib 导入库。   在运行时动态链接中,您应使用与以下代码类似的代码来调用 SampleDLL.dll 导出 DLL 函数。   ...   typedef VOID (*DLLPROC) (LPTSTR);   ...   HINSTANCE hinstDLL;   DLLPROC HelloWorld;   BOOL fFreeDLL;   hinstDLL = LoadLibrary("sampleDLL.dll");   if (hinstDLL != NULL)   {   HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");   if (HelloWorld != NULL)   (HelloWorld);   fFreeDLL = FreeLibrary(hinstDLL);   }   ...

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧