关于作者

姓名:

性别:男

出生日期:--

地区:

联系电话:

QQ:--

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

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:63
评论个数:25
留言条数:16




Powered by BlogDriver 2.1

为笑而狂的博客

 

欢迎访问为笑而狂的博客

文章

BGCPro有bug,删除了

经过一段时间的使用,发现有bug,可能是使用D版的原因,在调试程序时有莫名其妙的错误,停止使用了,现在想找一个开源的界面类。VC写界面真的很痛苦

以下是转自CSDN的文章,希望能找到一些有用的东西。 

界面技术概述
在做“HOOK文件打开/保存对话框”的过程中,我首先研究了界面库的相关知识。界面库一般都是由C/C++这种中低级语言编码,这是因为在Windows下的界面库实现技术大都以直接操作控制Windows的消息和调用Windows的API为主,这就是这种中低级语言的优势了。无论何种界面库,最为根本的原理就是获得或者截获窗口的某些消息,按照自己的需要处理这些消息,画出自己需要的界面。
按照Windows下的界面库的使用方法来分类,可以分为两种:
1、 通过派生、继承界面库中的类来使用库。这类界面库现在是占绝大多数。这类界面库通常可以对同种类型的控件、窗口自己控制显示风格。这种类型的界面库典型的代表就是GuiToolkit、ProfUIS。
2、 通过Link头文件,使用DLL来使用的界面库。这类界面库一般都是商业化的界面库。这类界面库一般对于同种类型的控件、窗口都是显示统一的风格。这种类型的界面库的典型代表是Skin++、AppFace。
上面的分类,其实同时也代表着两种界面库实现技术,也就是获取用于自绘窗口的消息的两种来源:
1、 通过子类化、超类化改变窗口风格。其实就是调用Windows的API SetWindowLong或者通过类的派生和继承来改变Windows窗口的默认的消息处理函数。
2、 使用HOOK技术改变Windows的默认消息处理。

一、SetWindowLong
 SetWindowLong(HWND hWnd,//需要改变UI的窗口的窗口句柄
Int nIndex,//替换窗口的默认消息处理函数时为GWL_WNDPROC
Long dwNewLong)//新的默认的窗口消息处理函数
调用这个API函数可以替换一个窗口的默认的消息处理函数,这样就可以在新的窗口消息处理函数中截获到目标窗口的相关消息,然后根据需要处理这些消息。这个API用于获取当前窗口的消息,它不能获取窗口中子窗口的消息。
 这种类型的界面库,一般是开源的或者是提供了头文件和Lib文件的界面库。
这个Windows API大家很可能很少直接调用,但是它的封装――――SubclassWindow这个成员函数,我想大家都使用过。先让我们看看各种类型的子类化过程中的SetWindowLong都藏在什么地方,它们是如何工作的。
1、 MFC下的实现
在MFC程序中,可以先在工程中添加一个用于子类化的窗口类,然后就可以通过ClassWizard这个工具来完成剩下的子类化的工作了。我们以一个自定义的Button类CMyButton类来举例。在打开ClassWizard为我们的基于Dialog的工程中的一个Button资源添加一个对应的变量的时候我们就可以看到可以直接定义了CMyButton类,如下图:
 我们为ID为IDC_BUTTON1的一个按钮资源定义一个类型为CMyButton的成员变量m_MyBtn。这时候ClassWizard就会在重载的虚函数DoDataExchange中为我们添加上一条语句,如下:
void CMFCSampleDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CMFCSampleDlg)
 DDX_Control(pDX, IDC_BUTTON1, m_MyBtn);//这就是ClassWizard为我们添加的
 //}}AFX_DATA_MAP
}
让我们来看看DDX_Control这个函数为我们做了什么,在MFC的源代码DLGDATA.CPP文件中我们能找到这个函数的源代码:
void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd& rControl)
{
 if (rControl.m_hWnd == NULL)    // not subclassed yet
 {
 ……

  //注意看看,噢,原来SubclassWindow在这里
  if (!rControl.SubclassWindow(hWndCtrl))
  {
   ASSERT(FALSE);      // possibly trying to subclass twice?
   AfxThrowNotSupportedException();
  }
……

 }
}

让我们再看看MFC下的SubclassWindow这个成员函数的实现,在MFC的源代码wincore.cpp中可以看到所有MFC下窗口类的基类CWnd中的SubclassWindow的实现:
BOOL CWnd::SubclassWindow(HWND hWnd)
{
 if (!Attach(hWnd))
  return FALSE;

 // allow any other subclassing to occur
 PreSubclassWindow();

 // now hook into the AFX WndProc
 WNDPROC* lplpfn = GetSuperWndProcAddr();
 //注意看看,原来它也是使用SetWindowLong啊
 WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,
  (DWORD)AfxGetAfxWndProc());
 ……
 …

 return TRUE;
}
上面的源代码跟踪说明,ClassWizard为我们自动完成的子类化工作中,其实也是调用的SetWindowLong这个Windows API。当然在MFC下你也可以自己调用SubclassWindow这个成员函数去手动的完成子类化。

2、 ATL/WTL下的实现
在ATL的源代码中,文件ATLWIN.H文件中,我们可以找到所有ATL窗口类的基类CwindowImplBaseT中的SubClassWindow的实现:
template <class TBase, class TWinTraits>
BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd)
{
 ATLASSERT(m_hWnd == NULL);
 ATLASSERT(::IsWindow(hWnd));
 m_thunk.Init(GetWindowProc(), this);
 WNDPROC pProc = (WNDPROC)&(m_thunk.thunk);
 //注意看看,它也是调用SetWindowLong的!
 WNDPROC pfnWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc);
 if(pfnWndProc == NULL)
  return FALSE;
 m_pfnSuperWindowProc = pfnWndProc;
 m_hWnd = hWnd;
 return TRUE;
}
在ATL/WTL没有MFC下的那种自动子类化的机制,如果需要子类化一般都是直接调用SubclassWindow这个成员函数。

二、SetWindowsHookEx
SetWindowsHookEx( int idHook,//HOOK的类型
HOOKPROC lpfn,//HOOK的回调函数
HINSTANCE hMod,//应用程序的实例句柄
DWORD dwThreadId)//线程的ID
SetWindowsHookEx这个API可以设置很多种类型的HOOK,它们分别在不同的时间截获线程ID为dwThreadId的线程的不同消息。在编写界面库的时候一般设置类型为WH_CALLWNDPROC的HOOK,用以在窗口处理消息之前获得消息并进行处理。在HOOK的CALLBACK函数中可以获得窗口句柄,进而获得窗口的类型和窗口的风格,这样就可以知道需要处理的消息类型。一般都是从截获WM_CREATE消息开始,然后处理WM_PAINT、WM_NCPAINT等等和UI有关的消息以达到自绘窗口UI的目的。
使用HOOK技术的界面库中当然也可以使用SetWindowLong技术,在C++写的界面库中一般是在获得了窗口句柄以后,即使用SetWindowLong技术将窗口句柄关联到一种特定的窗口类上。并且可以根据窗口的风格让同一个类做出不同风格的显示效果,可惜的是现在市面上的界面库都很少进行这种比较繁琐的区分工作。
这种类型的界面库一般都是以DLL文件格式出现,现在最为流行的是将界面库封装为一个COM DLL,在应用程序中创建这个COM组件,获得相关的接口,调用相关的接口函数来为应用程序安装HOOK。在做“Picasso风格的文件打开/保存对话框”过程中,我们一直以Yahoo Messenger的Save对话框作为参考,其实Yahoo Messenger就是使用的这种类型的界面库的。Yahoo messenger的界面库截获了进程中所有线程的消息,并做了相关的处理,所以它可以截获一些系统的窗口的创建消息和UI相关的消息,以达到改变Windows系统窗口的显示风格的目的。

