【转帖】深入浅出Dll(介绍函数导出、类导出、钓子dll、不同语言混合编程方法、插件等的实现方法)

来源:互联网 发布:uk国家域名缩写 编辑:程序博客网 时间:2024/05/01 07:20
 

深入浅出Dll(介绍函数导出、类导出、钓子dll、不同语言混合编程方法、插件等的实现方法)

深入浅出Dll
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//                          flipcode@msn.com
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
所有代码均经过测试,如有问题可留言
一。简单的dll函数调用有两种方式:
     1。显式调用2。隐式调用.如下例子
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// dlltest.cpp : Dll 撰写
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#include <windows.h>
#include <stdio.h>
extern "C" __declspec(dllexport) int Add(int n1, int n2);
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
if(ul_reason_for_call==DLL_PROCESS_ATTACH)
{
//TRACE0("DLL Initializing!/n");
}
else if(ul_reason_for_call=DLL_PROCESS_DETACH)
{
//TRACE0("DLL Terminating!/n");
}
    return TRUE;
}
__declspec(dllexport) Add(int n1, int n2)
{
char szTxt[1024];
sprintf(szTxt,"%d + %d =%d",n1,n2,n1+n2);
MessageBox(0,szTxt,"Dll Respond here:",0);
return 1;
}
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// usedll.cpp : Dll 调用测试
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#include <windows.h>
//#define USESTATELIB
// 隐式链接方式:
#ifdef USESTATELIB
  #pragma comment(lib,"lib//dlltest.lib")
  extern "C" __declspec(dllimport) int Add(int n1, int n2);
#endif
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
#ifndef USESTATELIB
// 显式调用方式:
    HINSTANCE hIns=LoadLibrary("dlltest.dll");
if(!hIns)
{
MessageBox(0,"couldn't find dlltest.dll","error:",0);
return 0;
}
typedef int (DLL_Add)(int , int );
DLL_Add *testit=(DLL_Add*) GetProcAddress(hIns,"Add");
if(testit)
{
(*testit)(1,2);
}else
{
MessageBox(0,"couldn't find Add from dlltest.dll","error:",0);
return 0;
}
FreeLibrary(GetModuleHandle("dlltest.dll"));
#else
//隐式链接方式:
int nres= Add(1, 2);
#endif
return 0;
}

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 二。 导出类
// 注意CTest类应该定义在文件头,dllexport最好用宏作判断定义,
// 而这里仅作测试。
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1。dll生成:
// tdll.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
class __declspec(dllexport) CTest
{
private:
int m_nvalue;
public:
CTest() : m_nvalue(0) {}
int Getvalue() const;
};
int CTest::Getvalue() const
{
return m_nvalue;
}
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
 )
{
    return TRUE;
}
2。调用(记得把上述生成的tdll.dll及tdll.lib拷过来):
#pragma comment(lib, "tdll.lib")
class __declspec(dllimport) CTest
{
private:
int m_nvalue;
public:
CTest() : m_nvalue(1) {}
int Getvalue() const;
};
int main(int argc, char* argv[])
{
CTest a;
int kk = a.Getvalue();
return 0;
}
运行正确。
有趣的现象是:
    当我把应用端的dllimport错误的改成dllexport,程序依然可以build通过
   (不过注释了#pragma comment(lib, "tdll.lib")就不行),运行时结果
  是1(为区别于dll中初始值0,我把应用端的初始为1)。
    嗯,这是因为编译器在处理应用端的CTest时,编译确实看得见类结构,而链接时
  在tdll.lib中也可找Getvalue()相应符号,所以build成功,运行时既然应用端自己
    的CTest是dllexport,当然就是直接运行它了。
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//三。使用钓子的dll
// 1.参见我的QQ本马程序:
// http://blog.gameres.com/thread.asp?BlogID=386&threadid=13701    
// 2.另一个用于按F3弹出dll框供输出debug信息的dll,代码如下:
// 用vc创建一个win32dll,并在菜单中选择insert下的resource,插入一个dialog按ctrl+s存盘,并编写内容如下:
// logdll.h内容:
#ifdef LOGDLL_EXPORTS
#define LOGDLL_API __declspec(dllexport)
#else
#define LOGDLL_API __declspec(dllimport)
#endif
extern "C" LOGDLL_API void LG( const char* szTxt );
// logdll.cpp 内容:
//
#include <windows.h>
#include "logdll.h"
#include "resource.h"
// global data:
#pragma comment( linker, "section:Shared,rws" )
#pragma data_seg( "Shared" )
HHOOK     g_hhook     = NULL;
HINSTANCE g_hInstance = NULL;
HWND      g_pLogDlg   = NULL;
BOOL      g_bShowDlg  = FALSE;
#pragma data_seg()
//------------------------------------------------------------------------
void Init();
void Finish();
BOOL CreateLogDlg();
BOOL ShowLogDlg();
void InstallHook();
void UninstallHook();
LRESULT CALLBACK KeyboardProc( int, WPARAM, LPARAM );
INT_PTR CALLBACK LgDlgProc( HWND, UINT, WPARAM, LPARAM );

//------------------------------------------------------------------------
/*
*/
BOOL CreateLogDlg()
{
    if( g_pLogDlg ) return TRUE;
if( FALSE == g_hInstance ) return FALSE;
    g_pLogDlg = CreateDialog( g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)LgDlgProc );
return TRUE;
}
//------------------------------------------------------------------------
/*
*/
BOOL ShowLogDlg()
{
if( NULL == g_pLogDlg )
{
if( FALSE == CreateLogDlg() )
return FALSE;
}
    return ShowWindow( g_pLogDlg, (g_bShowDlg = !g_bShowDlg) ? SW_SHOW : SW_HIDE );
}
//------------------------------------------------------------------------
/*
*/
void InstallHook()
{
if( g_hhook == NULL ) {
g_hhook = SetWindowsHookEx( WH_KEYBOARD, (HOOKPROC)KeyboardProc, g_hInstance, 0 );
}
}
//------------------------------------------------------------------------
/*
*/
void UninstallHook()
{
if( g_hhook ) {
UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
}
}

