动态库的设计与实现

编写DllMain函数
DllMain函数是DLL模块的默认入口点。当Windows加载 DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数DLLMain。DLLMain函数不仅在将DLL链接加载到进程时被调 用,在DLL模块与进程分离时(以及其它时候)也被调用。下面是一个框架DLLMain函数的例子。

HINSTANCE g_hInstance; 
extern “C” int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved) 
{ 
if(dwReason==DLL_PROCESS_ATTACH) 
{ 
TRACE0(“EX22A.DLL Initializing!\n”); 
//在这里进行初始化 
} 
else if(dwReason=DLL_PROCESS_DETACH) 
{ 
TRACE0(“EX22A.DLL Terminating!\n”); 
//在这里进行清除工作 
} 
return 1;//成功 
}

如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所表明的那样。
模块句柄
进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识。进程自己还有一个HINSTANCE句柄。所有这些模块句柄都只有在特定的 进程内部有效,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这个两种类 型可以替换使用。进程模块句柄几乎总是等于0x400000,而DLL模块的加载地址的缺省句柄是0x10000000。如果程序同时使用了几个DLL模 块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为加载程序对DLL代码进行了重定位。
模块句柄对于加载资源特别重要。Win32 的FindResource函数中带有一个HINSTANCE参数。EXE和DLL都有其自己的资源。如果应用程序需要来自于DLL的资源,就将此参数指定为DLL的模块句柄。如果需要EXE文件中包含的资源,就指定EXE的模块句柄。
  但是在使用这些句柄之前存在一个问题,你怎样得到它们呢?如果需要得到EXE模块句柄,调用带有Null参数的Win32函数GetModuleHandle;如果需要DLL模块句柄,就调用以DLL文件名为参数的Win32函数GetModuleHandle。
应用程序怎样找到DLL文件
如果应用程序使用LoadLibrary显式链接,那么在这个函数的参数中可以指定DLL文件的完整路径。如果不指定路径,或是进行隐式链接,Windows将遵循下面的搜索顺序来定位DLL:
  1. 包含EXE文件的目录,
  2. 进程的当前工作目录,
  3. Windows系统目录,
  4. Windows目录,
  5. 列在Path环境变量中的一系列目录。
   这里有一个很容易发生错误的陷阱。如果你使用VC++进行项目开发,并且为DLL模块专门创建了一个项目,然后将生成的DLL文件拷贝到系统目录下,从 应用程序中调用DLL模块。到目前为止,一切正常。接下来对DLL模块做了一些修改后重新生成了新的DLL文件,但你忘记将新的DLL文件拷贝到系统目录 下。下一次当你运行应用程序时,它仍加载了老版本的DLL文件,这可要当心!
调试DLL程序
Microsoft 的VC++是开发和测试DLL 的有效工具,只需从DLL项目中运行调试程序即可。当你第一次这样操作时,调试程序会向你询问EXE文件的路径。此后每次在调试程序中运行DLL时,调试 程序会自动加载该EXE文件。然后该EXE文件用上面的搜索序列发现DLL文件,这意味着你必须设置Path环境变量让其包含DLL文件的磁盘路径,或者 也可以将DLL文件拷贝到搜索序列中的目录路径下。
DLL分配的内存如何在EXE里面释放
保证内存分配和清除的统一性:如果一个DLL提供一个能够分配内存的函数,那么这个DLL同时应该提供一个函数释放这些内存。数据的创建和清除应该在同一个层次上。
曾经遇到过这样的例子:在dll中分配了一块内存,通过PostMessage将其地址传给应用。然后应用去释放它,结果总是报异常。
如果exe用 MFC Appwizard方式生成, dll用win32方式生成,则运行时会出现错误。进一步用单步跟踪,发现mfc方式和win32方式下的new操作符是用不同方式实现的,源程序分别在VC目录的文件 Afxmem.cpp和new.cpp中。有兴趣的话可以自已跟踪一下。
因为dll输出函数后,并不知道是哪一个模拟调用它,因此new和delete配对时最好在一个文件中,这样可以保证一致性。
问题主要在于DLL和EXE主程序中分配内存的堆不一样,你可以不用new和delete,而是用
1) ::HeapAlloc(::GetProcessHeap(),…)和::HeapFree(::GetProcessHeap(),…)
2) ::GlobalAlloc()和::GlobalFree()
这两对API,这样无论在DLL中还是在主程序中都是在进程默认堆中分配,就不会出错了。
还有一个办法,就是把dll的Settings的C/C++选项卡的Code Generation的Use Run-time liberary改成Debug Multithreaded DLL,在Release版本中改成Multithreaded DLL,就可以直接使用new和delete了。不过MFC就不能用Shared模式了。
VS配置动态库工程
新建DLL项目
测试项目的配置
调试的时候要同时打开两个工程,将DLL工程生成的dll文件以及lib文件复制到测试工程的可执行文件目录以及源文件平级的目录。
第一套API函数的动态库程序
动态库程序源代码

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef struct __SCK_HANDLE{
    char    version[64];
    char    ip[128];
    int     port;
    unsigned char   *p;
    int     plen;
}SCK_HANDLE;


