关于作者

姓名:

性别:其他

出生日期:--

地区:

联系电话:

QQ:--

婚否:保密
用户名:wpf2006
笔名:wpf2006
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:143
评论个数:18
留言条数:11




Powered by BlogDriver 2.1

wpf2006的博客

 

欢迎访问wpf2006的博客

文章

Visual C++程序调试方法入门
概述

  调试是一个程序员最基本的技能,其重要性甚至超过学习一门语言。不会调试的程序员就意味着他即使会一门语言,却不能编制出任何好的软件。

  这里我简要的根据自己的经验列出调试中比较常用的技巧,希望对大家有用。

  本文约定,在选择菜单时,通过/表示分级菜单,例如File/Open表示顶级菜单File的子菜单Open。

  设置

  为了调试一个程序,首先必须使程序中包含调试信息。一般情况下,一

- 作者: wpf2006 2008年10月23日, 星期四 06:11  回复(0) |  引用(0) 加入博采

How to control the LED of your Pocket PC

How to control the LED of your Pocket PC

By Stephane Sibue, January 11, 2001.
Print version

The LED of the Pocket PC

 The LED is a part of Pocket PC specification. Generally it allows to indicate either that the battery is attached or that the Pocket PC is directly connected to the cradle. A Pocket PC can implement several LEDS, but usually there is only one. This article is translated from CodePPC.com.

The necessary functions not declared in the SDK

Functions for handling LEDs can be found in 'coredll.dll' library, but they are not declared in the SDK (who knows why?). It is only the 'NLed.h' file that can make them appear, but the prototypes of the functions are not declared in this file.

The first thing to do is to include the 'NLed.h' file and to declare 2 functions for handling LEDs:

# include < nled.h > extern " C " { BOOL NLedGetDeviceInfo(INT nID, PVOID pOutput); BOOL NLedSetDevice(INT nID, PVOID pOutput); }

First of all, how many LEDs are there on my Pocket PC?

Initially, it is necessary to know how many LEDs are present, knowing that the number of the first one is 0. To carry out this checking, we will use the 'NLedGetDeviceInfo' function:

int GetLedCount() { NLED_COUNT_INFO nci; int wCount = 0; if(NLedGetDeviceInfo(NLED_COUNT_INFO_ID, (PVOID) &nci)) wCount = (int) nci.cLeds; return wCount; }

In case of a problem, this function returns 0. It also returns 0 if a Pocket PC does not have a LED to handle. In 99 cases out of a hundred this function returns 1.

Off, On or Blink

We can now modify the state of our LED. According to the contents of the 'NLed.h' file, the LED can appear in 3 states: off, on, and blink (stop, functioning, and blinking). Each state corresponds to a value of the 'OffOnBlink' parameter of the 'NLED_SETTINGS_INFO' structure. This structure also allows to implement other effects but this exceeds the topic of this article, we will return to this implementation later. Thus we will create a function, which takes the desired state of our LED as a parameter, which can be 0 for idle (off), 1 for fixed functioning (on), and 2 for the effect of blinking (blink):

void SetLedStatus(int wLed, int wStatus) { NLED_SETTINGS_INFO nsi; nsi.LedNum = (INT) wLed; nsi.OffOnBlink = (INT) wStatus; NLedSetDevice(NLED_SETTINGS_INFO_ID, &nsi); }

Thus, to light up the LED n°1, it is enough to write:

SetLedStatus(0, 1);

to make it blink:

SetLedStatus(0, 2);

and finally to estinguish it:

SetLedStatus(0, 0);

- 作者: wpf2006 2008年07月12日, 星期六 05:15  回复(0) |  引用(0) 加入博采

如何解决模拟器加载问题(ppc2003+winxp sp2+evc4)

SYMPTOMS

Consider the following scenario. You install a Microsoft Windows CE emulator, Microsoft eMbedded Visual C++, Microsoft Visual Studio 2003, or Windows CE Platform Builder on a computer that is running Windows XP or Windows XP Service Pack 2 (SP2). The computer has Data Execution Prevention (DEP) hardware. You restart the computer. In this scenario, you receive the following error message:
Devices or applications disabled

'Virtual PC/Windows CE Emulator' will cause Windows to become unstable. Windows has prevented these drivers from loading.
You may also receive the following error message when you try to start Windows CE 4.x Emulator:
Emulator for Windows CE

One or more files from the Emulator for Windows CE installation is missing. Please reinstall Emulator for Windows CE and try again.

CAUSE

This problem occurs in Windows XP and in Windows XP Service Pack 1 (SP1) because the Windows CE emulators do not work correctly if Physical Address Extension (PAE) mode is enabled. This problem occurs with Windows XP SP2 because Windows XP SP2 does not allow the Virtual PC/Windows CE Emulator driver (VPCAppSv.sys) to load when hardware-based DEP is enabled. This is part of the Windows XP SP2 Driver Protection feature.

Note PAE mode must be enabled for DEP to work. Windows XP automatically enables PAE mode to support hardware-based DEP. You do not have to make an entry in the Boot.ini file to enable PAE mode.

RESOLUTION

Important These steps may increase your security risk. These steps may also make the computer or the network more vulnerable to attack by malicious users or by malicious software such as viruses. We recommend the process that this article describes to enable programs to operate as they are designed to or to implement specific program capabilities. Before you make these changes, we recommend that you evaluate the risks that are associated with implementing this process in your particular environment. If you decide to implement this process, take any appropriate additional steps to help protect the system. We recommend that you use this process only if you really require this process.

To resolve this problem and to run Window CE 4.x Emulator, disable hardware-based DEP on the computer that is running Windows XP SP2. To do this, follow these steps:
1.Click Start, click Run, type sysdm.cpl, and then click OK.
2.In the System Properties dialog box, click the Advanced tab.
3.Under Start and Recovery, click Settings.
4.In the Startup and Recovery dialog box, click Edit.
5.Disable PAE mode by removing the /pae option if it exists.
6.If you are using Windows XP SP2, remove the /noexecute option if it exists, and then add the /execute option.
7.On the File menu, click Save.
8.To exit Notepad, click Exit on the File menu.
9.To close System Properties, click OK two times.
10.Restart your computer.

- 作者: wpf2006 2008年06月29日, 星期日 08:03  回复(0) |  引用(0) 加入博采

马云创业点评语录(一)
做战略最忌讳的是面面俱到,集所有的资源在一点突破才有可能赢,面面俱到什么都不可能赢,真正的领导通过别人拿结果,在诱惑面前永远是真诚者,讲真话,讲心理话的人能赢,愚蠢的人用嘴讲话,聪明的人用脑子讲话,智慧的人用心讲话..........

- 作者: wpf2006 2008年06月17日, 星期二 18:49  回复(0) |  引用(0) 加入博采

在VC中透明浮动按键的实现
有一种按键,看起来是一幅完整的图片,当鼠标移到按键区域时,图片的一部分凸现,形成一个按键,当鼠标移走时又恢复原来状态。

最近,看了一些关于浮动按键的代码,其原理大致上跟CBitmapButton差不多,用数幅位图代表按键的各个状态,响应鼠标的各种消息来设置按键的状态,实现按键的浮动显示,但是这样的按键却不能和周围的背景混和成一幅图片。

为了实现“透明”按键,可以简单地做个试验:先在对话框中加入一个BUTTON,通过属性框选“Owner Draw”风格,再加入一个PICTURE,并加入图片,将BUTTON移到PICTURE上。运行结果发现,按键没有显示出来,但在按键区域按下鼠标时,该按键仍然能发出WM_COMMAND消息,这样一个纯透明的按键建立了。显然,这个按键是毫无使用意义的,因为用户不知道按键的位置,必须让用户容易觉察到按键的位置,可以把这个按键改造一下:

(首先从CButton派生出一个新类CDrawButton)

·把按键的标题显示出来

这个实现起来比较简单,我们可以重载CButton类的成员函数DrawItem(),

void CDrawButton::DrawItem
(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC dc;
CRect rect=lpDrawItemStruct- >rcItem;//得到按键区域
CString sCaption;
dc.Attach(lpDrawItemStruct- >hDC); //得到设备环境CDC
VERIFY(lpDrawItemStruct- >CtlType==ODT_BUTTON);
GetWindowText(sCaption);//得到按键的标题
dc.SetBkMode(TRANSPARENT);//透明显示
CFont* m_pOldFont=dc.SelectObject(m_pFont);
dc.DrawText(sCaption,&rect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
dc.SelectObject(m_pOldFont);
}

其中的m_pFont是成员变量,它保存了对话框的字体指针,为了按键的标题风格与对话框的字体风格一致,在初始化时调用对话框的成员函数GetFont()即可得到指向对话框字体的CFont类指针。

·使按键浮动显示

要通过自绘来表示按键的各种状态,可填写DRAWITEMSTRUCT来通知DrawItem()函数需要做什么,我们先了解一下DRAWITEMSTRUCT:

typedef struct tagDRAWITEMSTRUCT{
UINT CtlType; // 控件类型
UINT CtlID;// 控件的ID号
UNIT itemID;//菜单项的索引
UINT itemAction;// 绘图操作
UINT itemState; // 状态
HWND hwndItem; // 控件的窗口句柄
HDC hDC; // 相关的设备环境
RECT rcItem;//控件的范围
DWORD itemData;//指定与菜单项相联系的应用程序定义的32位值
}DRAWITEMSTRUCT;

利用这个结构先做一个按键状态设置函数:
void CDrawButton::SetButtonMode(UINT action, UINT mode)
{
// TODO: Add your message handler code
here and/or call default
DRAWITEMSTRUCT DIS;
DIS.CtlType = ODT_BUTTON;
DIS.CtlID = GetDlgCtrlID();
DIS.itemAction = action;
DIS.itemState = mode;
DIS.hwndItem = GetSafeHwnd();
DIS.hDC = GetDC()- >GetSafeHdc();
GetClientRect(&(DIS.rcItem));
SendMessage(WM_DRAWITEM,(WPARAM)
GetSafeHwnd(),(LPARAM)&DIS);
ReleaseDC(CDC::FromHandle(DIS.hDC));
}

这样,我们可以响应鼠标的各种消息来设置按键的各种状态:
void CDrawButton::OnMouseMove
(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code
here and/or call default
CRect rect;
GetClientRect(&rect);
if(rect.PtInRect(point)){
if (mBtnStats==BTN_NORMAL){
SetButtonMode(ODA_SELECT, ODS_FOCUS);
SetCapture();
}
}
else{
//AutoLoad(GetDlgCtrlID(),GetParent());
SetButtonMode(ODA_DRAWENTIRE,ODS_DEFAULT);
ReleaseCapture();
}

CButton::OnMouseMove(nFlags, point);
}

这里,mBtnStats是个UINT类型的成员变量,它可以有三种自定义状态:
BTN_NORMAL 正常状态
BTN_UP 鼠标移入按键区域或释放鼠标
BTN_DOWN 按下鼠标
(可以再加一种DISABLE状态)

当在按键区域释放鼠标时,必须发送WM_COMMAND消息:
void CDrawButton::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code
here and/or call default
CRect rect;
GetClientRect(&rect);
if(rect.PtInRect(point)){
if (mBtnStats==BTN_DOWN)
GetParent()- >SendMessage(WM_COMMAND,
MAKELPARAM(GetDlgCtrlID(),BN_CLICKED),
(LPARAM)GetSafeHwnd());
SetCapture();
}
else{
SetButtonMode(ODA_DRAWENTIRE,ODS_DEFAULT);
ReleaseCapture();
}

CButton::OnLButtonUp(nFlags, point);
}

接着就是绘制按键的各种状态:由于按键必须“透明”,所以在按下和释放时只在按键区域的四周加上一个3D边框就行了。而在正常状态下,则必须去掉边框恢复背景。但如何恢复背景图象呢?我是这样做的:在按键初始化时,先把被按键覆盖了的区域保存在一个CBitmap类中,以后需要重绘按键时就把这个CBitmap画在按键上就行了。
void CDrawButton::DrawItem
(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: Add your code to draw the specified item
CDC dc;
CRect rect=lpDrawItemStruct- >rcItem;
CString sCaption;
dc.Attach(lpDrawItemStruct->hDC);
//得到绘制的设备环境CDC
VERIFY(lpDrawItemStruct- >CtlType==ODT_BUTTON);

if (lpDrawItemStruct- >itemAction & ODA_DRAWENTIRE){
//重绘控件(正常状态)
mBtnStats=BTN_NORMAL;
if (m_pBitmap!=0){
CDC memDC;
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(m_pBitmap);
dc.BitBlt(0, 0, rect.Width(), rect.Height(),
&memDC, 0, 0, SRCCOPY);
memDC.DeleteDC();
}
//显示按键标题
GetWindowText(sCaption);
dc.SetBkMode(TRANSPARENT);
if (m_pFont!=0){
CFont* m_pOldFont=dc.SelectObject(m_pFont);
dc.DrawText(sCaption,&rect,
DT_CENTER|DT_VCENTER|DT_SINGLELINE);
dc.SelectObject(m_pOldFont);
}
}

if ((lpDrawItemStruct- >itemState & ODS_SELECTED) &&
(lpDrawItemStruct- >itemAction & ODA_SELECT)){
//按下鼠标
mBtnStats=BTN_DOWN;
dc.Draw3dRect(&rect,RGB(128,128,128),RGB(192,192,192));
rect.top=rect.top+1;rect.bottom=rect.bottom-1;
rect.left=rect.left+1;rect.right=rect.right-1;
dc.Draw3dRect(&rect,RGB(0,0,0),RGB(255,255,255));
}

if(!(lpDrawItemStruct- >itemState & ODS_SELECTED) &&
(lpDrawItemStruct- >itemAction & ODA_SELECT)){
//释放鼠标或鼠标进入按键区域
mBtnStats=BTN_UP;
dc.Draw3dRect(&rect,RGB(255,255,255),RGB(0,0,0));
rect.top=rect.top+1;rect.bottom=rect.bottom-1;
rect.left=rect.left+1;rect.right=rect.right-1;
dc.Draw3dRect(&rect,RGB(192,192,192),RGB(128,128,128));
}

dc.Detach();
}