//------------------------------------------------------------------------
/*
*/
LOGDLL_API void LG( const char* szTxt )
{
// here set the text info to your dialog's text ctrl
}
void Init()
{
InstallHook();
}
void Finish()
{
UninstallHook();
if( g_pLogDlg )
{
DestroyWindow( g_pLogDlg );
g_pLogDlg = NULL;
}
}
//------------------------------------------------------------------------
/*
*/
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
g_hInstance = (HINSTANCE) hModule;
    switch( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
Init();
break;
case DLL_THREAD_ATTACH:
Init();
break;
case DLL_THREAD_DETACH:
Finish();
break;
case DLL_PROCESS_DETACH:
Finish();
break;
    }
    return TRUE;
}
//------------------------------------------------------------------------
/*
*/
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LRESULT res = CallNextHookEx( g_hhook, nCode, wParam, lParam);
if( (lParam & (1 << 31)) && (wParam == VK_F3) && (nCode == HC_ACTION) )
{
ShowLogDlg();
}
return res;
}
//------------------------------------------------------------------------
/*
*/
INT_PTR CALLBACK LgDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch ( msg )
    {
        case WM_INITDIALOG:
            return TRUE;
        case WM_COMMAND:
            switch( LOWORD(wParam) )
            {
                case IDCANCEL:
                case IDOK:
                    EndDialog( hDlg, TRUE );
g_bShowDlg = FALSE;
                    return TRUE;
            }
            break;
        case WM_DESTROY:
            break;
    }
    return FALSE;
}
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 四。不同语言混合编程方法
//      标题太夸张了点,这里只是通过一种语言编写dll给另一种语言使用而已。例如:在VC中写dll而在vb中调用。这个在我转载的文章中已有祥细例子,我就不多说了,参见:(引用别人的“库”,又少写了很多“代码”,呵呵)
http://blog.gameres.com/thread.asp?BlogID=386&threadid=13945
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 五。插件dll相关撰写
// (我写了以下例子来说明3dmax的导出插件是如何编写与实现,
// 源码简单就不说明了)
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
这里我们的程序我用app.h和app.cpp实现,而别人写的插件我用plugtest.h与plugtest.cpp实现.
1.先写我们的程序(用建立普通win32程序方法创建)
// app.h头文件内容:
#include <windows.h>
// extern "C"
// Define USING_IMPORT by app's project setting that this head file local at
#ifdef USING_IMPORT
#define THIS_CLASS_EXPORT __declspec(dllimport)
#else
#define THIS_CLASS_EXPORT __declspec(dllexport)
#endif
class THIS_CLASS_EXPORT TestExport
{
public:
                 TestExport()  {};
virtual  ~TestExport() {};
virtual int  DoExport()    = 0;
};
class THIS_CLASS_EXPORT ClassDesc
{
public:
virtual HINSTANCE HInstance() = 0;
virtual void* Create()        = 0; // { return new TestExport's Derive class }
};
// app.cpp 文件内容:
//
#include "app.h"
#include <stdio.h>
#include <IO.H>
typedef ClassDesc* (*PFN_DESC)();
void TestCall( const char* szName )
{
HINSTANCE hDll     = LoadLibrary( szName );
    PFN_DESC pPlugDesc = (PFN_DESC)GetProcAddress( hDll, "Get_PlugDesc" );
ClassDesc* pDesc = (*pPlugDesc)();
if( pDesc )
{
TestExport* pExp = (TestExport*)pDesc->Create();
if( pExp )
{
pExp->DoExport();
}
}
}
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
// search all dll:
    struct _finddata_t c_file;
    long hFile;