//------------------第一套api接口---Begin--------------------------------//
//客户端初始化 获取handle上下
__declspec(dllexport)
int cltSocketInit(void **handle /*out*/)
{
    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    printf("sck init begin\n");
    hdl = (SCK_HANDLE*)malloc(sizeof(SCK_HANDLE));


    if (NULL == hdl)
    {
        ret = -1;
        printf("(SCK_HANDLE*)malloc(sizeof(SCK_HANDLE))failed!\n");
        return ret;
    }

    strcpy(hdl->ip, "192.168.6.254");
    hdl->port = 8081;
    *handle = hdl;
    printf("sck init end\n");
    return ret;

}

//客户端发报文
__declspec(dllexport)
int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/)
{
    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    if (handle == NULL || buf == NULL)
    {
        ret = -2;
        return ret;
    }

    hdl = (SCK_HANDLE *)handle;

    hdl->p = (unsigned char *)malloc(sizeof(unsigned char)*buflen);
    if (hdl->p == NULL)
    {
        ret = -1;
        printf("malloc(sizeof(unsigned char)*buflen)failed!\n");
        return ret;
    }

    memcpy(hdl->p,buf,buflen);
    hdl->plen = buflen;

    return ret;
}

//客户端收报文
__declspec(dllexport)
int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/)
{

    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    if (handle == NULL || buf == NULL)
    {
        ret = -2;
        return ret;
    }

    hdl = (SCK_HANDLE *)handle;

    memcpy(buf,hdl->p,hdl->plen);

    *buflen = hdl->plen;

    return ret;
}

//客户端释放资源
__declspec(dllexport)
int cltSocketDestory(void *handle/*in*/)
{
    int ret = 0;

    SCK_HANDLE *hdl = NULL;

    if (handle == NULL)
    {
        ret = -2;

        return ret;
    }

    hdl = (SCK_HANDLE*)handle;

    if (hdl->p != NULL)
    {
        free(hdl->p);
        hdl->p = NULL;
    }

    free(hdl);

    return 0;
}
//------------------第一套api接口---End-----------------------------------//

测试程序源代码
头文件

//written by  wangbaoming1999@163.com
//20140323 23:10
/*
下面定义了一套socket客户端发送报文接受报文的api接口
请写出这套接口api的调用方法
*/

#ifndef _INC_Demo01_H
#define _INC_Demo01_H

#ifdef  __cplusplus
extern "C" {
#endif

    //------------------第一套api接口---Begin--------------------------------//
    //客户端初始化 获取handle上下
    int cltSocketInit(void **handle /*out*/);

    //客户端发报文
    int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/);

    //客户端收报文
    int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/);

    //客户端释放资源
    int cltSocketDestory(void *handle/*in*/);
    //------------------第一套api接口---End-----------------------------------//


    //------------------第二套api接口---Begin--------------------------------//
    int cltSocketInit2(void **handle);

    //客户端发报文
    int cltSocketSend2(void *handle, unsigned char *buf, int buflen);
    //客户端收报文
    int cltSocketRev2(void *handle, unsigned char **buf, int *buflen);
    int cltSocketRev2_Free(unsigned char **buf);
    //客户端释放资源

    int cltSocketDestory2(void **handle);
    //------------------第二套api接口---End--------------------------------//

#ifdef  __cplusplus
}
#endif

#endif  /* _INC_Demo01_H */