接着就必须一些初始化工作,其中最关键就是把被按键覆盖了的区域保存进CBitmap类中,我们知道CDC::StretchBlt()函数可以把位图的指定区域从一个设备拷贝到另一个设备中,这样可以很方便地把窗口或对话框的某个区域保存,条件是获得其DC:
void CDrawButton::LoadBack(CWnd *pParent)
{
ASSERT(GetStyle() & BS_OWNERDRAW);
if (m_pBitmap!=0) return;

CRect rect;
GetWindowRect(&rect);
pParent- >ScreenToClient(&rect);//获得按键区域
CPaintDC dc(pParent);
if (m_pBitmap==0) m_pBitmap=new CBitmap;//初始化位图
m_pBitmap- >CreateCompatibleBitmap
(&dc,rect.Width(),rect.Height());
CDC memDC;
memDC.CreateCompatibleDC(&dc);
memDC.SelectObject(m_pBitmap);
memDC.StretchBlt(0, 0, rect.Width(),rect.Height(), &dc,
rect.left, rect.top,
rect.Width(),rect.Height(), SRCCOPY);//保存
memDC.DeleteDC();
m_pFont=pParent- >GetFont();//获得窗口或对话框的字体

ModifyStyle(0,WS_VISIBLE);//显示按键

SetBitmapMode(ODA_DRAWENTIRE,0);//绘制按键
}

而使这个类和对话框上的按键产生联系还必须调用一下SubclassDlgItem():
BOOL CDrawButton::AutoLoad(UINT nID, CWnd *pParent)
{
// first attach the CDrawButton to the dialog control

if (m_pBitmap!=0) return FALSE;

if (!SubclassDlgItem(nID, pParent)) return FALSE;

LoadBack(pParent);

return TRUE;
}


这个类还必须具有三个成员变量:
CFont* m_pFont;
CBitmap* m_pBitmap;
UINT mBtnStats;

在构造函数中初始化这些变量
m_pBitmap=0;
m_pFont=0;
//赋予0是可以的
mBtnStats=BTN_NORMAL;

在折构函数中拆除位图
if(m_pBitmap!=0) delete m_pBitmap;

这样,一个透明的浮动式按键类就做好了,具体实现方法以下:
1.接管对话框的BUTTON,首先在对话框上画一个BUTTON,再加一个PICTURE图片,BUTTON的风格必须加入OWNER DRAW及去掉VISIBLE,把BUTTON移到PICTURE上适当的位置,在对话框类加入CDrawButton类成员m_myButton,由于按键初始化时必须保存对话框的图象,而对话框在运行InitDialog()或第一次运行OnPaint()时对话框的控件还没有真正显示出来,我们只好在OnMouseMove()中进行初始化:

m_myButton.AutoLoad(IDC_BUTTON1,this);
AutoLoad()只运行一次。

2.动态建立CDrawButton,在对话框类或CxxxView类加入CDrawButton类成员m_myButton,可以在对话框的InitDialog()或CxxxView类的InitialUpdate()中加入:m_myButton.Create()函数,必须包含BS_OWNERDRAW而不能有WS_VISIBLE风格,然后在OnMouseMove()或OnDraw()中进行初始化:m_myButton.LoadBack(this);注意应加在OnDraw()的最后。

同样地,LoadBack()只运行一次。

(如果按键比背景的图片迟建立而具有可见(Visible)属性,则会把图片抹掉,所以必须去掉VISIBLE属性或不能加入WS_VISIBLE风格)

·当鼠标移到按键区域时,改变鼠标

这个很容易实现,不在这里多说了。

- 作者: wpf2006 2007年08月27日, 星期一 21:10  回复(0) |  引用(0) 加入博采

Symbian的体系架构小节
Application、Document、AppUi、ViewApplication属于应用程序的启动对象.

CWinApp类,它定义了应用程序的属性,这个类也创建文档。本应用类的基类就是

CAknApplication。
Document是做为程序用来存储数据用的,一个应用程序必须有一个Document文档

类的实例,可能是用来被加载AppUi的唯一要求,这个类的基类是CAknDocument.

AppUi,负责处理与应用有关的事件,比如说是options菜单选项、文件的打开和

关闭等。

注意它将图形绘制和基于屏幕的交互操作委派给自己所拥有的Views,也负责这些

views之间的切换。AppUi的基类是CAknAppUi或者CAknViewAppUi.

比较复杂点的是View,它主要是负责显示屏幕上那些可以与用户交互的数据,并

且把用户的操作反馈给AppUi,这个正如上面所说的,是处理与应用有关的事件。
view可以继承自CCoeControl或CAknDialog或者是CAknView,看出来没有,三种基

本结构view都是唱主角的。这个很重要,反正显示的任务就交给它了,甭管是传

统、对话框还是视图结构。

应用程序外观,它可以分为三种体系结构:传统的symbianOS控制体系结构、基于

对话框的体系结构、视图体系结构。运用什么样的界面取决于程序和界面布局的

需要。只是不管你使用哪种,都是从一个基类继承而来的,就好象是CView一样。
SymbianOS应用程序是在CCoeControl类的基础上派生出我们自己的view controls

,这些都存放在应用程序的control stack中,也就是我们应用程序的视图。这些

controls会根据应用程序的需要来创建释放或显示隐藏,以产生相应的操作。
如果主体应用是对话框,那我们更应该使用这样的体系结构,使用dialogs的好处

是我们光可以靠改变resource文件来修改内容和布局,而不需要重新编译那c++代

码。
使用view的应用程序每次只能有一个活动的view,当另一个view要激活时,当前

的view就要被释放。当一个view被释放后,所以的菜单,对话框以及包含的应用

都将被关闭。每个view都被当作一个应用UI对待,它必须提供一个Id() 函数以便

为系统所标识,它也要重载DoActivateL(), DoDeactivate

(),HandleForegroundEventL(),HandlCommandL(),HandleStatusPaneSizeChange

()函数以处理各种事件。

- 作者: wpf2006 2007年08月27日, 星期一 20:46  回复(0) |  引用(0) 加入博采

浅析COM的思想及原理

浅析COM的思想及原理

  COM--Component Object Model,即组件对象模型,它是微软提出的一套开发软件的方法与规范。它也代表了一种软件开发思想,那就是面向组件编程的思想。


  一、COM编程思想--面向组件编程思想(COP)

  众所周知,由C到C++,实现了由面向过程编程到面向对象编程的过渡。而COM的出现,又引出了面向组件的思想。其实,面向组件思想是面向对象思想的一种延伸和扩展。因此,就让我们先来回忆一下面向对象的思想吧。

  面向对象思想是将所有的操作以及所操作的对象都进行归类(由class实现),而它的目标是要尽量提高代码的可重用性(这也是面向对象相比面向过程最大的优点之一)。比如,有两个程序A和B都需要对class C的对象进行操作,那么class C的代码就可以重用了(即A和B都可以使用class C的代码)。但是,对于这一点,面向对象做得并不够好。还是举刚才的例子,程序A和B都要对class C的对象进行操作,那么,程序A和B的编程人员都必须将class C的代码拷贝过来,然后重新编译一次,这将是多么麻烦的事!况且,如果class C的代码没有公开,那这种重用就根本不可能实现了(除非程序A和B的编程人员和class C的编程人员是同一个人或者团队,但这样局限性就相当大了)。

  由于面向对象的这些局限性,很多程序员就会想,如果我们编程需要重用别人的成果时,不需要重新编译别人的代码那就好了。换句话说,我们要达到的目标是,直接重用别人的成果而不是重用别人的代码。这样说也许很抽象,举个例子大家就会比较明白。比如将class C的代码编译生成一个dll,那么当其他程序员想要重用class C时,就只需要在自己的程序中加载这个dll而不需要重新编译class C的代码了(这也就是组件必须要能动态链接的原因)。正是这种思路引出了面向组件的编程思想。

  下面,我就简单介绍一下面向组件的思想。在以前,应用程序总是被编写成一个单独的模块,就是说一个应用程序就是一个单独的二进制文件。后来在引入了面向组件的编程思想后,原本单个的应用程序文件被分隔成多个模块来分别编写,每个模块具有一定的独立性,也应具有一定的与本应用程序的无关性。一般来说,这种模块的划分是以功能作为标准的。比如,一个网上办公管理系统,从功能上说它需要包含网络通信、数据库操作等部分,我们就可以将网络通信和数据库操作的部分分别提出来做成两个独立的模块。那么,原本单个的应用程序就分隔成了三个模块:主控模块、通信模块和数据库模块。而这里的通信模块和数据库模块还可以做得使其具有一定的通用性,那么其他的应用程序也就可以利用这些模块了。这样做的好处有很多,比如当对软件进行升级的时候,只要对需要改动的模块进行升级,然后用重新生成的一个新模块来替换掉原来的旧模块(但必须保持接口不变),而其他的模块可以完全保持不变。这样,软件升级就变得更加方便,工作量也更小。

  说了这么多,总结一下:面向组件编程思想,归结起来就是四个字:模块分隔。这里的“分隔”有两层含义,第一就是要“分”,也就是要将应用程序(尤其是大型软件)按功能划分成多个模块;第二就是要“隔”,也就是每一个模块要有相当程度的独立性,要尽量与其他模块“隔”开。这四个字是面向组件编程思想的精华所在,也是COM的精华所在!理解了这四个字,也就真正理解了面向组件编程的思想。(这里说一点题外话,COM其实是一套规范或者说一套标准,但是在我看来,COM的核心还在于它的思想,也就是面向组件编程思想。标准谁都能定,但是思想只有一个!)

  二、COM的优点

  COM的优点也就是面向组件编程思想的优点。而面向组件编程思想有很多的优点,上面所说的便于软件升级只是其中之一。对于它的优点,我总结了一下,有下面几条:

  1、便于重用,使软件开发更快捷

  2、便于软件升级

  3、便于软件开发的分工协作

  4、便于用户定制自己的应用

  以上几点,第一和第二点都不用再多说了,前面讲面向组件编程思想的部分里面已经充分展示出了这两点优点。在这里我解释一下第三和第四点。

  如今的很多大型软件,都不可能由某一个人单独开发,甚至不会由某一个公司去单独开发。这是因为现在的很多大型软件,综合性太强,涉及的面也太广。而一个人的精力是有限的,不可能学会这么多方面的知识,也不可能掌握到这么多方面的编程技术,即使有可能,这样做的效率也是很低下的。所以,通常的情况是分工协作。仍以前面提到的网上办公管理系统为例,这个系统分为了三个模块:主控模块、通信模块和数据库模块。由于这三个模块具有相当的独立性,那么就可以将现有的所有开发人员分为三组,每一组负责一个模块。而这三组之间,只需要商量好相互间的接口就可以了。这样,对于每一个开发人员来说,就不需要掌握所有的编程技术,甚至不需要了解其他模块的具体实现,而软件仍然能有效的开发成功。这就是所谓的便于软件开发的分工协作了。

   除此之外,如果一个大型的软件希望允许用户在一定程度上定制自己的应用,那么COM也是最好的选择。比方说一个软件由两个模块组成,模块A和模块B,现在软件的开发商希望给予用户一定的灵活性,希望可以允许用户自己定制模块B来实现自己特定的应用,那么就只需要公开模块B的所有接口;而用户自己编程实现模块B时也只需要实现了所有的这些接口就行了。当然,这里面还有很多问题,比如COM组件的注册,这涉及到COM标准的一些细节,在这里不作讨论。

  三、COM中的几个重要概念

  1、组件:

  其实只要你仔细阅读了前面的部分,组件的概念应该已经很清楚了。这里所说的组件,就是前面反复在讨论的所谓“模块”。现在我只想强调一下组件需要满足的一些条件。首先是封装性,组件必须向外部隐藏其内部的实现细节,使从外部所能看到的只是接口。然后是组件必须能动态链接到一起,而不必像面向对象中的class一样必须重新编译。

  2、接口:

    由于组件向外部隐藏了其内部的细节,因此客户要使用组件时就必须通过一定的机制,也就是说要通过一定的方法来实现客户与组件之间的通信,这就需要接口。所谓接口就是组件对外暴露的、向外部客户提供服务的“连接点”。外部的客户见不到组件内部的细节,它所能看到的只是接口,客户也是通过接口来获取组件提供的服务。这有点像OSI网络协议分层模型,每一层就像一个组件,它内部的实现细节对于其他层是不可见的;而每一层通过“服务接入点”向其上层提供服务,这就像这里所说的接口。一般来说,接口总是固定的,也是公开的。组件的开发人员要实现这些接口,而客户则通过接口获得服务。正是接口的这种固定和公开,才使得组件和客户能够在不了解对方的情况下达成一致。

  3、客户:

  这里所说的客户不是指使用软件的用户,而是指要使用某一个组件的程序或模块。也就是说,这里的客户是相对组件来说的。

  四、COM的实现原理与雏形模拟

  COM编程的一个重要特点就是要模块化,说得具体一些,就是要将客户和组件分隔开来,而客户和组件之间又是通过接口来通信的。下面,我就介绍一下COM是怎样将客户与组件分隔开来,又是怎样利用接口来实现客户与组件间的通信的。

  首先我要讲讲接口。COM中的接口实际上是一个函数地址表,当组件实现了这个接口后,这个函数地址表中就填满了组件所实现的那些接口函数的地址。而客户也就是通过这个函数地址表获得组件中那些接口函数的指针,从而获得组件所提供的服务的。从某种意义上说,我们可以把接口理解为c++中的虚拟基类;或者说,在c++中可以用虚拟基类来实现接口!这是因为COM中规定的接口的存储结构,和c++中的虚拟基类在内存中的结构是一致的。其存储结构如下图:  
 
                                         虚函数表
               vtbl指针------>Fun1()指针-------->
                                       Fun2()指针-------->
                                       Fun3()指针-------->
                                       …………
  
  Vtbl指针指向一个虚函数表,而这个虚函数表的表项就是指向这些虚函数的指针。

  接口有了,那么组件又是怎样实现接口的呢?实际上,如果用虚拟基类来实现接口,那么组件就是对这个虚拟基类的继承。大家知道,当某个类继承于一个虚拟基类的时候,它就要实现这个虚拟基类里声明的虚函数,这就正好与组件实现接口这一点相吻合。举一个例子来说明,有一个接口InterfaceA,组件ComponentB要实现这个接口,那么就可以这样用c++语言来描述:

