你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:技术专栏 / C专栏
实例动态链接库编程
 

关键字 动态链接库 重载运算符
原作者姓名 戚高

介绍
在大型团队系统开发、团队合作和模块化编程的今天,动态链接库,COM占有着非常重要的作用,本文用实例的方法来说明动态链接库编程中如何调用动态链接库进行输入参数,返回参数以及返回结构数据等。

正文
比较大应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。
    Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。
    一般来说,DLL是一种磁盘文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。
    在Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。
    应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载。而显式链接与此相反。
采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
    显式链接方式对于集成化的开发语言(例如VB)比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。
    在隐式链接方式中,所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中;但如果采用显式链接方式,程序员可以决定DLL文件何时加载或不加载。显式链接在运行时决定加载哪个DLL文件。例如,可以将一个带有字符串资源的DLL模块以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对应的DLL文件。
    在我们常见的一些厂家提供的功能DLL库中常常会发现他们提供的一个DLL文件,一个LIB文件,一个H文件和一些相关的接口文档说明等,下面我们采用这种方法来实现实例说明动态链接库的编程方法,假如我们在一个应用程序中,需要向一个DLL文件传递或者存储一些变量,比如存储的变量内容为标示这类数据的一个ID, 数据项体内容,同时我们提供一个查询接口,通过数据标示ID返回这些数据项内容,提供一个返回所有数据项的内容。我们采用导出结构体的方法实现。

    首先设计我们的这个传递的数据结构体,我们定义规则为数据体内容为数据项的名称,数据内容里分一个Max和一个Min,我们可以定义一个计算规则,这里我们定义数据体的运算方法为Max*100+Min,然后在结构体设计中我们增加一个静态变量,当我们增加不同的对象定义的时候进行自动标示功能,同时我们重载==运算符,用来完成两个结构体比较功能,那么我们的结构体可以定义如下:
#define MAXMSGLENGTH    100
typedef struct _ItemInfo
{
    int        m_nCurID;
    static int m_nTotalID;
    int        m_nMaxVal;
    int        m_nMinVal;
    char       m_strName[MAXMSGLENGTH + 1];

    _ItemInfo()
    {
        m_nCurID = m_nTotalID;
        m_nTotalID++;
        m_nMaxVal = 0;
        m_nMinVal = 0;
        memset(m_strName, 0, sizeof(m_strName));
    }

    bool operator == (_ItemInfo &Item) const
    {
        return (m_nMaxVal * 100 + m_nMinVal) == (Item.m_nMaxVal * 100 + Item.m_nMinVal) ? true : false;
    }
    
    bool operator == (const int nTotalVal) const
    {
        return (m_nMaxVal * 100 + m_nMinVal) == nTotalVal ? true : false;
    }
}ITEMINFO;

然后我们利用VC6.0 WIZARD生成一个DLL的基本框架。为了使结构体结构清晰,可读性强点,我们这里增加一个数据处理类,用来处理该DLL需要完成的一系列数据处理功能。并定义一个链表存储传递进来的数据项内容。实现可以见下:
//添加一个ITEMINFO结构体数据项到DLL
void CDataProcess::AddItem(const char * strName, const int nMaxVal, const int nMinVal)
{
    ITEMINFO *pItem = new ITEMINFO();

    strcpy(pItem->m_strName, strName);
    pItem->m_nMaxVal = nMaxVal;
    pItem->m_nMinVal = nMinVal;

    m_arrayItem.AddTail(pItem);
}


//实现一个比较查询功能,通过传递一个已经存储到DLL的数据项标示并调用定义的结构体==进行判断获得结果
bool CDataProcess::EqualItem(const int nID, const int nMaxVal, const int nMinVal)
{
    int nTotalVal = nMaxVal * 100 + nMinVal;
    POSITION pos = m_arrayItem.GetHeadPosition();

    while (pos != NULL)
    {
        ITEMINFO *pItem = m_arrayItem.GetAt(pos);
        if (pItem->m_nCurID == nID && *pItem == nTotalVal)
        {
            return true;
        }

        m_arrayItem.GetNext(pos);
    }

    return false;
}

//通过数据项标示ID获得该数据项的内容
ITEMINFO * CDataProcess::GetItem(const int nID)
{
    POSITION pos = m_arrayItem.GetHeadPosition();

    while (pos != NULL)
    {
        ITEMINFO *pItem = m_arrayItem.GetAt(pos);
        if (pItem->m_nCurID == nID)
        {
            return pItem;
        }

        m_arrayItem.GetNext(pos);
    }

    return NULL;
}