最后介绍一下个比较好的开源的界面库或者例子。
1、 ClassXP(http://www.yonsm.net/read.php?26
一个个人的开源界面库,C语言写的,不完善,使用HOOK技术。Yahoo messenger的界面库就类似于这个界面库,只不过Yahoo messenger做的更完善而已。
2、 ProfUIS(http://www.codeproject.com/docking/prod_profuis.asp
一个部分开源的界面库,有完善功能的商业版。比较完善,MFC写的,使用的是SetWindowLong技术。
3、 GuiToolkit(http://www.codeproject.com/library/guitoolkit.asp
一个开源的界面库,比较完善,MFC写的,使用的是SetWindowLong技术。
4、 MSDN中的ControlSpy例子。
MSDN中的一个例子,用于了解各种控件的消息。

- 作者: suyong5968 2007年01月6日, 星期六 17:20  回复(2) |  引用(1) 加入博采

IceSword冰刃使用方法[转] 大 | 中 | 小
有这么一款软件,它专为查探系统中的幕后黑手——木马和后门而设计,内部功能强大,它使用了大量新颖的内核技术,使内核级的后门一样躲无所躲,它就是——IceSword冰刃。
 IceSword冰刃(以下简称IceSword)是一款斩断系统黑手的绿色软件。在笔者的使用中,IceSword表现很令笔者满意,绝对是一把强悍的瑞士军刀——小巧、强大。
1.IceSword的“防”
 打开软件,看出什么没?有经验的用户就会发现,这把冰刃可谓独特,它显示在系统任务栏或软件标题栏的都只是一串随机字串“CE318C”,而并是通常所见的软件程序名(见图1)。这就是IceSword独有的随机字串标题栏,用户每次打开这把冰刃,所出现的字串都是随机生成,随机出现,都不相同(随机五位/六位字串),这样很多通过标题栏来关闭程序的木马和后门在它面前都无功而返了。另外,你可以试着将软件的文件名改一下,比如改为killvir.exe,那么显示出来的进程名就变为了killvir.exe。现在你再试着关闭一下IceSword,是不是会弹出确认窗口?这样那些木马或后门就算通过鼠标或键盘钩子控制窗口退出按钮,也不能结束IceSword的运行了,只能在IceSword的面前乖乖就范了。

点击在新窗口中浏览此图片

 2.IceSword的“攻”

 如果IceSword只有很好的保护自身功能,并没有清除木马的能力,也不值得笔者介绍了。如果大家还记得本刊2005年第5期《如何查杀隐形木马》一文,那一定会觉得IceSword表现相当完美了。但这只所谓的隐身灰鸽子,在IceSword面前只算是小儿科的玩意。因为这只鸽子只能隐身于系统的正常模式,在系统安全模式却是再普通不过的木马。而IceSword的作者就在帮助中多次强调IceSword是专门针对功能强大的内核级后门设计的。今天,笔者通过一次经历来说明IceSword几招必杀技。
前段时间,笔者某位朋友的个人服务器(Windows 2003),出现异常,网络流量超高,朋友使用常规方法只可以清除简单的木马,并没有解决问题,怀疑是中了更强的木马,于是找来笔者帮忙。笔者在询问了一些情况后,直接登录到系统安全模式检查,谁知也没有什么特别发现。于是笔者尝试拿出IceSword这把“瑞士军刀”……
 第一步:打开IceSword,在窗口左侧点击“进程”按钮,查看系统当前进程。这个隐藏的“幕后黑手”马上露出马脚(见图2),但使用系统自带的“任务管理器”是看不到些进程的。注意,IceSword默认是使用红色显示系统内隐藏程序,但IceSword若在内核模块处显示多处红色项目并不都是病毒,我们还需要作进一步的技术分析及处理。
点击在新窗口中浏览此图片

别以为只是系统自带的任务管理器功能弱,未能发现。我们又用了IceSword与Process Explorer(另一款功能强大的进程查看软件)进行对比,同样也没办法发现“幕后黑手”的踪影
点击在新窗口中浏览此图片

第二步:点击窗口左侧的“服务”按钮,来查看系统服务。这时就可以看到如图4所示的情况了,这个木马的服务也是隐藏的,怪不得笔者未能发现行踪。
点击在新窗口中浏览此图片

第三步:既然看了服务,也应该查查注册表[HKEY_LOCAL_ MACHINE SYSTEM\ CurrentControlSet \Services]的情况。反正IceSword也提供查看/编辑注册表功能,正好和系统的“注册表编辑器”也来个对比,点击窗口左侧的“注册表”标签,然后打开依次展开[HKEY_LOCAL_ MACHINE\SYSTEM\CurrentControlSet\Services]项(见图5)。真是不比不知道,一比吓一跳。看来,系统内置工具还是选择“沉默”,还记得《如何查杀隐形木马》一文吧,虽说鸽子在正常模式下,它的主服务也能隐藏,但它在系统的“注册表编辑器”内完全是显示的,更不要说目前是安全模式。仔细看看,既然已经从IceSword得到可靠情报,得知“幕后黑手”位于系统目录E:\Windows\system32\wins下.
点击在新窗口中浏览此图片

第五步:剩下的事容易多了,在IceSword中点击“查看”标签下的“进程”按钮,右击刚刚发现的隐藏进程,选择“结束进程”。然后用IceSword删除那三个木马文件,最后,还要删除多余的服务项——那两个HackerDefender*的注册表键值即可。清理完这只“黑手”后,再使用杀毒软件重新杀一遍系统,确认没有其他的木马。
 上面的例子充分体现了IceSword的魅力所在。正如作者所言,IceSword大量采用新颖技术,有别于其他普通进程工具,比如IceSword就可以结束除Idle进程、System进程、csrss进程这三个进程外的所有进程,就这一点,其他同类软件就是做不到的。当然有些进程也不是随便可以结束的,如系统的winlogon.exe进程,一旦杀掉后系统就崩溃了,这些也需要注意。
 还等什么,这么好用、强悍的“瑞士军刀”,还不赶快准备一把防身?

- 作者: suyong5968 2006年10月27日, 星期五 14:43  回复(1) |  引用(1) 加入博采

创建多级目录

(自己的代码)

// 创建多级目录
BOOL CDownloadThread::MakeDirectory(CString strFilePath)
{
 HANDLE fFile;   //文件句柄
 WIN32_FIND_DATA fileinfo; //文件信息结构体
 CStringArray m_arr;  //字符串数组用于存储目录结构
 BOOL bOk;  //用于测试创建目录成功
 int nX = 0; //目录计数
 CString temStr = ""; //临时字符串

 fFile = FindFirstFile(strFilePath,&fileinfo);
 if (fileinfo.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)  //如果该目录存在
 {
  FindClose(fFile);
  return TRUE;
 }

 //不存在,开始作创建的准备工作
 m_arr.RemoveAll();     //清空字符串数组
 for (nX = 0;nX < strFilePath.GetLength();nX++)  //开始解析字符串
 {
  if (strFilePath.GetAt(nX) != '\\')   //如果字符不是"\"
  {
   temStr += strFilePath.GetAt(nX);  // 拷贝字符到临时字符串直到遇到路径分格符"\"
  }
  else    //遇到路径分格符"\"
  {
   m_arr.Add(temStr);    //增加解析的路径到字符串数组
   temStr += _T("\\");   //增加路径分格符
  }
  if (nX == strFilePath.GetLength()-1)   //最后一个
  {
   m_arr.Add(temStr);
  }
 }  
 FindClose(fFile);   //关闭文件

 //开始分级创建目录
 for (nX = 1; nX < m_arr.GetSize(); nX++)
 {
  temStr = m_arr.GetAt(nX);    //取解析好的路径
  bOk = ::CreateDirectory(temStr,NULL);   //从一级目录开始创建

  //如果目录存在,bOk将返回FALSE
  if(bOk)
  {
   SetFileAttributes(temStr,FILE_ATTRIBUTE_NORMAL); //如果创建目录成功设置属性为Normal
  }
 }
 //再检查一次目录是否创建成功
 fFile = FindFirstFile(strFilePath,&fileinfo);
 m_arr.RemoveAll();
 if (fileinfo.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
 {
  FindClose(fFile);   //目录存在
  return TRUE;
 }
 else
 {
  FindClose(fFile);  //不存在
  return FALSE;
 }
}

- 作者: suyong5968 2006年10月3日, 星期二 10:18  回复(0) |  引用(1) 加入博采

CppUnit使用心得

1.   在stdafx.h中添加

//装载CppUnit库
#ifndef _DEBUG
#pragma comment(lib, "cppunit_dll.lib")
#pragma comment(lib, "TestRunner.lib")
#else
#pragma comment(lib, "cppunitd_dll.lib")
#pragma comment(lib, "TestRunnerd.lib")
#endif

2.    在对话框程序的InitInstance()中注释掉

/*
 CTestDlg dlg;
 m_pMainWnd = &dlg;
 INT_PTR nResponse = dlg.DoModal();
 if (nResponse == IDOK)
 {
  // TODO: 在此放置处理何时用“确定”来关闭
  //对话框的代码
 }
 else if (nResponse == IDCANCEL)
 {
  // TODO: 在此放置处理何时用“取消”来关闭
  //对话框的代码
 }

 // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
 // 而不是启动应用程序的消息泵。
 */

添加
 CppUnit::MfcUi::TestRunner runner;
 CppUnit::Test *pSuite = CppUnit::TestFactoryRegistry::getRegistry("TestSetCFG").makeTest();
 runner.addTest(pSuite);
 runner.run();

包含              #include <cppunit/ui/mfc/TestRunner.h>
和测试头文件 #include "TestSetCFG.h"

3.   测试类的头文件添加代码

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestFixture.h>
#include <cppunit/Asserter.h>

class CTestSetCFG : public CppUnit::TestFixture
{
 CPPUNIT_TEST_SUITE(CTestSetCFG);
 CPPUNIT_TEST(Test1);
 CPPUNIT_TEST_SUITE_END();
public:
 CTestSetCFG(void);
 ~CTestSetCFG(void);
public:
 void setUp()
 {
 }
 void tearDown()
 {
 }
 void Test1(void);
};

4.   测试类的实现部分添加代码

#include "StdAfx.h"
#include ".\testsetcfg.h"
#include <cppunit/TestResult.h>

//注册
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CTestSetCFG,"TestSetCFG");
void CTestSetCFG::Test1(void)
{

   ..................测试代码
}

- 作者: suyong5968 2006年10月2日, 星期一 13:56  回复(0) |  引用(1) 加入博采

VS2005如何进行单元测试

Team版的VS2005里面包含了完整的Test功能,具体有:Unit Test,WebTest和LoadTest.这一整套的测试基本涵盖了软件开发会使用到的测试功能.

我这里先从单元测试开始介绍(Unit Test).说起单元测试,很多使用.net进行开发的人员也许马上就想起了NUnit,实际上它是个很好的工具,在VS2005出来之前,我也一直使用.不过现在VS2005已经提供了与NUnit一样,甚至还要强大的功能,我们又有什么理由不使用呢?

OK,进入正题.首先我们要解决一个问题,为什么我们需要做单元测试?这个问题,对有经验的老程序员来说,并不算是问题.一个软件在开发的过程中,倘若不是非常微型的软件,那么我们通常会划分出若干个功能模块来,然后一个模块一个模块的进行开发.每个子模块完成后,我们并不知道它是否能正常的运行,尤其是当这个模块只是个中间件类似的代码块,那么我们为了减少之后可能出现的问题和debug的难度(可以想象,如果在整合时期进行测试或者是甚至还有其他的模块需要依赖该模块才能进行开发的时候,尽早的测试将会是非常的重要),我们常常会对这单个模块进行测试,比如写段小程序,人为的写入几个参数来调用组件等.不用怀疑了,这就是单元测试.我相信,大部分的程序员都做过这样的工作,而且也许还有许多程序员就如我上面所说的,单独写段小程序来进行单元测试(我自己以前也是如此),现在我们需要认真考虑下下一个问题了:如何进行高效的,高可靠的,甚至自动化的单元测试?

VSTS里的Unit Test可以帮助我们实现我们希望的绝大多数功能.我们从实际的项目开发入手来介绍.假设我们新建了一个.net项目,嗯,这是一个有关缓存的子项目,名字叫MyCache.我们很认真的设计了项目的架钩,进行了可行性分析,接口和抽象的建立,具体对象的建立,关系建立,最后编码完成了.项目经理叫我们不要高兴的太早,他要求我们必须对这个项目进行可靠的单元测试,因为这个子项目非常重要,将会被许多项目引用.尽管我们很有信心,但是没有办法,我们依然需要进行单元测试.我们使用了Visual Studio Team System开发了这个项目,于是我们理所当然的使用自带的Unit Test工具进行单元测试.

Step1.我们需要建立项目文件与测试文件的映射关系.
难道要我们去手动创建吗?这可是整个项目啊,里面也许包含了几十个类,数百个方法…当然没那么复杂!实际上,我们需要做的工作很少,只是动动鼠标,等几秒就可以了:)
在VS2005的IDE环境下,选择menu里的Test,继续选New Test项,这时将跳出个窗体,里面可以选择测试项目类型,这里我们选择Unit Test Wizard,确定,输入测试项目名,然后将又出现一个窗体,里面包含你当前的solution里的所有project,我们选上我们的MyCache,确定.OK,看见一个进度条,这是在执行测试代码的映射工作,等结束后,你就会发现,已经建立了一个测试项目了,里面的文件完全对应你的目标项目,每个类包含的方法也是与目标类的方法一一对应,非常简单,cool,mission complete!

Step2.运行我们的测试项目.
接下来,我们怎么进行测试呢?里面有许多的类和方法,很多方法上还带有像TestMethod这样的标签属性,但是我们关心的是,如何进行测试?绝对不是通常的F5来运行:(,在VSTS里,单元测试实际上有专门的管理工具.再次选到menu上的Test选项,移到windows上展开自菜单,里面有好几个选项,我们选择TestManager打开.在IDE窗口内出现了一个视图结构的东西,在分割线的右边是一个listView,里面全是当前测试项目包含的方法,我们随便选几个方法给勾上,右键,Run Checked Test,下边马上有出现了Test Result窗体,里面就是刚才你选择的方法.如果不出意外的话,你的这个窗体内的方法result应该都是failed之类的数据,嗯,先不管这个,最起码,我们已经运行了一次测试项目了,虽然有些奇怪,不过我们已经知道了如何运行一个测试项目了,那么再进入下一个step吧:)

Step3.看看我们的测试代码里都有些什么.
虽然知道了怎么运行测试项目,但莫名其妙的全部出错,是怎么回事呢?我们进入测试项目具体的代码来看看.
我们会发现,每个测试类的名称就是对应的目标类的名称+"test",里面的方法也是如此,如果是构造函数,则是诸如
ConstructorTest或ConstructorTestN这样的形式,N为重载次数.每个方法里面的代码看上去也不奇怪,只是构造参数来调用而已,最后通过断言来判断(用过NUnit的朋友不会陌生吧?).我们试着直接把一个方法里的断言去掉看看,编译,TestManager,run,嘿,果然,去掉断言的方法就pass了!看来蒙老大不难呢,只要把所有的方法的断言都给去掉,然后给老大看测试后的结果,呵呵…

Step4.深入的了解一下方法上带有的属性的含义.
每个方法上几乎都带有TestMethod这个属性,我们直觉告诉我们,这肯定是表示被测试函数的意思.事实也正是如此,在Unit Test里,有许多测试属性,常用的如下:

属性描述

TestClass()

该属性表示一个测试装置。

TestMethod()

该属性表示一个测试用例。

AssemblyInitialize()

在执行为执行选择的第一个 TestClass() 中的第一个 TestMethod() 之前,执行带有该属性的方法。

ClassInitialize()

带有该属性的方法在执行第一个测试之前调用。

TestInitialize()

带有该属性的方法在执行每个 TestMethod() 之前调用。

TestCleanup()

带有该属性的方法在执行每个 TestMethod() 之后调用。

ClassCleanup()

带有该属性的方法在执行 ALL 测试之后调用。

AssemblyCleanup()

在执行为执行选择的第一个 TestClass() 中的第一个 TestMethod() 之后,执行带有该属性的方法。

Description()

提供关于给定 TestMethod() 的描述。

Ignore()

由于某种原因忽略 TestMethod()TestClass()

ExpectedException()

当测试特定异常时,如果使用该属性指定的异常不是从实现代码引发,则测试不会失败。


需要注意的是,上面的属性不是可以适用于所有方法的,比如AssemblyInitialize()和ClassInitialize()是必须是静态方法的属性.
我们可以把初始化的操作放在他们里进行.

Step5.修改测试方法及其断言.
到现在,我们的思路开始清晰起来了,我们要开始做真正的测试了,不是仅仅去掉断言就pass那么简单了:)
我们的测试思路应该是这样:我们调用该方法,需要传入什么值,会影响什么值,当它执行之后,会产生怎样的期待值?我们把期待值与实际的值想比较,同时写下断言失败的message.
还是以我们的MyCahce为例,假如我们有个ListCache类,里面有个AddItemToTop(item)方法,表示把一个item插入到当前链表的头部.我们实际的测试函数该这么写
Guid id = System.Guid.NewGuid();
Item item = new Item(id);
list.AddItemToTop(item);
Assert.AreEqual(id, roomList.FirstLinkedItem.Key, "插入后查询获得的key值与插入的对象的key值不相等!");
通过比对插入后的链表的头部的key与之前保存的key值来判断,这是不是一次成功的入.
这只是个很简单的例子,我们当然应该根据具体的方法需要实现的功能来定义测试代码.

Step6.OVER
完成了上面5部,相信你已经对VSTS的Unit Test非常的熟悉了,接下来需要做的就是把你需要的测试的method都提供正确的测试代码,注意,这里我们甚至不要考虑我们本身的项目究竟有没有实现该功能,但我们应该该知道,我们需要什么功能.我们只针对应该产生的结果写测试代码.当测试不通过时,我们只需要修改我们的目标项目,而不再需要修改我们的测试项目.这其实正是TDD(测试驱动开发)的思想,我们如果要验证我们的方法有没有错,只需要run一下test即可,真正实现了全自动化单元测试,这里边的实际开发效率的提高,只有你在真正体会过后才能明白:)