//接口:
class InterfaceA
{
  virtual void Fun1()=0;
  virtual void Fun2()=0;
};
//实现了接口InterfaceA的组件:
class ComponentB: public InterfaceA
{
  virtual void Fun1()
  {
     printf("Fun1\n");
  }
  virtual void Fun2()
  {
     printf("Fun2\n");
  }
};

  而客户只需要得到一个指向ComponentB实体的InterfaceA指针就可以获得ComponentB组件的服务了:

//使用了组件ComponentB的客户:
……
ComponentB CB;
InterfaceA *pIA=&CB;  //获得指向ComponentB实体的InterfaceA指针,以下客户就可以只通过接口来获取组件的服务
pIA->Fun1();
pIA->Fun2();
……

  但是我们注意到,这样做组件ComponentB和客户还是没有被完全分隔开。因为在客户代码里需要创建ComponentB实体,这对于只能看到接口而对组件一无所知的客户来说,是不可以接受的(比如客户不会知道组件的类名叫ComponentB)。解决这个问题的方法是在实现组件的动态链接文件(比如dll文件)里创建组件的实体,而不是在客户代码里创建组件实体。通常组件都是以dll的形式出现的,而在实现组件的dll里都会实现一个叫CreateInstance的函数,这个函数可以被外部的客户调用。它返回一个接口的指针,当客户调用这个函数后就能够获得指向组件实体的接口指针了。它的实现也很简单:

//在实现组件ComponentB的dll里:
InterfaceA *CreateInstance()
{
   ComponentB CB;
   InterfaceA *pIA=&CB;
   return pIA;
}

  当然,真正的CreateInstance函数没有这么简单,我上面的代码只是一个简单的模拟。有个CreateInstance函数之后,客户代码就变成了:

//使用了组件ComponentB的客户:
……
InterfaceA *pIA=CreateInstance();  //获得指向ComponentB实体的InterfaceA指针,以下客户就可以只通过接口来获取组件的服务
pIA->Fun1();
pIA->Fun2();
……

  这样,组件和客户就完全被分隔开了,而连接它们的只有接口以及一个CreateInstance的函数。

  以上就是COM的基本原理了。当然,我前面也说了,COM其实是一套规范,它定义了很多标准,比如COM规定每个接口都必须继承于一个叫IUnknown的接口。我这里基本上没有提及它的这些标准,只是希望能通过对它进行一个简单的模拟来说清楚它的实现原理。下面就给出我模拟COM机制实现的一套COM的雏形,希望能对大家理解COM有帮助。

  1、实现了组件ComponentB的ComponentDll.dll:

//Interface.h
//接口
class InterfaceA
{
public:
  virtual void Fun1()=0;
  virtual void Fun2()=0;
};
//Component.h
//组件(实现了接口InterfaceA)
class ComponentB: public InterfaceA
{
public:
 virtual void Fun1()
 {
  printf("Fun1\n");
 }
 virtual void Fun2()
 {
  printf("Fun2\n");
 }
};
//ComponentDll.cpp
//CreateInstance函数
ComponentB instance;
extern "C" _declspec(dllexport) InterfaceA *CreateInstance()
{
 InterfaceA *pIA=&instance;
 return pIA;
}

  2、客户Client.exe:

//Client.cpp
#include "Interface.h"
#pragma comment(lib,"ComponentDll")
int main(int argc, char* argv[])
{
 InterfaceA *pIA=0;
 pIA=CreateInstance();
 if(pIA!=0)
  pIA->Fun1();
 return 0;
}

- 作者: wpf2006 2007年08月27日, 星期一 20:09  回复(0) |  引用(0) 加入博采

MFC消息映射

在MFC类实现中,采用消息映射实现对消息的响应,从而改变了在SDK中的循环结构,使得消息的流转更加隐蔽。
在SDK中有分三部分构成:

WNDCLASS wnd;
wnd.lpfnWndProc = WndProc ;  // 首先将回调函数设置好

SDK循环结构,进行消息循环
   while(GetMessage(&msg,NULL,0,0))
   {
    TranslateMessage(&msg);
    DisptachMessage(&msg);
   }
而在另一处有回调函数的实现:
LPRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CREATE: ... break;
        case WM_PAINT: ... break;
        case WM_LBUTTONDOWN: ... break;
        case WM_LBUTTONUP:... break;
        case WM_MOUSEMOVE:... break;
        case WM_CHAR: ...   break;
        case WM_TIMER: ... break;
        case WM_CLOSE: ... break;
        case WM_DESTROYS: ... break;
        case WM_QUIT: ...  break;
        case WM_COMMAND:
          switch(LOWORD(wParam))
          {
             case ID_XXX: break;
             default: break;
           }
           break;
       default: break;
    }
    DefWindowProc(hWnd,msg,wParam,lParam);
}

消息映射中为:
在MFC类定义结构中申明一个语句:
    DECLARE_MESSAGE_MAP()
然后在全局空间实现中出现使用如下语句进行消息映射:
BEGIN_DECLARE
  ON_WM_LBUTTONDOWM()
  ON_WM_LBUTTONUP()
  ON_WM_MOUSEMOVE()
  ON_WM_PAINT()

  ON_COMMAND(ID,lpFunc)
END_DECALRE

在类成员函数的实现中,将上面的消息映射函数进行实现。常用命令有固定的消息名称:
     ON_WM_LBUTTONDOWN()映射函数为ONLBUTTONDOWN()函数,ON_WM_LBUTTONUP映射函数为LBUTTONUP()函数,ON_WM_MOUSEMOVE()映射函数为ONMOUSEMOVE()函数,ON_WM_PAINT()映射函数为ONPAINT()函数,而ON_COMMAND中的映射函数即为lpFunc函数。

     情况下,消息映射声明不通过手工进行修改,而是在ClassWizard中进行修改,这样修改,可以避免因为手工修改时遗漏某个部分或者是输入时出现的一些小bug。 

    MFC的消息流转为从当前类APP流转到CWndApp,再流转到CWndThread类,在流转到CCmdTarget类,当CCmdTarget类中消息还不符合时,在流转到CWndThread中进行消息匹配。

    虽然MFC消息比较难以理解,以及消息相对比较复杂,但是MFC消息函数的出现,使得对每个消息的实现变得很独立,比SDK中一连串的消息判断要清晰明白的多。

   深入浅出中将MFC中消息映射设置成这么一个结构: 
struct AFX_MSGMAP
{
AFX_MSGMAP* pBaseMessageMap;
AFX_MSGMAP_ENTRY* lpEntries;
};
struct AFX_MSGMAP_ENTRY // MFC 4.0 format
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (CCmdTarget::*AFX_PMSG)(void);
#define DECLARE_MESSAGE_MAP() \
static AFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAP messageMap; \
virtual AFX_MSGMAP* GetMessageMap() const;
其中的AFX_MSGMAP_ENTRY 又是另一个数据结构:
其中的AFX_PMSG 定义为函数指针:
然后我们定义一个宏:
#define DECLARE_MESSAGE_MAP() \
static AFX_MSGMAP_ENTRY _messageEntries[]; \
static AFX_MSGMAP messageMap; \
这个数据结构的填充有三个宏来实现:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
AFX_MSGMAP* theClass::GetMessageMap() const \
{ return &theClass::messageMap; } \
AFX_MSGMAP theClass::messageMap = \
{ &(baseClass::messageMap), \
(AFX_MSGMAP_ENTRY*) &(theClass::_messageEntries) }; \
AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
{
#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
rtual AFX_MSGMAP* GetMessageMap() const;
};

- 作者: wpf2006 2007年08月1日, 星期三 07:15  回复(0) |  引用(0) 加入博采

常用linux命令
1. man 对你熟悉或不熟悉的命令提供帮助解释

eg:man ls 就可以查看ls相关的用法

注:按q键或者ctrl+c退出,在linux下可以使用ctrl+c终止当前程序运行。

2. ls 查看目录或者文件的属*,列举出任一目录下面的文件

eg: ls /usr/man

ls -l

a.d表示目录(directory),如果是一个"-"表示是文件,如果是l则表示是一个连接文件(link)

b.表示文件或者目录许可权限.分别用可读(r),可写(w),可运行(x)。

3. cp 拷贝文件

eg: cp filename1 filename2 //把filename1拷贝成filename2

cp 1.c netseek/2.c //将1.c拷到netseek目录下命名为2.c

4. rm 删除文件和目录

eg: rm 1.c //将1.c这个文件删除

5. mv 移走目录或者改文件名

eg: mv filename1 filename2 //将filename1 改名为filename2

mv qib.tgz ../qib.tgz //移到上一级目录

6. cd 改变当前目录 pwd 查看当前所在目录完整路径

eg: pwd //查看当前所在目录路径

cd netseek //进入netseek这个目录

cd //退出当前目录

7. cat,more命令

将某个文件的内容显示出来。两个命令所不同的是:cat把文件内容一直打印出来,而 more则分屏显示

eg; cat>1.c //就可以把代码粘帖到1.c文件里,按ctrl+d 保存代码。

cat 1.c 或more 1.c //都可以查看里面的内容。

gcc -o 1 1.c //将1.c编译成.exe文件,我们可以用此命编译出代码。

8.chmod 命令 权限修改 用法:chmod 一位8进制数 filename。

eg: chmod u+x filenmame //只想给自己运行,别人只能读

//u表示文件主人, g 表示文件文件所在组。 o 表示其他人 ;r 表可读,w 表可写,x 表可以运行

chmod g+x filename //同组的人来执行

9. clear,date命令

clear:清屏,相当与DOS下的cls;date:显示当前时间。

10. mount 加载一个硬件设备

用法:mount [参数] 要加载的设备 载入点

eg: mount /dev/cdrom

cd /mnt/cdrom //进入光盘目录

11. su 在不退出登陆的情况下,切换到另外一个人的身份

用法: su -l 用户名(如果用户名缺省,则切换到root状态)

eg:su -l netseek (切换到netseek这个用户,将提示输入密码)

12.whoami,whereis,which,id

//whoami:确认自己身份

//whereis:查询命令所在目录以及帮助文档所在目录

//which:查询该命令所在目录(类似whereis)

//id:打印出自己的UID以及GID。(UID:用户身份唯一标识。GID:用户组身份唯一标识。每一个用户只能有一个唯一的UID和 GID)

eg: whoami //显示你自已登陆的用户名

whereis bin 显示bin所在的目录,将显示为:/usr/local/bin

which bin

13. grep,find

grep:文本内容搜索;find:文件或者目录名以及权限属主等匹配搜索