/* Find first .c file in current directory */
    if( (hFile = _findfirst( "*.dll", &c_file )) == -1L )
printf( "No *.dll files in current directory!/n" );
else
{
TestCall( c_file.name );
/* Find the rest of the .c files */
while( _findnext( hFile, &c_file ) == 0 )
{
TestCall( c_file.name );
}
_findclose( hFile );
}
return 0;
}
下面写一个插件来测试一下:
可在vc中用普通win32 dll的创建方法来创建以下一个dll内容:
// plugtest.cpp内容
//
#include <windows.h>
#include "../app/app.h"
//#pragma comment(lib, "app.lib")
HINSTANCE g_hInstance;
// example:
// notic: must not define USING_IMPORT(because this example using for client)
class Dll_Plug : public TestExport
{
int  DoExport()
{
MessageBox(0, "test ok", "ok", 0);
return 1;
}
};
class Test_plusClassDesc : public ClassDesc
{
HINSTANCE HInstance()
{
return g_hInstance;
}
void* Create()               
{
return new Dll_Plug;
}
};
static Test_plusClassDesc test_plusDesc;
extern "C" THIS_CLASS_EXPORT ClassDesc* Get_PlugDesc()                   
{
return &test_plusDesc;
}
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
 )
{
g_hInstance = (HINSTANCE)hModule;
    return TRUE;
}

[注:]
     1. 函数调用有不同的约定方式,其中带extern "C"的是c约定与不带
的是不同的,具体体现在输出的符号上,一般c约定的符号与函数名相同,
c++的则带xx@xxxx之类(这可用dll查看工具查看),主要是区别参数的不同(为了处理类似重载等),相关文章可参见我转载的文章:
  
http://blog.gameres.com/thread.asp?BlogID=386&threadid=13945
    2. dll查看工具推荐: process explorer.exe、 dependency walker.exe
    这些工具非常有用,如果你的项目经常与一堆dll打交道的话就知道
    怎么用它们来查出不同dll工程之间关系,查出找不到符号的原因。
    相关有关工具参见:
http://blog.gameres.com/show.asp?BlogID=386&column=469

 
 注册: 2004-5    状态: online 4   
 
flipcode
爱心战士
  发表于: 2005-3-11 14:23:00                          
 
--------------------------------------------------------------------------------
Re:深入浅出Dll
在ogdev.net上有人提问了一个问题,顺便把它剪到这里作为补充:
【回答者:变成浪子 | 等级:青铜会员 | 得分:0 】 [直接回答] [引用回答] 
回答时间:2005-3-10 17:00:00 
 
看了这么多,只想问一个问题.
你知道怎么样显式加载Dll中的类吗? 
见下面回答:

 
 

 注册: 2004-5    状态: online 5   
 
flipcode
爱心战士
  发表于: 2005-3-11 14:24:00                          
 
--------------------------------------------------------------------------------
Re:深入浅出Dll
显示加载dll中的类是个问题, 但是你可以用类似以下技巧来处理:
1. 撰写dll服务类:
// dllclass.h
//
#include <windows.h>
#ifdef DLLCLASS_EXPORTS
#define MY_API __declspec(dllexport)
#else
#define MY_API __declspec(dllimport)
#endif
class DllClass
{
public:
virtual void DoSomething() = 0;
protected:
DllClass(){}
virtual ~DllClass(){}
};
extern "C" MY_API DllClass *CreateExpClass( void *pParm );
extern "C" MY_API void DeleteExpClass();
typedef DllClass* (CREATE_EXPCLASS)( void *pParm );
typedef void (DELETE_EXPCLASS)();
// dllclass.cpp : Defines the entry point for the DLL application.
//
#include "dllclass.h"
class DllSample : public DllClass
{
public:
virtual void DoSomething() {
MessageBox( 0, "DllSample test!", "", 0 );
};
};
DllSample* g_pSample = NULL;
MY_API DllClass *CreateExpClass( void *pParm )
{
if( NULL == g_pSample ){
g_pSample = new DllSample;
}
return g_pSample;
}
MY_API void DeleteExpClass()
{
if( g_pSample ){
delete g_pSample;
g_pSample = NULL;
}
}
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
 )
{
// case DLL_PROCESS_DETACH: call DeleteExpClass() ....
    return TRUE;
}
2. 测试dll服务类:
// calldllclass.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include "../dllclass/dllclass.h"

int main(int argc, char* argv[])
{
HINSTANCE hIns = LoadLibrary( "dllclass.dll" );
CREATE_EXPCLASS* pExpCreate = (CREATE_EXPCLASS*) GetProcAddress( hIns , "CreateExpClass" );
DELETE_EXPCLASS* pExpDelete = (DELETE_EXPCLASS*) GetProcAddress( hIns , "DeleteExpClass" );
if( pExpCreate ){
DllClass* pExp = (*pExpCreate)( "test" );
if( pExp ){
pExp->DoSomething();
}
}
if( pExpDelete ){
(*pExpDelete)();
}
return 0;
}
 

 注册: 2004-5    状态: online 6   
 