测试文件

#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "socketclient.h"

int main(void)
{
    void *handle = NULL;
    int ret = 0;
    unsigned char sendbuf[1024] = { 0 };
    unsigned char recvbuf[1024] = { 0 };

    int send_len = 3;
    int recv_len = 3;

    strcpy(sendbuf,"adad");
    ret = cltSocketInit(&handle);
    if (ret != 0)
    {
        ret = -1;
        printf("cltSocketInit failed!\n");
    }

    //客户端发报文
    ret = cltSocketSend(handle, sendbuf, send_len);
    if (ret != 0)
    {
        ret = -2;
        printf("cltSocketSend failed!\n");
    }

    //客户端收报文
    ret = cltSocketRev(handle, recvbuf, &recv_len);
    if (ret != 0)
    {
        ret = -3;
        printf("cltSocketRev failed!\n");
    }

    //客户端释放资源
    ret = cltSocketDestory(handle);
    if (ret != 0)
    {
        ret = -4;
        printf("cltSocketDestory failed!\n");
    }
    system("PAUSE");
    return 1;
}

第二套加入日志功能的动态库
动态库的开发难点在测试和调试,只有具备良好日志功能的底层动态库才能降低动态库的开发。
日志功能头文件


//written by wangbaoming1999@163.com
//20140323
//itcastlog.h 日志头文件


#ifndef _ITCAST_LOG_H_
#define _ITCAST_LOG_H_

/*
#define IC_NO_LOG_LEVEL         0
#define IC_DEBUG_LEVEL          1
#define IC_INFO_LEVEL           2
#define IC_WARNING_LEVEL        3
#define IC_ERROR_LEVEL          4;
*/

/************************************************************************/
/* 
const char *file:文件名称
int line:文件行号
int level:错误级别
        0 -- 没有日志
        1 -- debug级别
        2 -- info级别
        3 -- warning级别
        4 -- err级别
int status:错误码
const char *fmt:可变参数
*/
/************************************************************************/
//实际使用的Level
extern int  LogLevel[5];
void ITCAST_LOG(const char *file, int line, int level, int status, const char *fmt, ...);

#endif

日志功能实现文件

#define _CRT_SECURE_NO_WARNINGS
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "itcastlog.h"

#define ITCAST_DEBUG_FILE_  "socketclient.log"
#define ITCAST_MAX_STRING_LEN       10240

//Level类别
#define IC_NO_LOG_LEVEL         0
#define IC_DEBUG_LEVEL          1
#define IC_INFO_LEVEL           2
#define IC_WARNING_LEVEL        3
#define IC_ERROR_LEVEL          4

int  LogLevel[5] = {IC_NO_LOG_LEVEL, IC_DEBUG_LEVEL, IC_INFO_LEVEL, IC_WARNING_LEVEL, IC_ERROR_LEVEL};

//Level的名称
char ICLevelName[5][10] = {"NOLOG", "DEBUG", "INFO", "WARNING", "ERROR"};

static int ITCAST_Error_GetCurTime(char* strTime)
{
    struct tm*      tmTime = NULL;
    size_t          timeLen = 0;
    time_t          tTime = 0;  

    tTime = time(NULL);
    tmTime = localtime(&tTime);
    //timeLen = strftime(strTime, 33, "%Y(Y)%m(M)%d(D)%H(H)%M(M)%S(S)", tmTime);
    timeLen = strftime(strTime, 33, "%Y.%m.%d %H:%M:%S", tmTime);

    return timeLen;
}

#define WIN32
static int ITCAST_Error_OpenFile(int* pf)
{
    char    fileName[1024];

    memset(fileName, 0, sizeof(fileName));
#ifdef WIN32
    sprintf(fileName, "c:\\%s",ITCAST_DEBUG_FILE_);
#else
    sprintf(fileName, "%s/log/%s", getenv("HOME"), ITCAST_DEBUG_FILE_);
#endif

    *pf = open(fileName, O_WRONLY|O_CREAT|O_APPEND, 0666);
    if(*pf < 0)
    {
        return -1;
    }

    return 0;
}

