以前版本的CE支持2种MDD(MDD表示分层结构的上层.PDD表示下层,平台相关层.),com_mdd和com_mdd2. CE5.0只支持com_mdd2,好处是简化PDD,并且能够实现多个物理串口共用一个dll驱动.使用com_mdd2要做的是在驱动的SOURCES里面把TARGETLIBS从com_mdd.lib改成com_mdd2.lib,增加-DUSE_NEW_SERIAL_MODEL到CDEFINE变量.(以前写过,这个选项表示定义了一个宏#define USE_SERIAL_MODEL)
device.exe在加载流驱动时候会调用驱动公开的***_Init函数初始化.位于在mdd.c中.
在COM_Init()中主要是初始化一个pSerialHead变量,这个是一个PHW_INDEP_INFO的结构.它的定义在在com_mdd2的目录下找到serpriv.h里面.
typedef struct __HW_INDEP_INFO {
CRITICAL_SECTION TransmitCritSec1; // @field Protects tx action
CRITICAL_SECTION ReceiveCritSec1; // @field Protects rx action
PHWOBJ pHWObj; // @field Represents PDD object.
PVOID pHWHead; // @field Device context for PDD.
(省略)
LIST_ENTRY OpenList; // @field Head of linked list of OPEN_INFOs
CRITICAL_SECTION OpenCS; // @field Protects Open Linked List + ref counts
PHW_OPEN_INFO pAccessOwner; // @field Points to whichever open has acess permissions
} HW_INDEP_INFO, *PHW_INDEP_INFO;
仔细查看COM_Init(),大致就可以了解驱动工作的机制.明白怎么和下层联系上.
COM_Init():
1.从注册表获得参数.读取builtin下面的串口注册内容.
2.GetSerialHead(DevIndex);获得下层接口
3.使用下层接口的初始化函数初始化具体硬件.
4.根据下层接口传来的参数,决定是否启动StartDispatchThread线程
注册表的位置是由device.exe在加载时候传递过来的,就是COM_Init的参数,也就是builtin下面的那些东西.通过源码可以看到,mdd只用了DeviceArrayIndex, Priority256的内容.
GetSerialHead()获得与下层的接口,参数DevIndex是从注册表DeviceArrayIndex中获得的.返回一个pHWObj的指针.pHWObj结构如下:
typedef struct __HWOBJ {
ULONG BindFlags; // Flags controlling MDD behaviour. Se above.
DWORD dwIntID; // Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
PHW_VTBL pFuncTbl;
} HWOBJ, *PHWOBJ;
pHWObj指针包含了一个函数列表的指针pFuncTbl,这指针指向的函数就是下层的函数接口了.如下:
typedef struct __HW_VTBL {
PVOID (*HWInit)(ULONG Identifier, PVOID pMDDContext, PHWOBJ pHWObj);
BOOL (*HWPostInit)(PVOID pHead);
ULONG (*HWDeinit)(PVOID pHead);
BOOL (*HWOpen)(PVOID pHead);
ULONG (*HWClose)(PVOID pHead);
(省略)
BOOL (*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
} HW_VTBL, *PHW_VTBL;
这2个结构定义都位于public/common/oak/inc/serhw.h中,在这里还可以看到前面定义的宏USE_NEW_SERIAL_MODEL产生的具体的影响.
至此,已经明白了mdd层和下层如何接口了.mdd层当然还做了许多其他的机制.比如超时,打开计数器,DCB等,如果关心应用的可以自己继续看mdd的代码.接下来分析pdd
========================================================
在pdd层的工作大概就2个,一是控制硬件,二是和上层打好关系. 先说上层接口,前面看到上层用了GetSerialHead()来获得接口.所以pdd里面要实现GetSerialHead()的函数,并且将接口返回给上层.在示例里面使用了一个serpddcm.lib.这个也可以不用的,搞的又多了一个中间层.也许是为了进一步抽象和简化pdd开发,也许提供什么机制(粗略看来,没发现).反正既然提供了就用吧.serpddcm.lib的源代码在serpddcm/cserpdd.cpp里面.关键是实现GetSerialHead函数:
GetSerialObject( DWORD DeviceArrayIndex )
{
PHWOBJ pSerObj;
pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) );
if ( !pSerObj )
return (NULL);
pSerObj->BindFlags = THREAD_IN_PDD;
pSerObj->dwIntID = DeviceArrayIndex;
pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl;
// Now return this structure to the MDD.
return (pSerObj);
}
关键是提供一个IoVTbl的函数列表:
const HW_VTBL IoVTbl = {
SerInit,
SerPostInit,
SerDeinit,
SerOpen,
SerClose,
(省略)
SerSetDCB,
SerSetCommTimeouts,
SerIoctl
};
逐个实现列表里面的函数.这里关注SerInit(),这个函数在COM_Init中被调用.如同mdd层一样的原理,这里使用了CreateSerialObject()函数来和下层接口.下层实现这个函数并返回一个类指针CSerialPDD给中间层.这个类的定义在public/common/oak/inc/cserpdd.h中.
========================================================
终于来到最底层的pdd.先看接口
CSerialPDD * CreateSerialObject(LPTSTR lpActivePath, PVOID pMdd,PHWOBJ pHwObj, DWORD DeviceArrayIndex)
{
CSerialPDD * pSerialPDD = NULL;
switch (DeviceArrayIndex) {
case 0:
pSerialPDD = new CPdd2440Serial1(lpActivePath,pMdd, pHwObj);
break;
case 1:
pSerialPDD = new CPdd2440Serial2(lpActivePath,pMdd, pHwObj);
break;
}
if (pSerialPDD && !pSerialPDD->Init()) {
delete pSerialPDD;
pSerialPDD = NULL;
}
return pSerialPDD;
}
返回一个类指针给上层.和c语言返回函数指针表一个道理了.