0x1前言
首先我们要用C、C++编写dll,肯定是需要一个合适的编写软件,什么!?你不知道什么是dll,那你这有必要去看看我得上一篇《初识Windows APi》了虽然篇幅不长,但是可以让你对dll有个大致得印象,由于本人用的CLion 2019的版本的,至于为什么不用VS,因为里面配置过于繁琐,很不友好,CLion安装包以及破解 群文件都有,可以去下载直接使用。话不多说,进入今天的正题:
利用C、C++简单的编写一个DLL文件
::: tip 开发环境:CLion 2019.3.3 编译器:WinGW64 ::: 打开CLion 新建一个 C Library项目DLL library.c默认代码 按下Ctrl+F9编译,右侧生成libDLL.dll文件
调用dll
新建一个项目 untitled1 ,在untitled1下创建一个lib目录,存放刚刚生成的dll文件, 配置CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
#项目名
project(untitled1 )
set(CMAKE_C_STANDARD 99)
#新增:指项目根目录下的lib目录
link_directories(lib)
add_executable(untitled1 main.c )
#新增:目标链接的dll文件
target_link_libraries(untitled1 libDLL.dll)
配置main.c:
#include <stdio.h>
void hello();
int main() {
printf("下面是一个dll的调用");
hello();
getchar();
return 0;
}
配置Configurations: 将lib目录的绝对路径填入(如用群里的中文插件可能导致这里显示异常) 然后编译运行结果如下: 到这里只是用实操演示一下dll的由来,只能对编写dll有一个认识,当然无法准确的理解,那么请继续往下看
详解
这里介绍一下dll动态链接库的核心入口函数 DLLMain() 跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。 以"DllMain"为关键字,来看看MSDN帮助文档怎么介绍这个函数的。
The DllMain function is an optional method of entry into a dynamic-link library (DLL) 。 (简要翻译:对于一个Dll模块,DllMain函数是可选的。)这句话很重要,很多初学者可能都认为一个动态链接库肯定要有DllMain函数。其实不然,像很多仅仅包含资源信息的DLL是没有DllMain函数的。
#include <windows.h>
_Bool WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpReserved ) // reserved
{
// Perform actions based on the reason for calling.
switch( fdwReason)
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}
因为返回类型是布尔类型,所以最后要return true hinstDLL参数:DLL模块的句柄。该值是DLL的基地址;
fdwReason [in]参数:指明了DLL被调用的原因,可以有以下4个取值: ::: tip
1. DLL_PROCESS_ATTACH: 当DLL被进程 <第一次>调用时,导致DllMain函数被调用, 同时ul_reason_for_call的值为DLL_PROCESS_ATTACH, 如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数, 不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。
2.DLL_PROCESS_DETACH: 当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。 ★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。
3.DLL_THREAD_ATTACH: 当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。 新创建的线程负责执行这次的DLL的DllMain函数, 只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。
4.DLL_THREAD_DETACH: 如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。 ::: ::: warning ★如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。 ::: lpReserved参数:系统保留,不让我们利用,目前没什么意义。 不过关于lPReserved参数,看到一位网友是这样说的:
“lpvReserved:为零表示隐式载入,不为零表示显示载入 不是没有什么意义”,我还没有深究,暂做参考