static void ITCAST_Error_Core(const char *file, int line, int level, int status, const char *fmt, va_list args)
{
    char str[ITCAST_MAX_STRING_LEN];
    int  strLen = 0;
    char tmpStr[64];
    int  tmpStrLen = 0;
    int  pf = 0;

    //初始化
    memset(str, 0, ITCAST_MAX_STRING_LEN);
    memset(tmpStr, 0, 64);

    //加入LOG时间
    tmpStrLen = ITCAST_Error_GetCurTime(tmpStr);
    tmpStrLen = sprintf(str, "[%s] ", tmpStr);
    strLen = tmpStrLen;

    //加入LOG等级
    tmpStrLen = sprintf(str+strLen, "[%s] ", ICLevelName[level]);
    strLen += tmpStrLen;

    //加入LOG状态
    if (status != 0) 
    {
        tmpStrLen = sprintf(str+strLen, "[ERRNO is %d] ", status);
    }
    else
    {
        tmpStrLen = sprintf(str+strLen, "[SUCCESS] ");
    }
    strLen += tmpStrLen;

    //加入LOG信息
    tmpStrLen = vsprintf(str+strLen, fmt, args);
    strLen += tmpStrLen;

    //加入LOG发生文件
    tmpStrLen = sprintf(str+strLen, " [%s]", file);
    strLen += tmpStrLen;

    //加入LOG发生行数
    tmpStrLen = sprintf(str+strLen, " [%d]\n", line);
    strLen += tmpStrLen;

    //打开LOG文件
    if(ITCAST_Error_OpenFile(&pf))
    {
        return ;
    }

    //写入LOG文件
    write(pf, str, strLen);
    //IC_Log_Error_WriteFile(str);

    //关闭文件
    close(pf);

    return ;
}


void ITCAST_LOG(const char *file, int line, int level, int status, const char *fmt, ...)
{
    va_list args;

    //判断是否需要写LOG
//  if(level!=IC_DEBUG_LEVEL && level!=IC_INFO_LEVEL && level!=IC_WARNING_LEVEL && level!=IC_ERROR_LEVEL)
    if(level == IC_NO_LOG_LEVEL)
    {
        return ;
    }

    //调用核心的写LOG函数
    va_start(args, fmt);
    ITCAST_Error_Core(file, line, level, status, fmt, args);
    va_end(args);

    return ;
}

第二套API头文件
(暂无)
第二套API实现文件

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "itcastlog.h"
typedef struct __SCK_HANDLE{
    char    version[64];
    char    ip[128];
    int     port;
    unsigned char   *p;
    int     plen;
}SCK_HANDLE;


//------------------第一套api接口---Begin--------------------------------//
//客户端初始化 获取handle上下
__declspec(dllexport)
int cltSocketInit(void **handle /*out*/)
{
    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    ITCAST_LOG(__FILE__, __LINE__, LogLevel[2], 0, "sck init begin\n");
    hdl = (SCK_HANDLE*)malloc(sizeof(SCK_HANDLE));


    if (NULL == hdl)
    {
        ret = -1;
        //ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], 0, "(SCK_HANDLE*)malloc(sizeof(SCK_HANDLE))failed!\n");
        ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], 0, "(SCK_HANDLE*)malloc(sizeof(SCK_HANDLE))failed!\n");
        return ret;
    }

    strcpy(hdl->ip, "192.168.6.254");
    hdl->port = 8081;
    *handle = hdl;
    ITCAST_LOG(__FILE__, __LINE__, LogLevel[2], 0, "sck init end\n");
    return ret;

}

//客户端发报文
__declspec(dllexport)
int cltSocketSend(void *handle /*in*/, unsigned char *buf /*in*/, int buflen /*in*/)
{
    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    if (handle == NULL || buf == NULL)
    {
        ret = -2;
        return ret;
    }

    hdl = (SCK_HANDLE *)handle;

    hdl->p = (unsigned char *)malloc(sizeof(unsigned char)*buflen);
    if (hdl->p == NULL)
    {
        ret = -1;
        ITCAST_LOG(__FILE__, __LINE__, LogLevel[4], 0, "malloc(sizeof(unsigned char)*buflen)failed!\n");
        return ret;
    }

    memcpy(hdl->p,buf,buflen);
    hdl->plen = buflen;

    return ret;
}