- 作者: suyong5968 2006年10月1日, 星期日 16:18  回复(1) |  引用(1) 加入博采

使用CppUnit进行单元测试

一. CppUnit的安装

      从http://sourceforge.net/projects/cppunit   CppUnit的源码包. CppUnit是开源产品 , 当前最高版本为1.12.0. (在上面的链接所指向的页面上选择 Development Snapshot ).

       下载后,将源码包解压缩到本地硬盘. 以C:为例, 解压到C:\CppUnit-1.11.0 .  接下来进行编译工作. 在src/目录下, 将CppUnitLibraries.dsw工程文件用vc 打开.  因为这个工程是vc6建的工程, 所以会提示是否将此工程转为vc.net的工程格式. 选择'全是'就行.  然后在'生成'菜单中选择'生成解决方案',编译整个工程(要包括debug和Release版本,不然会有找不到×××.lib文件错误)编译通过以后, 在lib/目录下,会生成若干lib,和dll文件, 都以cppunit开头. cppunitd表示debug版, cppunit表示release版.

        然后配置vc环境:  工具菜单->选项->项目->vc++目录, 将C:\cppunit-1.11.0\include加入到vc 的 头文件目录中. 将C:\cppunit-1.11.0\lib 加入到vc的 连接库目录中. 然后将 C:\cppunit-1.11.0\lib路径加入到系统路径中(因为我们的测试程序需要cppunit的dll文件,所以要dll所在路径加入到系统路径(path)变量中, 或者将这些文件拷贝到系统目录中也可以).