flipcode
爱心战士
  发表于: 2005-3-16 16:56:00                          
 
--------------------------------------------------------------------------------
Re:深入浅出Dll
注意:
服务类供使用的函数必需是虚函数(当然按需求你也可以将它写成纯虚的).
对了,顺便说一下,halflife2里面有一个所谓接口类用的就是此法,只不过它把创建函数用宏来自动作成,并且通过一名称与此函数相关联并把它加入到一链表中得以统一管理.
当使用方需要创建此类时,只要装入对应dll并通过它导出的一个函数传入对应字符串就可以得到对应类的创建函数.之后就可以直接调用此类的虚拟函数了
另外提示一下,在vc下的com接口实现可用类似上述的虚函数方法处理
flipcode 2005-8-29 11:31:46
 
 注册: 2004-5    状态: online 7   
 
flipcode
爱心战士
  发表于: 2005-3-17 11:27:00                          
 
--------------------------------------------------------------------------------
Re:深入浅出Dll
还是放一放halflife2的接口类代码吧
enum IFACE_RESULT{
IFACE_OK,
IFACE_FAILED
};
#define  DLL_EXPORT   extern "C" __declspec( dllexport )
#define  DLL_IMPORT   extern "C" __declspec( dllimport )
#define CREATEINTERFACE_PROCNAME "CreateInterface"
typedef void* (*CreateInterfaceFn)(const char *pName, int *pReturnCode);
typedef void* (*InstantiateInterfaceFn)();
// Used internally to register classes.
class InterfaceReg
{
public:
InterfaceReg(InstantiateInterfaceFn fn, const char *pName);
public:
InstantiateInterfaceFn m_CreateFn;
const char *m_pName;
InterfaceReg *m_pNext; // For the global list.
static InterfaceReg *s_pInterfaceRegs;
};
InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL;
InterfaceReg::InterfaceReg( InstantiateInterfaceFn fn, const char *pName ) :
m_pName(pName)
{
m_CreateFn = fn;
m_pNext = s_pInterfaceRegs;
s_pInterfaceRegs = this;
}
// ------------------------------------------------------------------------------------ //
// CreateInterface.
// ------------------------------------------------------------------------------------ //
DLL_EXPORT void *CreateInterface( const char *pName , int *pReturnCode );
void* CreateInterface( const char *pName, int *pReturnCode )
{
InterfaceReg *pCur;
for(pCur=InterfaceReg::s_pInterfaceRegs; pCur; pCur=pCur->m_pNext)
{
if(strcmp(pCur->m_pName, pName) == 0)
{
if ( pReturnCode )
{
*pReturnCode = IFACE_OK;
}
return pCur->m_CreateFn();
}
}
if ( pReturnCode )
{
*pReturnCode = IFACE_FAILED;
}
return NULL;
}
// Use this if you want to write the factory function.
#define EXPOSE_INTERFACE( className , interfaceName , versionName ) /
    static void *__Create##className##_Interface() { return (interfaceName*) new className; } /
    static InterfaceReg  __g_Create##interfaceName##_Reg( __Create_##className##_Interface , versionName );
// Use this to expose a singleton interface with a global variable you've created.
#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) /
static void* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;}/
static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName);
// Use this to expose a singleton interface. This creates the global variable for you automatically.
#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) /
static className __g_##className##_singleton;/
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton)
// 举例: 展开下面的宏:
//  EXPOSE_SINGLE_INTERFACE(VPanelWrapper, IPanel, VGUI_PANEL_INTERFACE_VERSION);
// static VPanelWrapper __g_VPanelWrapper_singleton;
// static void* __CreateVPanelWrapperIPanel_interface()
// {
// return (IPanel *)&__g_VPanelWrapper_singleton;
// }
// static InterfaceReg __g_CreateVPanelWrapperIPanel_reg( __CreateVPanelWrapperIPanel_interface, VGUI_PANEL_INTERFACE_VERSION );
// 最后一行是定义一个全局唯一的对象(其中将__CreateVPanelWrapperIPanel_interface()函数作为创建函数传入,
// 将字串VGUI_PANEL_INTERFACE_VERSION作为对应标识名称传入),这会触发构造函数InterfaceReg(InstantiateInterfaceFn fn, const char *pName)的运行,
// 从而自动将创建函数关联上对应标识并插入到链表. 使用者调用导出的函数CreateInterface并通过传入字符标识就可找到对就创建函数.
//=========================================================================================================
 
flipcode 2005-3-17 11:35:34
 
 
原创粉丝点击