//导出存储在结构体中间数据项指针
CList * CDataProcess::GetItem()
{
    return &m_arrayItem;
}

//静态实例化一个类对象。
CDataProcess * CDataProcess::GetInstance()
{
    if (m_Instance == NULL)
    {
        m_Instance = new CDataProcess();
    }

    return (CDataProcess *)m_Instance;
}

添加DLL接口
extern "C" _declspec(dllexport) void AddItem(const char * strName, const int nMaxVal, const int nMinVal)
{
    CDataProcess::GetInstance()->AddItem(strName, nMaxVal, nMinVal);
}

extern "C" _declspec(dllexport) bool EqualItem(const int nID, const int nMaxVal, const int nMinVal)
{
    return CDataProcess::GetInstance()->EqualItem(nID, nMaxVal, nMinVal);
}

extern "C" _declspec(dllexport) ITEMINFO * GetItem(const int nID)
{
    return CDataProcess::GetInstance()->GetItem(nID);
}

extern "C" _declspec(dllexport) CList * GetAllItem()
{
    return CDataProcess::GetInstance()->GetItem();
}

编译运行程序并生成相应的DLL,如果我们采用显式链接的时候,我们需要进行LoadLibrary,然后GetProcAddress,这样过程比较繁琐,这里我们采用很多厂家的那种方法,我们将这些导出功能封装到一个H文件中间去,当程序调用的时候直接加入H文件,然后包含这个H文件就可以直接访问DLL中间的接口了。
#ifndef __PROCESS_DLL_H__
#define __PROCESS_DLL_H__

#include

#pragma comment(lib, "DataCtrl.lib")

// 下列 ifdef 块是创建使从 DLL 导出更简单的
//宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 PROCESS_EXPORTS
// 符号编译的。在使用此 DLL 的
//任何其他项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// PROCESS_API 函数视为是从此 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef PROCESS_EXPORTS
#define PROCESS_API extern "C" __declspec(dllexport)
#else
#define PROCESS_API extern "C" __declspec(dllimport)
#endif

#define __DLLPROJECT__  

#ifdef __DLLPROJECT__  
#define DLL_CLASS AFX_CLASS_EXPORT    
#else    
#define DLL_CLASS AFX_CLASS_IMPORT    
#endif  

//导出结构体
#define MAXMSGLENGTH    100

typedef struct _ItemInfo
{
    int        m_nCurID;
    static int m_nTotalID;
    int        m_nMaxVal;
    int        m_nMinVal;
    char       m_strName[MAXMSGLENGTH + 1];  
}ITEMINFO;
  
//导出函数
PROCESS_API void AddItem(
            const char * strName,
            const int nMaxVal,
            const int nMinVal);

PROCESS_API bool EqualItem(
            const int nID,
            const int nMaxVal,
            const int nMinVal);

PROCESS_API ITEMINFO * GetItem(const int nID);

PROCESS_API CList * GetAllItem();

#endif;


下面建立一个测试程序测试这个DLL。
将相应的DLL,LIB,H文件拷贝到建立的测试DLL目录下,并把H文件加入到工程中间。详细代码请看示例工程。
//测试添加数据项到DLL接口
void CTestDllDlg::OnAdd()
{
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);

    AddItem(m_AddName, m_AddMax, m_AddMin);
}

//测试数据相关数据查询相等接口
void CTestDllDlg::OnQuery()
{
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);

    bool bEqualed = EqualItem(m_EqualID, m_EqualMax, m_EqualMin);
}


//测试通过数据数据项标示获得该数据项内容
void CTestDllDlg::OnQueryitem()
{
    // TODO: Add your control notification handler code here
    UpdateData(TRUE);

    ITEMINFO * pItem = GetItem(m_QueryItemID);
    char * strName = pItem->m_strName;
}


//测试获得数据项并进行遍历
void CTestDllDlg::OnItemlist()
{
    // TODO: Add your control notification handler code here
    CList * m_arrayItem;
    m_arrayItem = GetAllItem();

    POSITION pos = m_arrayItem->GetHeadPosition();

    while (pos != NULL)
    {
        ITEMINFO *pTmpItem = m_arrayItem->GetAt(pos);

        m_arrayItem->GetNext(pos);
    }
}


综:
这里讨论了利用DLL进行模块程序的开发,其中动态链接库如何导出结构体指针等是很多学习动态链接库的人经常遇到的不知道如何解决的问题。在程序开发过程中,程序的结构化要非常清晰,同时要对相关的功能代码进行封装,希望对寻找相关处理方法的人有帮助。
如果您有更好的建议或者方法请和我联系:QQ:5516853.

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089