//客户端收报文
__declspec(dllexport)
int cltSocketRev(void *handle /*in*/, unsigned char *buf /*in*/, int *buflen /*in out*/)
{

    SCK_HANDLE * hdl = NULL;
    int ret = 0;

    if (handle == NULL || buf == NULL)
    {
        ret = -2;
        return ret;
    }

    hdl = (SCK_HANDLE *)handle;

    memcpy(buf,hdl->p,hdl->plen);

    *buflen = hdl->plen;

    return ret;
}

//客户端释放资源
__declspec(dllexport)
int cltSocketDestory(void *handle/*in*/)
{
    int ret = 0;

    SCK_HANDLE *hdl = NULL;

    if (handle == NULL)
    {
        ret = -2;

        return ret;
    }

    hdl = (SCK_HANDLE*)handle;

    if (hdl->p != NULL)
    {
        free(hdl->p);
        hdl->p = NULL;
    }

    free(hdl);

    return 0;
}
//------------------第一套api接口---End-----------------------------------//





//------------------第二套api接口---Begin--------------------------------//
__declspec(dllexport)
int cltSocketInit2(void **handle)
{
    return cltSocketInit(handle);
}

//客户端发报文
__declspec(dllexport)
int cltSocketSend2(void *handle, unsigned char *buf, int buflen)
{
    return cltSocketSend(handle, buf, buflen);
}
//客户端收报文
__declspec(dllexport)
int cltSocketRev2(void *handle, unsigned char **buf, int *buflen)
{
    SCK_HANDLE * hdl = NULL;
    int ret = 0;
    unsigned char *tmp = NULL;

    if (handle == NULL || buf == NULL)
    {
        ret = -2;
        return ret;
    }

    hdl = (SCK_HANDLE *)handle;

    tmp = (unsigned char *)malloc(sizeof(unsigned char)*(hdl->plen));
    if (tmp == NULL)
    {
        ret = -2;
        return ret;
    }
    memcpy(tmp,hdl->p,hdl->plen);
    *buflen = hdl->plen;
    *buf = tmp;
    return ret;
}

__declspec(dllexport)
int cltSocketRev2_Free(unsigned char **buf)
{
    int ret = 0;
    if (buf == NULL)
    {
        ret = -2;
        return ret;
    }

    if (*buf != NULL)
    {
        free(*buf);
    }

    *buf = NULL;
    return 0;
}
//客户端释放资源
__declspec(dllexport)
int cltSocketDestory2(void **handle)
{
    SCK_HANDLE *tmp = NULL;
    int ret = 0;
    if (handle == NULL)
    {
        ret = -2;
        return ret;
    }

    tmp = *handle;

    if (tmp != NULL)
    {
        if (tmp->p != NULL)
        {
            free(tmp->p);
            tmp->p = NULL;
        }
        free(tmp);
        *handle = NULL;
    }
    return 0;
}

//------------------第二套api接口---End--------------------------------//

第二套API测试文件
日志文件存放在由ITCAST_Error_OpenFile函数指定的sprintf(fileName, “c:\%s”,ITCAST_DEBUG_FILE_);中

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "socketclient.h"


int main(void)
{
    void *handle = NULL;
    int ret = 0;
    unsigned char sendbuf[1024] = { 0 };
    unsigned char *recvbuf=NULL;

    int send_len = 3;
    int recv_len = 0;
    strcpy(sendbuf,"adad");

    ret = cltSocketInit2(&handle);
    if (ret != 0)
    {
        ret = -1;
        printf("cltSocketInit2 failed!\n");
    }

    //客户端发报文
    ret = cltSocketSend2(handle, sendbuf, send_len);
    if (ret != 0)
    {
        ret = -2;
        printf("cltSocketSend2 failed!\n");
    }

    //客户端收报文
    ret = cltSocketRev2(handle, &recvbuf, &recv_len);
    if (ret != 0)
    {
        ret = -3;
        printf("cltSocketRev2 failed!\n");
    }


    ret = cltSocketRev2_Free(&recvbuf);
    if (ret != 0)
    {
        ret = -4;
        printf("cltSocketRev2_Free failed!\n");
    }
    //客户端释放资源
    ret = cltSocketDestory2(&handle);
    if (ret != 0)
    {
        ret = -5;
        printf("cltSocketDestory2 failed!\n");
    }
    system("PAUSE");
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值