二. CppUnit 的使用

       以上工作完成以后,就可以正式使用CppUnit了.

        我们新建一个控制台工程, 假定工程名为TDD1. 假设我们将在这个工程下开发, 那么根据测试驱动的原理,我们需要先建立一个单元测试框架.  在CppUnit下, 可以选择控制台方式和UI方式两种表现方案.我们选择UI方式.  那么现在在原来工程的基础上,再新增加一个对话框工程. 假设名为:Unit_Test .

        在CppUnit中, 是以TestCase为最小的测试单位, 若干TestCase组成一个TestSuite. 所以我们要先建立一个TestCase.

        新建一个类, 命名为CTestCase , 让其从CppUnit::TestCase派生.  为其新增一个方法,假设为 void doTest(); 我们将在这个函数中写入我们的一些测试代码.  切记要包含头文件

#include


class CTestCase : public CppUnit::TestCase
{
public:
    CTestCase(void);
    ~CTestCase(void);
    void doTest();
};

接下来, 我们要对我们的TestCase进行声明. 声明用到了三个宏.  

    CPPUNIT_TEST_SUITE();
    CPPUNIT_TEST();
    CPPUNIT_TEST_SUITE_END();

第一个宏声明一个测试包,第二个宏声明一个测试用例. 现在我们的CTestCase类看上去象这样.

class CTestCase : public CppUnit::TestCase
{
    CPPUNIT_TEST_SUITE(CTestCase);
    CPPUNIT_TEST(doTest);
    CPPUNIT_TEST_SUITE_END();
public:
    CTestCase(void);
    ~CTestCase(void);
    void doTest();
};

接下来,我们要注册我们的测试suite. 使用CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()来注册一个测试suite. 这个宏的第二个参数是我们注册的suite的名字. 在这里我们可以用字符串来代替, 但我们用一个静态函数来返回这个suite的名字.

    //TestCase.h 

    static std::string getSuiteName();

    //TestCase.cpp

    std::string CTestCase::getSuiteName()
    {
            return 'TDDExample3';
    }

    记得要在TestCase.h中包含 #include

    然后在 TestCase.cpp注册我们的suite.

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CTestCase , CTestCase::getSuiteName() );

     接下来我们写一个注册函数, 使其在运行期生成一个Test.

    

    static CppUnit::Test* getSuite();

    CppUnit::Test* CTestCase::getSuite()
    {
           CppUnit::TestFactoryRegistry& reg =
                 CppUnit::TestFactoryRegistry::getRegistry  ( CTestCase::getSuiteName() );
                 return reg.makeTest();
      }

      记住包含头文件:

       #include

       最后, 我们的单元测试建立一个UI测试界面.

       我们在CUnit_TestApp::InitInstance()函数中,将原先显示主对话框的代码以下面的代码取代:

    CppUnit::MfcUi::TestRunner runner;
    runner.addTest(CTestCase::getSuite());
    runner.run();

    必须先包含头文件:

#include
#include 'TestCase.h'
    到此为止, 我们已经建立好一个简单的单元测试框架. 我们可以在doTest()中加入我们的测试代码,来进行测试了.CppUnit会将测试结果通过一个界面显示出来.

     但此时我们虽然能编译通过,但在连接时会发生错误. 所以将下面两个库加入到工程中. 也可以在TestCase.h 中显式的加入:

#pragma comment(lib,'cppunit.lib')
#pragma comment(lib,'testrunnerd.lib')

   下面是完整的程序清单:

     //TestCase.h

#pragma once

#include

#include
#include
#include

#pragma comment(lib,'cppunit.lib')
#pragma comment(lib,'testrunnerd.lib')

class CTestCase : public CppUnit::TestCase
{
    CPPUNIT_TEST_SUITE(CTestCase);
    CPPUNIT_TEST(doTest);
    CPPUNIT_TEST_SUITE_END();
public:
    static std::string getSuiteName();
    static CppUnit::Test* getSuite();

public:
    CTestCase(void);
    ~CTestCase(void);
    void doTest();
};

//TestCase.Cpp

#include 'StdAfx.h'
#include '.\testcase.h'

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CTestCase , CTestCase::getSuiteName() );

CTestCase::CTestCase(void)
{
}

CTestCase::~CTestCase(void)
{
}

void CTestCase::doTest()
{
}


std::string CTestCase::getSuiteName()
{
    return 'TDDExample3';
}

CppUnit::Test* CTestCase::getSuite()
{
    CppUnit::TestFactoryRegistry& reg =
        CppUnit::TestFactoryRegistry::getRegistry( CTestCase::getSuiteName() );
    return reg.makeTest();
}

//Unit_Test.cpp

#include
#include 'TestCase.h'

BOOL CUnit_TestApp::InitInstance()
{

 InitCommonControls();

 CWinApp::InitInstance();

 AfxEnableControlContainer();

   CppUnit::MfcUi::TestRunner runner;
    runner.addTest(CTestCase::getSuite());
    runner.run();

 return FALSE;
}

现在编译,运行, 如图一:

进度条呈现绿色, 表示我们此次测试通过. 事实上我们的doTest是一个空函数,什么代码也没有写,当然能通过了. :)

     下面我们在doTest()中加点代码进去.

      char* p = NULL; p[0]='c';

     这段代码会引起一个访问异常. 看UnitCpp怎么来报告这段代码的错误. 

编译,运行如图2:



这时CppUnit会告诉我们dotest出现一个错误. 并提供了错误的一些信息.

          这样我们用CppUnit搭建一个简单单元测试框架.

参考文章:

   CppUnit测试框架入门 

  作者Blog http://blog.csdn.net/cpluser/

- 作者: suyong5968 2006年09月7日, 星期四 10:34  回复(0) |  引用(1) 加入博采

单元测试

单元测试

经过系统的几个月测试,对测试有一定的了解,发现单元测试是测试一个非常重要的一部分,于是对单元测试发生了兴趣,通过阅读资料、实践。对单元测试进行了一些总结,希望能够对大家有些帮助,得到抛砖引玉的作用。

1.     什么是单元测试

单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。

2.     为什么要使用单元测试

编写代码时,一定会反复调试保证它能够编译通过。如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。但代码通过编译,只是说明了它的语法正确;却无法保证它的语义也一定正确,没有任何人可以轻易承诺这段代码的行为一定是正确的。

      幸运,单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。但我们对单元测试也有一些认识的误区,误区有哪些呢?

1、  编写单元测试太花时间了

在开发时越早发现BUG,就能节省更多的时间,降低更多的风险。如果不及时修改错误,一后期发现的错误,调试修改很困难,更浪费时间;二是维护越多,代码的结构越乱,甚至改变当初的实际思路。

2、  运行测试的时间太长了

  合适的测试是不会让这种情况发生的。实际上,大多数测试的执行都是非常快的,因此你在几秒之内就可以运行成千上万个测试。但是有时某些测试会花费很长的时间。这时,需要把这些耗时的测试和其他测试分开。通常可以每天运行这种测试一次,或者几天一次。

3、  测试代码并不是我的工作

工作就是保证代码能够正确的完成,恰恰相反,测试代码是不可缺少的工作。

4、  不清楚代码的行为,所以也就无从测试

如果实在不清楚代码的行为,那么估计现在并不是编码的时候。如果并不知道代码的行为,那么你又如何知道你编写的代码是正确的呢?

5、  但是这些代码都能够编译通过

代码通过编译只是验证它的语法通过,但并不能保证它的行为就一定正确。

6、 项目进度吃紧时少做些测试,时间富裕时多做测试

  这是不重视软件测试的表现,也是软件项目过程管理混乱的表现,必然会降低软件测试的质量。一个软件项目的顺利实现需要有合理的项目进度计划,其中包括合理的测试计划,对项目实施过程中的任何问题,都要有风险分析和相应的对策,不要因为开发进度的延期而简单的缩短测试时间、人力和资源。因为缩短测试时间带来的测试不完整,对项目质量的下降引起的潜在风险,往往造成更大的浪费。克服这种现象的最好办法是加强软件过程的计划和控制,包括软件测试计划、测试设计、测试执行、测试度量和测试控制。

7、  软件测试是没有前途的工作,只有程序员才是软件高手

  项目的成功往往靠个别全能程序员决定,他们负责总体设计和程序详细设计,认为软件开发就是编写代码,给人的印象往往是程序员是真正的牛人,具有很高的地位和待遇。因此,在这种环境下,软件测试很不受重视,软件测试人员的地位和待遇自然就很低了,甚至软件测试变得可有可无。随着市场对软件质量的不断提高,软件测试将变得越来越重要,相应的软件测试人员的地位和待遇将会逐渐提高。在微软等软件过程比较规范的大公司,软件测试人员的数量和待遇与程序员没有多大差别,优秀测试人员的待遇甚至比程序员还要高。软件测试将会成为一个具有很大发展前景的行业,软件测试大有前途,市场需要更多具有丰富测试技术和管理经验的测试人员,他们同样是软件专家。

3.     单元测试有哪些优点

1、它是一种验证行为。

      程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

2、它是一种设计行为。

      编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。

3、它是一种编写文档的行为。

      单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

4、它具有回归性。

      自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随的快速运行测试。

4.     什么时候进行单元测试

单元测试越早越好,早到什么程度?XP开发理论讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的随便返回一个值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。

5.     由谁来进行单元测试

单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核。

6.     单元测试的任务

单元测试任务包括:1 模块接口测试;2 模块局部数据结构测试;3 模块边界条件测试;4 模块中所有独立执行通路测试;5 模块的各条错误处理通路测试。

模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。测试接口正确与否应该考虑下列因素:

1 、输入的实际参数与形式参数的个数是否相同;