eg: grep success *    /*查找当前目录下面所有文件里面含有success字符的文件

14. kill 可以杀死某个正在进行或者已经是dest状态的进程

eg; ps ax

15. passwd 可以设置口令

16. history 用户用过的命令

eg: history //可以显示用户过去使用的命令

17. !! 执行最近一次的命令

18. mkdir命令

eg: mkdir netseek //创建netseek这个目录

19. tar 解压命令

eg: tar -zxvf nmap-3.45.tgz //将这个解压到nmap-3.45这个目录里

20. finger 可以让使用者查询一些其他使用者的资料

eg: finger //查看所用用户的使用资料

finger root //查看root的资料

- 作者: wpf2006 2007年07月16日, 星期一 15:04  回复(0) |  引用(0) 加入博采

蓝屏故障
蓝屏故障 IRQL_NOT_LESS_OR_EQUAL ***STOP:0x0000000a(0x000...
0x0000000A:IRQL_NOT_LESS_OR_EQUAL 分析:主要是由问题的驱动程序、有缺陷或不兼容的硬件与软件造成的。从技术角度讲,表明在内核模式中存在以太高的进程内部请求级别(IRQL)访问其没有权限访问的内存地址。

- 作者: wpf2006 2007年07月2日, 星期一 10:43  回复(0) |  引用(0) 加入博采

NDIS中间层驱动程序
概要:开发一个NDIS驱动是一项相对复杂的工作,这一方面是由于核心驱动本身有更多的限制和要求,有更多的“游戏规则”要求开发者理解和掌握,NDIS的复杂性把难度更是提高了,本文以PassThru为例,加上自己的理解,讲述了NDIS驱动的处理过程和在PassThru的基础上进行扩展的基本方法,本文并不是一个入门读物,所以没有提及任何核心驱动开发的相关知识,本文主要讲述的是NDIS中间层对数据包处理的流程。在阅读过程中,关于相关API的用法,或其它信息,请参看DDK文档。

一  NDIS驱动程序分类.
    NDIS(Network Driver Interface Specification)是Windows网络驱动程序接口标准,NDIS驱动程序分为三类:
1. NDIS Miniport NIC Driver: 底层的微端口NIC驱动,这就是网络设备的物理的驱动程序了。
2. NDIS Protocol Driver: 高层的协议驱动,用来实现某个具体的协议栈,如TCP/IP协议栈,
   并向上导出TDI接口。
3. NDIS Intermediate Driver: 中间层驱动,位于Miniport Driver和Protocol Driver之间。

二  NDIS驱动结构简介.
 
                                 TDI(Transport Driver Interface)
          _______________________________________________________
                           |                     |  
                   ________|__________    _______|_______  
                  |                   |  |               |
           _____  |  LAN Protocols    |  |               |
           |    | |___________________|  |               |
           |    |_____________________   |    Native     |
           |                          |  |    Media      |
           | N       LAN Media Type   |  |    Aware      |
           | D   _____________________|  |    Protocol   |
           | I  |   __________________   |               |
           | S  |  |                  |  |               |
           |    |  |NDIS Intermediate |  |               |
           | I  |  |__________________|  |_______________|
           | N  |_________________________________________
           | T                                            |
           | E             Native Media Type              |    
           | R  __________________________________________|
           | F  |  _________________    __________________
           | A  | |                 |  |                  |
           | C  | | NDIS Miniport   |  |  NDIS Miniport   |
           | E  | |_________________|  |__________________|
           |    |_________________________________________
           |                                              |
           |                NDIS Interface                |
           |______________________________________________|
                    ________________    ______________
                   |                |  |              |
                   |    NetCard     |  |   NetCard    |
                   |________________|  |______________|
                                    
图一
    其中,最上层是一个NDIS Protocol Driver,它向上提供一个Transport Driver Interface(TDI),向下通过NDIS接口与下面的NDIS中间层的上边界交互,NDIS中间层的下边界通过NDIS接口与下层的NDIS Miniport Driver交互。最后,由NDIS Miniport Driver利用NDIS接口与物理网络设备NetCard交互。NetCard是由不同的网卡设备产商提供的,而NDIS接口库是由Microsoft开发好的,为什么NDIS Miniport Driver不是直接与物理网卡交互,而是通过NDIS接口与下物理网卡交互呢?(我想很多人都会和我当初一样,有这个疑问)。
    事实上,这是由于Windows系统为了提高可移植性,而设计出一个硬件抽象层(HAL),硬件抽象层在内部处理不同的硬件之间的差异,并且暴露出一个统一的接口给核心驱动开发者。例如:在Intel构架的系统中,内存和外部设备的端口采用分别编址,如果要从某个外部设备的端口上读写数据的话,可能要通过专用指令IN/OUT读写,而在Alpha构架的系统上,采用的是统一编址的方式,所以对外部设备的IO端口进行读写的话还是通过访问内存的指令,HAL提供一组服务支持函数,如果要访问外部设备上的端口数据可以使用READ_PORT_UCHAR/WRITE_PORT_UCHAR等等,核心驱动开发者不用去考虑不同硬件构架的之间的差异。在NDIS Miniport Driver中,NetCard驱动的程序,正是这样通过NDIS接口提供的一组类似功能的函数,与物理的网络设备进行交互。其中,最上层是一个NDIS Protocol Driver,它向上提供一个Transport Driver Interface(TDI),向下通过NDIS接口与下面的NDIS中间层的上边界交互,NDIS中间层的下边界通过NDIS接口与下层的NDIS Miniport Driver交互。最后,由NDIS Miniport Driver利用NDIS接口与物理网络设备NetCard交互。

三 NDIS驱动程序的数据处理流程
         ________________________    _____
        |                        |  |     |
        |    Transport Driver    |  |     |
        |________________________|  |     |  
        | Protocol Xxx - Media X |  |     |
        +------------------------+  |     |
         ___________________________|     |
        |___________________________      |                        
                                    |     |          
         ________________________   |     |
        | Miniport Xxx - Media X |  |     |
        +------------------------|  |     |
        |                        |  |     |
        |  Intermediate Driver   |  |     |
        |________________________|  |     |  
        | Protocol Xxx - Media Y |  |     |
        +------------------------+  |     |
                                    |     |
         ___________________________|     |
        |___________________________      |
                                    |     |
         ________________________   |     |
        | Miniport Xxx - Media Y |  |     |
        +------------------------|  |     |
        |                        |  |     |
        |      NIC Driver        |  |     |
        |________________________|  |     |  
                                    |     |
         ___________________________|     |
        |_________________________________|  
             _________________
            |                 |
            |       NIC       |
            |_________________|
                   图二    

3.1 三种NDIS驱动程序的关系。                    
    通常一个NDIS Protocol Driver 的上边沿导出TDI接口,并在其下边沿向NDIS注册一组Protocolxxx操作例程;一个NDIS Miniport Driver则在其下边沿通过NDIS接口操作物理网络设备,并在其上边沿向NDIS注册一组Miniportxxx操作例程。当一个中间层介入的时候,必需遵守这个规则,因此,中间层驱动对上层来说,扮演一个Miniport Driver的角色,它在上边沿向NDIS注册一组Miniportxxx函数;对于下层Miniport Driver来说,中间层驱动扮演一个Protocol Driver的角色,因此它在下边沿向NDIS注册一组Protocolxxx函数。Miniport Driver通过调用NdisMRegisterMiniport向NDIS注册一组MiniportXxx函数。其中原型如下:
    NDIS_STATUS NdisMRegisterMiniport(
        IN NDIS_HANDLE  NdisWrapperHandle,
         IN PNDIS_MINIPORT_CHARACTERISTICS  MiniportCharacteristics,
         IN UINT  CharacteristicsLength
         );
    其中,NdisWrapperHandls是之前通过调用NdisMInitializeWrapper取得的句柄MiniportCharacteristics包含一组MiniportXxx函数指针。

    Protocol Driver 通过调用 NdisRegisterProtocol向NDIS注册一组ProtocolXxx函数。其中原型如下:
    VOID NdisRegisterProtocol(
        OUT PNDIS_STATUS  Status,
        OUT PNDIS_HANDLE  NdisProtocolHandle,
        IN PNDIS_PROTOCOL_CHARACTERISTICS  ProtocolCharacteristics,
        IN UINT  CharacteristicsLength
        );
    其中,ProtocolCharacteristics包含一组ProtocolXxx函数。由于NDIS Intermediate Driver的双重性,它需要调用 NdisIMRegisterLayeredMiniport 向NDIS注册,并向上层导出一组MiniportXxx函数,之后,调用NdisRegisterProtocol向NIDS注册,并向下层导出一组ProtocolXxx函数。当一个NDIS中间层介入后,如图二所示。

3.2 NDIS数据发送流程:
    当上层协议驱动要发数据时,调用NdisSend/NdisSendPackets请求NDIS发送数据包,NDIS将会调用紧接其下的中间层驱动的MiniportSend/MiniportSendPackets,中间层驱动MiniportSend/MiniportSendPackets有机会在这里对包进行必要的操作,然后,中间层驱动再次调用NdisSend/NdisSendPackets请NDIS发送数据包,NDIS将调用其下层的Miniport Driver的MiniportSend/MiniportSendPackets,底层MiniportSend/MiniportSendPackets通过NDIS接口控制物理网络设备,将数据发送出去。在上层请求发送数据包时,上层分配了相关的资源(如内存),希望在下层完成发送动作后,能够及时的收回相关的资源,所以,当上层调用NdisSend/NdisSendPackets返回 NDIS_STATUS_PENDING以外的任何值时,上层就可以释放资源了,如果得到返回的结果是NDIS_STATUS_PENDING话,说明下层还没有完成发送请求,以后,等下层最终完成发送请求时,下层调用NdisMSendComplete请求NDIS通知上层可以释放资源了,于是NDIS调用上层注册的ProtocolSendComplete函数,上层在这它的ProtocolSendComplete中释放了资源后,再次调用NdisMSendComplete请求NDIS通知更上层。

3.3 NDIS数据接收流程:
    当底层网络设备有数据到来的时候,将触发中断,相应的中断处理程序接管中断后,将可能调用Miniport Driver所注册的中断处理例程(ISR),Miniport Driver通常在这里把网卡上的数据考贝到Miniport Driver缓冲区队列中去,出于效率的考虑,Miniport Driver这时可能不会立即通知上层处理新的数据,因为很可能,马上还有随后的新的数据到来,当接收到的包的数量达到一定程度的时候,Miniport Driver会调用NdisMIndicateReceivePacket指示新的NDIS新数据的到来,这时候,NdisMIndicateReceivePacket的调用将导致NDIS调用位于Miniport上层的中间层向NDIS注册的下边沿ProtocolReceivePacket函数。中间层驱动程序的ProtocolReceivePacket可以对收到的数据包进行相应的处理,之后,可以选择再次调用NdisMIndicateReceivePacket请求NDIS通知更上层数据包的到来,这时,NDIS调用更上层注册的ProtocolReceive函数,上层的ProtocolReceive对数据包进行必要的处理后,继续调用NdisMIndicateReceivePacket请求NDIS,通知更上层,最终数据包传到协议驱动中,由相关的协议栈进行处理。
    有时候,在这种级连的上传过程中,并不是那么的顺利,例如,由于某种原因,如:网络设备的驱动程序的可用缓冲区数量减少到某个指定的数量时,网络设备的驱动程序调用NdisMxxxIndicateReceivePacket请求NDIS通知上层数据的到来,这时NDIS将调用上层注册的ProtocolReceive,上层在ProtocolReceive中进行必要的处理后,进一步调用NdisMxxxIndicateReceivePacket使得NDIS调用上层的协议驱动注册的ProtocolReceive。在一些不太理想的情况下,一次中断,从网络设备中接收到的数据对某个协议来说并不是一个完整的协议数据包。(一般情况下,其余的数据bit已经在链路途中了,并随后立即就会到达网络设备,这有可能就发生在一个CPU在处理网络设备中断的同时,网络设备的板载存储器上就已经收到了其余的数据了,甚至DMA控制器可能已经把这些数据传到了系统的主存储器上了),这时,上层的ProtocolReceive都无法进行正常的处理(一般对某个包进行处理,都要以相关的协议为依据,进行分析)。这时,在上传到某一层时,可以调用NdisTransferData请求NDIS把随后的信息传上来,这时,NDIS又将在向上传递的途中回过头来向下调用下面的MiniportTransferData,下层重复调用NdisTransferData把这个请求传送到底程的Miniport Driver。如果在上层调用NdisTransferData时不是返回NDIS_STATUS_PENDING,则上层可以继续它的处理,而如果返回NDIS_STATUS_PENDING,底程在最终完成请求时,调用NdisMTransferDataComplete请求NDIS通知上层传送完成,这将导致上层注册的ProtocolTransferDataComplete被调用,上层调用NdisMTransferDataComplete请求NDIS通知更上层。由于硬件技术的发展,网络设备板载存储的增加,系统主存储器的增加,以及网络传输能力的改善,NDIS那么迂回的通过MdisMxxxIndicateReceivePacket,ProtocolReceive,MiniportTransferData这一条坎坷的路径进行数据处理的情况似乎越来越少见了。
    接收过程是由下层传递上去的,同样,底层的Miniport分配了一些资源,如用于存储这个数据包内容的内存,我们希望这些资源最终能极时的被归还,以供以后使用。一个包在从下到上的传递过程时,如果某一层的ProtocolReceive/ProtocolReceivePacket有兴趣对这个包进行处理的话,则需要检查这个包的OOB信息段是不是携带NDIS_STATUS_RESOURCES,如果是的话,说明其下层资源紧缺,希望上层在处理的时候,自己考贝一份副本,以供自己使用,因为下层希望自己能够尽快收回这个包的资源,在这里,上层以后可以用自己的那份拷贝指示上层数据包的到来,这样的话,以后,中间层希望上层处理完后,能够收回所有权。另一方面,底层的Miniport并不是每一次都会在OOB信息段中设置NDIS_STATUS_RESOURCES标志的,这在很多情况下是不必要的。当上层协议驱动完成处理时,可以调用NdisReturnPackets通知NDIS,相应包已经处理完成,可以安全的释放相关资源了,于是NDIS将调用其下层注册的MiniportReturnPackets,下层在这里释放与这个包相关的资源,并继续调用NdisReturnPackets,请求NDIS把这个通知传给更下层。
    
    这样一来,发数据由上层发起请求往下传,而接收数据从下层往上传,有没有可能一次接收的过层是由上面发起往下传的呢?这是没有必要的,为什么?我的应用程序不就主动调用WSARecv或WSARecvFrom等函数主动向下传递的一次收数据的请求的吗?事实上是这样的,你的接收请求经过SPI,TDI,到了最终的协议驱动时,如果协议驱动中,指定的套接字上(最重要的是端口和目的IP了)有相应的数据的能满足你的这一次的读请求的话,就完成你的请求,如果不能的话,则阻塞了。当下层有数据来的时候,数据传递到协议驱动时,协议驱动会检查包头的信息并查看当前不是有应用程序打开过相应的端口,以及和对应的目标建立过连接,如果有的话,就把数据存到协议栈中的缓冲区中去,如果对应套接字上有阻塞的接收请求的话,就判断是不是能完成它的请求了,如果能了,就完成他,如果不能,继续等以后下层传上来再重复这个过程。否则就抛弃了,另外,如果,一个应用程序建立一个套接字,并与一台机器建立连接,对方发送了数据,协议栈会把数据保存起来,也许连接超时后,协议栈的缓冲区中还有应用程序没有接收的数据的话,也被抛弃了。所以,协议驱动从来都没有必要主动往下传递一个接收数据的请求。这一点,对于有一点网络程序调试经验的人来说,似乎可以直接找到一个的证据,在调试器的调用WSARecv前下一个断点,用Sniffer抓包,可能数据已经到来了,然后,再回到调试器中执行WSARecv,可以看到收到的就是先前Sniffer下来的数据了。
(注:用一个工作在NDIS协议层或是NDIS中间层的Sniffer来观查。)

3.4 NDIS 的数据包结构。
    发送NDIS Protocol Driver分配相关的包资源,请求NDIS向下传递发送动作,接收时,NDIS Miniport Driver分配相关的包资源请求NDIS向上传递接收动作,中间层要对包进行处理,首先通过包描述符查询出这个包中的所有Buffer描述符,然后,从每一个Buffer描述符中取得相应的数据。一个包描述符中包含了一个或多个Buffer描述符,每一个Buffer描述符中包含了这个Buffer中数据的缓冲区首地址及其大小等信息,另外,一个包描述符中还包括了一些保留给开发者自己使用的Reserved字段,其于的一些字段,并没有被公开。另外NDIS提供了一些宏和一些函数对这相应的描述符进行操作。为什么要把这个结构弄得这么复杂呢?这是由于面对分层的协议处理的时候,避免过多的数据考贝,比如对于TCP/IP协议来说,上层传递下来的数据,在经过TCP,IP层时,把TCP,IP协议头部那个Buffer分别加入这个Packet来就可以了,如果,要它们在一个Buffer中,则需要在经过TCP层时,TCP层建立一个新的Buffer,把头部放到这个Buffer中来,并把数据考贝到这个Buffer中来;到了IP层还要继续这一动作......

四 基于PassThru框架的中间层驱动程序的扩展实现
    Microsoft在DDK中附带PassThru提供了一个的中间层驱动框架,使得开发者能够相对容易的在这个基础实现自己的NDIS中间层驱动扩展。我们将在PassThru的基础上实现一个基本的数据包操作的扩展。对于发送出去的数据包处理,只要在PassThru中的MiniportSend和MiniportSendPackets中加入必要的操作代码,而对于接收的数据包时,则需要在ProtocolReceive和ProtocolReceviePackets中加入必要的操作代码,在这里,我将以windows 2003 DDK的PassThru为例,进行扩展。

4.1 发送处理
VOID MPSendPackets(
    IN NDIS_HANDLE             MiniportAdapterContext,
    IN PPNDIS_PACKET           PacketArray,
    IN UINT                    NumberOfPackets
    )
{
    // 省略代码若干,请参看PassThru的源代码。
    
    // 分配一个新的包描述符。
    NdisAllocatePacket(&Status, &MyPacket, pAdapt->SendPacketPoolHandle);
    if (Status == NDIS_STATUS_SUCCESS)
    {
        PSEND_RSVD        SendRsvd;
        SendRsvd = (PSEND_RSVD)(MyPacket->ProtocolReserved);
        // 把原来的包描述符保存在新分配的包描述符中的Reserved字段中,原因在后面描述。
        SendRsvd->OriginalPkt = Packet;
    }  
    // 调用BuildMyPacket对包进行自己的处理(更改包的内容,或其它动作)。
    if (BuildMyPacket(pAdapt, Packet, MyPacket) == FALSE)
    {
        // 如果处理失败,则把原始的包信息Copy到MyPacket。
        // 这是为了在处理失败的情况下,也让原始的信息能发出去。
        NDIS_PACKET_FIRST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(Packet);
        NDIS_PACKET_LAST_NDIS_BUFFER(MyPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(Packet);
    }
    // ......
    // 请求NDIS向下层传递发送动作。
    NdisSend(&Status, pAdapt->BindingHandle, MyPacket);
    // 如果NdisSend返回非NDIS_STATUS_PENDING则释放自己的资源。
    if (Status != NDIS_STATUS_PENDING)
    {
        DestroyMyPacket(MyPacket);
        NdisFreePacket(MyPacket);        
    }
      
    // .......
    // 如果NdisSend返回非NDIS_STATUS_PENDING,说明下层已成功发送完成
    // 调用NdisMSendComplete请求NDIS通知上层释放资源,这将导致上层注册的
    // ProtocolSendComplete被调用,上层在ProtocolSendComplete中释放资源后
    // 将继续调用NdisMSendComplete请求NDIS把这个通知往上传递。
    if (Status != NDIS_STATUS_PENDING)
    {
        NdisMSendComplete(ADAPT_MINIPORT_HANDLE(pAdapt), Packet, Status);
    }
    // 如果返回了 NDIS_STATUS_PENDING,则在该层的资源还不能释放,在底层
    // 完成发送时,底层将调用NdisMSendComplete,请求NDIS向上传递这个通知。
    // 该层在自己的ProtocolSendComplete释放相应的资源。
}
    
VOID PtSendComplete(
    IN  NDIS_HANDLE            ProtocolBindingContext,
    IN  PNDIS_PACKET           Packet,
    IN  NDIS_STATUS            Status
    )
{  
    // .......
    // 上面发送的时候,我们分配了一个新的包描述符,并把上层的包描述符保存在
    // ProtocolReserved中,现在,把这个上层的包描述符还原出来。
    PSEND_RSVD        SendRsvd;
    SendRsvd = (PSEND_RSVD)(Packet->ProtocolReserved);
    Pkt = SendRsvd->OriginalPkt;
    
    // .......
    // 释放自己的资源
    DestroyMyPacket(Packet);
    NdisDprFreePacket(Packet);
    // 用上层的包描述符请求NDIS通知上层释放资源
    NdisMSendComplete(pAdapt->MiniportHandle, Pkt, Status);
}


BOOLEAN BuildMyPacket(
    IN PADAPT              pAdapt,
    IN PNDIS_PACKET        original_packet,
    OUT PNDIS_PACKET       MyPacket
    )
{
    PSEND_RSVD  SendRsvd;
    NDIS_STATUS Status;
    NDIS_PHYSICAL_ADDRESS phyaddr = {-1};
    PVOID pcontent = NULL;
    ULONG total_length = 0, current_length = 0;
    PNDIS_BUFFER MyBuffer;

    // 分配新的内存
    Status = NdisAllocateMemory((PVOID)&pcontent, 2014, 0, phyaddr);
    if (NDIS_STATUS_SUCCESS != Status)
        return FALSE;
    NdisZeroMemory(pcontent, 2014);
    
    // 把包中的数据Copy到自己的Buffer中来。
    NdisQueryPacket(packet, NULL, NULL, &ndis_buffer, &total_length);
    while (NULL != ndis_buffer)
    {
        NdisQueryBufferSafe(ndis_buffer, &address, &current_length, NormalPagePriority);
        NdisMoveMemory(pcontent, address, current_length);
        (PUCHAR)pcontent += current_length;
        NdisGetNextBuffer(ndis_buffer, &ndis_buffer);
    }
    
    // 分配新的一个Buffer描述符                    
    NdisAllocateBuffer(&Status, &MyBuffer, pAdapt->SendPacketPoolHandle,
        pcontent, total_length);                    
    if (NDIS_STATUS_SUCCESS != Status)
    {
        NdisFreeMemory(pcontent, 2014, 0);
        return FALSE;
    }
    
    // 在这里对包的内容进行你自己的处理,如果修改了内容的话,由于这是在协议栈之下,
    // 所以要重新修正CheckSum。调整MyBuffer,和MyPacket相关信息,如长度等。
    // 注意,这里的数据已经是网络字节数据,所以在x86的处理器上要注意字节顺序的问题
    // 对于少量内容的修改,重新扫描整个Buffer修正CheckSum是不值得的。
    // RFC关于Nat的文档中详细描述了基于差异分析的修正CheckSum的方法,并给出了具体算法实现。
    
    // 把新的包描述符存放到新的包描述符中的MiniportReserved中去,原因在DestroyMyPacket中解释
    SendRsvd = (SEND_RSVD)MyPacket->MiniportReserved;
    SendRsvd->OriginalPkt = MyPacket;
    NdisChainBufferAtFront(MyPacket, MyBuffer);
    return TRUE;
}


VOID DestroyMyPacket(PNDIS_PACKET MyPacket)
{
    PNDIS_BUFFER MyBuffer;
    PVOID address;
    PNDIS_BUFFER tmpBuffer, MyBuffer;
    ULONG current_length;
    PSEND_RSVD  SendRsvd;

    // 由于在 BuildMyPacket,分配资源失败的情况下,仍然用原包发送出去,
    // 在这种情况下,是不用释放相关的Buffer资源的
    // BuildMyPacket 当分配成功时,我把新包中的 MiniportReserved 字段
    // 指向了新的包描述符,我以这个为依据判断是否需要释放相关的Buffer资源。
    SendRsvd = (PSEND_RSVD)MyPacket->MiniportReserved;
    if (MySendRsvd->OriginalPkt != MyPacket)
        return;
    NdisUnchainBufferAtFront(MyPacket ,&MyBuffer);     
    while (NULL != MyBuffer)
    {
        NdisQueryBufferSafe(MyBuffer, &address, &current_length, NormalPagePriority);
        NdisFreeMemory(address, current_length, 0);
        tmpBuffer = MyBuffer;
        NdisGetNextBuffer(tmpBuffer, &MyBuffer);
        NdisFreeBuffer(tempBuffer);
    }
}

4.2 接收处理
    接收的时候,由于那个TransferData的曲折过程,使得接收处理要相对复杂一点点,在ProtocolReceive和ProtocolReceivePacket中的处理不同。但是由于2003 DDK中的PassThru中,没有对数据进行任何处理,所以,它的ProtocolReceive的处理相对来说,简单了一些。
NDIS_STATUS PtReceive(
    IN  NDIS_HANDLE         ProtocolBindingContext,
    IN  NDIS_HANDLE         MacReceiveContext,
    IN  PVOID               HeaderBuffer,
    IN  UINT                HeaderBufferSize,
    IN  PVOID               LookAheadBuffer,
    IN  UINT                LookAheadBufferSize,
    IN  UINT                PacketSize
    )
{
    // .......

    do
    {
        // 分配一个新的包描述符
        NdisDprAllocatePacket(&Status, &MyPacket, pAdapt->RecvPacketPoolHandle);
        if (Status == NDIS_STATUS_SUCCESS)
        {    
            // PassThru维护了一个自己的接收队列,当收到下层的包时,PassThru并不是立刻
            // 调用NdisMIndicateReceive/NdisMXxxIndicateReceive请求NDIS通知上层新数据
            // 的到来,而是在自己的队列中缓冲起来,当自己的队列满了以后,PassThru将一
            // 次性调用NdisMIndicateReceive请求NDIS通知上层数据的到来。
            // 通常最底程的Miniport Driver也有同样的处理机制。
            PtQueueReceivedPacket(pAdapt, MyPacket, TRUE);
            // MyPacket的一个副本被Copy到PassThru中的队列去了,现在可以释放这一个Packet了。
            NdisDprFreePacket(MyPacket);
            break;      // 注意这里,跳出了整个do-while了。
        }
       else
       {
            //
            // The miniport below us uses the old-style (not packet)
            // receive indication. Fall through.
            //
       }

        if (Packet != NULL)
        {
            // 当执行到这里,说明前面没有跳出do-while循环,也就是说,包申请失败
            // 进一步说明,PassThru的缓冲队列满了,于是有必要,调用 NdisMIndicateReceive
            // 请求NDIS通知上层数据包的到来了,这将导致上层注册的 ProtocolReceivePacket
            // 被调用.
            PtFlushReceiveQueue(pAdapt);
        }
        
        // ......
        // 把队列中的包通知了给上层接收,但是这一个包,由于分配新的描述符失败
        // 所以并没有通知给上层,因此,在这里,进行单独的处理。
        
        pAdapt->IndicateRcvComplete = TRUE;
        // 以下是进行各种协议相关的处理。
        switch (pAdapt->Medium)
        {
        case NdisMedium802_3:
        case NdisMediumWan:
            NdisMEthIndicateReceive(pAdapt->MiniportHandle,
                                MacReceiveContext,
                                HeaderBuffer,
                                HeaderBufferSize,
                                LookAheadBuffer,
                                LookAheadBufferSize,
                                PacketSize);
        break;

        case NdisMedium802_5:
            NdisMTrIndicateReceive(pAdapt->MiniportHandle,
                                MacReceiveContext,
                                HeaderBuffer,
                                HeaderBufferSize,
                                LookAheadBuffer,
                                LookAheadBufferSize,
                                PacketSize);
        break;
    case NdisMediumFddi:
        NdisMFddiIndicateReceive(pAdapt->MiniportHandle,
                                 MacReceiveContext,
                                 HeaderBuffer,
                                 HeaderBufferSize,
                                 LookAheadBuffer,
                                 LookAheadBufferSize,
                                 PacketSize);
         break;

    default:
         ASSERT(FALSE);
         break;
        }
    } while(FALSE);

    return Status;
}

    注意,PassThru 并没有对收到的包进行任何处理,因此,在它的ProtocolReceive中,没有调用NdisTransferData,请求NDIS传送这个包其余的数据,它直接Indicate,把这个工作交给了上层去处理,如果,你的中间层要处理的话,就得在放入队列前面,调用NdisTransferData,如果返回成功,则在紧接其下进行处理,如果返回 NDIS_STATUS_PENDING 的话,就把处理放到ProtocolTransferDataComplete中处理。所以,你的ProtocolReceive应该看起来是这样的过程:
1.  在把包加入PassThru的队列前,调用NdisTransferData.请求NDIS通知下层传递其余的数据。
    (这里回头走了一段曲折的路了)。
2A. 如果返回成功,则进行处理,如修改数据,重新修正CheckSum,
    在X86平台上可要注意字节顺序的问题了。
2B. 如果返回 NDIS_STATUS_PENDING 就在 ProtocolTransferDataComplete进行2A的处理。
3.  处理完成后,排入PassThru的队列。
    由于,现在网络设备硬件的发展,内存容量的提高,底程的Miniport Driver通常有一个类似PassThru的缓存处理机制,走这条曲折的线路上来,似乎很少见了。

    在搞懂了ProtocolReceive后,rotocolReceivePacket就更简单了。
INT
PtReceivePacket(
    IN NDIS_HANDLE            ProtocolBindingContext,
    IN PNDIS_PACKET           Packet
    )

{
    // .......
    // 进行你自己的处理,修改包的内容,修正CheckSum等操作,参考前面接收过程
    // 取得Packet中的内容
    Status = NDIS_GET_PACKET_STATUS(Packet);
    if (Status == NDIS_STATUS_RESOURCES)
    {
        // 如果下层设置了NDIS_STATUS_RESOURCES,说明下层由于资源
        // 紧缺等原因,要求上层经快处理,于是产生一个考贝的
        // 包描述符排入队列,马上Indicate,并调用NdisReturnPacket请求
        // NDIS归还下层的资源。这是PassThru的处理方法
        // 事实上,如果,你自己要修改这个包,你已经有一个新的Packet和Buffer
        // 以及相关内容了,只要把你的这个新的包排入队列,并调用 NdisReturnPacket
        // 归还下层的资源,而不立即Indicate上层,也是可以的。
        PtQueueReceivedPacket(pAdapt, Packet, TRUE);
    }
    else
    {
        PtQueueReceivedPacket(pAdapt, MyPacket, FALSE);
    }
    
    if (Status == NDIS_STATUS_RESOURCES)
    {
        NdisDprFreePacket(MyPacket);
    }
    // ......
}

    当上层处理完后,调用NdisReturnPacket请求NDIS归还下层的资源时,NDIS调用下层注册的MiniportReturnPacket在这里释放资源后,再调用NdisReturnPackets通知更下层。
VOID MPReturnPacket(
    IN NDIS_HANDLE             MiniportAdapterContext,
    IN PNDIS_PACKET            Packet
    )

{
    // 如果你和我一样,在接收的时候,自己处理不成功的情况下,就把原来的数据往上传
    // 你就可以像我处理发送的方法一样,利用那几个Reserve的字段,在这里判断,
    // 并进行必要的资源释放。
    // ......
    // 通知下层释放资源
    NdisReturnPackets(&Packet, 1);
    // ......
}
    注意:对于一个像TCP这样面像连接的苏方的数据包来说,你要是改变了它的长度的话,那问题就更复杂了,你在协议栈的下方,上层协议栈不知道这个修改,而你把它发到目标计算机去,目标计算机得到的长度是修改后的,那双方的SEQ,ACK就不能同步了,这样的话,你必须记录下你的改动,并对以后的通信,做相应的修正。不然,你一改的话,接下来的通信就RST了。

- 作者: wpf2006 2007年06月27日, 星期三 13:59  回复(0) |  引用(0) 加入博采

NAT在NDIS中间层驱动中的实现



1.概要
    相信在IPv6的时代到来之前,NAT仍然是解决大多数人上网的主要途径,而且它在企业内网Intranet中也扮演着十分重要的角色.
    NAT的全称是Network Address Translator(网络地址转换),其主要作用是把内网IP地址转换成为全球唯一的可定位的外部IP地址,从而使得局域网内的所有用户可以通过一个或者少数几个IP地址与全球的Internet通信,不仅节约了IP地址,而且在一定程度上保护了内部网络.
    由于工作需要,笔者希望编写一个具有NAT功能的软件,将同一个网段内把本机设为网关的拥有私有IP的主机发来的数据包转发到外部网络,并把响应信息返回给对应的主机.这个问题在不同的层次上做就有不同的解决方案,由于笔者也是网络新手,走了不少弯路:
    首先,企图在用户层利用原始套接字(Raw Socket)来实现,但是系统拥有对未开放端口的自动复位功能,每当我们转发一个数据包时,需要占用系统的一个端口,但是这点系统并不知道,它接收到对于这个端口的回应信息时,会认为本端口不存在,并发送一个带有复位标志的数据包请求对方断开连接.这就阻拦了所有非本机请求的连接,所以这个方案首先被否定了.
    随后,不得不往系统下面走,准备在核心态实现.当然越简单越好,于是笔者选择了Filter-Hook驱动.Filter-Hook Driver, 事实上不是一种新的网络驱动,它只是扩展了IP过滤驱动(IP Filter Driver)的功能,是一种内核模式驱动(Kernel Mode Driver). 在Filter-Hook Driver中我们提供回调函数(callback),然后使用IP Filter Driver注册回调函数。这样当数据包发送和接收时,IP Filter Driver会调用回调函数。可惜梦想再一次破灭,这个回调函数的返回值只有PF_FORWARD,PF_DROP,PF_PASS三种,并不能把修改后的数据包主动发送出去.
    只有在向底层走了,NDIS应该是必经之路.而且经过两次失败,发现闭门造车是不可行的,偶然在网上搜索到了几篇文章,听说在NDIS的中间层驱动中可以实现NAT,新的探索之路就这样开始了......

2.NAT简介
    NAT(Network Address Translator)的出现并不是偶然的,一方面是由于IPv4的创造者们没有想到,Internet以及TCP/IP发展如此迅速,在他们还们完全享受TCP/IP的成功带来的快感之前,32位的IP地址竟然不够用了;另一方面我们必须保证某些特殊的主机在于局域网络连接的同时,保持对外界直接曝光,但是由需要与外界在受控的情况下通讯.下图是一个典型的NAT示意.
       \ | /                  .                               /
   +---------------+  WAN     .           +-----------------+/
   |Regional Router|----------------------|Stub Router w/NAT|---
   +---------------+          .           +-----------------+\
                              .                      |        \
                              .                      |  LAN
                              .               ---------------
                        Stub border

   下面举一个具体的例子说明两个处于内网的主机是如何通过NAT通信的
                                       \ | /
                                     +---------------+
                                     |Regional Router|
                                     +---------------+
                                   WAN |           | WAN
                                       |           |
                   Stub A .............|....   ....|............ Stub B
                                       |           |
                     {s=198.76.29.7,^  |           |  v{s=198.76.29.7,
                      d=198.76.28.4}^  |           |  v d=198.76.28.4}
                       +-----------------+       +-----------------+
                       |Stub Router w/NAT|       |Stub Router w/NAT|
                       +-----------------+       +-----------------+
                             |                         |
                             |  LAN               LAN  |
                       -------------             -------------
                                 |                 |
               {s=10.33.96.5, ^  |                 |  v{s=198.76.29.7,
                d=198.76.28.4}^ +--+             +--+ v d=10.81.13.22}
                                |--|             |--|
                               /____\           /____\
                             10.33.96.5       10.81.13.22

图中有两个残桩网络A和B,现在假设A中的一台主机10.33.96.5需要同B中的10.81.13.22通信,它必须把自己发送的数据报的目的地址设置为B的一个外网地址198.76.28.4,并在NAT中把源地址转换成A的外部地址198.76.29.7,才能使数据包顺利抵达广域网中的路由器,并转到B,在由B网的NAT把数据包发送给10.81.13.22.
    随着NAT多年的发展,出现了很多不同风格,应用于不同场合的NAT.笔者实现的是传统NAT中的一种特殊情况NAPT(Network Address Port Translation),它把所有内网的IP地址都转换成同一个外部IP地址,并通过不同的端口来区分各个不同的内部主机.

3.中间层驱动NDIS Intermediate Drivers
    所谓中间层驱动是指位于微端口和协议之间的驱动,实际上它是微软在网络驱动中留出来的接口,便于用户实现自己对数据包的处理.在协议驱动层看来,它就是微端口;在微端口看来,它又是协议层驱动.因此,如果需要在此层实现自己对数据包的处理函数,不仅要在上边缘注册MiniportXxx Function,还要在下边缘注册ProtocolXxx Function.一般在这个层次做工作的同志都会学习并了解DDK的一个经典Sample:Passthru.如果对它不了解,可以去看看Addylee前几天的文章"基于PassThru的NDIS中间层驱动程序扩展",讲得很清晰.

4.NAPT的具体实现
    程序整体框架依然是基于PaaThru的,具体要注意的问题有以下几点:
4.1 转发表的维护
typedef struct _PortNode
{
    USHORT inport;                      //内网端口
    USHORT export;                //转发端口
    USHORT report;                //远程端口
    ULONG inip;                //内网IP
    ULONG reip;                //远程IP
    struct _PortNode * next;            //链表指针
}PortNode;

PortNode * first = NULL;                    //全局变量,转发表的头结点

NTSTATUS
DriverEntry(
    IN    PDRIVER_OBJECT        DriverObject,
    IN    PUNICODE_STRING        RegistryPath
    )
{
        ......
    Status = NdisAllocateMemory(&first,sizeof(PortNode), 0,HighestAcceptableMax);
    if(Status == NDIS_STATUS_SUCCESS)
    {
        NdisZeroMemory(first,sizeof(PortNode));
        //首结点的inip表示本主机地址
        first->inip = 本主机IP            
        //首结点的reip表示本主机所在的网络地址
        first->reip = first->inip & 0x00ffffff;
    }
    ......
}

4.2 校验和的计算
USHORT CheckSum(USHORT *buffer, int size)
{
    unsigned long cksum=0;
    while(size >1)
    {
        cksum += * buffer++;
        size -=sizeof(USHORT);
    }
    if(size)
    {
        cksum += *(UCHAR*)buffer;
    }
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >>16);
    return (USHORT)(~cksum);
}
   IP TCP UDP三种包校验和的计算方法是一致的,本文采用的方法是简单地重新计算整个包的校验和,在RFC1631中,作者提出了一种差量计算法以提高计算速度,并且给出了C语言版的源代码.

4.3 对收到的数据包的过滤和转发
INT
PtReceivePacket(
    IN    NDIS_HANDLE            ProtocolBindingContext,
    IN    PNDIS_PACKET        Packet
    )
{
    ......

    
    PUCHAR       pPacketContent;
        PUCHAR       pBuf;
        UINT         BufLength;
        MDL          * pNext;
        UINT         i,j;
    BOOLEAN      transflag = FALSE;
    PNDIS_BUFFER MyBuffer;
    PIP_Header   pIPHeader;
    
    ......

    NdisDprAllocatePacket(&Status,
                           &MyPacket,
                           pAdapt->RecvPacketPoolHandle);

    if(Status == NDIS_STATUS_SUCCESS)
    {
        //add by thinking 06.6.3
        //把数据包内容从Packet拷贝到pPacketContent
        Status= NdisAllocateMemory( &pPacketContent, 2000, 0,HighestAcceptableMax);
        if (Status!=NDIS_STATUS_SUCCESS )     return Status;
        NdisZeroMemory (pPacketContent, 2000);
        NdisQueryBufferSafe(Packet->Private.Head, &pBuf, &BufLength, 32 );
        NdisMoveMemory(pPacketContent, pBuf, BufLength);
        i = BufLength;
        pNext = Packet->Private.Head;
        for(;;)
        {
            if(pNext == Packet->Private.Tail)
                break;
            pNext = pNext->Next;
            if(pNext == NULL)
                break;
            NdisQueryBufferSafe(pNext,&pBuf,&BufLength,32);
            NdisMoveMemory(pPacketContent+i,pBuf,BufLength);
            i+=BufLength;
        }
        if(pPacketContent[12] == 8 &&  pPacketContent[13] == 0 )  //is ip packet
        {
            ULONG netip;
            pIPHeader = (PIP_Header)(pPacketContent+14);
            netip = pIPHeader->ipSource & 0x00ffffff;
            //对收到的数据包进行过滤,只转发需要转发的包
            if(pIPHeader->ipDestination == first->inip && netip != first->reip)
            //如果目的地址是本主机,并且源IP不是本网段地址,则转发给内网主机
            {
                DbgPrint("\nTransInPacket...\n");
                for(j=0;j<=i;j++)
                    DbgPrint("%x ",pPacketContent[j]);
                //修改发给内网的数据包头
                transflag = TransIn(pPacketContent);
            }
            else if(pIPHeader->ipDestination != 0xffffffff &&
                (pIPHeader->ipDestination & 0x00ffffff) != first->reip &&
                netip == first->reip)
            //如果目的地址不是广播地址,而且是外网地址,源地址是内网IP,则转发给外网
            {
                DbgPrint("\nTransOutPacket...\n");
                for(j=0;j<=i;j++)
                    DbgPrint("%x ",pPacketContent[j]);
                //修改发给外网的数据包头
                transflag = TransOut(pPacketContent);
            }
        }

        if(!transflag)
        {
               //按照原来的方式往上提交    
               ......
        }
        else
        {
            //转发的一段关键代码
            NdisAllocateBuffer(&Status,&MyBuffer,pAdapt->SendPacketPoolHandle,pPacketContent,i);
            NdisChainBufferAtFront(MyPacket, MyBuffer);
            Resvd =(PRSVD)(MyPacket->ProtocolReserved);
            Resvd->OriginalPkt = MyPacket;
                MyPacket->Private.Head->Next = NULL;
            MyPacket->Private.Tail = NULL;
            NdisSetPacketFlags(MyPacket, NDIS_FLAGS_DONT_LOOPBACK);
            NdisReturnPackets(&Packet, 1);
            NdisSend(&Status,pAdapt->BindingHandle,MyPacket);
            if(Status != NDIS_STATUS_PENDING)
            {
                NdisUnchainBufferAtFront(MyPacket ,&MyBuffer); //从MyPacket中解除buffer
                NdisQueryBufferSafe(MyBuffer, &pPacketContent, &BufLength,32 );
                if(pPacketContent != NULL)
                    NdisFreeMemory(pPacketContent,BufLength, 0);
                NdisFreeBuffer(MyBuffer);
            }
            return 0;
        }
    ......
}

4.4 数据包头的修改
    根据具体情况修改数据包的IP包头,TCP包头,或者UDP包头,并且在修改的同时继续维护转发表,下面只给出TransIn的代码,TransOut与其原理相同.
BOOLEAN TransIn(PUCHAR pPacketContent)
{
    PortNode * inmap;
    PIP_Header pIPHeader = (PIP_Header)(pPacketContent+14);
    USHORT iphdrlen = (pIPHeader->iphVerLen & 0x0f) * sizeof(ULONG);
    UCHAR checkbuff[2000] = {0};

    if(pIPHeader->ipProtocol == 6)
    {
        PTCP_Header pTCPHeader;
        USHORT tcphdrlen;
        pTCPHeader = (PTCP_Header)(pPacketContent+14 + iphdrlen);
        //tcphdrlen = ((pTCPHeader->dataoffset & 0xf0) >> 4) * sizeof(ULONG);
        tcphdrlen = htons(pIPHeader->ipLength) - iphdrlen;
        inmap = InMapping(pIPHeader->ipSource,pTCPHeader->sourcePort,
            pTCPHeader->destinationPort);
        if(inmap == NULL)
            return FALSE;

        //修改目的地址和目的端口,校验和
        pIPHeader->ipDestination = inmap->inip;
        pTCPHeader->destinationPort = inmap->inport;
        pIPHeader->ipChecksum = 0;
        pTCPHeader->checksum = 0;

        //填充TCP伪首部
        psdhdr.saddr = pIPHeader->ipSource;
        psdhdr.daddr = pIPHeader->ipDestination;
        psdhdr.len = htons(tcphdrlen);
        psdhdr.mbz = 0;
        psdhdr.proto = 6;

        //计算TCP首部校验和
        NdisMoveMemory(checkbuff,&psdhdr,sizeof(psdhdr));
        NdisMoveMemory(checkbuff+sizeof(psdhdr),pTCPHeader,tcphdrlen);
        pTCPHeader->checksum = CheckSum((USHORT *)checkbuff,sizeof(psdhdr)+tcphdrlen);

        //计算IP首部校验和
        pIPHeader->ipChecksum = CheckSum((USHORT *)pIPHeader,iphdrlen);

        return TRUE;
    }
    else if(pIPHeader->ipProtocol == 17)
    {
        PUDP_Header pUDPHeader;
        USHORT udplen;
        pUDPHeader = (PUDP_Header)(pPacketContent+14 + iphdrlen);
        udplen = htons(pUDPHeader->len);
        inmap = InMapping(pIPHeader->ipSource,pUDPHeader->sourcePort,
            pUDPHeader->destinationPort);
        if(inmap == NULL)
            return FALSE;

        //修改目的地址和目的端口,校验和
        pIPHeader->ipDestination = inmap->inip;
        pUDPHeader->destinationPort = inmap->inport;
        pIPHeader->ipChecksum = 0;
        pUDPHeader->checksum = 0;

        //填充UDP伪首部
        psdhdr.saddr = pIPHeader->ipSource;
        psdhdr.daddr = pIPHeader->ipDestination;
        psdhdr.len = pUDPHeader->len;
        psdhdr.mbz = 0;
        psdhdr.proto = 17;

        //计算UDP校验和,包括所有UDP数据
        NdisMoveMemory(checkbuff,&psdhdr,sizeof(psdhdr));
        NdisMoveMemory(checkbuff+sizeof(psdhdr),pUDPHeader,udplen);
        pUDPHeader->checksum = CheckSum((USHORT *)checkbuff,sizeof(psdhdr)+udplen);

        //计算IP首部校验和
        pIPHeader->ipChecksum = CheckSum((USHORT *)pIPHeader,iphdrlen);

        return TRUE;
    }
    else
        return FALSE;
}

5.小结
    本文简单介绍了传统NAT在中间层驱动中的实现,很多地方都可以进行改进.例如:校验和的计算可以采用差量计算法以减少计算延迟;转发表的维护可以采用树型结构(而不是本文中的链表)以减少转发表的查找时间;定时对转发表进行清理,释放长时间不用的端口,以节约系统资源;构建ARP机制,并动态维护相关主机的MAC地址;通过共享内存或者修改驱动对象的DispatchTable与用户层进行通信,从而动态调整驱动功能.

- 作者: wpf2006 2007年06月25日, 星期一 17:34  回复(0) |  引用(0) 加入博采

c++友元函数示例
#include <iostream>
using namespace std;
class complex
{
        public:
                complex(double r=0.0,double i=0.0){real=r;imag=i;}
                friend complex operator + (complex c1,complex c2);
                void display();
        private:
                double real;
                double imag;
};
complex operator + (complex c1,complex c2)
{return complex(c2.real+c1.imag,c2.imag+c1.imag);}
void complex::display()
{cout<<real<<","<<imag<<endl;}
int main()
{
        complex c1(5,4),c2(2,10),c3;
        c3=c1+c2;
        c3.diplay();
        return 0;
}

- 作者: wpf2006 2007年05月10日, 星期四 11:39  回复(0) |  引用(0) 加入博采

111

TCHAR *info = new TCHAR[1024]; 

wcscpy(info,str.GetBuffer(str.GetLength()));

- 作者: wpf2006 2007年05月8日, 星期二 11:23  回复(0) |  引用(0) 加入博采

在evc中打开网页程序

  SHELLEXECUTEINFO   ShExecInfo   =   {0};  
  ShExecInfo.cbSize   =   sizeof(SHELLEXECUTEINFO);  
  ShExecInfo.fMask   =   SEE_MASK_NOCLOSEPROCESS;  
  ShExecInfo.hwnd   =   NULL;  
  ShExecInfo.lpVerb   =   NULL;   
  ShExecInfo.lpFile   =   TEXT("http://www.163.com");                   
  ShExecInfo.lpParameters   =NULL;          
  ShExecInfo.lpDirectory   =   NULL;  
  ShExecInfo.nShow   =   SW_SHOWNORMAL;          
  ShellExecuteEx(&ShExecInfo);  


- 作者: wpf2006 2007年04月6日, 星期五 17:53  回复(0) |  引用(0) 加入博采

DES算法

2、DES算法

一、DES算法

  美国国家标准局1973年开始研究除国防部外的其它部门的计算机系统的数据加密标准,于1973年5月15日和1974年8月27日先后两次向公众发出了征求加密算法的公告。加密算法要达到的目的(通常称为DES 密码算法要求)主要为以下四点:

 

☆提供高质量的数据保护,防止数据未经授权的泄露和未被察觉的修改;

☆具有相当高的复杂性,使得破译的开销超过可能获得的利益,同时又要便于理解和掌握;

☆DES密码体制的安全性应该不依赖于算法的保密,其安全性仅以加密密钥的保密为基础;

☆实现经济,运行有效,并且适用于多种完全不同的应用。

    1977年1月,美国政府颁布:采纳IBM公司设计的方案作为非机密数据的正式数据加密标准(DES棗Data Encryption Standard)。

  目前在国内,随着三金工程尤其是金卡工程的启动,DES算法在POS、ATM、磁卡及智能卡(IC卡)、加油站、高速公路收费站等领域被广泛应用,以此来实现关键数据的保密,如信用卡持卡人的PIN的加密传输,IC卡与POS间的双向认证、金融交易数据包的MAC校验等,均用到DES算法。
  DES算法的入口参数有三个:Key、Data、Mode。其中Key为8个字节共64位,是DES算法的工作密钥;Data也为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。
  DES算法是这样工作的:如Mode为加密,则用Key 去把数据Data进行加密, 生成Data的密码形式(64位)作为DES的输出结果;如Mode为解密,则用Key去把密码形式的数据Data解密,还原为Data的明码形式(64位)作为DES的输出结果。在通信网络的两端,双方约定一致的Key,在通信的源点用Key对核心数据进行DES加密,然后以密码形式在公共通信网(如电话网)中传输到通信网络的终点,数据到达目的地后,用同样的Key对密码数据进行解密,便再现了明码形式的核心数据。这样,便保证了核心数据(如PIN、MAC等)在公共通信网中传输的安全性和可靠性。
  通过定期在通信网络的源端和目的端同时改用新的Key,便能更进一步提高数据的保密性,这正是现在金融交易网络的流行做法。
  DES算法详述
  DES算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位,整个算法的主流程图如下:
其功能是把输入的64位数据块按位重新组合,并把输出分为L0、R0两部分,每部分各长32位,其置换规则见下表:
58,50,12,34,26,18,10,2,60,52,44,36,28,20,12,4,
  62,54,46,38,30,22,14,6,64,56,48,40,32,24,16,8,
  57,49,41,33,25,17, 9,1,59,51,43,35,27,19,11,3,
  61,53,45,37,29,21,13,5,63,55,47,39,31,23,15,7,
  即将输入的第58位换到第一位,第50位换到第2位,...,依此类推,最后一位是原来的第7位。L0、R0则是换位输出后的两部分,L0是输出的左32位,R0 是右32位,例:设置换前的输入值为D1D2D3......D64,则经过初始置换后的结果为:L0=D58D50...D8;R0=D57D49...D7。
  经过16次迭代运算后。得到L16、R16,将此作为输入,进行逆置换,即得到密文输出。逆置换正好是初始置的逆运算,例如,第1位经过初始置换后,处于第40位,而通过逆置换,又将第40位换回到第1位,其逆置换规则如下表所示:
  40,8,48,16,56,24,64,32,39,7,47,15,55,23,63,31,
  38,6,46,14,54,22,62,30,37,5,45,13,53,21,61,29,
  36,4,44,12,52,20,60,28,35,3,43,11,51,19,59,27,
  34,2,42,10,50,18,58 26,33,1,41, 9,49,17,57,25,
放大换位表
  32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10,11,
  12,13,12,13,14,15,16,17,16,17,18,19,20,21,20,21,
  22,23,24,25,24,25,26,27,28,29,28,29,30,31,32, 1,
单纯换位表
  16,7,20,21,29,12,28,17, 1,15,23,26, 5,18,31,10,
  2,8,24,14,32,27, 3, 9,19,13,30, 6,22,11, 4,25,
  在f(Ri,Ki)算法描述图中,S1,S2...S8为选择函数,其功能是把6bit数据变为4bit数据。下面给出选择函数Si(i=1,2......8)的功能表:
选择函数Si
S1:
  14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
  0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
  4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
  15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13,
S2:
  15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
  3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
  0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
  13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9,
S3:
  10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
  13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
  13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
  1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12,
S4:
  7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
  13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
  10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
  3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14,
S5:
  2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
  14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
  4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
  11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3,
S6:
  12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
  10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
  9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
  4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13,
S7:
  4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
  13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
  1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
  6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12,
S8:
  13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
  1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
  7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
  2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11,
在此以S1为例说明其功能,我们可以看到:在S1中,共有4行数据,命名为0,1、2、3行;每行有16列,命名为0、1、2、3,......,14、15列。
  现设输入为: D=D1D2D3D4D5D6
令:列=D2D3D4D5
  行=D1D6
  然后在S1表中查得对应的数,以4位二进制表示,此即为选择函数S1的输出。下面给出子密钥Ki(48bit)的生成算法
  从子密钥Ki的生成算法描述图中我们可以看到:初始Key值为64位,但DES算法规定,其中第8、16、......64位是奇偶校验位,不参与DES运算。故Key 实际可用位数便只有56位。即:经过缩小选择换位表1的变换后,Key 的位数由64 位变成了56位,此56位分为C0、D0两部分,各28位,然后分别进行第1次循环左移,得到C1、D1,将C1(28位)、D1(28位)合并得到56位,再经过缩小选择换位2,从而便得到了密钥K0(48位)。依此类推,便可得到K1、K2、......、K15,不过需要注意的是,16次循环左移对应的左移位数要依据下述规则进行:
循环左移位数
1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1
  以上介绍了DES算法的加密过程。DES算法的解密过程是一样的,区别仅仅在于第一次迭代时用子密钥K15,第二次K14、......,最后一次用K0,算法本身并没有任何变化。

二、DES算法理论图解

 

DES的算法是对称的,既可用于加密又可用于解密。下图是它的算法粗框图。其具体运算过程有如下七步。

 

三、DES算法的应用误区 

  DES算法具有极高安全性,到目前为止,除了用穷举搜索法对DES算法进行攻击外,还没有发现更有效的办法。而56位长的密钥的穷举空间为256,这意味着如果一台计算机的速度是每一秒种检测一百万个密钥,则它搜索完全部密钥就需要将近2285年的时间,可见,这是难以实现的,当然,随着科学技术的发展,当出现超高速计算机后,我们可考虑把DES密钥的长度再增长一些,以此来达到更高的保密程度。
  由上述DES算法介绍我们可以看到:DES算法中只用到64位密钥中的其中56位,而第8、16、24、......64位8个位并未参与DES运算,这一点,向我们提出了一个应用上的要求,即DES的安全性是基于除了8,16,24,......64位外的其余56位的组合变化256才得以保证的。因此,在实际应用中,我们应避开使用第8,16,24,......64位作为有效数据位,而使用其它的56位作为有效数据位,才能保证DES算法安全可靠地发挥作用。如果不了解这一点,把密钥Key的8,16,24,..... .64位作为有效数据使用,将不能保证DES加密数据的安全性,对运用DES来达到保密作用的系统产生数据被破译的危险,这正是DES算法在应用上的误区,留下了被人攻击、被人破译的极大隐患。

- 作者: wpf2006 2007年04月2日, 星期一 12:00  回复(0) |  引用(0) 加入博采

取得设备信息
2  取得设备信息
TCHAR wszMachineName[128];
 SystemParametersInfo(SPI_GETOEMINFO, sizeof(wszMachineName),
             &wszMachineName, 0);
取得设备OEM信息。
TCHAR wszVersion[256];
 SystemParametersInfo(SPI_GETPLATFORMTYPE, sizeof(wszVersion),
             &wszVersion, 0);
取得平台信息。

- 作者: wpf2006 2007年04月2日, 星期一 10:38  回复(0) |  引用(0) 加入博采

如何获得deviceid
如何获取 Pocket PC 2002 装置的序列号(以下例程获取的装置的序列号通常是Flash ROM的ID号.)?
--------------------------------------------------------------------------------
   从 Pocket PC 2000 开始, 微软就建议OEM厂商提供一个叫 KernelIoControl 的函数, 以便用户能访问Pocket PC 2002装置内建的序列号; 遗憾的是, 几乎没有厂商提供这个支持.
从 Pocket PC 2002 开始, 微软开始强制OEM厂商提供此函数. 目前市面上所有经过biplip测试的Pocket PC 2002装置都支持这个函数调用.
以下是个调用例子:

#include 
extern "C" __declspec(dllimport) 
BOOL KernelIoControl(
  DWORD dwIoControlCode, LPVOID lpInBuf, DWORD nInBufSize, 
  LPVOID lpOutBuf, DWORD nOutBufSize, LPDWORD lpBytesReturned
);

#define IOCTL_HAL_GET_DEVICEID CTL_CODE(FILE_DEVICE_HAL, 21, METHOD_BUFFERED, FILE_ANY_ACCESS)

CString GetSerialNumberFromKernelIoControl()
{
 DWORD dwOutBytes;
 const int nBuffSize = 256;
 byte arrOutBuff[nBuffSize];

 BOOL bRes = ::KernelIoControl(IOCTL_HAL_GET_DEVICEID, 
                                     0, 0, arrOutBuff, nBuffSize, &dwOutBytes);

 if (bRes) {
  CString strDeviceInfo;
  for (unsigned int i = 0; i   CString strNextChar;
   strNextChar.Format(TEXT("%02X", arrOutBuff[i]);
   strDeviceInfo += strNextChar;
  }
  CString strDeviceId = 
   strDeviceInfo.Mid(40,2) + 
   strDeviceInfo.Mid(45,9) + 
   strDeviceInfo.Mid(70,6);

  return strDeviceId;
 } else {
  return _T("";
 }
}

- 作者: wpf2006 2007年04月2日, 星期一 10:06  回复(0) |  引用(0) 加入博采

WindowsCE下Unicode和Ansi字符间互相转换的例子
嵌入开发(WinCE)的一些经验[摘录] ---1
WindowsCE下Unicode和Ansi字符间互相转换的例子
纵所周知,WindowsCE下编程99%的问题都和Unicode有关.比如文件编辑,一般都保存为Ansi格式;无线通讯中控制Modem需要发送的AT指令,必须是Ansi格式;网络通讯中,PC端一般都是Ansi的,为了和PC上协议兼容,在WindowsCE中必须把要发送的一个指令从Unicode转换成Ansi格式...等等.很多初学者对于这些问题总是感到很麻烦.其实WindowsCE中有标准的API实现了Unicode和Ansi字符间的互转.下面就是网络通讯中的程序片断.
m_psocket是指向一个从CCeSocket派生的类,如果没有连接的话其为NULL;
m_snd是要发送的CString,和一个EDIT相对应. 
  //发送函数片断
  void CClient1Dlg::OnButtonSend() 
  {
        // TODO: Add your control notification handler code here
        if (!m_psocket) //无Socket连接,退出
        {
                MessageBox(TEXT("无连接!",TEXT("信息");
                return;
        }
        UpdateData(TRUE); //保存输入的字符串到m_snd
        unsigned char buf[129]; //发送缓冲区
        ZeroMemory(buf,sizeof(buf)); //缓冲区清零
        CString tmpstr(m_snd); //复制要发送的字符串
        int multibytelen=WideCharToMultiByte( //计算从Unicode转换到Ansi后需要的字节数
        CP_ACP, //根据ANSI code page转换
        WC_COMPOSITECHECK | WC_DEFAULTCHAR, //转换出错用缺省字符代替
        tmpstr.GetBuffer(m_snd.GetLength()), //要转换的字符串地址
                m_snd.GetLength(), //要转换的个数
                0, //转换后字符串放置的地址
                0, //最多转换字符的个数,为0表示返回转换Unicode后需要多少个字节
                0, //缺省的字符:"\0"
                0 //缺省的设置
         ;
        WideCharToMultiByte( //转换Unicode到Ansi
                CP_ACP,
                WC_COMPOSITECHECK | WC_DEFAULTCHAR,
                tmpstr.GetBuffer(m_snd.GetLength()),
                m_snd.GetLength(),
                (char *)buf, //转换到缓冲区中
                128, //最多128个字节
                0,
                0
          ;
        int sendcount=m_psocket->Send(buf,multibytelen+1); //发送转换后的缓冲区
        CString statusstr;
        statusstr.Format(TEXT("共发送字节数:%d",sendcount);
        m_status.SetWindowText(statusstr); //更新显示栏
  }
程序接收到的字符串最后保存到CString tmpstr中. 
  //接收函数片断
  void MyCeSocket::OnReceive(int nErrorCode) 
  {
        // TODO: Add your specialized code here and/or call the base class
        unsigned char p[129]; //接受缓冲区
        ZeroMemory(p,sizeof(p)); //接收缓冲区清零
        this->Receive(p,128); //接收128个字节
        int widecharlen=MultiByteToWideChar( //计算从Ansi转换到Unicode后需要的字节数
                CP_ACP,
                MB_COMPOSITE,
                (char*)p, //要转换的Ansi字符串
                -1, //自动计算长度
                0,
                0
        ;
        CString tmpstr;
        tmpstr.GetBuffer(widecharlen); //为转换后保存Unicode字符串分配内存
        MultiByteToWideChar( //从Ansi转换到Unicode字符
                CP_ACP,
                MB_COMPOSITE,
                (char*)p,
                -1,
                tmpstr.GetBuffer(widecharlen), //转换到tmpstr
                widecharlen //最多转换widecharlen个Unicode字符
        ;
        m_clientdlg->m_listbox.InsertString(0,tmpstr); //插入到listbox中显示
        CCeSocket::OnReceive(nErrorCode);
  }
注意:以上代码都是在WindowsCE样板机上运行,PC端发送和接收到的字符均为Ansi格式的.把以上代码稍微修改一下用到自己的产品中,就可以利用以前的协议无缝连接PDA和PC.
以上代码在MicroSoft Embed Visual C++ 3.0 + 联想天玑5100(WindowsCE3.0)
+ Eagle Tec 10M CF卡通过.

- 作者: wpf2006 2007年04月2日, 星期一 10:04  回复(0) |  引用(0) 加入博采

全程键盘钩子的一种简单实现
给DLL初学者——全程键盘钩子的一种简单实现 [转]

标题   给DLL初学者——全程键盘钩子的一种简单实现     选择自 mahongxi 的 Blog  
关键字   关键字:DLL 键盘钩子 
出处    
 
 随着中间件技术的发展, DLL越来越为程序员所关注,因为使用DLL具有一系列优点,所以程序设计人员可能更多的在自己的软件中采用这种技术。

下面我就把以前做过的一个简单的全程键盘钩子分析一下。

钩子[以下简称Hook]是应用程序在Microsoft Windows 消息处理过程中设置的用来监控消息流并且处理系统中尚未到达目的窗口的某一类型消息过程的机制。如果Hook过程在应用程序中实现,若应用程序不是当前窗口时,该Hook就不起作用;如果Hook在DLL中实现,程序在运行中动态调用它,它能实时对系统进行监控。根据需要,我们采用的是在DLL中实现Hook的方式[关于HOOK更详细的资料请查阅资料]。

在VC中新建一Win32 Dynamic-Link Library 工程,工程名为KBLock。AppWizard会生成相关文件,编译生成的KBLock.cpp:

#include "stdafx.h"

#include "KBLock.h"

 

 

HHOOK hhkHook=NULL;                            //定义钩子句柄

HINSTANCE hInstance=NULL;                  //程序实例

 

//下面的DLLMain相当于Win32程序中的WinMain函数,是入口点

BOOL APIENTRY DllMain( HANDLE hModule, 

                       DWORD  ul_reason_for_call, 

                       LPVOID lpReserved

                                    

{

    switch (ul_reason_for_call)

       {

              case DLL_PROCESS_ATTACH:

              case DLL_THREAD_ATTACH:

              case DLL_THREAD_DETACH:

              case DLL_PROCESS_DETACH:

                     break;

    }

       hInstance=(HINSTANCE)hModule;          //得到DLL实例

    return TRUE;

}

 

 

//这是处理键盘消息的主要函数,在其中进行禁止操作

LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)

{

       if (nCode < 0)

       {

              return CallNextHookEx(hhkHook,nCode,wParam,lParam);

       }

       if (nCode != HC_ACTION)

       {

              return CallNextHookEx(hhkHook,nCode,wParam,lParam);

       }

 

//给出提示:键盘已经被锁定,要进行判断,看是否已有提示窗口,否则会弹个没完

       if (!::FindWindow(0, "KeyBoard Locked")

       {

              ::MessageBox(0,"键盘已经锁定!!!","KeyBoard Locked",MB_OK);

       }

    return 1;   //没有return CallNextHookEx(hhkHook,nCode,wParam,lParam)则不会把消息//传递下去,所以我们的键盘就不起作用了

}

// This is an example of an exported variable

//导出函数:启动键盘锁定

BOOL EnableKeyboardCapture()

{

       if(!(hhkHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)HookProc,hInstance,0)))

              return FALSE;

       return TRUE;

}

//导出函数:解除键盘锁定

BOOL DisableKeyboardCapture()

 {

        return UnhookWindowsHookEx(hhkHook);

 }

 

 

上面就是DLL中最重要的代码,当然要使DLL能正常工作还要编辑KBLock.h文件:

__declspec(dllexport) BOOL EnableKeyboardCapture();       //加载钩子

__declspec(dllexport) BOOL DisableKeyboardCapture();       //卸载钩子

再编辑KBLock.def

; KBLock.def : Declares the module parameters for the DLL.

 

LIBRARY      "KBLock"

DESCRIPTION  'KBLock Windows Dynamic Link Library'

 

EXPORTS

    ; Explicit exports can go here

 

 EnableKeyboardCapture @1

 DisableKeyboardCapture @2

 

这样我们用Depends.exe查看这个DLL时,就会发现这两个导出函数了。

 

DLL方面的工作已经完成,这样我们就可以在程序中调用它了。

虽然DLL是由VC开发的,但调用它的前台程序可以用任何其它支持DLL调用的语言如:VB、VC、DELPHI、Win32asm实现,下面还是以VC为例,实现DLL的调用。

建一基于Dialog的工程,在其中加入两个按钮:“Lock KeyBoard”“UnLock”

在CexeDlg类中加入一个成员函数:

/*sign = TRUE 锁定  sign = FALSE解锁*/

