Windows API 编程-----DLL编程之禁止加载自己

  和可执行文件一样,动态链接库也有自己的入口地址,如果系统或者当前进程的某个线程调用LoadLibrary函数加载或者使用FreeLibrary卸载该动态链接库的时候,会自动使用3个特定的堆栈参数跳转到该地址来运行。入口函数是为了完成动态链接库代码的初始化和善后工作,比如卸载后的资源释放。

  这三个参数具有特殊的含义。

  BOOL APIENTRY DllMain(

    HMODULE hModule,

    DWORD ul_reason_for_call,

    LPVOID lpReserved
  )


  第一个参数是实例句柄,这个所谓的句柄实际上就是该加载进程在内存中的景象地址,注意进程句柄和动态链接库的模块句柄是不一样的。比如,如果用户想使用hModule去加载该动态链接库中的一个位图资源,那将永远得不到结果。动态链接库的模块句柄依附于加载进程,脱离进程独立于系统空间中的动态链接库是不存在的,也没有意义。动态链接库总是要加载到进程中的某个地址,该地址就是动态链接库的模块句柄,这个句柄不同于进程实例句柄,它是相对于进程入口地址的一个偏移量,只有通过模块句柄才能寻址到该动态链接库中嵌套的各种窗口或者其他资源。

  ul_reason_for_call 参数表示该动态链接库是在什么条件下被加载的,即加载的原因。当用户显式采用LoadLibrary(Ex)函数加载一个动态链接库或者由进程本身隐式加载该库时,该入口函数就会被调用,入口参数 ul_reason_for_call 这时就等于DLL_PROCESS_ATTACH,入口函数的返回值就是LoadLibrary函数的返回值,它们都是通过EAX寄存器来传递的。根据LoadLibrary(Ex)函数的说明,这个函数如果返回一个NULL值,表示加载失败。实际上在DllMain 函数中,只要返回FALSE,LoadLibrary(Ex)函数就会返回一个NULL值。如果动态链接库需要排他调用,可以在入口点函数中判断加载进程的文件名,或者履行其他合法性检查;如果不希望被某个进程调用,可以直接在DllMain函数中返回FALSE。DLL_PROCESS_ATTACH分支往往用于实现系统初始化,比如建立数据库链接,创建钩子函数,分配系统资源,保存入口进程实例句柄等。

  同样,当进程不再使用该动态链接库,比如调用ExitProcess函数或者显式调用FreeLibrary函数时,系统又会使用DLL_PROCESS_DETACH参数执行相关分支。用于释放系统资源、断开数据库链接、卸载钩子函数、关闭文件、释放内存等。

  注意:DLL_PROCESS_DETACH分支的执行是有条件的,进程的意外终止,并不会执行DLL_PROCESS_DETACH分支语句,这样分支中的关闭资源、断开链接、关闭文件等善后语句就无法执行,这将会造成一些数据的潜在丢失。因此除非万不得已,不要轻易使用TerminateProcess函数终止进程执行。

  如果线程创建时DLL完成到进程空间的映射,这样系统会使用DLL_THREAD_ATTACH进入入口点。而系统执行ExitThread函数时会执行DLL_THREAD_DETACH分支。同样的规则适用于线程,用户不要轻易使用TerminateThread函数,这也会导致一些不可预知的内存泄漏或者资源没有释放和数据丢失。

  如果用户不在乎DLL_THREAD_DETACH和DLL_THREAD_ATTACH通知,又希望提高创建和撤销线程的性能,可以在收到DLL_THREAD_ATTACH通知时,调用DisableThreadLibraryCalls函数。

  注意:关于lpReserved参数----在静态(隐式)加载和调用LoadLibray函数实现动态(显式)加载动态库时,这两种情况是不一样的,动态加载时这个值为0。如果用户希望自己编写的的动态链接库只能被动态加载或者只能被静态加载,可以通过判断这个参数来实现。