2 输入的实际参数与形式参数的属性是否匹配;  

3 、输入的实际参数与形式参数的量纲是否一致;

4 调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;

5 调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;

6、调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;

7 调用预定义函数时所用参数的个数、属性和次序是否正确;

8 、是否存在与当前入口点无关的参数引用;

9 是否修改了只读型参数;

10 、对全程变量的定义各模块是否一致;

11、是否把某些约束作为参数传递。

如果模块内包括外部输入输出,还应该考虑下列因素:
  1 、文件属性是否正确;

  2 OPEN/CLOSE语句是否正确;

  3 格式说明与输入输出语句是否匹配;

  4、缓冲区大小与记录长度是否匹配;

  5、文件使用前是否已经打开;

  6、是否处理了文件尾;

  7、是否处理了输入/输出错误;

  8、输出信息中是否有文字性错误;

检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:

  1 、不合适或不相容的类型说明;

  2、变量无初值;

  3、变量初始化或省缺值有错;

  4、不正确的变量名(拼错或不正确地截断);

  5出现上溢、下溢和地址异常。

  除了局部数据结构外,如果可能,单元测试时还应该查清全局数据(例如FORTRAN的公用区)对模块的影响。

  在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。此时设计测试用例是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。此时基本路径测试和循环测试是最常用且最有效的测试技术。计算中常见的错误包括:

 1 误解或用错了算符优先级;

 2、混合类型运算;

 3、变量初值错;

 4、精度不够;

 5、表达式符号错。

比较判断与控制流常常紧密相关,测试用例还应致力于发现下列错误:

  1、不同数据类型的对象之间进行比较;

  2、错误地使用逻辑运算符或优先级;
  3、因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;
  4、比较运算或变量出错;
  5、循环终止条件或不可能出现;
  6、迭代发散时不能退出;
  7、错误地修改了循环变量。
  一个好的设计应能预见各种出错条件,并预设各种出错处理通路,出错处理通路同样需要认真测试,测试应着重检查下列问题:
  1、输出的出错信息难以理解;
  2、记录的错误与实际遇到的错误不相符;
  3、在程序自定义的出错处理段运行之前,系统已介入;
  4、异常处理不当;
  5、错误陈述中未能提供足够的定位出错信息。
    
边界条件测试是单元测试中最后,也是最重要的一项任务。众的周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。

7.     单元测试用例及用例设计

了解了单元测试的任务,下面介绍测试用例,测试用例也是单元测试的核心,决定你的单元测试是否成功。测试用例的核心是输入数据。预期输出是依据输入数据和程序功能来确定的,也就是说,对于某一程序,输入数据确定了,预期输出也就可以确定了,至于生成/销毁被测试对象和运行测试的语句,是所有测试用例都大同小异的。

单元测试测试用例一般采用逻辑覆盖法和基本路径法进行设计。

7.1.   逻辑覆盖法

逻辑覆盖是以程序内部的逻辑结构为基础的测试用例设计技术,这一方法要求测试人员对程序的逻辑结构有清楚的了解。逻辑覆盖可分为:语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖与路径覆盖。

1. 语句覆盖就是设计若干个测试用例,运行所测程序,使得每一可执行语句至少执行一次。

2. 判定覆盖就是设计若干个测试用例,运行所测程序,使得程序中每个判断的取真分支和取假分支至少经历一次。

3. 条件覆盖就是设计若干个测试用例,运行所测程序,使得程序中每个判断的每个条件的可能取值至少执行一次。

4. 判定--条件覆盖就是设计足够的测试用例,使得判断中每个条件的所有可能取值至少执行一次,同时每个判断的所有可能判断结果也至少执行一次。

5. 条件组合覆盖就是设计足够的测试用例,运行所测程序,使得每个判断的所有可能的条件取值组合至少执行一次。

6. 路径测试就是设计足够的测试用例,覆盖程序中所有可能的路径。

7.2.   基本路径法

基本路径测试法是在程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行路径集合,从而设计测试用例的方法。设计出的测试用例要保证在测试中程序的每个可执行语句至少执行一次。基本路径测试法包括以下5个方面:

1. 程序的控制流图:描述程序控制流的一种图示方法。

2. 程序环境复杂性:McCabe复杂性度量;从程序的环路复杂性可导出程序基本路径集合中的独立路径条数,这是确定程序中每个可执行语句至少执行依次所必须的测试用例数目的上界。

3. 导出测试用例。

4. 准备测试用例,确保基本路径集中的每一条路径的执行。

5. 图形矩阵:是在基本路径测试中起辅助作用的软件工具,利用它可以实现自动地确定一个基本路径集。

7.3.   单元测试用例设计案例

好久前看过一篇非常不错的单元测试用例设计案例,但没有保留下来,感觉好可惜。一个好的单元测试案例对单元测试的入门是非常重要,希望这个案例能够帮助你对单元测试一些了解和对单元测试重要性的一个认识。

例如币种换算单元函数,要对这个函数进行单元测试。应该怎么去做个函数的单元测试呢?首先考虑的是该币种换算单元函数的输入输出参数,其次是输出结果与期望值是否相等;再次建立单元测试处理类,并相应建立测试该函数的测试方法,按照JUnit规范一般是test****()

开始准备建立单元测试类及其测试方法,币种换算函数的输入参数为:目标币种,转换币种,金额,输出为转换后目标币种的金额。建立第一个简单的单元测试处理类及单元测试测试方法:

Public BigDecimal testCurryChange(){

           引用币种换算函数并输入测试参数,假如测试参数为:目标币种为人民币,转换币种为美元,金额为100.00。

           BigDecimal bgResult = 799.95;

           If(bdResult.equals(函数(”10”,”32”,100.00== false){

                    Throw new Exception(“转换错误!”);

           }

}

  第一个单元测试函数已经写完毕,可以进行单元测试,但不能完全测试币种换算函数是否完全正确,第一金额的不同转换结构可能不同;第二不同币种之间转换,结果不同。那下一步应该怎么做呢?

可以采用这样的方法,建立同上的多个函数,但输入参数不一样,如金额不一样;目标币种与转换币种不一样。这样也可以在不同的方向来检测币种换算函数是否正确。一般情况下对于这种情况下,金额输入参数处理会才用边界发,取一个最小值和最大值作为输入参数进行检验,另外是看实际情况,要换算多少中币种,进行穷举法,把所有的币种进行处理,根据金额,币种的组合分别进行测试。这儿就不一一写出代码。

但是上述方法虽然是一个可行的方法,但处理起来比较复杂,如果币种换算比较多,合起来要写很多的单元测试方法,用起来比较烦琐,那应该怎么处理呢?在这种情况下可以采用数组的方法+循环的方法来实现,这样就很容易避开上述方法的烦琐。具体实现就不列举出来,大家可以自行研究一下。

写单元测试也是一个非常有趣的事情,上述方法在不同的情况都有可能实现,不是每一个单元测试案例都要追求最完美,能够达到要求和实现目标即可。还有很多写测试案例的方法就不一一列举,大家可以根据实际情况而使用最好的方法。

8.     单元测试工具介绍

不同的开发语言,单元测试的工具也不一样。JAVA语言开发系统:目前比较流行的是JUnit.Net语言开发系统:目前比较流行的是:NUnitC/C++语言开发系统:目前比较流行的是CPPUnit。目前系统开发是用的JAVA,于是对JUnit研究的比较多,有机会可以和大家共同探讨一下,其他的如果有兴趣,大家也可以自行研究。还有很多的单元测试工具,可以根据具体情况而选择合适自己的单元测试工具。

  JUnit的简介:

         http://blog.csdn.net/askmyself/archive/2005/10/10/498599.aspx

        

  NUnit的简介:

         http://blog.csdn.net/litp/archive/2005/11/11/527684.aspx

       http://blog.csdn.net/litp/archive/2005/11/11/527684.aspx

 

  CPPUnit的简介:

         http://blog.csdn.net/lzw2005/archive/2005/09/23/488013.aspx

http://blog.csdn.net/casualgame/archive/2005/03/27/332162.aspx

  

通过单元测试的阅读和了解,希望能够把单元测试工具JUnit引入到目前的系统开发中来,而在实践过程中发现并不是十分可行,在进行测试的参数中并不能很好地获得一个数据库连接。于是通过别的方法来代替JUnit而达到单元测试,自己编写一个或多个单元测试业务处理类+Log4j(日志记录)也同样能够实现单元测试,相对来说这样比较麻烦,要自己写对象的创建释放、测试业务处理等等。在实际情况中,或者能够同样遇到这样的问题,希望大家能够在实践过程中找到自己合适的方法,而不是一定要采用单元测试工具。

9.     总结

总而言之,单元测试会让我们的开发工作变得更加轻松,让我们对自己的代码更加自信。无论是大型项目还是小型项目,无论是时间紧迫的项目还是时间宽裕的项目,只要代码不是一次写完永不改动,编写单元测试就一定超值,它已成为我们编码不可缺少的一部分。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=562609

 

- 作者: 2006年08月13日, 星期日 09:09  回复(0) |  引用(0) 加入博采

VC编程实现IE风格的界面
使用过IE浏览器的朋友都知道IE界面上的扁平工具条、地址栏,扁平工具栏上的按钮正常状态下为扁平态,按钮上的图像为灰色,当鼠标放在按钮上时,按钮突起(这种状态称为手柄),并且其上的图像变得鲜艳醒目,一些按钮上还有汉字说明或标有小黑三角的下拉按钮,单击时显示下拉菜单,这些技术是怎么实现的呢,本文针对这些问题介绍了如何利用VC编程来实现它们。

   IE风格的实现主要在主框架类的CMainFrame::OnCreate()实现,它的主要思想如下:首先定义一个CReBar对象,用以作工具条、地址栏的容器,然后分别声明图像列表对象img用于存储工具栏上按钮的热点图像和正常状态下显示的图像。为了显示扁平工具栏,需要用CreateEx()函数创建CToolBar对象m_wndToolBar,用ModifyStyle()函数将工具栏的风格设为扁平类型,你不能用CToolBar::Create() 或 CToolBar:: SetBarStyle()设置这种新风格。CToolBar 类不支持TBSTYLE_FLAT。要解决这个问题,必须绕过CToolBar类,使用CWnd::ModifyStyle()。工具栏对象调用SetButtonInfo()设置按钮的风格为TBSTYLE_DROPDOWN,就可以将工具栏按钮设置为附带有下拉按钮。至于按钮带有中文提示,用工具栏的SetButtonText()就可以轻松实现了。下面是实现IE风格界面的部分代码和注释:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  CReBar m_wndReBar;//声明CReBar对象
  CImageList img;//声明图像列表对象
  CString str;
  if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
   return -1;
  if (!m_wndReBar.Create(this))//创建CReBar对象
  {
   TRACE0("Failed to create rebar\n");
   return -1; // fail to create
  }
  if (!m_wndToolBar.CreateEx(this))//创建工具条对象
  {
   TRACE0("Failed to create toolbar\n");
   return -1; // fail to create
  }
  // set up toolbar properties
  m_wndToolBar.GetToolBarCtrl().SetButtonWidth(50, 150);
  file://设置工具条上按钮的最大、最小尺寸
  m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
  file://工具条可以带有下拉按钮
  img.Create(IDB_HOTTOOLBAR, 22, 0, RGB(255, 0, 255));
  file://向图像列表装载热点图像资源,IDB_HOTTOOLBAR为热点图像资源ID
  m_wndToolBar.GetToolBarCtrl().SetHotImageList(&img);//工具条装载热点图像
  img.Detach();
  img.Create(IDB_COLDTOOLBAR, 22, 0, RGB(255, 0, 255));
  file://图象列表装载正常状态的图像资源,IDB_COLDTOOLBAR为图像资源ID
  m_wndToolBar.GetToolBarCtrl().SetImageList(&img);//将图像装入工具条
  img.Detach();
  m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT | TBSTYLE_TRANSPARENT);
  file://工具条为扁平风格
  m_wndToolBar.SetButtons(NULL, 9);//工具条上有9个按钮
  // set up each toolbar button

  file://以下分别对九个按钮分别设置风格和按钮汉语提示
  m_wndToolBar.SetButtonInfo(0, ID_BUTTON0, TBSTYLE_BUTTON, 0);
  str.LoadString(IDS_ BUTTON0);
  m_wndToolBar.SetButtonText(0, str);
  m_wndToolBar.SetButtonInfo(1, ID_BUTTON1, TBSTYLE_BUTTON, 1);
  str.LoadString(IDS_ BUTTON1);
  m_wndToolBar.SetButtonText(1, str);
  m_wndToolBar.SetButtonInfo(2, ID_BUTTON2, TBSTYLE_BUTTON, 2);
  str.LoadString(IDS_ BUTTON2);
  m_wndToolBar.SetButtonText(2, str);
  m_wndToolBar.SetButtonInfo(3, ID_BUTTON3, TBSTYLE_BUTTON, 3);
  str.LoadString(IDS_ BUTTON3);
  m_wndToolBar.SetButtonText(3, str);
  m_wndToolBar.SetButtonInfo(4, ID_BUTTON4, TBSTYLE_BUTTON, 4);
  str.LoadString(IDS_ BUTTON4);
  m_wndToolBar.SetButtonText(4, str);
  m_wndToolBar.SetButtonInfo(5, ID_BUTTON5, TBSTYLE_BUTTON, 5)
  str.LoadString(IDS_ BUTTON5);
  m_wndToolBar.SetButtonText(5, str);
  m_wndToolBar.SetButtonInfo(6, ID_BUTTON6, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 6);
  str.LoadString(IDS_ BUTTON6);
  m_wndToolBar.SetButtonText(6, str);
  m_wndToolBar.SetButtonInfo(7, ID_BUTTON7, TBSTYLE_BUTTON, 7);
  str.LoadString(IDS_ BUTTON7);
  m_wndToolBar.SetButtonText(7, str);
  m_wndToolBar.SetButtonInfo(8,ID_BUTTON8, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 8);
  str.LoadString(IDS_ BUTTON8);
  m_wndToolBar.SetButtonText(8, str);
  file://重新调整按钮的尺寸
  CRect rectToolBar;
  m_wndToolBar.GetItemRect(0, &rectToolBar);//得到工具条第一个按钮的尺寸
  m_wndToolBar.SetSizes(rectToolBar.Size(), CSize(30,20));
  file://第一个参数为按钮尺寸,第二个参数为图像尺寸
  file://创建一个组合框作为地址栏
  if (!m_wndAddress.Create(CBS_DROPDOWN | WS_CHILD, CRect(0, 0, 200, 120), this, AFX_IDW_TOOLBAR + 1))
  {
   TRACE0("Failed to create combobox\n");
   return -1; // fail to create
  }
  file://加入工具栏、地址栏
  m_wndReBar.AddBar(&m_wndToolBar);
  str.LoadString(IDS_ADDRESS);
  m_wndReBar.AddBar(&m_wndAddress, str, NULL, RBBS_FIXEDBMP | RBBS_BREAK);
file://定义REBARBANDINFO对象,对工具条和地址栏设置理想尺寸
  REBARBANDINFO rbbi;
  rbbi.cbSize = sizeof(rbbi);
  rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_SIZE;
  rbbi.cxMinChild = rectToolBar.Width();
  rbbi.cyMinChild = rectToolBar.Height();
  rbbi.cx = rbbi.cxIdeal = rectToolBar.Width() * 9;
  m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);//设置工具栏尺寸
  rbbi.cxMinChild = 0;
  CRect rectAddress;
  rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE;
  m_wndAddress.GetEditCtrl()->GetWindowRect(&rectAddress);
  rbbi.cyMinChild = rectAddress.Height() + 10;
  rbbi.cxIdeal = 200;
  m_wndReBar.GetReBarCtrl().SetBandInfo(2, &rbbi);//设置地址栏尺寸
  m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
  CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED);
  if (!m_wndStatusBar.Create(this) ||
   !m_wndStatusBar.SetIndicators(indicators,
   sizeof(indicators)/sizeof(UINT)))
   {
    TRACE0("Failed to create status bar\n");
    return -1; // fail to create
   }
   return 0;
  }

   以上代码在Windows2000和Visual C++环境下编译通过,程序运行正常,有兴趣的朋友可以动手亲自实验一下

- 作者: suyong5968 2006年08月1日, 星期二 17:01  回复(1) |  引用(1) 加入博采

检测和删除系统中的木马(Trojan Horse)教程
一、木马(Trojan Horse)介绍 木马全称为特洛伊木马(Trojan Horse,英文则简称为Trojan)。此词语来源于古希腊的神话故事,传说希腊人围攻特洛伊城,久久不能得手。后来想出了一个木马计,让士兵藏匿于巨大的木马中。大部队假装撤退而将木马摈弃于特洛伊城下,让敌人将其作为战利品拖入城内。木马内的士兵则乘夜晚敌人庆祝胜利、放松警惕的时候从木马中爬出来,与城外的部队里应外合而攻下了特洛伊城。

  在计算机安全学中,特洛伊木马是指一种计算机程序,表面上或实际上有某种有用的功能,而含有隐藏的可以控制用户计算机系统、危害系统安全的功能,可能造成用户资料的泄漏、破坏或整个系统的崩溃。在一定程度上,木马也可以称为是计算机病毒。

  由于很多用户对计算机安全问题了解不多,所以并不知道自己的计算机是否中了木马或者如何删除木马。虽然现在市面上有很多新版杀毒软件都称可以自动清除木马病毒,但它们并不能防范新出现的木马病毒(哪怕宣传上称有查杀未知病毒的功能)。而且实际的使用效果也并不理想。比如用某些杀毒软件卸载木马后,系统不能正常工作,或根本发现不了经过特殊处理的木马程序。本人就测试过一些经编程人员改装过的著名木马程序,新的查杀毒软件是连检查都检测不到,更不用说要删除它了(哪怕是使用的是的病毒库)。因此最关键的还是要知道特洛伊木马的工作原理,由其原理着手自己来检测木马和删除木马。用手工的方法极易发现系统中藏匿的特洛伊木马,再根据其藏匿的方式对其进行删除。
二、木马工作的原理

  在Windows系统中,木马一般作为一个网络服务程序在种了木马的机器后台运行,监听本机一些特定端口,这个端口号多数比较大(5000以上,但也有部分是5000以下的)。当该木马相应的客户端程序在此端口上请求连接时,它会与客户程序建立一TCP连接,从而被客户端远程控制。

  既然是木马,当然不会那么容易让你看出破绽,对于程序设计人员来说,要隐藏自己所设计的窗口程序,主要途径有:在任务栏中将窗口隐藏,这个只要把 Form的Visible属性调整为False,ShowInTaskBar也设为False。那么程序运行时就不会出现在任务栏中了。如果要在任务管理器中隐身,只要将程序调整为系统服务程序就可以了。

  好了,现在我们对木马的运行有了大体了解。让我们从其运行原理着手来看看它藏在哪。既然要作为后台的网络服务器运行,那么它就要乘计算机刚开机的时候得到运行,进而常驻内存中。想一想,Windows系统刚启动的时候会通过什么项目装入而运行一些程序呢?你可能会想到“开始->程序- >启动”中的项目!没错,这是Windows启动时要运行的东西,但要是木马服务器程序明显地放在这就不叫木马了。

  木马基本上采用了Windows系统启动时自动加载应用程序的方法,包括有win.ini、system.ini和注册表等。

  在win.ini文件中,[WINDOWS]下面,“run=”和“load=”行是Windows启动时要自动加载运行的程序项目,木马可能会在这现出原形。必须要仔细观察它们,一般情况下,它们的等号后面什么都没有,如果发现后面跟有路径与文件名不是你熟悉的或以前没有见到过的启动文件项目,那么你的计算机就可能中上木马了。当然你也得看清楚,因为好多木马还通过其容易混淆的文件名来愚弄用户。如AOL Trojan,它把自身伪装成command.exe文件,如果不注意可能不会发现它,而误认它为正常的系统启动文件项。

  在system.ini文件中,[BOOT]下面有个“shell=Explorer.exe”项。正确的表述方法就是这样。如果等号后面不仅仅是 explorer.exe,而是“shell=Explorer.exe 程序名”,那么后面跟着的那个程序就是木马程序,明摆着你已经中了木马。现在有些木马还将explorer.exe文件与其进行绑定成为一个文件,这样的话,这里看起来还是正常的,无法瞧出破绽。

  隐蔽性强的木马都在注册表中作文章,因为注册表本身就非常庞大、众多的启动项目及易掩人耳目。
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce

  上面这些主键下面的启动项目都可以成为木马的容身之处。如果是Windows NT,那还得注意HKEY-LOCAL-MACHINE\Software\SAM下的东西,通过regedit等注册表编辑工具查看SAM主键,里面下应该是空的。

  木马驻留计算机以后,还得要有客户端程序来控制才可以进行相应的“黑箱”操作。要客户端要与木马服务器端进行通信就必须得建立一连接(一般为TCP连接),通过相应的程序或工具都可以检测到这些非法网络连接的在。具体如何检测,在第三部分有详细介绍。

  三、检测木马的存在

  知道木马启动运行、工作的原理,我们就可以着手来看看自己的计算机有没有木马存在了。

  首先,查看system.ini、win.ini、启动组中的启动项目。由“开始->运行”,输入msconfig,运行Windows自带的“系统配置实用程序”。

  1、查看system.ini文件

  选中“System.ini”标签,展开[boot]目录,查看“shell=”这行,正常为“shell=Explorer.exe”
,如果不是这样,就可能中了木马了。下图所示为正常时的情况:

  2、查看win.ini文件

  选中win.ini标签,展开[windows]目录项,查看“run=”和“load=”行,等号后面正常应该为空
3、查看启动组

  再看看启动标签中的启动项目,有没有什么非正常项目?要是有象netbus、netspy、bo等关键词,
极有可能就是木马了。本人一般都将启动组中的项目保持在比较精简的状态,不需要或无大用途的项目都
屏蔽掉了
4、查看注册表

  由“开始-运行”,输入regedit,确定就可以运行注册表编辑器。再展开至:
“HKEY-LOCAL-MACHINESoftwareMicrosoftWindowsCurrentVersionRun”目录下,查看键值中有没有自己
不熟悉的自动启动文件项目,比如netbus、netspy、netserver等的单词。注意,有的木马程序生成的
服务器程序文件很像系统自身的文件,想由此伪装蒙混过关。比如Acid Battery木马,它会在注册表项
“HKEY-LOCAL-MACHINESOFTWAREMicrosoftWindowsCurrentVersionRun”下加入
Explorer=“CWINDOWSexpiorer.exe”,木马服务器程序与系统自身的真正的Explorer之间只有一个字母
的差别!
通过类似的方法对下列各个主键下面的键值进行检查:
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnceEx
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices
HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce


  如果操作系统是Windows NT,还得注意HKEY-LOCAL-MACHINE\Software\SAM下面的内容,如果有项目,那极有可能就是木马了。正常情况下,该主键下面是空的。

  当然在注册表中还有很多地方都可以隐藏木马程序,上面这些主键是木马比较常用的隐身之处。除此之外,象HKEY-CURRENT-USER\ Software\Microsoft\Windows\CurrentVersion\Run、HKEY-USERS\****\Software\ Microsoft\Windows\CurrentVersion\Run的目录下都有可能成为木马的藏身之处。最好的办法就是在HKEY-LOCAL -MACHINE\Software\Microsoft\Windows\CurrentVersion\Run或其它主键下面找到木马程序的文件名,再通过其文件名对整个注册表进行全面搜索就知道它有几个藏身的地方了。

  如果有留意,注册表各个主键下都会有个叫“(默认)”名称的注册项,而且数据显示为“(未设置键值)”,也就是空的。这是正常现象。如果发现这个默认项被替换了,那么替换它的就是木马了。

  4、其它方法

  上网过程中,在进行一些计算机正常使用操作时,发现计算机速度明显起了变化、硬盘在不停的读写、鼠标不听使唤、键盘无效、自己的一些窗口在未得到自己允许的情况下被关闭、新的窗口被莫名其妙地打开.....这一切的不正常现象都可以怀疑是木马客户端在远程控制你的计算机。

  如果怀疑你现在正在被木马控制,那么不要慌张地去拔了网线或抽了Modem上的电话线。有可能的话,最好可以逮到“黑”你的那个家伙。下面就介绍一下相应的方法:

  由“开始->运行”,输入command,确定,开一个MS-DOS窗口。或者由“开始->程序->MS-DOS”来打开它。在MS-DOS窗口的命令行键入“netstat”查看目前已与本计算机建立的连接。如下图所示:
显示出来的结果表示为四列,其意思分别为Proto:协议,Local Address:本地地址,Foreign Address
:远程地址,State:状态。在地址栏中冒号的后面就是端口号。如果发现端口号码异常(比如大于5000
),而Foreign Address中的地址又不为正常网络浏览的地址,那么可以判断你的机器正被
Foreign Address中表示的远程计算机所窥视着。在对应行的Foreign Address中显示的IP地址就是目前非
法连接你计算机的木马客户端。

  当网络处于非活动状态,也就是目前没什么活动网络连接时,在MS-DOS窗口中用netstat命令将看不
到什么东西。此时可以使用“netstat -a”,加了常数“-a”表示显示计算机中目前处于监听状态的端口
。对于Windows98来说,正常情况下,会出现如下的一些处于监听状态的端口(安装有NETBEUI协议):
如果出现有不明端口处于监听(LISTENING)状态,而目前又没有进行任何网络服务操作,那么在监听该
端口的就是特洛伊木马了!如下图所示的23456和23457端口都处于监听状态,很明显是木马造成的。
注意,使用此方法查询处于监听状态的端口,一定要保证在短时间内(最好5分钟以上)没有运行任何
网络冲浪软件,也没有进行过任何网络操作,比如浏览网页,收、发信等。不然容易混淆对结果的判断。

四、删除木马

  好了,用上面的一些方法发现自己的计算机中了木马,那怎么办?当然要将木马删除了,难道还要保留它!首先要将网络断开,以排除来自网络的影响,再选择相应的方法来删除它。

  1、由木马的客户端程序

  由先前在win.ini、system.ini和注册表中查找到的可疑文件名判断木马的名字和版本。比如“netbus”、“netspy” 等,很显然对应的木马就是NETBUS和NETSPY。从网上找到其相应的客户端程序,下载并运行该程序,在客户程序对应位置填入本地计算机地址: 127.0.0.1和端口号,就可以与木马程序建立连接。再由客户端的卸除木马服务器的功能来卸除木马。端口号可由“netstat -a”命令查出来。

  这是最容易,相对来说也比较彻底载除木马的方法。不过也存在一些弊端,如果木马文件名给另外改了名字,就无法通过这些特征来判断到底是什么木马。如果木马被设置了密码,既使客户端程序可以连接的上,没有密码也登陆不进本地计算机。当然要是你知道该木马的通用密码,那就另当别论了。还有,要是该木马的客户端程序没有提供卸载木马的功能,那么该方法就没什么用了。当然,现在多数木马客户端程序都是有这个功能的。

  2、手工

  不知道中的是什么木马、无登陆的密码、找不到其相应的客户端程序、......,那我们就手工慢慢来删除这该死的木马吧。

  用msconfig打开系统配置实用程序,对win.ini、system.ini和启动项目进行编辑。屏蔽掉非法启动项。如在 win.ini文件中,将将[WINDOWS]下面的“run=xxx”或“load=xxx”更改为“run=”和“load=”;编辑 system.ini文件,将 [BOOT]下面的“shell=xxx”,更改为:“shell=Explorer.exe”。

  转贴于 华夏黑客同盟 http://www.77169.org
 用regedit打开注册表编辑器,对注册表进行编辑。先由上面的方法找到木马的程序名,再在整个注册表中搜索,并删除所有木马项目。由查找到的木马程序注册项,分析木马文件在硬盘中的位置(多在C:\WINDOWS和C:\WINDOWS\COMMAND目录下)。启动到纯MS-DOS状态(而不是在Windows环境中开个MS-DOS窗口),用del命令将木马文件删除。如果木马文件是系统、隐藏或只读文件,还得通过“attrib -s -h -r”将对应文件的属性改变,才可以删除。

  为保险起见,重新启动以后再由上面各种检测木马的方法对系统进行检查,以确保木马的确被删除了。

  目前也有一些木马是将自身的程序与Windows的系统程序进行了绑定(也就是感染了系统文件)。比如常用到的Explorer.exe,只要 Explorer.exe一得到运行,木马也就启动了。这种木马可以感染可执行文件,那就更象病毒了。由手工删除文件的方法处理木马后,一运行 Explorer.exe,木马又得以复生!这时要删除木马就得连Explorer.exe文件也给删除掉,再从别人相同操作系统版本的计算机中将该文件 Copy过来就可以了。

  五、结束语

  Internet上每天都有新的木马冒出来,所采取的隐蔽措施也是五花八门。在信息社会里,计算机用户对自己的资料进行保密、防止泄漏也变得越来越重要。防范木马袭击也只是提高计算机安全性的一个方面。技术的发展,本文所讲述的检测、删除木马方法也总有一天会失效,最主要还是要靠用户提高警惕,防范所有的不安全事件于未然。

典型案例一:
我的机器已中了木马病毒Trojan.TRC.mIRC.f
是在c:\WINNT\system32\sy5tem文件中,我想要把文件删掉,可是提示为:源文件正在被使用,瑞星杀毒软件又不能杀掉,我的系统是win2oooprofessinal,并不能运行msconfig命令,请教:该怎么办?
回复:
从Windows XP中剥离的Msconfig程序完全可以在Windows 2000中使用。顺便提供 pchome下载点。

把它COPY 到Win2000的WinntSystem目录下,然后直接运行。

程序首先会弹出一个出错的消息框,提示找不到以下几个系统文件:Config.sys、Autoexec.bat、System.ini 及Win.ini,“忽略”它,点击“确定”之后就会看到 Msconfig 程序窗口。

- 作者: suyong5968 2006年07月28日, 星期五 15:03  回复(0) |  引用(1)/a> 加入博采

C#的office操作[word&Excel]

C#实现对Word文件读写

        手头上的一个项目报表相对比较简单,所以报表打印采用VBA引擎,通过定制Word模版,然后根据模版需要填充数据,然后OK,打印即可。

  实现方法:首先需要引用VBA组建,我用的是Office2003 Professional,Dll版本号为Microsoft Word11.0

  另外当然还需要引用Interop.Word.Dll.

  代码如下:

///#region 打开Word文档,并且返回对象wDoc,wDoc
///
/// 打开Word文档,并且返回对象wDoc,wDoc
///
/// 完整Word文件路径+名称
/// 返回的Word.Document wDoc对象
/// 返回的Word.Application对象
public static void CreateWordDocument(string FileName,ref Word.Document wDoc,ref Word.Application WApp)
{
if(FileName == "") return;
Word.Document thisDocument = null;
Word.FormFields formFields = null;
Word.Application thisApplication = new Word.ApplicationClass();
thisApplication.Visible = true;
thisApplication.Caption = "";
thisApplication.Options.CheckSpellingAsYouType = false;
thisApplication.Options.CheckGrammarAsYouType = false;

Object filename = FileName;
Object ConfirmConversions = false;
Object ReadOnly = true;
Object AddToRecentFiles = false;

Object PasswordDocument = System.Type.Missing;
Object PasswordTemplate = System.Type.Missing;
Object Revert = System.Type.Missing;
Object WritePasswordDocument = System.Type.Missing;
Object WritePasswordTemplate = System.Type.Missing;
Object Format = System.Type.Missing;
Object Encoding = System.Type.Missing;
Object Visible = System.Type.Missing;
Object OpenAndRepair = System.Type.Missing;
Object DocumentDirection = System.Type.Missing;
Object NoEncodingDialog = System.Type.Missing;
Object XMLTransform = System.Type.Missing;

try
{
Word.Document wordDoc =
thisApplication.Documents.Open(ref filename, ref ConfirmConversions,
ref ReadOnly, ref AddToRecentFiles, ref PasswordDocument, ref PasswordTemplate,
ref Revert,ref WritePasswordDocument, ref WritePasswordTemplate, ref Format,
ref Encoding, ref Visible, ref OpenAndRepair, ref DocumentDirection,
ref NoEncodingDialog, ref XMLTransform );

thisDocument = wordDoc;
wDoc = wordDoc;
WApp = thisApplication;
formFields = wordDoc.FormFields;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}

}
#endregion

http://ike.126.com
调用上面静态方法,打开目标文件并且把DataGrid中数据填充到对应Word标签中去

///#region Word填充数据(For Example)
///
/// Word填充数据
///
private void WordLoadData()
{
Word.Document wDoc=null;
Word.Application wApp=null;
sysFun.CreateWordDocument("E:\\监测报告(new).dot",ref wDoc,ref wApp);

//对标签"C"进行填充
object bkmC="C";
if(wApp.ActiveDocument.Bookmarks.Exists("C") == true)
{
wApp.ActiveDocument.Bookmarks.get_Item
(ref bkmC).Select();
}
wApp.Selection.TypeText(this.txt1.Text);
object bkmG = "TWaterTable3";
object unit;
object count; //移动数
object extend;


extend = Word.WdMovementType.wdExtend;
unit = Word.WdUnits.wdCell;
//把DataGrid中数据填充到标签TWaterTable3上
if(wApp.ActiveDocument.Bookmarks.Exists("TWaterTable3") == true)
{
wApp.ActiveDocument.Bookmarks.get_Item
(ref bkmG).Select();

for(int i=0;i {
if(i==0)
{
count=1;
}
else
{
count=0;
}
//需填充5列数据
wApp.Selection.Move(ref unit,ref count);
wApp.Selection.TypeText(gridEX1.GetRow(i).Cells[0].Text);
count=1;

wApp.Selection.Move(ref unit,ref count);
wApp.Selection.TypeText(gridEX1.GetRow(i).Cells[1].Text);

wApp.Selection.Move(ref unit,ref count);
wApp.Selection.TypeText(gridEX1.GetRow(i).Cells[2].Text);

wApp.Selection.Move(ref unit,ref count);
wApp.Selection.TypeText(gridEX1.GetRow(i).Cells[3].Text);

wApp.Selection.Move(ref unit,ref count);
wApp.Selection.TypeText(gridEX1.GetRow(i).Cells[4].Text);
//换行
wApp.Selection.MoveRight(ref unit,ref count,ref extend);
}
}
}
#endregion


  然后就OK了,在对标签表控制要注意列循环和换行.


C#操作Excel(导入导出)


  有很多朋友说需要C#导出到Excel的代码,现共享给大家


///
/// 读取Excel文档
///
/// 文件名称
/// 返回一个数据集
public DataSet ExcelToDS(string Path)
{
string strConn = "Provider=Microsoft.Jet.OLEDB.4.0;" +"Data Source="+ Path +";"+"Extended Properties=Excel 8.0;";
OleDbConnection conn = new OleDbConnection(strConn);
conn.Open();
string strExcel = "";
OleDbDataAdapter myCommand = null;
DataSet ds = null;
strExcel="select * from [sheet1$]";
myCommand = new OleDbDataAdapter(strExcel, strConn);
ds = new DataSet();
myCommand.Fill(ds,"table1");
return ds;
}


///
/// 写入Excel文档
///
/// 文件名称
public bool SaveFP2toExcel(string Path)
{
try
{
string strConn = "Provider=Microsoft.Jet.OLEDB.4.0;" +"Data Source="+ Path +";"+"Extended Properties=Excel 8.0;";
OleDbConnection conn = new OleDbConnection(strConn);
conn.Open();
System.Data.OleDb.OleDbCommand cmd=new OleDbCommand ();
cmd.Connection =conn;
//cmd.CommandText ="UPDATE [sheet1$] SET 姓名='2005-01-01' WHERE 工号='日期'";
//cmd.ExecuteNonQuery ();
for(int i=0;i {
if(fp2.Sheets [0].Cells[i,0].Text!="")
{
cmd.CommandText ="INSERT INTO [sheet1$] (工号,姓名,部门,职务,日期,时间) VALUES('"+fp2.Sheets [0].Cells[i,0].Text+ "','"+
fp2.Sheets [0].Cells[i,1].Text+"','"+fp2.Sheets [0].Cells[i,2].Text+"','"+fp2.Sheets [0].Cells[i,3].Text+
"','"+fp2.Sheets [0].Cells[i,4].Text+"','"+fp2.Sheets [0].Cells[i,5].Text+"')";
cmd.ExecuteNonQuery ();
}
}
conn.Close ();
return true;
}
catch(System.Data.OleDb.OleDbException ex)
{
System.Diagnostics.Debug.WriteLine ("写入Excel发生错误:"+ex.Message );
}
return false;
}


  这种方法是相当有效的。
http://ike.126.com

- 作者: 2006年07月6日, 星期四 11:20  回复(1) |  引用(1) 加入博采