BOOL CExeDlg::KBLock(BOOL sign)

{

       hDLL=::LoadLibrary((LPCTSTR)"KBLock";       //加载DLL

       if (hDLL!=NULL)

       {loadhook=(LOADHOOK)::GetProcAddress (hDLL,"EnableKeyboardCapture";

       unloadhook=(UNLOADHOOK)::GetProcAddress (hDLL,"DisableKeyboardCapture";

       

       if(loadhook==NULL||unloadhook==NULL)

       {::MessageBox(0,"对不起,本功能不能使用!!!","Somthing Wrong",MB_OK);

       return 0;

       }

       if(sign)

              loadhook();

       else

       {

              unloadhook();

              ::FreeLibrary(hDLL);

       }

       return 1;

       }

       ::MessageBox(0,"动态库加载失败!!!","Somthing Wrong",MB_OK);

       return 0;

}

 

其中用到了事先定义好的全局变量:

typedef BOOL (CALLBACK *LOADHOOK)();

typedef BOOL (CALLBACK *UNLOADHOOK)();

HINSTANCE hDLL=NULL;

LOADHOOK loadhook;

UNLOADHOOK unloadhook;

 

这样我们在两个按钮中分别加入KBLock(TRUE); 和 KBLock(FALSE);即可。

- 作者: wpf2006 2007年04月2日, 星期一 10:03  回复(0) |  引用(0) 加入博采