代码示例:

dllmain.cpp(生成mydll.dll):

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include<stdio.h>
#include<Psapi.h>
#include<Windows.h>
typedef void(*pFnPtr)(char*);

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                    )
{

    char szName[MAX_PATH];
    if (lpReserved != 0)
    {
        //只允许动态加载,静态加载将提示错误并退出
        MessageBox(NULL, "只允许动态加载", "Sorry", MB_OK);
        return FALSE;
    }
    GetModuleBaseName(GetCurrentProcess(),NULL ,szName,MAX_PATH);//获取当前主进程的base name
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        //故意将调用进程的名字改为test.exe,该动态链接库将无法加载。
        if (strcmp(szName, "test.exe") == 0)
            return FALSE;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C"
{
    //主调进程将调用该函数,该函数再调用主调进程的ExeFn函数
    _declspec(dllexport) int fnImportingDLL()
    {
        MessageBox(NULL, "Dll Function called!", "mydll", MB_OK);
        pFnPtr fn = (pFnPtr)::GetProcAddress(GetModuleHandle(NULL), "ExeFn");
        if (fn)
            fn("梦回吹角连营");
        else
        {
            MessageBox(NULL, "It Did not work:", "From DLL", MB_OK);
            return -1;
        }
    }
}

main.cpp(生成console.exe):

#include<iostream>
#include<Windows.h>
#define _DYNAMIC_
#ifndef _DYNAMIC_
#pragma comment(lib,"mydll.lib")    //静态加载
extern "C" _declspec(dllexport) int fnImportingDLL();
#endif
typedef int(*pfnImportingDLL)();

using namespace std;
int main(int argc, char* argv[])
{
#ifdef _DYNAMIC_    //动态加载
    pfnImportingDLL fnImportingDLL = NULL;
    HMODULE hModule=::LoadLibrary("mydll.dll");
    if (hModule == NULL)
    {
        cout << "无法加载mydll.dll" << endl;
        return -1;
    }
    fnImportingDLL =(pfnImportingDLL) GetProcAddress(hModule, "fnImportingDLL");
    if (fnImportingDLL == NULL)
    {
        cout << "找不到fnImportingDLL函数" << endl;
        return -2;
    }
#endif
    cout << "I‘m going...\n";
    fnImportingDLL();
    cout << "Game Over\n";
#ifdef _DYNAMIC_
    ::FreeLibrary(hModule);
#endif
    return 0;
}

extern "C"
{
    //该函数将有动态链接库中的函数来调用
    _declspec(dllexport) void ExeFn(char* lpszMessage)
    {
        MessageBox(NULL, lpszMessage, "From Exe", MB_OK);
    }
}

将生成的 console.exe文件和mydll.dll文件放在同一个文件夹下

注释掉 _DYNAMIC_ 宏将静态加载mydll.dll,这样会弹出对话框提示只能动态加载,因为在DllMain中检查了lpReserved的值并做了判断。

将console.exe的名字改为test.exe,然后在cmd中运行test.exe,将显示无法加载mydll.dll。

时间: 2024-12-23 21:57:58

Windows API 编程-----DLL编程之禁止加载自己的相关文章

链接库DLL的概念,加载方式的区别

使用LR进行基于windows socket协议做接口测试,只提供了lr_load_dll方法来动态加载动态链接库.之前学习阶段,对TinyXML的学习,使用的静态链接库,当时在程序调用的时候方法也跟LR里的不一样,那问题来了:lib和dll的区别是什么,每种链接库有多少种加载方式,怎么加载呢. 链接库可以向应用程序提供一些函数,变量和类.动态链接库的动态调用(也叫显式调用,手工加载)我是可以运用了,但是静态调用(也叫隐式调用,自动加载).静态链接库:lib中的函数不仅被连接,全部实现都被直接包

dll的两种加载方式(pend)+ delayload

看过关于动态库的调用例子,于是决定动手做一做:dll的对外接口声明头文件,Mydll.h: //Mydll.h #include <stdio.h> #include <stdlib.h> #include "Public.h" #define DLL_EXPORT /*extern "c"*/ __declspec(dllexport) //导出 #define CUST_API _stdcall //标准调用 DLL_EXPORT voi

ArcGIS API for Silverlight 调用GP服务加载等值线图层

原文:ArcGIS API for Silverlight 调用GP服务加载等值线图层 第二篇.Silverlight客户端调用GP服务 利用ArcGIS API for Silverlight实现GP服务调用,这里的雨量数据是通过一个WebService获取而来,主要信息是雨量站点的经纬度坐标值和某个时间段内的降雨量值三个主要字段. 以下是核心代码部分: <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/pr

[转] 从 dll 程序集中动态加载窗体

无涯 原文 从 dll 程序集中动态加载窗体 [原创] 昨天晚上花了一晚上时间写了一个从程序集中动态加载窗体的程序.将任何包含窗体的代码编译成 dll 文件,再把 dll 文件拷贝到本程序的目录下,本程序运行时即可动态检查到 dll 文件中的窗体,将窗体类的类型在程序菜单中显示出来,点击菜单即可运行对应的窗体. 本程序主要用到了 Assembly 类动态加载程序集,再得到程序集中包含类的 Type 类型,动态生成类实例,动态调用类方法.个人觉得这是一种提供高度松耦合,可随意扩展的程序结构框架,希

net core手动加载dll,无法自动加载其依赖项

用的net core版本是2.1,也许在后续的版本中已经修复了这个问题 今天在尝试用net core写demo的时候,发现了这个问题.因为都是使用DI,所以就没有我的网站项目里直接引用一些实现类库,而是放到了同一个目录下,在网站启动的时候用代码去加载进来.然而在实际的运行过程成中发现,指定的dll会自动加载,但是其依赖的nuget包里的dll不会被加载进来,在Google了很久,也发现了很多人提出过这个问题,在GitHub上也有人提过https://github.com/dotnet/coref

基于Windows api手柄映射编程

一个手柄/键盘映射程序,无外乎就四部分:一.界面:二.接收:三.处理:四.输出. 界面就不多说了. 接收,就是接收手柄的输出.这部分有多种方法,比如windows API和DirectX,这里我们选前者. 处理,就是将接收到的数据映射为输出数据. 输出,就是向操作系统发送假的键盘事件,从而完成映射过程. 接收部分: 那么,我们先来进行知识的准备.为了完成接收部分,我们需要了解和手柄相关的windows API.其中常用的较重要的函数如下: joyGetDevCaps               

Java 编程下使用 Class.forName() 加载类【转】

在一些应用中,无法事先知道使用者将加载什么类,而必须让使用者指定类名称以加载类,可以使用 Class 的静态 forName() 方法实现动态加载类.下面的范例让你可以指定类名称来获得类的相关信息. package cn.sunzn.demo; public class ClassDemo { public static void main(String[] args) { try { Class c = Class.forName(args[0]); System.out.println("类

Java 编程下使用 Class.forName() 加载类

在一些应用中,无法事先知道使用者将加载什么类,而必须让使用者指定类名称以加载类,可以使用 Class 的静态 forName() 方法实现动态加载类.下面的范例让你可以指定类名称来获得类的相关信息. package cn.sunzn.demo; public class ClassDemo { public static void main(String[] args) { try { Class c = Class.forName(args[0]); System.out.println("类

Windows Service 项目中 Entity Framework 无法加载的问题

Windows Service 项目引用了别的类库项目,别的项目用到了 Entity Framework(通过Nuget引入),但是我的 Windows Service 无法开启,于是我修改了 App.config,加入 EF 的配置信息后能开启,但是业务代码无法进入,通过日志发现错误: System.InvalidOperationException: The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlPro