基本思想相当简单,但细节可能会有些棘手。
因此,要让相关程序隐式链接到新 DLL 而不是旧 DLL,新 DLL 必须与旧 DLL 具有相同的文件名。在典型情况下,它还需要位于 Windows 加载程序可以找到它的位置,而不是旧位置。
同时,新的 DLL 需要能够链接到旧的。一种简单(但脆弱)的方法是调用它LoadLibrary
来加载旧的,当它这样做时,它指定旧 DLL 的完整路径。
我有时看到的另一种方法是在安装新 DLL 时查找并重命名旧 DLL。然后新的DLL安装在旧DLL的原名下,所以程序会找到它,而新的DLL可以找到旧的,因为它知道它的新名称。
从那里开始,这通常是一件相当简单的事情,让您的新 DLL 加载旧 DLL,使用 获取每个适用函数的地址GetProcAddress
,并拥有一个通过指向函数的指针转发给旧函数的函数。
例如:
Old_dll.cpp:
extern "C" {
__declspec(dllexport) int Add(int a, int b) { return a + b; }
__declspec(dllexport) int Sub(int a, int b) { return a - b; }
}
new_dll.cpp:
#include <windows.h>
int(*pAdd)(int a, int b) = NULL;
int(*pSub)(int a, int b) = NULL;
HMODULE mod = NULL;
extern "C" {
__declspec(dllexport) int Mul(int a, int b) { return a * b; }
__declspec(dllexport) int Div(int a, int b) { return a / b; }
__declspec(dllexport) int Add(int a, int b) { return pAdd(a, b); }
__declspec(dllexport) int Sub(int a, int b) { return pSub(a, b); }
BOOL WINAPI DllMain(_Out_ HINSTANCE hInstance, _In_ ULONG ulReason, LPVOID Reserved)
{
if (mod == NULL) {
mod = LoadLibrary("old_dll.dll");
typedef int(*arith)(int, int);
pAdd = (arith)GetProcAddress(mod, "Add");
pSub = (arith)GetProcAddress(mod, "Sub");
}
return true;
}
}
new_dll.h:
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int Add(int, int);
int Sub(int, int);
int Mul(int, int);
int Div(int, int);
void init(void);
#ifdef __cplusplus
}
#endif
使用_dll.cpp:
#include <iostream>
#include "new_dll.h"
int main() {
std::cout << Add(Mul(2, 3), Div(8, 4));
}
生成文件:
use_dll.exe: use_dll.cpp new_dll.lib
cl use_dll.cpp new_dll.lib
new_dll.lib: new_dll.cpp old_dll.dll
cl /LD new_dll.cpp
old_dll.dll: old_dll.cpp
cl /LD old_dll.cpp
因此,假设我没有跳过任何内容,您应该能够将它们放在一个目录中,键入nmake
,它就会全部构建。完成后,您应该有几个 DLL、一个可执行文件和其他一些杂项(.lib、.exp 等)。如果您运行可执行文件,它应该打印出8
,它是通过使用 new_dll 获得的,而 new_dll 又使用 old_dll 中的函数。
至于让你的旧主程序使用你添加的功能,如果它不是为了这样做而编写的:在任何细节级别上覆盖这个都远远超出了这里的答案范围。真正简短的答案是它需要某种反射机制,以便程序可以找到可用的函数以及如何调用它们。
这对于诸如为某种脚本语言嵌入解释器以及让用户使用脚本语言中可用的新功能编写新程序之类的事情非常实用。至于修改原始程序本身的工作方式,并以某种方式知道它以前从未听说过的新功能可以为它做一些有用的事情,并修改其代码以自动使用该功能......嗯,这可能是可能的,但它是当然不是我可以在这里介绍的那种东西。
如果您确实想使用反射来使函数可用(即使它们不是自动使用的),您可能需要查看 COM。它为此类工作的大多数部分提供了机制(但请注意:使用 COM 可能会导致整个程序发生重大变化,远远超出用一个 DLL 替换另一个 DLL 的简单任务)。