September 2005 Archives

C# 用 DOM 操作 XML

     我用的是一种很笨的方法,但可以帮助初学者了解访问XML节点的过程。

 

已知有一个XML文件(bookstore.xml)如下:

<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
</bookstore>

 

1、往<bookstore>节点中插入一个<book>节点:

   XmlDocument xmlDoc=new XmlDocument();
   xmlDoc.Load("bookstore.xml");

   XmlNode root=xmlDoc.SelectSingleNode("bookstore");//查找<bookstore>
   XmlElement xe1=xmlDoc.CreateElement("book");//创建一个<book>节点
   xe1.SetAttribute("genre","李赞红");//设置该节点genre属性
   xe1.SetAttribute("ISBN","2-3631-4");//设置该节点ISBN属性

 

   XmlElement xesub1=xmlDoc.CreateElement("title");
   xesub1.InnerText="CS从入门到精通";//设置文本节点
   xe1.AppendChild(xesub1);//添加到<book>节点中
   XmlElement xesub2=xmlDoc.CreateElement("author");
   xesub2.InnerText="候捷";
   xe1.AppendChild(xesub2);
   XmlElement xesub3=xmlDoc.CreateElement("price");
   xesub3.InnerText="58.3";
   xe1.AppendChild(xesub3);

 

   root.AppendChild(xe1);//添加到<bookstore>节点中
   xmlDoc.Save("bookstore.xml");

//===============================================

结果为:

<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book genre="李赞红" ISBN="2-3631-4">
    <title>CS从入门到精通</title>
    <author>候捷</author>
    <price>58.3</price>
  </book>
</bookstore>

 

2、修改节点:将genre属性值为“李赞红“的节点的genre值改为“update李赞红”,将该节点的子节点<author>的文本修改为“亚胜”。

    XmlNodeList nodeList=xmlDoc.SelectSingleNode("bookstore").ChildNodes;//获取bookstore节点的所有子节点
   foreach(XmlNode xn in nodeList)//遍历所有子节点
   {
    XmlElement xe=(XmlElement)xn;//将子节点类型转换为XmlElement类型
    if(xe.GetAttribute("genre")=="李赞红")//如果genre属性值为“李赞红”
    {
     xe.SetAttribute("genre","update李赞红");//则修改该属性为“update李赞红”

 

     XmlNodeList nls=xe.ChildNodes;//继续获取xe子节点的所有子节点
     foreach(XmlNode xn1 in nls)//遍历
     {
      XmlElement xe2=(XmlElement)xn1;//转换类型
      if(xe2.Name=="author")//如果找到
      {
       xe2.InnerText="亚胜";//则修改
       break;//找到退出来就可以了
      }
     }
     break;
    }
   }

 

   xmlDoc.Save("bookstore.xml");//保存。

//==================================================

最后结果为:

<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book genre="update李赞红" ISBN="2-3631-4">
    <title>CS从入门到精通</title>
    <author>亚胜</author>
    <price>58.3</price>
  </book>
</bookstore>

 

3、删除 <book genre="fantasy" ISBN="2-3631-4">节点的genre属性,删除 <book genre="update李赞红" ISBN="2-3631-4">节点。

XmlNodeList xnl=xmlDoc.SelectSingleNode("bookstore").ChildNodes;

 

   foreach(XmlNode xn in xnl)
   {
    XmlElement xe=(XmlElement)xn;
    if(xe.GetAttribute("genre")=="fantasy")
    {
     xe.RemoveAttribute("genre");//删除genre属性
    }
    else if(xe.GetAttribute("genre")=="update李赞红")
    {
     xe.RemoveAll();//删除该节点的全部内容
    }
   }
   xmlDoc.Save("bookstore.xml");

//===========================================
最后结果为:

<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book>
  </book>
</bookstore>

 

4、显示所有数据。

   XmlNode xn=xmlDoc.SelectSingleNode("bookstore");

 

   XmlNodeList xnl=xn.ChildNodes;
   
   foreach(XmlNode xnf in xnl)
   {
    XmlElement xe=(XmlElement)xnf;
    Console.WriteLine(xe.GetAttribute("genre"));//显示属性值
    Console.WriteLine(xe.GetAttribute("ISBN"));

 

    XmlNodeList xnf1=xe.ChildNodes;
    foreach(XmlNode xn2 in xnf1)
    {
     Console.WriteLine(xn2.InnerText);//显示子节点点文本
    }
   }

java中四种操作xml方式的比较

                 java中四种操作xml方式的比较


1. 介绍
1)DOM(JAXP Crimson解析器)
                   
    DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。
           
2)SAX
                   
    SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。


    选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说,选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。


    DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。


    SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。


3)JDOM http://www.jdom.org
                     
    JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。


    JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。


    JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。


    JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。
          
4)DOM4J http://dom4j.sourceforge.net
   
    虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。


    为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。


    在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。


    DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。


2. 比较
1)DOM4J性能最好,连Sun的JAXM也在用DOM4J。目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J.


2)JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出。在小文档情况下还值得考虑使用DOM和JDOM。虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。


3)SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。


3. 四种xml操作方式的基本使用方法


xml文件:
<?xml version="1.0" encoding="GB2312"?>
  <RESULT>
    <VALUE>
      <NO>A1234</NO>
      <ADDR>四川省XX县XX镇XX路X段XX号</ADDR>
    </VALUE>
    <VALUE>
      <NO>B1234</NO>
      <ADDR>四川省XX市XX乡XX村XX组</ADDR>
    </VALUE>
  </RESULT>


1)DOM
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
public class MyXMLReader{
  public static void main(String arge[]){
    long lasting =System.currentTimeMillis();
    try{ 
         File f=new File("data_10k.xml");
         DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
         DocumentBuilder builder=factory.newDocumentBuilder();
         Document doc = builder.parse(f);
         NodeList nl = doc.getElementsByTagName("VALUE");
         for (int i=0;i<nl.getLength();i++){
           System.out.print("车牌号码:" +                                     doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());
           System.out.println("车主地址:" +
                  doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());
         }
     }catch(Exception e){
        e.printStackTrace();
     }
  }
}


2)SAX
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
public class MyXMLReader extends DefaultHandler {
  java.util.Stack tags = new java.util.Stack();
  public MyXMLReader() {
    super();
  }
  public static void main(String args[]) {
    long lasting = System.currentTimeMillis();
    try {
           SAXParserFactory sf = SAXParserFactory.newInstance();
           SAXParser sp = sf.newSAXParser();
           MyXMLReader reader = new MyXMLReader();
           sp.parse(new InputSource("data_10k.xml"), reader);
         } catch (Exception e) {
            e.printStackTrace();
         }
    System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");
  }
  public void characters(char ch[], int start, int length) throws SAXException {
    String tag = (String) tags.peek();
    if (tag.equals("NO")) { 
       System.out.print("车牌号码:" + new String(ch, start, length));
    }
    if (tag.equals("ADDR")) {
       System.out.println("地址:" + new String(ch, start, length));
    }
  }
  public void startElement(String uri,String localName,String qName,Attributes attrs) {
    tags.push(qName);
  }


3) JDOM
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.*;
public class MyXMLReader {
  public static void main(String arge[]) {
    long lasting = System.currentTimeMillis();
    try {
       SAXBuilder builder = new SAXBuilder(); 
       Document doc = builder.build(new File("data_10k.xml")); 
       Element foo = doc.getRootElement(); 
       List allChildren = foo.getChildren(); 
       for(int i=0;i<allChildren.size();i++) { 
         System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());
         System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());
       }
     } catch (Exception e) {
         e.printStackTrace();
     }
  }
}


4)DOM4J
import java.io.*;
import java.util.*;
import org.dom4j.*;
import org.dom4j.io.*;
public class MyXMLReader {
  public static void main(String arge[]) {
    long lasting = System.currentTimeMillis();
    try {
          File f = new File("data_10k.xml");
          SAXReader reader = new SAXReader();
          Document doc = reader.read(f);
          Element root = doc.getRootElement();
          Element foo;
          for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {
              foo = (Element) i.next();
              System.out.print("车牌号码:" + foo.elementText("NO"));
              System.out.println("车主地址:" + foo.elementText("ADDR"));
          }
     } catch (Exception e) {
        e.printStackTrace();
     }
  }
}


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

系统故障解决小方法:regsvr32命令小集

系统及系统自带的软件(如:ie,wmplayer等)出错,大部分由于动态链接库 (DLL) 文件或 ActiveX 控件 (OCX) 文件损坏,会导致各种系统故障,甚至重装IE,甚至系统,问题依旧.这时您可以使用 Regsvr32 工具修复.

使用方法:开始→运行,输入regsvr32 *.dll 或regsvr32 *.ocx

一、轻松修复IE浏览器


regsvr32 Shdocvw.dll
regsvr32 Oleaut32.dll
regsvr32 Actxprxy.dll
regsvr32 Mshtml.dll
regsvr32 Urlmon.dll
regsvr32 jscript.dll
同时运行以上命令可以解决以下IE问题:
IE不能打开新的窗口,用鼠标点击超链接也没有任何反应的问题;网页显示不完整,JAVA效果不出现,网页不自动跳转,打开某些网站时总提示‘无法显示该页’;状态栏显示‘网页上有错误’的提示。

二、Windows无法在线升级

----regsvr32 wupdinfo.dll

三.XP系统的搜索功能、帮助和支持.管理工具等,打开无任何反应------regsvr32 shdocvw.dll


四.控制面板中的添加/删除程序时,双击它的图标后无反应,或者打开后自动关闭了或打开后一片空白.

regsvr32 appwiz.cpl
regsvr32 mshtml.dll
regsvr32 jscript.dll
regsvr32 msi.dll
regsvr32 c:\program files\common files\system\ole db\oledb32.dll
regsvr32 c:\program files\common files\system\ado\msado15.dll
regsvr32 mshtmled.dll
regsvr32 /i shdocvw.dll
regsvr32 /i shell32.dll

五.文件夹中不使用缩略图查看文
2000: regsvr32 C:\Winnt\System32\thumbvw.dll
XP:  regsvr32 shimgvw.dll

  
六.搜索’功能的搜索助理操作面板空白及系统还原功能无法使用等。如图1
解决方法:regsvr32 vbscript.dll  
        regsvr32 jscript.dll

七.WINplayer 9打开时提示‘出现内部应用程序错误’,如图2:
regsvr32 jscript.dll

八. 恢复Windows默认的文件关联

rundl32l setup.dll,InstallHinfSection DefaultInstall 132 c:\windows\inf\shell.in
regsvr32.exe /i shdocvw.dll
regsvr32 /i shell32.dll
regsvr32.exe /i shdoc401.dll

九:内存不能读写,如图2。
除去硬件原因:如内存不兼容等。软件原因大部分也是.dll文件损坏。
当然如果是个别软件运行时出现这个问题,重装那个软件看看能不能解决。
需要修复的.DLL文件如下:
regsvr32 actxprxy.dll
regsvr32 shdocvw.dll
regsvr32 oleaut32.dll
regsvr32 actxprxy.dll
regsvr32 mshtml.dll
regsvr32 msjava.dll
regsvr32 browseui.dll
regsvr32 urlmon.dll

注1:一个重要参数 /U(卸载.DLL或.ocx文件,导致系统错误)

如:regsvr32 /u jscript.dll  就会出现上图七中的错误.

注2:有的系统故障要用注册很多个.dll文件,可以用记事本建一个文件,把那些命令拷贝进去.然后另存为"修复.bat",运行"修复.bat"就可以修复了.

我的修复.bat,注册上面的所有.dll文件
regsvr32 Shdocvw.dll
regsvr32 Oleaut32.dll
regsvr32 Actxprxy.dll
regsvr32 Mshtml.dll
regsvr32 Urlmon.dll
regsvr32 jscript.dll
regsvr32 wupdinfo.dll
rundll32 setup.dll,InstallHinfSection DefaultInstall 132 c:\windows\inf\shell.inf
regsvr32.exe /i shdocvw.dll
regsvr32 /i shell32.dll
regsvr32.exe /i shdoc401.dll
regsvr32 shdocvw.dll
regsvr32 vbscript.dll  

深入理解 abstract class 和 interface

邓辉 、孙鸣(dhui@263.net)



abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。


理解抽象类


abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?


在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。


在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


从语法定义层面看abstract class和interface


在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。


使用abstract class的方式定义Demo抽象类的方式如下:


abstract class Demo {
abstract void method1();
abstract void method2();



使用interface的方式定义Demo抽象类的方式如下:


interface Demo {
void method1();
void method2();

}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。


对于abstract class和interface在语法定义层面更多的细节问题,不是本文的重点,不再赘述,读者可以参阅参考文献〔1〕获得更多的相关内容。


从编程层面看abstract class和interface


从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。


首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。


其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。


在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。


同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。


从设计理念层面看abstract class和interface


上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。


前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。


考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:


使用abstract class方式定义Door:


abstract class Door {
abstract void open();
abstract void close();
}

使用interface方式定义Door:


interface Door {
void open();
void close();
}

其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。


如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。


解决方案一:


简单的在Door的定义中增加一个alarm方法,如下:


abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}

或者


interface Door {
void open();
void close();
void alarm();
}

那么具有报警功能的AlarmDoor的定义方式如下:


class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}

或者


class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }


这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。


解决方案二:


既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。


显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。


如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。


如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:


abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。


结论


abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。


参考文献
[1] Thinking in Java, Bruce Eckel
[2] Design Patterns Explained: A New Perspective on Object-Oriented Design, Alan Shalloway
and James R. Trott
[3] Effective C++: 50 Specific Ways to Improve Your Programs and Design, Scott Meyers



关于作者

邓辉,软件工程师,主要兴趣在OO、Generic Programming。可以通过dhui@263.net联系到作者。
孙鸣,软件工程师,目前在一个大型通信公司从事数据网管的开发,主要兴趣在Java和数据库。可以通过dhui@263.net联系到作者。

[心得].Net 配置文件研究

| 1 Comment

.net 配置文件:App.Config(放在运行程序同一个目录下改名为 程序名.exe.Config)
Web.Config

一、访问:


1.访问 <appSettings> 节点
<appSettings> 
 <add key="aa" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=qq.mdb"/> 
 <add key="bb" value="bb"/> 
</appSettings> 


方法1 :
string conString1=System.Configuration.ConfigurationSettings.AppSettings["aa"]; 

方法2:
System.Configuration.AppSettingsReader appReader=new System.Configuration.AppSettingsReader(); 
string conString2=Convert.ToString(appReader.GetValue("bb",typeof(string))); 

方法一在书写上简单明朗。
方法二写法稍微多些,一次读取多个配置点的数值,可以考虑方法二。


2.访问自定义节点
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="users">
      <section name="user1" type="System.Configuration.NameValueSectionHandler" />
    </sectionGroup>
  </configSections>
  <users>
    <user1>
      <add key="fid" value="fid=17" />
      <add key="tid" value="tid=6530445" />
      <add key="formhash" value="9c633d92" />
      <add key="cdb_sid" value="vQ6cmg" />
      <add key="cdb_cookietime" value="315360000" />
      <add key="cdb_auth" value="UA0ADwEPAVUGAApTBQRSXgwFUVBVC1MAAldTUANTBQFrCF8FAA1bVg07CAgJBFhf" />
    </user1>
  </users>
</configuration>

NameValueCollection usr1 = ConfigurationSettings.GetConfig("users/user1") as NameValueCollection;

string temp = (String)(usr1["fid"]);


疑问:在 <add key="fid" value="fid=17" /> value 中如果有 "&" 符号,似乎创建 NameValueCollection 会失败,不得其解,以后再研究。

下次再研究写入配置信息,以及检测配置信息是否被更改。

Red Hat Linux 9 安装心得

最近为了搭J2EE服务器,由于Windows XP不太稳定,又耗资源,于是决定装一个Linux。为了安装使用方便,当然是选择使用最广泛的Red Hat Linux,其最新版本是9。


由于系统已经安装了XP,而且我的主要工作还是在XP下进行的,因此决定安装双系统。


参考了网上相关资料后,结合我的机器的具体配置,经历了几次实践后,终于成功实现双系统,并且通过XP的OS Loader引导Linux。


1. 首先,从ftp://ftp.redhat.com下载3个ISO光盘镜像文件,放到一个FAT32的分区中,这样就可以直接从硬盘安装,而不必刻录到光盘上再安装。注意,最好放在根目录下,可以省去安装过程中指定目录的麻烦。


2. 将第一张盘shrike-i386-disc1.iso加载到虚拟光驱中,把dosutils目录复制出来,放到FAT32分区中。


3. 用DOS软盘启动电脑,进入FAT32分区的dosutils目录,运行autoboot,启动Red Hat Linux 9 安装程序。


4. Red Hat Linux 9 安装程序启动后,会让你选择安装方式,选择从硬盘安装,并输入ISO文件所在的驱动器,我的FAT32分区对应的是/dev/hda4,因此输入/dev/hda4。


5. 接下来是Red Hat Linux 9的标准安装,注意要不破坏XP的引导系统,可以将GRUB或LILO安装到Linux启动分区而不是硬盘的MBR中。安装过程中制作一张启动软盘,安装完成后用软盘重启计算机。


6. 进入Linux系统后,先挂载FAT32分区,比如我将它挂到/mnt/dos下,使用命令
mount -t vfat /dev/hda4 /mnt/dos


7. 使用dd命令复制Linux根分区的第一个扇区到一个文件中,以便使用Windows XP的NT Boot Loader启动Linux。由于我的Linux装在/dev/hda2,因此用root登陆,输入:
#dd if=/dev/hda2 of=/mnt/dos/linuxboot.lnx bs=512 count=1
文件应该存放到FAT32分区或软盘中。


8. 重启进入XP,将FAT32分区的linuxboot.lnx复制到C:盘根目录下,在系统属性中打开启动选项,编辑boot.ini,在[operating systems]下添加:
C:\linuxboot.lnx="Red Hat Linux 9"


9. 重启XP,会出现启动菜单
Microsoft Windows XP Professional
Red Hat Linux 9
选择Red Hat Linux 9就可以直接启动Linux了!


以上的关键是我的C:盘使用的NTFS格式,Linux无法识别,只能通过FAT32分区或者软盘来交换数据。


此外,如果没有制作启动盘,可以将第一张光盘里的loadlin.exe(在dosutils目录下)和vmlinuz(在dosutils\autoboot下)复制到软盘,用DOS启动后,输入:
loadlin vmlinuz root=/dev/hda2 ro
也可以启动Linux,但是要注意,启动后的内核版本显示为2.4.20-8BOOT,而不是2.4.20-8,因此很多驱动无法正确加载,也就无法配置网卡声卡等,但可以通过步骤6-8实现硬盘启动,这样就能正确配置系统了。

java语言已经内置了多线程支持,所有实现Runnable接口的类都可被启动一个新线程,新线程会执行该实例的run()方法,当run()方法执行完毕后,线程就结束了。一旦一个线程执行完毕,这个实例就不能再重新启动,只能重新生成一个新实例,再启动一个新线程。


Thread类是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法:


Thread t = new Thread();
t.start();


start()方法是一个native方法,它将启动一个新线程,并执行run()方法。Thread类默认的run()方法什么也不做就退出了。注意:直接调用run()方法并不会启动一个新线程,它和调用一个普通的java方法没有什么区别。


因此,有两个方法可以实现自己的线程:


方法1:自己的类extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:


public class MyThread extends Thread {
    public run() {
        System.out.println("MyThread.run()");
    }
}


在合适的地方启动线程:new MyThread().start();


方法2:如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口:


public class MyThread extends OtherClass implements Runnable {
    public run() {
        System.out.println("MyThread.run()");
    }
}


为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:


MyThread myt = new MyThread();
Thread t = new Thread(myt);
t.start();


事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:


public void run() {
    if (target != null) {
        target.run();
    }
}


线程还有一些Name, ThreadGroup, isDaemon等设置,由于和线程设计模式关联很少,这里就不多说了。

Eclipse快速上手指南 (4)

| 1 Comment

6.在Eclipse中使用CVS

版本控制在团队开发中是必不可少的。
CVS是优秀的开源版本控制软件,Eclipse本身就内置了对CVS的支持,只需简单配置,即可使用CVS

首先我们要正确安装并配置好
CVS服务器,通常Linux Server都自带CVS服务,不过命令行操作比较繁琐。Windows下也有简单易用的CVS服务器,这里我们推荐CVSNT,可以下载CVSNT 2.0.51a,安装并启动CVSNT



然后切换到Repositories面板,添加一个Repository,命名为/cvs-javaCVSNT会提示是否初始化这个Repository,选择是:


然后在Advanced面板上选中“Pretend to be a Unix CVS version”:


然后,在Windows账户中为每一个开发人员添加用户名和口令。

现在,
CVSNT的安装配置已经完成,下一步,启动Eclipse,我们可以使用原有的Hello工程,或者新建一个Project,然后选择菜单Window->Show View->Other,打开CVS->CVS Repositories



然后点击按钮,添加一个Repository



注意用户名和口令直接填Windows的用户名和口令,然后选中“Validate Connection on Finish”,点击Finish完成:


首先,我们要将一个现有的工程放到CVS服务器中,切换到Package Explorer,选中Hello工程,右键点击,选择Team->Share Project…



使用刚才我们添加的Repository,继续,并将所有文件都添加到CVS中,最后Eclipse提示Commit



填入一个简单的注释,确定,然后Eclipse会把整个工程提交到CVS服务器,可以在Package Explorer中看到图标发生了变化,Hello.java文件后面会有版本号1.1。在CVS Repositories面板中刷新,可以看到刚添加进来的工程:



在团队开发中,当创建了一个基本的工程并提交到CVS后,别的开发人员首先要Check Out这个工程到各自的本地计算机上,这里为了演示,首先我们在Package Explorer中删除Hello工程,然后打开CVS Repositories(如果没有看到Repository就按照上面的方法添加Repository),选择Hello工程,右键点击,选择Check Out As…



作为一个Project签出,就可以在Package Explorer中看到签出的工程。

当对某些源文件作了修改后,需要提交更改到CVS服务器。选中更改的文件或工程,右键点击,选择Team->Commit…



然后填入简单的注释,就可以提交到CVS服务器上了,可以看到源文件的版本号变成了1.2

以上简单介绍了如何搭建CVS服务器以及在Eclipse中如何使用CVS,可以参考CVS手册以便了解BranchMerge等更多功能的使用。

Eclipse快速上手指南 (3)

| 1 Comment

5. 在Eclipse中使用Ant


Ant是Java平台下非常棒的批处理命令执行程序,能非常方便地自动完成编译,测试,打包,部署等等一系列任务,大大提高开发效率。如果你现在还没有开始使用Ant,那就要赶快开始学习使用,使自己的开发水平上一个新台阶。

Eclipse中已经集成了Ant,我们可以直接在Eclipse中运行Ant。

以前面建立的Hello工程为例,创建以下目录结构:



新建一个build.xml,放在工程根目录下。build.xml定义了Ant要执行的批处理命令。虽然Ant也可以使用其它文件名,但是遵循标准能更使开发更规范,同时易于与别人交流。


通常,src存放Java源文件,classes存放编译后的class文件,lib存放编译和运行用到的所有jar文件,web存放JSP等web文件,dist存放打包后的jar文件,doc存放API文档。


然后在根目录下创建build.xml文件,输入以下内容:



<?xml version="1.0"?>
<project name="Hello world" default="doc">


 <!-- properies -->
    <property name="src.dir" value="src" />
    <property name="report.dir" value="report" />
    <property name="classes.dir" value="classes" />
    <property name="lib.dir" value="lib" />
    <property name="dist.dir" value="dist" />
 <property name="doc.dir" value="doc"/>


    <!-- 定义classpath -->
    <path id="master-classpath">
        <fileset file="${lib.dir}/*.jar" />
        <pathelement path="${classes.dir}"/>
    </path>


    <!-- 初始化任务 -->
    <target name="init">
    </target>


    <!-- 编译 -->
    <target name="compile" depends="init" description="compile the source files">
        <mkdir dir="${classes.dir}"/>
        <javac srcdir="${src.dir}" destdir="${classes.dir}" target="1.4">
            <classpath refid="master-classpath"/>
        </javac>
    </target>


    <!-- 测试 -->
    <target name="test" depends="compile" description="run junit test">
        <mkdir dir="${report.dir}"/>
        <junit printsummary="on"
                haltonfailure="false"
                failureproperty="tests.failed"
                showoutput="true">
            <classpath refid="master-classpath" />
            <formatter type="plain"/>
            <batchtest todir="${report.dir}">
                <fileset dir="${classes.dir}">
                    <include name="**/*Test.*"/>
                </fileset>
            </batchtest>
        </junit>
        <fail if="tests.failed">
        ***********************************************************
        ****  One or more tests failed!  Check the output ...  ****
        ***********************************************************
        </fail>
    </target>


    <!-- 打包成jar -->
    <target name="pack" depends="test" description="make .jar file">
     <mkdir dir="${dist.dir}" />
        <jar destfile="${dist.dir}/hello.jar" basedir="${classes.dir}">
            <exclude name="**/*Test.*" />
            <exclude name="**/Test*.*" />
        </jar>
    </target>


    <!-- 输出api文档 -->
    <target name="doc" depends="pack" description="create api doc">
     <mkdir dir="${doc.dir}" />
     <javadoc destdir="${doc.dir}"
            author="true"
            version="true"
            use="true"
            windowtitle="Test API">
            <packageset dir="${src.dir}" defaultexcludes="yes">
                <include name="example/**" />
            </packageset>
            <doctitle><![CDATA[<h1>Hello, test</h1>]]></doctitle>
            <bottom><![CDATA[<i>All Rights Reserved.</i>]]></bottom>
            <tag name="todo" scope="all" description="To do:" />
        </javadoc>
    </target>
</project>


以上xml依次定义了init(初始化),compile(编译),test(测试),doc(生成文档),pack(打包)任务,可以作为模板。


选中Hello工程,然后选择“Project”,“Properties”,“Builders”,“New…”,选择“Ant Build”:



填入Name:Ant_Builder;Buildfile:build.xml;Base Directory:${workspace_loc:/Hello}(按“Browse Workspace”选择工程根目录),由于用到了junit.jar包,搜索Eclipse目录,找到junit.jar,把它复制到Hello/lib目录下,并添加到Ant的Classpath中:



然后在Builder面板中钩上Ant_Build,去掉Java Builder:



再次编译,即可在控制台看到Ant的输出:



Buildfile: F:\eclipse-projects\Hello\build.xml


init:


compile:
       [mkdir] Created dir: F:\eclipse-projects\Hello\classes
       [javac] Compiling 2 source files to F:\eclipse-projects\Hello\classes


test:
       [mkdir] Created dir: F:\eclipse-projects\Hello\report
       [junit] Running example.HelloTest
       [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.02 sec


pack:
       [mkdir] Created dir: F:\eclipse-projects\Hello\dist
         [jar] Building jar: F:\eclipse-projects\Hello\dist\hello.jar


doc:
       [mkdir] Created dir: F:\eclipse-projects\Hello\doc
     [javadoc] Generating Javadoc
     [javadoc] Javadoc execution
     [javadoc] Loading source files for package example...
     [javadoc] Constructing Javadoc information...
     [javadoc] Standard Doclet version 1.4.2_04
     [javadoc] Building tree for all the packages and classes...
     [javadoc] Building index for all the packages and classes...
     [javadoc] Building index for all classes...
     [javadoc] Generating F:\eclipse-projects\Hello\doc\stylesheet.css...
     [javadoc] Note: Custom tags that could override future standard tags:  @todo. To avoid potential overrides, use at least one period character (.) in custom tag names.
     [javadoc] Note: Custom tags that were not seen:  @todo
BUILD SUCCESSFUL
Total time: 11 seconds


Ant依次执行初始化,编译,测试,打包,生成API文档一系列任务,极大地提高了开发效率。将来开发J2EE项目时,还可加入部署等任务。并且,即使脱离了Eclipse环境,只要正确安装了Ant,配置好环境变量ANT_HOME=<Ant解压目录>,Path=…;%ANT_HOME%\bin,在命令行提示符下切换到Hello目录,简单地键入ant即可。

Eclipse快速上手指南 (2)

| 1 Comment

4. 在Eclipse中使用JUnit


测试对于保证软件开发质量有着非常重要的作用,单元测试更是必不可少,JUnit是一个非常强大的单元测试包,可以对一个/多个类的单个/多个方法测试,还可以将不同的TestCase组合成TestSuit,使测试任务自动化。Eclipse同样集成了JUnit,可以非常方便地编写TestCase。

我们创建一个Java工程,添加一个example.Hello类,首先我们给Hello类添加一个abs()方法,作用是返回绝对值:


下一步,我们准备对这个方法进行测试,确保功能正常。选中Hello.java,右键点击,选择New->JUnit Test Case:


Eclipse会询问是否添加junit.jar包,确定后新建一个HelloTest类,用来测试Hello类。


选中setUp()和tearDown(),然后点击“Next”:


选择要测试的方法,我们选中abs(int)方法,完成后在HelloTest.java中输入:


JUnit会以以下顺序执行测试:(大致的代码



try {
    HelloTest test = new HelloTest(); // 建立测试类实例
    test.setUp(); // 初始化测试环境
    test.testAbs(); // 测试某个方法
    test.tearDown(); // 清理资源
}
catch…


setUp()是建立测试环境,这里创建一个Hello类的实例;tearDown()用于清理资源,如释放打开的文件等等。以test开头的方法被认为是测试方法,JUnit会依次执行testXxx()方法。在testAbs()方法中,我们对abs()的测试分别选择正数,负数和0,如果方法返回值与期待结果相同,则assertEquals不会产生异常。


如果有多个testXxx方法,JUnit会创建多个XxxTest实例,每次运行一个testXxx方法,setUp()和tearDown()会在testXxx前后被调用,因此,不要在一个testA()中依赖testB()。

直接运行Run->Run As->JUnit Test,就可以看到JUnit测试结果:


绿色表示测试通过,只要有1个测试未通过,就会显示红色并列出未通过测试的方法。可以试图改变abs()的代码,故意返回错误的结果(比如return n+1;),然后再运行JUnit就会报告错误。


如果没有JUnit面板,选择Window->Show View->Other,打开JUnit的View:


JUnit通过单元测试,能在开发阶段就找出许多Bug,并且,多个Test Case可以组合成Test Suite,让整个测试自动完成,尤其适合于XP方法。每增加一个小的新功能或者对代码进行了小的修改,就立刻运行一遍Test Suite,确保新增和修改的代码不会破坏原有的功能,大大增强软件的可维护性,避免代码逐渐“腐烂”。

Eclipse快速上手指南 (1)

Eclipse是一款非常优秀的开源IDE,非常适合Java开发,由于支持插件技术,受到了越来越多的开发者的欢迎。最新的Eclipse 3.0不但界面作了很大的增强,而且增加了代码折叠等众多优秀功能,速度也有明显的提升。配合众多令人眼花缭乱的插件,完全可以满足从企业级Java应用到手机终端Java游戏的开发。本文将带您手把手步入Eclipse的广阔天地,详细介绍在Eclipse下如何开发普通Java程序,Web应用,J2EE应用,手机Java程序,以及如何进行单元测试,重构,配置CVS等详细内容。


我的开发环境是JDK1.4.2+Eclipse3.0+Windows XP SP2,如果你在其他平台上遇到任何问题,欢迎来信交流。


1. 安装JDK1.4


Eclipse是一个基于Java平台的开发环境,它本身也要运行在Java虚拟机上,还要使用JDK的编译器,因此我们必须首先安装JDK。JDK1.4是目前最稳定的版本,同时也是Eclipse运行的必须条件。先从SUN的官方站点http://java.sun.com下载JDK1.4 Windows版,目前最新的是1.4.2_06,然后运行j2sdk-1_4_2_06-windows-i586-p.exe安装,你可以自行设定安装目录,我把它安装到D:\software\j2sdk1.4目录下。


接下来要配置环境变量,以便Java程序能找到已安装的JDK和其他配置信息。右键点击“我的电脑”,选择“属性”,在弹出的对话框中选择“高级”,“环境变量”,就可以看到环境变量对话框:


上面是用户变量,只对当前用户有效,下面是系统变量,对所有用户都有效。如果你希望所有用户都能使用,就在系统变量下点击“新建”,填入:


JAVA_HOME是JDK的安装目录,许多依赖JDK的开发环境都靠它来定位JDK,所以必须保证正确无误。


下一步,找到系统变量Path,点击“编辑”,在最后添上JDK的可执行文件的所在目录,即%JAVA_HOME%\bin,我的对应目录便是D:\software\j2sdk1.4\bin,附加到Path中即可,注意要以分号“;”隔开:


注意:如果系统安装了多个Java虚拟机(比如安装了Oracle 9i就有自带的JDK1.3),必须把JDK1.4的路径放在其他JVM的前面,否则Eclipse启动将报错。


最后一个系统变量是CLASSPATH,Java虚拟机会根据CLASSPATH的设定来搜索class文件所在目录,但这不是必需的,可以在运行Java程序时指定CLASSPATH,比如在Eclipse中运行写好的Java程序时,它会自动设定CLASSPATH,但是为了在控制台能方便地运行Java程序,我建议最好还是设置一个CLASSPATH,把它的值设为“.”,注意是一个点“.”代表当前目录。用惯了Windows的用户可能会以为Java虚拟机在搜索时会搜索当前目录,其实不会,这是UNIX中的习惯,出于安全考虑。许多初学Java的朋友兴匆匆地照着书上写好了Hello,world程序,一运行却弹出java.lang.NoClassDefFoundError,其实就是没有设置好CLASSPATH,只要添加一个当前目录“.”就可以了。


2. 安装Eclipse 3.0


配置好JDK后,下一步便是安装Eclipse 3.0,可以从Eclipse的官方站点http://www.eclipse.org上下载,你会看到如下版本:


● Eclipse SDK
● RCP Runtime Binary
● RCP SDK
● Platform Runtime Binary
● Platform SDK
● JDT Runtime Binary


Eclipse SDK包括了Eclipse开发环境,Java开发环境,Plug-in开发环境,所有源代码和文档,如果你需要所有的功能,可以下载这个版本。


如果你和我一样,只是用Eclipse开发Java应用,而不是开发Eclipse插件或者研究Eclipse代码,那么下载一个Platform Runtime Binary再加上JDT Runtime Binary是最好的选择。


下载eclipse-platform-3.0-win32.zip和eclipse-JDT-3.0.zip后,将它们解压到同一个目录,勿需安装,直接找到目录下的eclipse.exe运行,出现启动画面:


稍等片刻,Eclipse界面就出来了。


如果遇到错误,启动失败,可以检查Eclipse目录下的log文件,我曾经遇到过XmlParser异常,仔细检查发现原来Path中还有一个Oracle的Java1.3版本的虚拟机,将它从Path中去掉后Eclipse启动正常。


3. 第一个Java程序


运行Eclipse,选择菜单“File”,“New”,“Project”,新建一个Java Project,我把它命名为HelloWorld,然后新建一个Java Class:


我把它命名为HelloWorld,并且填上Package为example,钩上“public static void main(String[] args)”,点击“Finish”,Eclipse自动生成了代码框架,我们只需在main方法中填入:


默认设置下,Eclipse会自动在后台编译,我们只需保存,然后选择“Run”,“Run As”,“Java Application”,即可在Eclipse的控制台看到输出。


要调试Java程序也非常简单,Run菜单里包含了标准的调试命令,可以非常方便地在IDE环境下调试应用程序。


1.4版本支持:


选择菜单“Window”,“Preferences”,在对话框中找到“Java”,“Compiler”,“Compliance and Classfiles”,将编译选项改成1.4,就可以使用JDK1.4版的assert(断言)语法,使得测试更加方便:

使用SMTP协议发送邮件

使用SMTP协议发送邮件,可以不通过SMTP服务器,直接将邮件发送到邮件服务器。很多服务器端程序可能需要向很多用户发送邮件,直接通过SMTP发送可能是最有效的。

关于SMTP协议定义在RFC821,可以在此看中文版

第一步:通过目标email查找邮件服务器。
例如:asklxf@sohu.com,其邮件服务器地址为:sohumx.sohu.com



import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;


public class Smtp {


    public static void main(String[] args) throws Exception {
        // DNS服务器,看看本机的DNS配置
        String dns = "dns://192.168.1.1";
        // 邮箱后缀:
        String domain = "sohu.com";
        Hashtable env = new Hashtable();
        env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
        env.put("java.naming.provider.url", dns);
        DirContext ctx = new InitialDirContext(env);
        Attributes attr = ctx.getAttributes(domain, new String[]{"MX" });
        NamingEnumeration servers = attr.getAll();
        // 列出所有邮件服务器:
        while(servers.hasMore()) {
            System.out.println(servers.next());
        }
    }
}


第二步:直接连接邮件服务器的25端口,用SMTP协议发送邮件。
这里使用sohu信箱,邮件服务器为sohumx.sohu.com,收信人必须在此服务器上:



import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;


public class Smtp {


    private static String END_FLAG = "\r\n";


    public static void main(String[] args) throws Exception {
        String mx = "sohumx.sohu.com";
        InetAddress addr = InetAddress.getByName(mx);
        Socket socket = new Socket(addr, 25);


        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();


        // 连接成功后服务器会响应:
        response(in);


        // 首先发送HELO命令:
        send("HELO
www.javasprite.com" + END_FLAG, out);
        response(in);


        // 然后发送发件人地址:
        send("MAIL FROM: someone@somewhere.com" + END_FLAG, out);
        response(in);


        // 设置收件人地址:
        send("RCPT TO: asklxf@sohu.com" + END_FLAG, out);
        response(in);


        // 开始发送邮件正文:
        send("DATA" + END_FLAG, out);
        response(in);


        send("From: someone@somewhere.com" + END_FLAG, out);
        send("To: asklxf@sohu.com" + END_FLAG, out);
        send("Subject: Test without smtp server" + END_FLAG, out);
        send("Content-Type: text/plain;" + END_FLAG, out);
        send(END_FLAG + END_FLAG, out);


        // 发送邮件正文,如果用中文,需要BASE64编码:
        send("text message body!" + END_FLAG, out);
        // 每行以\r\n结束,不可过长,可拆成多行。


        // 以"\r\n.\r\n"作为结束标志:
        send(END_FLAG + "." + END_FLAG, out);
        response(in);


        // 结束并确认发送:
        send("QUIT" + END_FLAG, out);
        response(in);
        in.close();
        out.close();
        socket.close();
    }


    public static void response(InputStream in) throws Exception {
        byte[] buffer = new byte[1024];
        int n = in.read(buffer);
        String s = new String(buffer, 0, n);
        // 服务器会返回:### Text
        // 具体含义见RFC821
        System.out.println(s);
    }


    public static void send(String s, OutputStream out) throws Exception {
        byte[] buffer = s.getBytes();
        out.write(buffer);
        // 不要忘了flush(),否则可能在缓冲区:
        out.flush();
    }
}


Ok,打开outlook收信,会发现有一封来自someone@somewhere.com的信件。

第三步:处理服务器返回码,各种异常,包装成Java组件以便重用:



public interface SendMail {
    void send(String from, String to, String subject, String text)
}

public class SendMailImpl extends Thread implements SendMail {
    // TODO: 自己写......
}

Java的ClassLoader与Package机制

为了深入了解Java的ClassLoader机制,我们先来做以下实验:



package java.lang;
public class Test {
    public static void main(String[] args) {
        char[] c = "1234567890".toCharArray();
        String s = new String(0, 10, c);
    }
}


String类有一个Package权限的构造函数String(int offset, int length, char[] array),按照默认的访问权限,由于Test属于java.lang包,因此理论上应该可以访问String的这个构造函数。编译通过!执行时结果如下:



Exception in thread "main" java.lang.SecurityException: Prohibited package name:
 java.lang
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$100(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)


奇怪吧?要弄清为什么会有SecurityException,就必须搞清楚ClassLoader的机制。


Java的ClassLoader就是用来动态装载class的,ClassLoader对一个class只会装载一次,JVM使用的ClassLoader一共有4种:


启动类装载器,标准扩展类装载器,类路径装载器网络类装载器


这4种ClassLoader的优先级依次从高到低,使用所谓的“双亲委派模型”。确切地说,如果一个网络类装载器被请求装载一个java.lang.Integer,它会首先把请求发送给上一级的类路径装载器,如果返回已装载,则网络类装载器将不会装载这个java.lang.Integer,如果上一级的类路径装载器返回未装载,它才会装载java.lang.Integer。


类似的,类路径装载器收到请求后(无论是直接请求装载还是下一级的ClassLoader上传的请求),它也会先把请求发送到上一级的标准扩展类装载器,这样一层一层上传,于是启动类装载器优先级最高,如果它按照自己的方式找到了java.lang.Integer,则下面的ClassLoader都不能再装载java.lang.Integer,尽管你自己写了一个java.lang.Integer,试图取代核心库的java.lang.Integer是不可能的,因为自己写的这个类根本无法被下层的ClassLoader装载。


再说说Package权限。Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如启动类装载器装载了java.lang.String,类路径装载器装载了我们自己写的java.lang.Test,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。

用Java构建稳定的Ftp服务器 (1)

| 3 Comments

Ftp服务是最常用的网络服务之一,虽然在www风行的今天,Ftp已经远不如以前使用得广泛,但是在许多大学等科研单位,Ftp仍然是最常用的文件交换方式。


构建一个Ftp服务器要比构建一个Ftp客户端来得简单,因为服务器不需要复杂的图形界面。相比传统的C/C++,使用Java的多线程和网络编程能令我们更轻易地开发出稳定可靠的Ftp服务器。


Ftp协议简介
File Transfer Protocol,文件传输协议,顾名思义,Ftp就是用于文件的传输,Ftp协议是基于TCP协议的,因此,在一个Ftp会话开始前,客户端和服务器必须首先建立一个TCP连接,这个TCP连接通常被称作控制连接,客户端通过此连接向服务器发送FTP命令,服务器处理命令后,将返回一个响应码。


每个命令必须有最少一个响应,如果是多个,要易于区别。FTP响应由三个数字构成,后面是一些文本。数字带有足够的信息,客户端程序不用知道后面的文本就知道发生了什么。文本信息与服务器相关,不同的用户,不同的服务器可能有不同的文本信息。文本和数字以空格间隔,文本后以换行符(\n)结束。如果文本多于一行,第一行内要有信息表示这是多行文本,最后一行也要标记为结束行。比如客户端发送获取当前目录的命令“PWD”,服务器的响应可能是:



200 /pub/incoming


响应码的三位数字都有明确的含义:



  • 1xx 确定预备应答,这类响应用于说明命令被接受,但请求的操作正在被初始化,在进入下一个命令前等待另外的应答。
  • 2xx 确定完成应答,要求的操作已经完成,可以执行新命令。
  • 3xx 确定中间应答,命令已接受,但要求的操作被停止。
  • 4xx 暂时拒绝完成应答,未接受命令,但错误是临时的,过一会儿可以再次发送消息,比如服务器忙。
  • 5yz 永远拒绝完成应答,此类响应码一般表示错误,如拒绝登陆。

第二位数字代表的意义:



  • x0x 格式错误;
  • x1x 此类应答是为了请求信息的;
  • x2x 此类应答是关于控制和数据连接的;
  • x3x 关于认证和帐户登录过程;
  • x4x 未使用;
  • x5x 此类应答是关于文件系统的;

常见的相应有:



  • 200 命令执行成功;
  • 202 命令未实现;
  • 230 用户登录;
  • 331 用户名正确,需要口令;
  • 450 请求的文件操作未执行;
  • 500 命令不可识别
  • 502 命令未实现

一个Ftp会话过程中,始终有一个控制连接,如果客户端请求文件,则会有一个数据连接,但FTP协议规定:只要关闭了控制连接,数据连接(如果有)也必须关闭。


不同的FTP服务器对FTP命令的支持程度可能不同,但是TCP标准定义了所有FTP服务器都必须实现的命令,我们的目标就是构建一个实现这个最小命令集的FTP服务器。


待续

Eclipse使用技巧

Eclipse使用技巧


1.在源代码中快速跳转


eclipse中的跳转甚至比VS.Net还方便,方法是按住Ctrl键,然后鼠标指向变量名,方法名,类名,就会出现链接,点击就可跳到定义处。


2.实时语法检查


编辑区右侧如果有红色小方块,直接点击就可跳到有错的行;黄色小方块是警告,可以忽略,但最好检查一下;如果某个函数尚未完成,要提醒自己注意怎么办?加上注释// TODO,右侧就会有蓝色小方块,提示你此处尚未完成。当一个源码的右侧没有任何提示时,说明这个文件已经完成了。


3.自动生成getter/setter方法


只需要申明protected,private类成员变量,然后在Package Explore中找到该类,右键点击,选择“Source”“Generate Getters and Setters”。


4.更改类名/变量名


如果涉及到多处修改,不要直接在源码中更改,在Package Explore中找到要改名的类或变量,右键点击,选择“Refactor”“Rename”,eclipse会自动搜索所有相关代码并替换,确保不会遗漏或改错。


:我用的版本是Eclipse 3.0

在Eclipse中使用JUnit进行单元测试

如何编写测试代码肯定是开发人员最头疼的。JUnit是一个非常强大的单元测试包,可以对一个/多个类的单个/多个方法测试,还可以将不同的TestCase组合成TestSuit,使测试任务自动化。


本文简单介绍如何在eclipse中使用JUnit创建一个TestCase来测试一个简单的类。


我们写一个要测试的类Simple如下:



package jexi.test;
public class Simple {
    private int n;
    public Simple(int n) {
        this.n = n;
    }
    // 返回绝对值:
    public int foo() {
        return n>0 ? n : (-n);
    }
}


foo()方法返回绝对值,下一步,我们准备用JUnit对这个foo()方法进行全面测试。


首先,在eclipse中,创建一个java工程,把plugins\org.junit_3.8.1\junit.jar包含进去:


然后写好Simple.java,为它创建一个JUnit Test Case:



在弹出的对话框中填入测试类的名字:SimpleTest,勾上setUp():



编写测试代码:



package jexi.test;
import junit.framework.TestCase;

public class SimpleTest extends TestCase {
    private Simple s1, s2;

    protected void setUp() throws Exception {
        super.setUp();
        s1 = new Simple(10);
        s2 = new Simple(-7);
    }

    public void testFoo() {
        assertTrue(s1.foo()==10);
        assertTrue(s2.foo()==7);
    }
}


其中setUp()方法是构造初始化环境,我们在setUp中创建两个Simple的实例,testFoo()是用来测试foo()的测试方法,总是以test+方法名构成,然后在测试方法中测试:s1.foo()==10,如果返回值与期待的结果10相等,assertTrue()就执行成功,我们现在可以运行Run->Run As...->JUnit Test,左侧会显示测试结果:



如果我们把Simple的foo()方法改成:


    public int foo() {
        return n;
    }


再次运行JUnit Test,现在assertTrue(s2.foo()==7);测试结果就不正确了,JUnit会报告哪一行结果不正确:



双击就可以快速定位到测试失败的方法调用上。


总结


JUnit功能非常强大,是代码质量的可靠保证。精心设计的TestCase可以反复使用,将来对某个类进行了更改,只需要运行一遍TestCase就知道改动对客户端有无影响。若干个TestCase还可以组合成TestSuit,结合Ant使得编译,测试,运行整个过程自动化,只需要查看测试结果就可以知道哪些代码出了问题。

状态模式之星际应用

一个对象有多种状态,在不同的状态下,同一种方法有不同的行为。如果用swich-case语句,将有大量的条件分支和逻辑代码混在一起。状态模式将每个状态封装到一个独立的类中,利用多态性使得不同状态下同一种方法表现不同的行为。


状态模式的UML图如下:





星际中人族的机枪兵
Marine有两种状态:普通状态和打了兴奋针后的状态,两种状态下机枪兵的开枪频率是不同的,我们用状态模式来实现机枪兵的fire()方法。


 


首先定义抽象状态State接口,这个接口指定了机枪兵的fire行为:


public interface State {
    public void fire();
}

State接口有一个fire()方法,我们实现两个子类NormalStateExcitedState,分别表示普通状态和打了兴奋针后的状态,并实现具体的fire方法:


public class NormalState implements State {
    public void fire() {
       System.out.println("普通状态每秒开枪1次。");
    }
}
public class ExcitedState implements State {
    public void fire() {
       System.out.println("兴奋状态每秒开枪2次。");
    }
}

最后,定义机枪兵类Marine,每个Marine的实例代表一个机枪兵:


public class Marine {
    // 保持一个状态类的实例:
    private State state = new NormalState();
 
    // 为机枪兵设置状态:
    public void setState(State state) {
       this.state = state;
    }
 
    // fire()方法,实际调用的是state变量的fire()方法:
    public void fire() {
       state.fire();
    }
}

最后我们看看如何在客户端控制一个机枪兵的状态:


public static void main(String[] args) {
    // 创建一个机枪兵的实例:
    Marine marine = new Marine();
    // 调用fire()方法:
    marine.fire();
    // 设置为兴奋状态:
    marine.setState(new ExcitedState());
    // 再调用fire()方法:
    marine.fire();
}

对同一个Marine对象调用两次fire()方法,屏幕输出为:


普通状态每秒开枪1次。
兴奋状态每秒开枪2次。

可见机枪兵在两种状态下的同一个fire()方法有不同的行为。


使用状态模式的好处是每个状态被封装到一个独立的类中,这些类可以独立变化,而主对象中没有繁琐的swich-case语句,并且添加新的状态非常容易,只需要从State派生一个新类即可。

细说 Java 之 util 类

  线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。


Collection
List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
Set
Map
├Hashtable
├HashMap
└WeakHashMap


Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
    Iterator it = collection.iterator(); // 获得一个迭代子
    while(it.hasNext()) {
      Object obj = it.next(); // 得到下一个元素
    }

  由Collection接口派生的两个接口是List和Set。


List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。


LinkedList类
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));


ArrayList类
  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。


Vector类
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。


Stack 类
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。


Set接口
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。


Map接口
  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。


Hashtable类
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
    Hashtable numbers = new Hashtable();
    numbers.put(“one”, new Integer(1));
    numbers.put(“two”, new Integer(2));
    numbers.put(“three”, new Integer(3));

  要取出一个数,比如2,用相应的key:
    Integer n = (Integer)numbers.get(“two”);
    System.out.println(“two = ” + n);

  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
  Hashtable是同步的。


HashMap类
  HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。


WeakHashMap类
  WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。


总结
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。


(参考:Sun JDK1.4.1 API DOC)

通过 JNI 远程注册表访问

| 1 Comment

一种实用实现

Nigel Morton
软件工程师, IBM e-BIT — 商业事件基础结构
2002 年 8 月


本文是 Brian Venn 的文章 利用远程注册表访问(developerWorks,2002 年 1 月)的后续。本文不需要 InstallShield 就可以启用注册表查询(正如 Brian Venn 原先所描述的那样),这看起来是个不错的想法。用于注册表操作的所有函数都包含在 Windows API 中,所以这看起来是个试用部分 Windows API 的理想机会。

必要的函数
要启用远程注册表操作,必需实现两个函数:对远程注册表读出和写入的能力。


读和写函数已经被封装进了 RegConnect DLL。该 DLL 本身隐藏了这些函数实际操作原理的复杂性,稍后我们将对此进行描述。DLL 执行实际连接/断开和读/写函数,并提供了两个易于使用的函数。


可以通过任何能够利用 DLL 的语言(如 C 或 Visual Basic)调用这些函数。要允许从 Java 环境使用 DLL,得做一些额外的工作。


清单 123说明了这些函数的原型。

清单 1. C/C++ 原型函数





int WINAPI RegDBGetKeyValueEx(LPCTSTR MachineName, HKEY RegKey, LPCTSTR KeyName,
LPCTSTR ValueName, LPTSTR KeyValue) ;

int WINAPI RegDBSetKeyValueEx(LPCTSTR MachineName, HKEY RegKey, LPCTSTR KeyName,
LPCTSTR ValueName, LPTSTR KeyValue) ;


清单 2. Visual Basic 原型函数





Declare Function RegDBGetKeyValueEx Lib "RegConnect.dll" (ByVal MachineName As String,
ByVal RegKey As Long, ByVal KeyName As String, ByVal ValueName As String,
ByVal KeyValue As String)
As Integer

Declare Function RegDBSetKeyValueEx Lib "RegConnect.dll" (ByVal MachineName As String,
ByVal RegKey As Long, ByVal KeyName As String, ByVal ValueName As String,
ByVal KeyValue As String)
As Integer


清单 3. Java 原型函数





public native String RegDBGetKeyValueEx(String MachineName, int RegKey,
String KeyName, String ValueName) ;

注:因为函数将字符串作为调用结果返回,所以对于 Java 没有 KeyValue 参数。

public native int RegDBSetKeyValueEx(String MachineName, int RegKey, String KeyName,
String ValueName, String KeyValue) ;

公用类 GetKey 和 SetKey 定义了调用和变量。这些类可以用来调用 DLL 函数。


函数参数
对于所有语言,参数如下:


  • MachineName:您希望询问的机器的名称 ― 如果这是一个空字符串,则询问本地机器
  • RegKey:您希望查询的根键值,即 HKEY_LOCAL_MACHINE
  • KeyName:您希望查询的键名称,即 SOFTWARE\IBM\DB2
  • ValueName:您希望查询的键中值的名称,即 DB2 Folder Name
对于获取键(除了 Java 函数以外):

  • KeyValue:在该参数中将返回键值
对于设置键:

  • KeyValue:您希望设置的键值


使用函数
通过用所需参数调用各个函数来实现对这些函数的调用。当调用 RegDBGetKeyValueEx 时,某些语言需要用于返回字符串的预定义存储器。如果没有预先分配这个存储器,那么,该函数可能会(并且很可能会)失败。


正如其实现的那样,RegDBGetKeyValueEx 函数仅限于返回注册表字符串(REG_SZ)和数字(DWORD)数据。还请注意,RegDBGetKeyValueEx 仅返回字符串 ― DWORD 值是作为与其等价的 ASCII 数值返回的。


当使用 RegDBSetKeyValueEx 函数来创建键时,如果该键不存在,则将创建键名和键值,记住这一点很重要。如果键已经存在,则原先的值将被覆盖。还请注意,正如其实现的那样,RegDBSetKeyValueEx 将仅存储字符串值。


清单 4显示了一个用 Visual Basic 函数封装 RegDBGetKeyValueEx 函数的示例。

清单 4. Visual Basic 示例





Function GetRegValue(szMachine$, hKey&, szRegTree$, szKey$) As String
Dim Temp&, sReturn$

sReturn$ = Space(128) 'Pre-define storage for the returned value
Temp& = RegDBGetKeyValueEx(szMachine$, hKey&, szRegTree$,
szKey$, sReturn$)
GetRegValue = Left$(sReturn$, InStr(sReturn$, Chr$(0)))

End Function


为了在 Java 环境中使用这些函数,使用类 GetKey 和 SetKey。 清单 5中的下列示例演示了使用 Java 设置和检索键。

清单 5. Java 示例





// Test for registry read/write using JNI call to RegConnect.dll

class RegTest
{
public static void main(String[] args)
{
// Set a registry key value
SetKey skey = new SetKey() ;

skey.RegKey = skey.HKEY_LOCAL_MACHINE ;
skey.MachineName = "" ;
//Change this to a remote machine if you wish
skey.KeyName = "software\\IBM\\MyKey" ;
skey.ValueName ="test" ;
skey.KeyValue ="My NEW reg value" ;
skey.iResult = skey.RegDBSetKeyValueEx(skey.MachineName, skey.RegKey,
skey.KeyName,
skey.ValueName, skey.KeyValue) ;
System.out.println("Set Result is " + skey.iResult); //Display the result of the call

// Now read the value back again
GetKey gkey = new GetKey() ;

gkey.RegKey = gkey.HKEY_LOCAL_MACHINE ;
gkey.MachineName = "" ;
//Change this to a remote machine if you wish
gkey.KeyName = "software\\IBM\\MyKey" ;
gkey.ValueName ="test" ;
gkey.KeyValue = gkey.RegDBGetKeyValueEx(gkey.MachineName, gkey.RegKey,
gkey.KeyName, gkey.ValueName) ;
System.out.println("Data is " + gkey.KeyValue); //Display the key value
}
}


DLL 代码(完成所有工作的那部分)
本文所提供的实现 Registry 功能的 DLL 使用标准 Windows API 调用来执行其函数。两个函数的初始需求都是连接到远程注册表。使用下列函数调用实现这一点:






// Connect to the remote registry
lRC = RegConnectRegistry(cMachine, hRegKey, &hRemoteKey) ;

这个函数调用的重要参数是正在连接的远程机器名和您希望访问的注册表的根。这些参数包含于用于机器名的 cMachine 参数和用于注册表根的 hRegKey 参数中。hRegKey 参数实际上只是一个长数据类型,它是用与所需注册表根相关的值设置的。这些值被声明为常数值,即 HKEY_CURRENT_USER。剩下的那个参数返回一个指向该注册表的句柄,其余函数使用该句柄来访问该远程注册表。


一旦获得了到远程机器的注册表的连接,就可以执行对该注册表读或写的函数。


如果正在调用设置键值的函数,则要设置的注册表项可能不存在。为了确保该项存在,调用 RegCreateKeyEx 函数。这样做的效果是创建该项(如果它不存在)或仅打开该项以便访问(如果它已经存在)。返回的 hKey 值是一个句柄,它指向下列函数调用中使用的注册表键。下列代码显示了执行这个操作的函数调用:






// Initialize variables
lRC = RegCreateKeyEx(hRemoteKey, lpKeyName, 0, REG_NONE,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisp) ;

当读取键时,只需要获得指向注册表键的句柄。使用 API 调用 RegOpenKeyEx 来获取必要的 hKey 注册表句柄,如下所示。






lRC = RegOpenKeyEx(hRemoteKey, lpKeyName, 0, KEY_ALL_ACCESS, &hKey);

注册表项可以是多种类型,包括字符串和数字。当读入项时,了解正在读取的项的类型很重要。这可以通过调用 RegQueryValueEx 获取的注册表键数据类型进行确定。dwKeyDataType 参数返回键的数据类型。下面显示了该函数。






// Got key, get value. First, get the size of the key.
lRC = RegQueryValueEx(hKey, lpValueName, NULL, &dwKeyDataType, NULL, &dwKeySize);

我们可以使用 dwKeyDataType 值确定得到的是字符串值还是数字值,并查询该键以便正确返回该值,如下所示。






if(dwKeyDataType == REG_DWORD) //DWORD (Numeric value)
{
char cData[128] = {0} ;
lRC = RegQueryValueEx(hKey, lpValueName, NULL, &dwKeyDataType, (LPBYTE)cData
&dwKeySize);
ltoa((LONG)cData[0], lpszKeyValue, 10) ;
}
else
// Now get the actual key value
lRC = RegQueryValueEx(hKey, lpValueName, NULL, &dwKeyDataType, (LPBYTE)lpszKeyValue,
&dwKeySize);

为了使 DLL 函数保持简单,RegDBGetKeyValueEx 函数只返回字符串值。因为注册表可以包含数值,所以所有返回的数值都被转换成字符串。使用调用应用程序自身的函数,很容易将该字符串转换回数字值。如上所示,ltoa 函数是用来将数值转换成字符串的。在任一函数的结束部分都必须关闭注册表。这是通过调用 RegCloseKey API 函数完成的。


DLL 的 Java 支持
Java 能够通过使用 JNI 接口调用 DLL 中的函数。要允许与 Java 一起使用 DLL,需要在类文件中设置用于 DLL 的 Java 可调用函数。首要任务是为每个函数创建一个 Java 类。用于 GetKey 函数的类如下:






// Class to read a registry key from a local or remote machine
public class GetKey{
int RegKey ; //Key value, that is, HKEY_CLASSES_ROOT
String MachineName ; //Machine name we want to interrogate
String KeyName ; //Name of key we want to read
String ValueName ; //Value of key we want to read
String KeyValue ; //Return value from key

public native String RegDBGetKeyValueEx(String MachineName, int RegKey,
String KeyName, String ValueName) ;
public static final int HKEY_CLASSES_ROOT = 0x80000000 ;
public static final int HKEY_CURRENT_USER = 0x80000001 ;
public static final int HKEY_LOCAL_MACHINE = 0x80000002 ;
public static final int HKEY_USERS = 0x80000003 ;
public static final int HKEY_CURRENT_CONFIG = 0x80000005 ;
public static final int HKEY_DYN_DATA = 0x80000006 ;

static
{
System.loadLibrary("RegConnect") ;
}
}


如代码所示,值和函数都与 C 或 Visual Basic 所使用的值和函数非常相似。


一旦创建了类,就可以使用 JDK 函数 Javah 来为 DLL 创建一个能包含在 C 代码中的头文件。该头文件包含类函数的所有 C 实现代码。这和打开一个命令提示符、更改到驻留 Java 源文件的目录以及运行命令 javah GetKey 一样简单。这个操作将创建可以被包含到 C 代码中的 GetKey.h 文件。相同过程也适用于创建 SetKey.h 头文件。


最后,需要在 JNI 代码中封装 C 函数。从 Javah JDK 实用程序创建的头文件中复制函数原型:






// Java implementation of RegDBGetKeyValueEx
JNIEXPORT jobject JNICALL Java_GetKey_RegDBGetKeyValueEx(JNIEnv* env, jobject this_obj,
jstring jMachineName, jint hRegKey, jstring jKeyName, jstring jValueName)

接下来,为调用 C 函数所需的每个字符串创建参数:






LPCTSTR lpMachineName ;
LPCTSTR lpKeyName ;
LPCTSTR lpValueName ;
char cKeyValue[255] ;

最后,为函数返回声明 Java 对象:






jobject ret ;

当进行 JNI 调用时,Java 向该函数传递一个对象。这个对象必需是非绑定的,并且获取了字符串。JNI 函数 GetStringUTFChars 执行这个任务。用下列代码用来获取字符串:






lpMachineName = (*env)->GetStringUTFChars(env, jMachineName, NULL) ;
lpKeyName = (*env)->GetStringUTFChars(env, jKeyName, NULL) ;
lpValueName = (*env)->GetStringUTFChars(env, jValueName, NULL) ;

现在,我们已准备好调用 C 函数了。下面显示了该调用,它带有从 Java 对象获取的参数:






RegDBGetKeyValueEx(lpMachineName, (HKEY)hRegKey, lpKeyName, lpValueName, cKeyValue) ;

我们现在已经完成了 Java 对象的使用,所以需要释放它们使用的字符串和内存。ReleaseStringUTFChars 函数执行这个任务,如下所示:






(*env)->ReleaseStringUTFChars(env, jMachineName, NULL) ;
(*env)->ReleaseStringUTFChars(env, jKeyName, NULL) ;
(*env)->ReleaseStringUTFChars(env, jValueName, NULL) ;

最后,我们需要将从注册表获取的字符串存放到正在返回的 Java 对象,然后将该对象返回到 Java 程序。JNI 函数 NewStringUTF 执行该任务,如下所示:






ret = (*env)->NewStringUTF(env, cKeyValue) ;
return ret ;

这就是所有步骤。如果您检查所提供的代码,就会发现 SetKey 函数几乎和 GetKey 函数相同。


示例代码
本文提供的代码(请参阅 参考资料)包含下列内容:


  • RegConnect.DLL 源代码,Visual Studio 6 工程以及预构建的发行版 DLL
  • Java 类和头文件以及用来试验函数的 Java 样本
  • 封装了函数调用并演示函数的 Visual Basic 工程


结束语
本文说明了如何通过使用 DLL 执行 Windows API 函数调用来实现本地或远程注册表操作。它提供了从 Java 和 Visual Basic 环境使用 DLL 的样本。注册表操作可以成为任何程序的有用补充,因为它允许保存和检索运行时配置数据。


参考资料






关于作者
Nigel Morton 是在英国 Hursley Park 工作的软件工程师。自从 2000 年 9 月以来,他一直为 IBM 的商业集成(Business Integration)项目工作。在加入 IBM 以前,他开发过银行业和电信业的集成解决方案和消息传递软件。可通过 nmorton1@uk.ibm.com与 Nigel Morton 联系。

所有 ODBC 数据源名都存放在 Windows 注册表下的:HKEY_LOCAL_MACHINE\SOFTWARE\ODBC\ODBC.INI\ODBC Data Sources(系统 DSN)和 HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources(用户DSN)键值中。

一个完整的网上追捕的原程序

前面讲过很多的用asp+实现网长追捕的程序,现在就在这里给大家提供这个完整应用的原程序,希望大家首先先看看
以前的文章,带者问题来看这个程序,其实是很简单的。同时我也给大家带来了这个程序的下载!
这个程序现在还是 有些 小问题,比如在 处理通过IP 地址查找 主机域名的时候,如果输入的不是合法的IP地址,则程序会
返回一个错误,大家 如果有兴趣,可以自己修改修改这个程序的:)
<%@ Import NameSpace="System.Net" %>
<% @Import Namespace="System.Net.Sockets" %>
<%@ Import Namespace="System.IO" %>
<script language="C#" runat=server>
String strRet;
protected void doClick(Object Src, EventArgs E){

if(dropdown1.SelectedItem.Value.ToInt16()==2){


IPHostEntry hostInfo = DNS.GetHostByName(txtSearch.Text);
strRet= hostInfo.AddressList[0].ToString() + "<br>"; //域名转换成IP地址
}
else{
IPHostEntry hostInfo = DNS.GetHostByAddr(txtSearch.Text);
strRet= hostInfo.Hostname + "<br>"; //IP地址转换成域名
}


if(CheckBox1.Checked){
String host=txtSearch.Text;


TCPClient tcpc = new TCPClient();
strRet=strRet + "SMTP Server:" + TcpConnect(tcpc,host,25) + "<br>"; //SMTP 端口


tcpc = new TCPClient();
strRet=strRet + "WWW Server:" + TcpConnect(tcpc,host,80) + "<br>"; //WWW 端口


tcpc = new TCPClient();
strRet=strRet + "FTP Server: " + TcpConnect(tcpc,host,21) + "<br>"; //FTP 端口


tcpc = new TCPClient();
strRet=strRet + "Pop3 Server:" + TcpConnect(tcpc,host,110) + "<br>"; //Pop 端口


tcpc = new TCPClient();
strRet=strRet + "代理 Server:" + TcpConnect(tcpc,host,1080) + "<br>"; //Socket5 端口 代理服务器使用的端口


tcpc = new TCPClient();
strRet=strRet + "DNS Server:" + TcpConnect(tcpc,host,53) + "<br>"; //DNS 端口
}
showmsg.Text=strRet;
}


String TcpConnect(TCPClient tcpc,String host,int port){
//这个函数的功能是 检查 指定的 host 的 port 是否在用?
String strRet="服务没有找到";
if(0 == tcpc.Connect(host,port)){
//联结服务器成功
strRet="正在运行";
}
return strRet;
}


</script>
<html>
<head>
<title>WEB追捕</title>
<SCRIPT LANGUAGE="JScript">
//这两个函数是功能是对 剪贴板 进行存取
function doufucopy() {
textRange = txtSearch.createTextRange();
textRange.execCommand("Copy");
}
function doufupaste() {
textRange = txtSearch.createTextRange();
textRange.execCommand("Paste");
}
-->
</SCRIPT>
</head>
<body>
<form id=testForm runat=server>
<asp:DropDownList id="dropdown1" runat="server">
<asp:ListItem ID=ListItem1 Value=1>查找域名</asp:ListItem>
<asp:ListItem ID=ListItem2 Value=2>查找IP</asp:ListItem>
</asp:DropDownList>
<asp:TextBox runat=server id=txtSearch />
<br>
<asp:CheckBox id="CheckBox1" runat="server" Text="检查对方的机器" />
<br>
<asp:Button runat=server id=do Text="查找" onClick=doClick />
</form>
<asp:Label id=showmsg runat=server />
<input type=button value="复制" onclick="doufucopy();">
<input type=button value="粘贴" onclick="doufucopy();">
</body>
</html>

C#独立域名查询

 

whois.aspx


 


<% @Page Language="C#" %>
<% @Import Namespace="System.Net.Sockets" %>
<% @Import Namespace="System.Text" %>
<% @Import Namespace="System.IO" %>
<% @Import Namespace="System.Collections" %>
<script language="C#" runat="server">
void doQuery(Object sender, EventArgs e)
{
  String strDomain = txtDomain.Text;
  char[] chSplit = {'.'};
  string[] arrDomain = strDomain.Split(chSplit);
  // es darf genau ein domain name + ein suffix sein
  if (arrDomain.Length != 2)
  {
    return;
  }


  // das suffic darf nur 2 oder 3 zeichen lang sein
  int nLength = arrDomain[1].Length;
  if (nLength != 2 && nLength != 3)
  {
    return;
  }


  Hashtable table = new Hashtable();
  table.Add("at", "whois.nic.at");
  table.Add("de", "whois.denic.de");
  table.Add("be", "whois.dns.be");
  table.Add("gov", "whois.nic.gov");
  table.Add("mil", "whois.nic.mil");


  String strServer = "whois.OnlineNIC.com";
  if (table.ContainsKey(arrDomain[1]))
  {
    strServer = table[arrDomain[1]].ToString();
  }
  else if (nLength == 2)
  {
    // 2-letter TLD's always default to RIPE in Europe
    strServer = "whois.ripe.net";
  }
 
  String strResponse;
  bool bSuccess = DoWhoisLookup(strDomain, strServer, out strResponse);
  if (bSuccess)
  {
    txtResult.Text = strResponse;
  }
  else
  {
    txtResult.Text = "Lookup failed";
  }
}


bool DoWhoisLookup(String strDomain, String strServer, out String strResponse)
{
  strResponse = "none";
  bool bSuccess = false;


  TcpClient tcpc = new TcpClient();
  try
  {
    tcpc.Connect(strServer, 43);
  }
  catch(SocketException ex)
  {
    strResponse = "Could not connect to Whois server";
    return false;
  }


  strDomain += "\r\n";
  Byte[] arrDomain = Encoding.ASCII.GetBytes(strDomain.ToCharArray());
  try
  {
 Stream s = tcpc.GetStream();
 s.Write(arrDomain, 0, strDomain.Length);
 
 StreamReader sr = new StreamReader(tcpc.GetStream(), Encoding.ASCII);
 StringBuilder strBuilder = new StringBuilder();
 string strLine = null;


 while (null != (strLine = sr.ReadLine()))
 {
  strBuilder.Append(strLine+"<br>");
 }
 tcpc.Close();
  
 bSuccess = true;
 strResponse = strBuilder.ToString();
  }
  catch(Exception e)
  {
 strResponse = e.ToString();
  }
   
    return bSuccess;
}
</script>
<html>
<head>
<title></title>
</head>
<body>


<form runat="server">
域名whois查询(.NET版): <asp:TextBox id="txtDomain" value="/oblog4/3cts.com" runat="server" />
&nbsp;<asp:Button id="btnQuery" OnClick="doQuery" text="Query!" runat="server" />
<BR><HR width="100%"><BR>
<asp:label id="txtResult" runat="server" />
</form>


</body>
</html>



 

其实只要使用系统内置的存储过程sp_spaceused就可以得到表的相关信息


如:sp_spaceused 'tablename'


以下是为了方便写的一个存储过程,目的是把当前的所有表的相关信息全部都保存在一个指定的表里面


CREATE PROCEDURE get_tableinfo AS

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[tablespaceinfo]'and OBJECTPROPERTY(id, N'IsUserTable'= 1)
create table  tablespaceinfo                         --创建结果存储表
              (nameinfo varchar(50) , 
               rowsinfo 
int , reserved varchar(20) , 
               datainfo 
varchar(20)  , 
               index_size 
varchar(20) , 
               unused 
varchar(20) )


delete from tablespaceinfo --清空数据表

declare @tablename varchar(255)  --表名称

declare @cmdsql varchar(500)

DECLARE Info_cursor CURSOR FOR 
select o.name  
from dbo.sysobjects o where OBJECTPROPERTY(o.id, N'IsTable'= 1 
     
and o.name not like N'#%%'  order by o.name

OPEN Info_cursor

FETCH NEXT FROM Info_cursor 
INTO @tablename 

WHILE @@FETCH_STATUS = 0
BEGIN

  
if exists (select * from dbo.sysobjects where id = object_id(@tablenameand OBJECTPROPERTY(id, N'IsUserTable'= 1)
  
execute sp_executesql 
         N
'insert into tablespaceinfo  exec sp_spaceused @tbname',
          N
'@tbname varchar(255)',
          
@tbname = @tablename

  
FETCH NEXT FROM Info_cursor 
  
INTO @tablename 
END

CLOSE Info_cursor
DEALLOCATE Info_cursor
GO

 


执行存储过程
exec get_tableinfo

查询运行该存储过程后得到的结果
select *
from tablespaceinfo 
order by cast(left(ltrim(rtrim(reserved)) , len(ltrim(rtrim(reserved)))-2) as int) desc


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


HOW TO:使用 Osql 工具管理 SQL Server 桌面引擎 (MSDE 2000)


转自msdn




本页










































































概要
  何为 Osql?
  如何使用 Osql?
    交互式输入 Transact-SQL 语句
    提交 Osql 作业
  连接到 SQL Server 桌面引擎 (MSDE 2000)
  管理 MSDE 2000
    新建登录帐户
    访问数据库
    如何更改登录密码
    创建数据库
    备份和还原数据库
    附加和分离数据库
参考
这篇文章中的信息适用于:


概要


“SQL Server 桌面引擎”(也叫 MSDE 2000)没有自己的用户界面,因为它主要设计为在后台运行。用户通过 MSDE 2000 嵌入的程序与它交互。随 MSDE 2000 提供的唯一工具是 osql 。可执行文件 Sql.exe 在 MSDE 2000 的默认实例的 MSSQL\Binn 文件夹中。本文重点讨论如何通过使用 osql 工具管理 MSDE 2000。







回到顶端

何为 Osql?

osql 工具是一个 Microsoft Windows 32 命令提示符工具,您可以使用它运行 Transact-SQL 语句和脚本文件。osql 工具使用 ODBC 数据库应用程序编程接口 (API) 与服务器通讯。







回到顶端

如何使用 Osql?

一般情况下,可以这样使用 osql 工具:








用户通过与使用命令提示符时相似的方式交互输入 Transact-SQL 语句。
用户提交 osql 作业,方法是:







指定单个要运行的 Transact-SQL 语句。 - 或 -

将该工具指向一个包含要运行的 Transact-SQL 语句的脚本文件。

交互式输入 Transact-SQL 语句

如要显示 osql 工具的区分大小写的选项列表,请在命令提示符下键入如下内容,然后按 ENTER 键:

osql -?

如想了解关于 osql 工具的每一选项的更多信息,请参见“SQL Server 联机图书”中的“osql Utility”主题。

如要交互输入 Transact-SQL 语句,请按照下列步骤操作:















1. 确认 MSDE 2000 正在运行。
2. 连接到 MSDE 2000(有关更多信息,请参见标题为“连接到 SQL Server 桌面引擎 (MSDE 2000)”的部分)。
3. osql 命令提示符下,键入 Transact-SQL 语句,然后按 ENTER 键。 当您在输入的每一行后按 ENTER 键时,osql 将缓存该命令行上的语句。









如要运行当前缓存的语句,请键入“Go”,接着按 ENTER 键。

如要运行一批 Transact-SQL 语句,请分别在单独的行上输入每一个 Transact-SQL 命令。然后,在最后一行上键入“Go”以表示批处理命令的结束并运行当前缓存的语句。

运行结果出现在控制台窗口。

4. 当您在输入的每一行后按 ENTER 键时,如想从 osql 退出,请键入 QUIT 或 EXIT,并按 ENTER 键。


提交 Osql 作业

一般情况下,您可以用两种方法之一提交 osql 作业。您可以:







指定单个 Transact-SQL 语句。

- 或 -

将该工具指向一个脚本文件。
下面将详细介绍每一种方法。

指定单个 Transact-SQL 语句

如要针对 MSDE 2000 的本地默认实例运行 Transact-SQL,请键入与下面这一个类似的命令:

osql -E -q "Transact-SQL statement"

其中









-E 表示使用 Microsoft Windows NT 身份验证。

-而-

-q 表示运行 Transact-SQL 语句,但是在查询结束时不退出 osql
如要运行 Transact-SQL 语句并退出 osql,请使用 -Q 参数来代替 -q

将该工具指向一个脚本文件

如要将该工具指向一个脚本文件,请按照下列步骤操作:







1. 创建一个包含一批 Transact-SQL 语句的脚本文件(如 myQueries.sql)。
2. 打开命令提示符,键入与下面类似的一个命令,然后按 ENTER 键:

osql -E -i input_file

其中

input_file 是脚本文件及其完整路径。例如,如果脚本文件 myQueries.sql 在 C:\Queries 文件夹中,请将参数 input_file 替换为 C:\Queries\myQueries.sql。

该脚本文件的运行结果将出现在控制台窗口中。如果您想将运行结果定向到一个文件,请向上述命令中添加 -ooutput_file 参数。例如:

osql -E -i input_file -o output_file

其中

output_file 是输出文件及其完整路径。

如想消除输出结果中的编号和提示符号,请向上述命令中添加 -n 选项。例如:

osql -E -i input_file -o output_file -n






回到顶端

连接到 SQL Server 桌面引擎 (MSDE 2000)

如要连接到 MSDE 2000,请按照下列步骤操作:










1. 确认 MSDE 2000 正在运行。
2. 在承载您要连接的 MSDE 2000 实例的计算机上打开一个命令窗口。
3. 键入下面的命令,然后按 ENTER 键:

osql -E

这可以通过使用 Windows 身份验证将您连接到 MSDE 2000 的本地默认实例。

如要连接到 MSDE 2000 的一个命名实例,请键入:

osql -E -S servername\instancename

如果您收到了下面的错误消息,表明 MSDE 2000 可能未在运行,或者您可能为安装的 MSDE 2000 的命名实例提供了错误的名称:
[Shared Memory]SQL Server does not exist or access denied.
[Shared Memory]ConnectionOpen (Connect()).
如果您成功连接到了该服务器,就会出现下面的提示:

   1>
此提示表示 osql 已启动。现在,您可以交互输入 Transact-SQL 语句,运行结果将出现在命令提示行上。






回到顶端

管理 MSDE 2000

本文下面的部分将向您简单介绍管理 MSDE 2000 时最常用的 Transact-SQL 命令。


新建登录帐户

未提供有效登录 id 的用户无法连接到 SQL Server。可调用 sp_grantlogin 存储过程来授权一个 Microsoft Windows 网络帐户(一个组或者一个用户帐户),使之作为一个使用 Windows 身份验证连接到 SQL Server 实例的 SQL Server 登录帐户。下面的示例允许一个名为 Corporate\Test 的 Windows NT 用户连接到 SQL Server 实例:
EXEC sp_grantlogin 'Corporate\Test'
只有 sysadmin securityadmin 固定服务器角色的成员可以运行 sp_grantlogin 存储过程。有关这些角色的更多信息,请参见“SQL Server 联机图书”中“Roles, SQL Server Architecture”主题。

有关 sp_grantlogin 存储过程的更多信息,请参见“SQL Server 联机图书”中的“sp_grantlogin, Transact-SQL Reference”主题。

您可以使用 sp_addlogin 存储过程创建一个使用 SQL Server 身份验证建立 SQL Server 连接的新登录帐户。下面的示例为一个名叫“test”的用户创建了一个密码为“hello”的 SQL Server 登录:
EXEC sp_addlogin 'test','hello'
只有 sysadmin securityadmin 固定服务器角色的成员可以运行 sp_addlogin 存储过程。有关 sp_addlogin 存储过程的更多信息,请参见“SQL Server 联机图书”中的“sp_addlogin, Transact-SQL Reference”主题。


访问数据库

在用户连接到 SQL Server 的一个实例后,他们只有在 dbo 授予他们对数据库的访问权后才可以在数据库中执行活动。您可以使用 sp_grantdbaccess 存储过程为新用户向当前数据库中添加一个安全帐户。下面的示例为一个名叫 Corporate\BobJ 的 Microsoft Windows NT 的用户向当前数据库添加了一个帐户,并将其命名为“Bob”:
EXEC sp_grantdbaccess 'Corporate\BobJ', 'Bob'

sp_adduser 存储过程执行与 sp_grantdbaccess 存储过程相同的功能。因为包括 sp_adduser 存储过程是为了向后兼容,所以 Microsoft 建议您使用 sp_grantdbacess 存储过程。

只有 sysadmin 固定服务器角色、db_accessadmindb_owner 固定数据库角色的成员才可以运行 sp_grantdbaccess 存储过程。有关 sp_grantdbaccess 存储过程的更多信息,请参见“SQL Server 联机图书”中的“sp_grantdbaccess, Transact-SQL Reference”主题。


如何更改登录密码

如要修改登录密码,请使用 sp_password 存储过程。下面的示例将“test”登录的密码从“ok”更改为“hello”:
EXEC sp_password 'ok', 'hello','test'

执行权限默认授予正在更改其自己的登录密码的用户的公共角色。只有 sysadmin 角色才可以为其他用户更改登录密码。有关 sp_password 存储过程的更多信息,请参见“SQL Server 联机图书中”的“sp_password, Transact-SQL Reference”主题。

创建数据库

MSDE 2000 数据库由一个表的集合组成,这些表中包含数据和其他对象,如视图、索引、存储过程和事件触发器,这些内容定义为支持对数据执行的各种活动。如要创建 MSDE 2000 数据库,请使用“CREATE DATABASE”Transact-SQL 命令。有关 创建数据库的更多信息,请参见“SQL Server 联机图书”中的“Creating a Database”主题。

下面的示例创建了一个名为 Test 的数据库。因为没有向该命令行添加其他参数,所以 Test 数据库将与 model 数据库大小相同:
CREATE DATABASE Test
CREATE DATABASE 权限默认授予 sysadmin dbcreator 固定服务器角色的成员。有关“CREATE DATABASE”命令的更多信息,请参见“SQL Server 联机图书”中的“CREATE DATABASE, Transact-SQL Reference”主题。

如要创建一个新的数据库对象,请使用“CREATE Transact-SQL”命令。例如,要新建一个表,请使用“CREATE TABLE”Transact-SQL 命令。有关更多信息,请参考“SQL Server 联机图书”。


备份和还原数据库

SQL Server 的备份和还原组件为保护存储在 SQL Server 数据库中的关键数据提供了一个重要的保护措施。

通过适当的规划,您可以从许多故障中恢复,包括:











存储媒体故障。
用户错误。
服务器的永久丢失。
另外,备份和还原数据库还有其他方面的用途,例如将数据库从一个服务器复制到另一个服务器。通过从一台计算机上备份一个数据库和将此数据库还原到另一台计算机上,您可以快速方便地制作数据库的副本。

有关数据库备份和还原操作方面的更多信息,请参见“SQL Server 联机图书”中的“Backing Up and Restoring Databases”主题。

下面的示例为一个名为 mydb的数据库执行完全数据库备份,将此备份命名为 Mydb.bak,然后将此备份存储在 C:\Msde\Backup 文件夹中。

BACKUP DATABASE mydb TO DISK = 'C:\MSDE\Backup\mydb.bak'

下面的示例为一个名为 mydb 的数据库执行日志备份,将此备份命名为 Mydb_log.bak,然后将其存储在 C:\Msde\Backup 文件夹中:

BACKUP LOG mydb TO DISK = 'C:\MSDE\Backup\mydb_log.bak'

BACKUP DATABASE 和 BACKUP LOG 权限默认授予 sysadmin 固定服务器角色以及 db_ownerdb_backupoperator 固定数据库角色的成员。有关 BACKUP 语句的更多信息,请参见“SQL Server 联机图书”中的“BACKUP, Transact-SQL Reference”主题。


MSDE 包括 SQL Server 代理程序服务用以管理安排的作业。例如,您可以创建并安排一个 Transact-SQL 备份作业。SQL Server 代理程序服务管理作业安排。如想查看演示如何在 MSDE 2000 中使用各种存储过程执行和安排备份的示例代码,请参见下面的 Microsoft 知识库文章:

241397 (http://support.microsoft.com/kb/241397/EN-US/) HOWTO:Back Up a Microsoft Data Engine Database with Transact-SQL
有关 SQL Server 代理程序服务的更多信息,请参见“SQL Server 联机图书”中的“SQL Server Agent Service”主题。

备份数据库只是全部过程的一半。知道如何从备份中还原数据库也非常重要。下面的示例将一个名为 mydb 的数据库从备份文件 C:\Msde\Backup\Mydb.bak 中还原:

RESTORE DATABASE mydb FROM DISK ='C:\MSDE\Backup\mydb.bak'
如果将要还原的数据库不存在,则用户必须具有 CREATE DATABASE 权限才可以运行 RESTORE 语句。如果该数据库存在,则 RESTORE 权限默认授予 sysadmin dbcreator 固定服务器角色的成员,以及该数据库的所有者 (dbo)。有关 RESTORE 语句的更多信息,请参见“SQL Server 联机图书”中的“RESTORE, Transact-SQL Reference”主题。

附加和分离数据库

可以分离一个数据库的数据和事务日志文件然后将其重新附加到另一个服务器,或重新附加到同一服务器。分离一个数据库虽然从 SQL Server 中删除了该数据库,但构成该数据库的数据和事务日志文件没有任何改动。 然后您可以使用这些数据和事务日志文件将该数据库附加到任何 SQL Server 实例,其中包括从中分离该数据库的那一服务器。这使该数据库能够以与在被分离时完全相同的状态供在其他位置使用。有关更多信息,请参见 “SQL Server 联机图书”中的“Attaching and Detaching a Database”主题。

下面的示例将一个名为 mydb 的数据库从 SQL Server 的当前实例中分离出来:
EXEC sp_detach_db 'mydb'
只有 sysadmin 固定服务器角色的成员才可以运行 sp_detach_db 存储过程。有关 sp_detach_db 存储过程的更多信息,请参见“SQL Server 联机图书”中的“sp_detach_db, Transact-SQL Reference”主题。

下面的示例将来自名为 mydb 的数据库的两个文件附加到 SQL Server 的当前实例:
EXEC sp_attach_db @dbname = N'mydb',
@filename1 = N'C:\MSDE\Backup\mydb.mdf',
@filename2 = N'C:\MSDE\Backup\mydb.ldf'
大写字母“N”用来给“Unicode 字符串”常量添加前缀。“N”前缀代表 SQL-92 标准中的区域语言。有关详细信息,请参见 Microsoft 知识库中的以下文章:
239530 (http://support.microsoft.com/kb/239530/EN-US/) INF:Unicode String Constants in SQL Server Require N Prefix
只有 sysadmin dbcreator 固定服务器角色的成员才可以运行此过程。有关 sp_attach_db 存储过程的更多信息,请参见“SQL Server 联机图书”中的“sp_attach_db, Transact-SQL Reference”主题。 下面关于 osql 工具使用方面的信息适用于所有版本的 Microsoft SQL Server 2000。







回到顶端

参考


如要下载“SQL Server 2000 联机图书”的更新版本,请访问下面的 Microsoft Web 站点:
http://www.microsoft.com/sql/techinfo/productdoc/2000/books.asp (http://support.microsoft.com/?scid=http%3a%2f%2fwww.microsoft.com%2fsql%2ftechinfo%2fproductdoc%2f2000%2fbooks.asp)
如要下载“SQL Server 联机图书”的 SQL Server 7.0 版,请访问下面的 Microsoft Web 站点:
http://download.microsoft.com/download/SQL70/File/2/Win98/En-US/SQLBOL.exe (http://download.microsoft.com/download/sql70/file/2/win98/en-us/sqlbol.exe)
有关 MSDE 2000 的更多信息,请参见下列 Microsoft 知识库文章:
319930 (http://support.microsoft.com/kb/319930/EN-US/) HOW TO:Connect to Microsoft Desktop Engine

241397 (http://support.microsoft.com/kb/241397/EN-US/) HOWTO:Back Up a Microsoft Desktop Engine Database with Transact-SQL






回到顶端


JAVA 23设计模式一点就通

一、创建型模式

  FACTORY?人才市场:以往是要哪个人才,就找哪个人才,效率低,现在有了人才市场,我们只需直接去人才市场挑一个好了;

  BUILDER?生产流水线:以前是手工业作坊式的人工单个单个的生产零件然后一步一步组装做,好比有了工业革命,现在都由生产流水线代替了。如要造丰田汽车,先制定汽车的构造如由车胎、方向盘、发动机组成。再以此构造标准生产丰田汽车的车胎、方向盘、发动机。然后进行组装。最后得到丰田汽车;

  PROTOTYPE?印刷术的发明:以前只能临贴才能保持和别人的字迹基本相同,直从印刷技术发明,从而保证了复制得和原物一模一样;

  SINGLETON?唯一:以前是商标满天飞,相同的商标难免造成侵权,直从有商标保护法后,就保证了不会再产生第家企业使用相同的商标;


  二、结构型模式

  ADAPTER?集众人之私,成一己之公:武当派张三丰会太极拳,少林派智空大师会金刚般若掌,如果他们两个都成为我的师傅,我就既会太极拳,又会金刚般若掌了;

  DECORATOR?青出于蓝而胜于蓝:武当派张三丰会太极拳,是我师傅,他教会了我太极拳,但我自己还会点蒙古式摔交,张三丰却不会。于是我就成了DECORATOR模式的实现;

  BRIDGE?白马非马:马之颜色有黑白,马之性别有公母。我们说"这是马"太抽象,说"这是黑色的公马"又太死板,只有将颜色与性别和马动态组合,"这是(黑色的或白色的)(公或母)马"才显得灵活而飘逸,如此bridge模式精髓得矣。

  COMPOSITE?大家族:子又生孙,孙又生子,子子孙孙,无穷尽也,将众多纷杂的人口组织成一个按辈分排列的大家族即是此模式的实现;

  FACADE?求同存异:高中毕业需读初中和高中,博士也需读初中和高中,因此国家将初中和高中普及成九年制义务教育;

  FLYWEIGHT?一劳永逸:认识三千汉字,可以应付日常读书与写字,可见头脑中存在这个汉字库的重要;

  PROXY?垂帘听政:犹如清朝康熙年间的四大府臣,很多权利不在皇帝手里,必须通过辅佐大臣去办;

  三、行为模式

  CHAIN OF RESPONSIBLEITY?租房:以前为了找房到处打听,效率低且找不到好的房源。现在有了房屋中介,于是向房屋中介提出租房请求,中介提供一个合适的房源,满意则不再请求,不满意继续看房,直到满意为止;

  COMMAND?借刀杀人:以前是想杀谁就杀,但一段时间后领悟到,长此以往必将结仇太多,于是假手他人,挑拨他人之间的关系从而达到自己的目的;

  INTERPRETER?文言文注释:一段文言文,将它翻译成白话文;

  ITERATOR?赶尽杀绝:一个一个的搜索,绝不放掉一个;

  MEDIATOR?三角债:本来千头万绪的债务关系,忽出来一中介,包揽其一切,于是三角关系变成了独立的三方找第四方中介的关系;

  MEMENTO?有福同享:我有多少,你就有多少;

  OBSERVER?看守者:一旦被看守者有什么异常情况,定会及时做出反应;

  STATE?进出自由:如一扇门,能进能出,如果有很多人随时进进出出必定显得杂乱而安全,如今设一保安限制其进出,如此各人进出才显得规范;

  STRATEGY?久病成良医:如人生病可以有各种症状,但经过长期摸索,就可以总结出感冒、肺病、肝炎等几种;

  TEMPLATE METHOD?理论不一定要实践:教练的学生会游泳就行了,至于教练会不会则无关紧要;

  VISITOR?依法治罪:因张三杀人要被处死,李四偷窃要被罚款。由此势必制定处罚制度,故制定法律写明杀人、放火、偷窃等罪要受什么处罚,经通过后须变动要小。今后有人犯罪不管是谁,按共条例处罚即是,这就是访问者模式诞生的全过程。


一个简单的有关 C# event 的例子!

对于一个刚刚接触 .net、c# 或者 oop 的人来说, events 和 delegates 的概念是比较复杂而难于理解的。而下面的这个例子我认为也许是说明 c#  Event Handling 最简单的例子之一(此话有点绝对)。


一个 Metronome(节拍器) class 创建 events,每3秒钟作一个 tick,然后有一个 Listener(收听者) class 来接收(或者说是收听)这个 Metronome(节拍器)发出的 ticks,每接收到一个 tick 就以 Console 的形式打印出 "HEARD IT"。


这是一个非常好的想法,能够使所谓的 新手(novice programmer),迅速理解 events 的概念及使用。


下面这篇代码已经通过测试,可以直接粘贴到一个空的 c# project 中 ……





using System;
namespace wildert
{
    public class Metronome
    {
        public event TickHandler Tick;
        public EventArgs e = null;
        public delegate void TickHandler(Metronome m, EventArgs e);
        public void Start()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(3000);
                if (Tick != null)
                {
                    Tick(this, e);
                }
            }
        }
    }
        public class Listener
        {
            public void Subscribe(Metronome m)
            {
                m.Tick += new Metronome.TickHandler(HeardIt);
            }
            private void HeardIt(Metronome m, EventArgs e)
            {
                System.Console.WriteLine("HEARD IT");
            }


        }
    class Test
    {
        static void Main()
        {
            Metronome m = new Metronome();
            Listener l = new Listener();
            l.Subscribe(m);
            m.Start();
        }
    }
}

Download source - 3.97 Kb


Features



  • Delegate invoke instance method with parameter.
  • Delegate invoke static method with parameter.
  • Delegate multi-casting: delegate invoking more than one methods at once.

Instructions


Just compile and run the sample. The code is documented and is self-explanatory.


Concept/background


Delegate are just simple mechanism to re-route calls to functions/methods that it references. The idea is simple. Invoking delegate invokes the methods/functions that the delegate references. The question is, why delegate? Why not just invoke the function/method directly?



  • The association between delegate and methods referenced by delegate is established in runtime - so, you get flexibility here.
  • Multi-cast: That's to associate a few methods/functions to ONE delegate. Invoking one delegate fires off ALL methods referenced by that one delegate all at once.

Code fragments


Here's your delegate



  • Delegate - Note that delegate has the same signature to all functions/methods that it references.
  • A delegate can reference more than one function/method - that's called multi-casting.
    public delegate int SomeDelegate(int nID, string sName);

Methods referenced by SomeDelegate can be found in WorkerClass

public class WorkerClass
{
//(1) First method (instance method) referenced by delegate:
public int InstanceMethod(int nID, string sName) {...}
//(2) Second method (static method) referenced by delegate:
static public int StaticMethod(int nID, string sName) {...}
};

The only difference between these two methods is that "InstanceMethod" is an instance method, that is, it must be invoked by an actual instance of WorkerClass. StaticMethod is a static member function - invoke it by: WorkerClass.StaticMethod(10,"aaa");


What the function does?



  • It will multiply "nID" by LENGTH of "sName". For example, nID=10, sName="aaa" (Length=3). Therefore return value="/oblog4/10x3=30."
  • It will write to console:
    "InstanceMethod invoked, return value=..." OR
    "StaticMethod invoked, return value=..."

Association between delegates and methods it references can be found in main(...)

//PART 1: invoking instance method            
WorkerClass wr = new WorkerClass();
SomeDelegate d1 = new SomeDelegate(wr.InstanceMethod);
//Associating delegate with wr.InstanceMethod

Console.WriteLine("Invoking delegate InstanceMethod, return={0}",
d1(5, "aaa") ); //Invoking wr.InstanceMethod with input parameters.

//PART 2: invoking static method
SomeDelegate d2 = new SomeDelegate(WorkerClass.StaticMethod);
//Associating delegate with WorkerClass.StaticMethod (NOTE: "wr"
//instance is NOT used. The class itself is used!!)
Console.WriteLine("Invoking delegate StaticMethod, return={0}",
d2(5, "aaa") ); //Invoking InstanceMethod with input parameters.

//PART 3: MultiCAST!
Console.WriteLine();
Console.WriteLine("Testing delegate multi-cast..");
SomeDelegate d3 = (SomeDelegate) Delegate.Combine(d1, d2);
Console.WriteLine("Invoking delegate(s) d1 AND d2 (multi-cast), return={0} ",
d3(5, "aaa") ); //Fire BOTH delegates (d1 AND d2) by firing d3!


Conclusion


That's it. It's simple right?




  VB.NET中还是有很多有用的东西的。比如VB.NET可以直接调用Microsoft.VisualBasic命名空间下的各种有用的函数,特别是一些类型验证函数(IsDate,IsNumeric)就十分有用。就拿判断是否为数字来说,在网上我找到了在C#中用到的三种方法(但都有各自的不足之处)
1、将字符串分解成Char,然后用Char.IsNumber(c)验证



public bool IsNumericChar(string str)
{
    
if (str == null || str.Length==0
    
{
        
return false
    }

    
foreach(char c in str) 
    

        
if (!Char.IsNumber(c)) 
        

            
return false
        }
 
    }
 
    
return true
}

缺点:只能验证正整形数字,对浮点数,负数无效。


2、用正则表达式来判断是否为数字



public bool IsNumericRegex(string str)
{
    System.Text.RegularExpressions.Regex reg 
= new System.Text.RegularExpressions.Regex(@"^[-]?\d+[.]?\d*$");   
    
return reg.IsMatch(str);  
}

缺点:数字类型的形式多样,像以科学计数法出现的是形式就不能判断,如:1.234568E+008。当然可以考虑修改正则表达式,但是终究不清楚还有何种形式我们没有考虑到。


3、用try...catch...将指定字符串转为数字,如果转换不成功则表示不是数字



public bool IsNumericTry(string str)
{
    
try
    
{
        
// 先判断是否为整形
        Int32.Parse(str);
    }

    
catch
    
{
        
// 在判断是否为浮点型
        try
        
{
            Double.Parse(str);
        }

        
catch
        
{
            
return false;
        }

    }

    
return true;
}

缺点:功能倒是实现了,不过性能要大打折扣。(有人测试过,不过我没有测试


  我们为什么不用微软提供的Microsoft.VisualBasic.IsNumeric(obj)函数呢?在C#中不能直接调用Microsoft.VisualBasic命名空间下的函数,但是可以考虑建一个VB.NET项目,然后在C#中引用它。方法如下:
1、建一个VB.NET项目,并添加一个名为Validator的验证函数类。



Namespace VBUtilities
    
Public Class Validator
        
Public Shared Function IsNumeric(ByVal obj As ObjectAs Boolean
            
Return Microsoft.VisualBasic.IsNumeric(obj)
        
End Function

    
End Class

End Namespace


2、在需要用到该函数的C#项目中引用该程序集。用Validator.IsNumeric(obj)的方法调用就可以了。


  利用Microsoft.VisualBasic命名空间下的函数,我们还可以实现更多的验证功能,比如验证是否是日期类型(用IsDate)。这样要省掉不少麻烦去自己编写。还有别的方法吗?拿出来一起分享一些吧!



.Net 常用加密算法类

| 1 Comment

 

.Net框架由于拥有CLR提供的丰富库支持,只需很少的代码即可实现先前使用C等旧式语言很难实现的加密算法。本类实现一些常用机密算法,供参考。其中MD5算法返回Int的ToString字串。返回数字字母型结果的算法参见之前Blog文章。


using System;
using System.IO;
using System.Data;
using System.Text;
using System.Diagnostics;
using System.Security;
using System.Security.Cryptography;


namespace com.Quickline.Encrypt
{
 /// <summary>
 /// 类名:HashEncrypt
 /// 作用:对传入的字符串进行Hash运算,返回通过Hash算法加密过的字串。
 /// 属性:[无]
 /// 构造函数额参数:
 /// IsReturnNum:是否返回为加密后字符的Byte代码
 /// IsCaseSensitive:是否区分大小写。
 /// 方法:此类提供MD5,SHA1,SHA256,SHA512等四种算法,加密字串的长
度依次增大。
 /// </summary>
 public class HashEncrypt
 {
  //private string strIN;
  private bool isReturnNum;
  private bool isCaseSensitive;
  
  public HashEncrypt(bool IsCaseSensitive,bool IsReturnNum)
  {
   this.isReturnNum = IsReturnNum;
   this.isCaseSensitive = IsCaseSensitive;
  }
  
  
  private string getstrIN(string strIN)
  {
   //string strIN = strIN;
   if (strIN.Length == 0)
   {
    strIN = "~NULL~";
   }
   if (isCaseSensitive == false)
   {
    strIN = strIN.ToUpper();
   }
   return strIN;
  }
  public string MD5Encrypt(string strIN)
  {
   //string strIN = getstrIN(strIN);
   byte[] tmpByte;
   MD5 md5 = new MD5CryptoServiceProvider();
   tmpByte =
md5.ComputeHash(GetKeyByteArray(getstrIN(strIN)));
   md5.Clear();


   return GetStringValue(tmpByte);


  }
  
  public string SHA1Encrypt(string strIN)
  {
   //string strIN = getstrIN(strIN);
   byte[] tmpByte;
   SHA1 sha1 = new SHA1CryptoServiceProvider();


   tmpByte = sha1.ComputeHash(GetKeyByteArray(strIN));
   sha1.Clear();


   return GetStringValue(tmpByte);


  }


  public string SHA256Encrypt(string strIN)
  {
   //string strIN = getstrIN(strIN);
   byte[] tmpByte;
   SHA256 sha256 = new SHA256Managed();


   tmpByte =
sha256.ComputeHash(GetKeyByteArray(strIN));
   sha256.Clear();


   return GetStringValue(tmpByte);


  }


  public string SHA512Encrypt(string strIN)
  {
   //string strIN = getstrIN(strIN);
   byte[] tmpByte;
   SHA512 sha512 = new SHA512Managed();


   tmpByte =
sha512.ComputeHash(GetKeyByteArray(strIN));
   sha512.Clear();


   return GetStringValue(tmpByte);


  }
  
  /// <summary>
  /// 使用DES加密(Added by niehl 2005-4-6)
  /// </summary>
  /// <param name="originalValue">待加密的字符串</param>
  /// <param name="key">密钥(最大长度8)</param>
  /// <param name="IV">初始化向量(最大长度8)</param>
  /// <returns>加密后的字符串</returns>
  public string DESEncrypt(string originalValue,string key,string IV)
  {
   //将key和IV处理成8个字符
   key += "12345678";
   IV += "12345678";
   key = key.Substring(0,8);
   IV = IV.Substring(0,8);


   SymmetricAlgorithm sa;
   ICryptoTransform ct;
   MemoryStream ms;
   CryptoStream cs;
   byte[] byt;


   sa = new DESCryptoServiceProvider();
   sa.Key = Encoding.UTF8.GetBytes(key);
   sa.IV = Encoding.UTF8.GetBytes(IV);
   ct = sa.CreateEncryptor();


   byt = Encoding.UTF8.GetBytes(originalValue);


   ms = new MemoryStream();
   cs = new CryptoStream(ms, ct,
CryptoStreamMode.Write);
   cs.Write(byt, 0, byt.Length);
   cs.FlushFinalBlock();


   cs.Close();


   return Convert.ToBase64String(ms.ToArray());


  }


  public string DESEncrypt(string originalValue,string key)
  {
   return DESEncrypt(originalValue,key,key);
  }


  /// <summary>
  /// 使用DES解密(Added by niehl 2005-4-6)
  /// </summary>
  /// <param name="encryptedValue">待解密的字符串</param>
  /// <param name="key">密钥(最大长度8)</param>
  /// <param name="IV">m初始化向量(最大长度8)</param>
  /// <returns>解密后的字符串</returns>
  public string DESDecrypt(string encryptedValue,string key,string IV)
  {
   //将key和IV处理成8个字符
   key += "12345678";
   IV += "12345678";
   key = key.Substring(0,8);
   IV = IV.Substring(0,8);


   SymmetricAlgorithm sa;
   ICryptoTransform ct;
   MemoryStream ms;
   CryptoStream cs;
   byte[] byt;


   sa = new DESCryptoServiceProvider();
   sa.Key = Encoding.UTF8.GetBytes(key);
   sa.IV = Encoding.UTF8.GetBytes(IV);
   ct = sa.CreateDecryptor();


   byt = Convert.FromBase64String(encryptedValue);


   ms = new MemoryStream();
   cs = new CryptoStream(ms, ct,
CryptoStreamMode.Write);
   cs.Write(byt, 0, byt.Length);
   cs.FlushFinalBlock();


   cs.Close();


   return Encoding.UTF8.GetString(ms.ToArray());


  }


  public string DESDecrypt(string encryptedValue,string key)
  {
   return DESDecrypt(encryptedValue,key,key);
  }


  private string GetStringValue(byte[] Byte)
  {
   string tmpString = "";


   if (this.isReturnNum == false)
   {
    ASCIIEncoding Asc = new ASCIIEncoding();
    tmpString = Asc.GetString(Byte);
   }
   else
   {
    int iCounter;


    for
(iCounter=0;iCounter<Byte.Length;iCounter++)
    {
     tmpString = tmpString +
Byte[iCounter].ToString();
    }
    
   }
   
   return tmpString;
  }


  private byte[] GetKeyByteArray(string strKey)
  {


   ASCIIEncoding Asc = new ASCIIEncoding();
   
   int tmpStrLen = strKey.Length;
   byte[] tmpByte = new byte[tmpStrLen-1];


   tmpByte = Asc.GetBytes(strKey);


   return tmpByte;


  }


 }
}




在 C# 中通过 P/Invoke 调用Win32 DLL

 

我在自己最近的编程中注意到一个趋势,正是这个趋势才引出本月的专栏主题。最近,我在基于 Microsoft® .NET Framework 的应用程序中完成了大量的 Win32® Interop。我并不是要说我的应用程序充满了自定义的 interop 代码,但有时我会在 .NET Framework 类库中碰到一些次要但又繁絮、不充分的内容,通过调用该 Windows® API,可以快速减少这样的麻烦。


因此我认为,.NET Framework 1.0 或 1.1 版类库中存在任何 Windows 所没有的功能限制都不足为怪。毕竟,32 位的 Windows(不管何种版本)是一个成熟的操作系统,为广大客户服务了十多年。相比之下,.NET Framework 却是一个新事物。


随着越来越多的开发人员将生产应用程序转到托管代码,开发人员更频繁地研究底层操作系统以图找出一些关键功能显得很自然 — 至少目前是如此。


值得庆幸的是,公共语言运行库 (CLR) 的 interop 功能(称为平台调用 (P/Invoke))非常完善。在本专栏中,我将重点介绍如何实际使用 P/Invoke 来调用 Windows API 函数。当指 CLR 的 COM Interop 功能时,P/Invoke 当作名词使用;当指该功能的使用时,则将其当作动词使用。我并不打算直接介绍 COM Interop,因为它比 P/Invoke 具有更好的可访问性,却更加复杂,这有点自相矛盾,这使得将 COM Interop 作为专栏主题来讨论不太简明扼要。


走进 P/Invoke


首先从考察一个简单的 P/Invoke 示例开始。让我们看一看如何调用 Win32 MessageBeep 函数,它的非托管声明如以下代码所示:

BOOL MessageBeep(
UINT uType // beep type
);

为了调用 MessageBeep,您需要在 C# 中将以下代码添加到一个类或结构定义中:

[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);

令人惊讶的是,只需要这段代码就可以使托管代码调用非托管的 MessageBeep API。它不是一个方法调用,而是一个外部方法定义。(另外,它接近于一个来自 C 而 C# 允许的直接端口,因此以它为起点来介绍一些概念是有帮助的。)来自托管代码的可能调用如下所示:

MessageBeep(0);

请注意,现在 MessageBeep 方法被声明为 static。这是 P/Invoke 方法所要求的,因为在该 Windows API 中没有一致的实例概念。接下来,还要注意该方法被标记为 extern。这是提示编译器该方法是通过一个从 DLL 导出的函数实现的,因此不需要提供方法体。


说到缺少方法体,您是否注意到 MessageBeep 声明并没有包含一个方法体?与大多数算法由中间语言 (IL) 指令组成的托管方法不同,P/Invoke 方法只是元数据,实时 (JIT) 编译器在运行时通过它将托管代码与非托管的 DLL 函数连接起来。执行这种到非托管世界的连接所需的一个重要信息就是导出非托管方法的 DLL 的名称。这一信息是由 MessageBeep 方法声明之前的 DllImport 自定义属性提供的。在本例中,可以看到,MessageBeep 非托管 API 是由 Windows 中的 User32.dll 导出的。


到现在为止,关于调用 MessageBeep 就剩两个话题没有介绍,请回顾一下,调用的代码与以下所示代码片段非常相似:

[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);

最后这两个话题是与数据封送处理 (data marshaling) 和从托管代码到非托管函数的实际方法调用有关的话题。调用非托管 MessageBeep 函数可以由找到作用域内的extern MessageBeep 声明的任何托管代码执行。该调用类似于任何其他对静态方法的调用。它与其他任何托管方法调用的共同之处在于带来了数据封送处理的需要。


C# 的规则之一是它的调用语法只能访问 CLR 数据类型,例如 System.UInt32 和 System.Boolean。C# 显然不识别 Windows API 中使用的基于 C 的数据类型(例如 UINT 和 BOOL),这些类型只是 C 语言类型的类型定义而已。所以当 Windows API 函数 MessageBeep 按以下方式编写时

BOOL MessageBeep( UINT uType )

外部方法就必须使用 CLR 类型来定义,如您在前面的代码片段中所看到的。需要使用与基础 API 函数类型不同但与之兼容的 CLR 类型是 P/Invoke 较难使用的一个方面。因此,在本专栏的后面我将用完整的章节来介绍数据封送处理。


样式


在 C# 中对 Windows API 进行 P/Invoke 调用是很简单的。但如果类库拒绝使您的应用程序发出嘟声,应该想方设法调用 Windows 使它进行这项工作,是吗?


是的。但是与选择的方法有关,而且关系甚大!通常,如果类库提供某种途径来实现您的意图,则最好使用 API 而不要直接调用非托管代码,因为 CLR 类型和 Win32 之间在样式上有很大的不同。我可以将关于这个问题的建议归结为一句话。当您进行 P/Invoke 时,不要使应用程序逻辑直接属于任何外部方法或其中的构件。如果您遵循这个小规则,从长远看经常会省去许多的麻烦。


图 1 中的代码显示了我所讨论的 MessageBeep 外部方法的最少附加代码。图 1 中并没有任何显著的变化,而只是对无包装的外部方法进行一些普通的改进,这可以使工作更加轻松一些。从顶部开始,您会注意到一个名为 Sound 的完整类型,它专用于 MessageBeep。如果我需要使用 Windows API 函数 PlaySound 来添加对播放波形的支持,则可以重用 Sound 类型。然而,我不会因公开单个公共静态方法的类型而生气。毕竟这只是应用程序代码而已。还应该注意到,Sound 是密封的,并定义了一个空的私有构造函数。这些只是一些细节,目的是使用户不会错误地从 Sound 派生类或者创建它的实例。


图 1 中的代码的下一个特征是,P/Invoke 出现位置的实际外部方法是 Sound 的私有方法。这个方法只是由公共 MessageBeep 方法间接公开,后者接受 BeepTypes 类型的参数。这个间接的额外层是一个很关键的细节,它提供了以下好处。首先,应该在类库中引入一个未来的 beep 托管方法,可以重复地通过公共 MessageBeep 方法来使用托管 API,而不必更改应用程序中的其余代码。


该包装方法的第二个好处是:当您进行 P/Invoke 调用时,您放弃了免受访问冲突和其他低级破坏的权利,这通常是由 CLR 提供的。缓冲方法可以保护您的应用程序的其余部分免受访问冲突及类似问题的影响(即使它不做任何事而只是传递参数)。该缓冲方法将由 P/Invoke 调用引入的任何潜在的错误本地化。


将私有外部方法隐藏在公共包装后面的第三同时也是最后的一个好处是,提供了向该方法添加一些最小的 CLR 样式的机会。例如,在图 1 中,我将 Windows API 函数返回的 Boolean 失败转换成更像 CLR 的异常。我还定义了一个名为 BeepTypes 的枚举类型,它的成员对应于同该 Windows API 一起使用的定义值。由于 C# 不支持定义,因此可以使用托管枚举类型来避免幻数向整个应用程序代码扩散。


包装方法的最后一个好处对于简单的 Windows API 函数(如 MessageBeep)诚然是微不足道的。但是当您开始调用更复杂的非托管函数时,您会发现,手动将 Windows API 样式转换成对 CLR 更加友好的方法所带来的好处会越来越多。越是打算在整个应用程序中重用 interop 功能,越是应该认真地考虑包装的设计。同时我认为,在非面向对象的静态包装方法中使用对 CLR 友好的参数也并非不可以。


DLL Import 属性


现在是更深入地进行探讨的时候了。在对托管代码进行 P/Invoke 调用时,DllImportAttribute 类型扮演着重要的角色。DllImportAttribute 的主要作用是给 CLR 指示哪个 DLL 导出您想要调用的函数。相关 DLL 的名称被作为一个构造函数参数传递给 DllImportAttribute。


如果您无法肯定哪个 DLL 定义了您要使用的 Windows API 函数,Platform SDK 文档将为您提供最好的帮助资源。在 Windows API 函数主题文字临近结尾的位置,SDK 文档指定了 C 应用程序要使用该函数必须链接的 .lib 文件。在几乎所有的情况下,该 .lib 文件具有与定义该函数的系统 DLL 文件相同的名称。例如,如果该函数需要 C 应用程序链接到 Kernel32.lib,则该函数就定义在 Kernel32.dll 中。您可以在 MessageBeep 中找到有关 MessageBeep 的 Platform SDK 文档主题。在该主题结尾处,您会注意到它指出库文件是 User32.lib;这表明 MessageBeep 是从 User32.dll 中导出的。


可选的 DllImportAttribute 属性


除了指出宿主 DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。


EntryPoint 在不希望外部托管方法具有与 DLL 导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在 Windows 中还可以通过它们的序号值绑定到导出的 DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非托管函数的序号值而不是函数名。


CharSet 对于字符集,并非所有版本的 Windows 都是同样创建的。Windows 9x 系列产品缺少重要的 Unicode 支持,而 Windows NT 和 Windows CE 系列则一开始就使用 Unicode。在这些操作系统上运行的 CLR 将Unicode 用于 String 和 Char 数据的内部表示。但也不必担心 — 当调用 Windows 9x API 函数时,CLR 会自动进行必要的转换,将其从 Unicode转换为 ANSI。


如果 DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttribute 的 CharSet 属性。然而,当 Char 或 String 数据是等式的一部分时,应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR 根据宿主 OS 使用适当的字符集。如果没有显式地设置 CharSet 属性,则其默认值为 CharSet.Ansi。这个默认值是有缺点的,因为对于在 Windows 2000、Windows XP 和 Windows NT® 上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。


应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情况是:您显式地指定了一个导出函数,而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW API 函数就是这样的一个例子,它只存在于基于 Windows NT 的操作系统中,并且只支持 Unicode;在这种情况下,您应该显式地使用 CharSet.Unicode。


有时,Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C 语言头文件。(如果您无法肯定要看哪个头文件,则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。)如果您发现该 API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏,则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在 WinUser.h 中声明的 GetMessage API,您也许会惊讶地发现它有 A 和 W 两种版本。


SetLastError 错误处理非常重要,但在编程时经常被遗忘。当您进行 P/Invoke 调用时,也会面临其他的挑战 — 处理托管代码中 Windows API 错误处理和异常之间的区别。我可以给您一点建议。


如果您正在使用 P/Invoke 调用 Windows API 函数,而对于该函数,您使用 GetLastError 来查找扩展的错误信息,则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为 true。这适用于大多数外部方法。


这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。然后,在包装方法中,可以通过调用类库的 System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error 方法来获取缓存的错误值。我的建议是检查这些期望来自 API 函数的错误值,并为这些值引发一个可感知的异常。对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在 System.ComponentModel 命名空间中定义的 Win32Exception,并将 Marshal.GetLastWin32Error 返回的值传递给它。如果您回头看一下图 1 中的代码,您会看到我在 extern MessageBeep 方法的公共包装中就采用了这种方法。


CallingConvention 我将在此介绍的最后也可能是最不重要的一个 DllImportAttribute 属性是 CallingConvention。通过此属性,可以给 CLR 指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi 的默认值是最好的选择,它在大多数情况下都可行。然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。


通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,许多 C-运行时 DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。


幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值 CallingConvention.Winapi 开始是最好的选择。然后,在 C 运行时 DLL 函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl。


数据封送处理


数据封送处理是 P/Invoke 具有挑战性的方面。当在托管和非托管代码之间传递数据时,CLR 遵循许多规则,很少有开发人员会经常遇到它们直至可将这些规则记住。除非您是一名类库开发人员,否则在通常情况下没有必要掌握其细节。为了最有效地在 CLR 上使用 P/Invoke,即使只偶尔需要 interop 的应用程序开发人员仍然应该理解数据封送处理的一些基础知识。


在本月专栏的剩余部分中,我将讨论简单数字和字符串数据的数据封送处理。我将从最基本的数字数据封送处理开始,然后介绍简单的指针封送处理和字符串封送处理。


封送数字和逻辑标量


Windows OS 大部分是用 C 编写的。因此,Windows API 所用到的数据类型要么是 C 类型,要么是通过类型定义或宏定义重新标记的 C 类型。让我们看看没有指针的数据封送处理。简单起见,首先重点讨论的是数字和布尔值。


当通过值向 Windows API 函数传递参数时,需要知道以下问题的答案:
















数据从根本上讲是整型的还是浮点型的?


如果数据是整型的,则它是有符号的还是无符号的?


如果数据是整型的,则它的位数是多少?


如果数据是浮点型的,则它是单精度的还是双精度的?


有时答案很明显,但有时却不明显。Windows API 以各种方式重新定义了基本的 C 数据类型。图 2 列出了 C 和 Win32 的一些公共数据类型及其规范,以及一个具有匹配规范的公共语言运行库类型。


通常,只要您选择一个其规范与该参数的 Win32 类型相匹配的 CLR 类型,您的代码就能够正常工作。不过也有一些特例。例如,在 Windows API 中定义的 BOOL 类型是一个有符号的 32 位整型。然而,BOOL 用于指示 Boolean 值 true 或 false。虽然您不用将 BOOL 参数作为 System.Int32 值封送,但是如果使用 System.Boolean 类型,就会获得更合适的映射。字符类型的映射类似于 BOOL,因为有一个特定的 CLR 类型 (System.Char) 指出字符的含义。


在了解这些信息之后,逐步介绍示例可能是有帮助的。依然采用 beep 主题作为例子,让我们来试一下 Kernel32.dll 低级 Beep,它会通过计算机的扬声器发生嘟声。这个方法的 Platform SDK 文档可以在 Beep 中找到。本机 API 按以下方式进行记录:

BOOL Beep(
DWORD dwFreq, // Frequency
DWORD dwDuration // Duration in milliseconds
);

在参数封送处理方面,您的工作是了解什么 CLR 数据类型与 Beep API 函数所使用的 DWORD 和 BOOL 数据类型相兼容。回顾一下图 2 中的图表,您将看到 DWORD 是一个 32 位的无符号整数值,如同 CLR 类型 System.UInt32。这意味着您可以使用 UInt32 值作为送往 Beep 的两个参数。BOOL 返回值是一个非常有趣的情况,因为该图表告诉我们,在 Win32 中,BOOL 是一个 32 位的有符号整数。因此,您可以使用 System.Int32 值作为来自 Beep 的返回值。然而,CLR 也定义了 System.Boolean 类型作为 Boolean 值的语义,所以应该使用它来替代。CLR 默认将 System.Boolean 值封送为 32 位的有符号整数。此处所显示的外部方法定义是用于 Beep 的结果 P/Invoke 方法:

[DllImport("Kernel32.dll", SetLastError=true)]
static extern Boolean Beep(
UInt32 frequency, UInt32 duration);

指针参数


许多 Windows API 函数将指针作为它们的一个或多个参数。指针增加了封送数据的复杂性,因为它们增加了一个间接层。如果没有指针,您可以通过值在线程堆栈中传递数据。有了指针,则可以通过引用传递数据,方法是将该数据的内存地址推入线程堆栈中。然后,函数通过内存地址间接访问数据。使用托管代码表示此附加间接层的方式有多种。


在 C# 中,如果将方法参数定义为 ref 或 out,则数据通过引用而不是通过值传递。即使您没有使用 Interop 也是这样,但只是从一个托管方法调用到另一个托管方法。例如,如果通过 ref 传递 System.Int32 参数,则在线程堆栈中传递的是该数据的地址,而不是整数值本身。下面是一个定义为通过引用接收整数值的方法的示例:

void FlipInt32(ref Int32 num){
num = -num;
}

这里,FlipInt32 方法获取一个 Int32 值的地址、访问数据、对它求反,然后将求反过的值赋给原始变量。在以下代码中,FlipInt32 方法会将调用程序的变量 x 的值从 10 更改为 -10:

Int32 x = 10;
FlipInt32(ref x);

在托管代码中可以重用这种能力,将指针传递给非托管代码。例如,FileEncryptionStatus API 函数以 32 位无符号位掩码的形式返回文件加密状态。该 API 按以下所示方式进行记录:

BOOL FileEncryptionStatus(
LPCTSTR lpFileName, // file name
LPDWORD lpStatus // encryption status
);

请注意,该函数并不使用它的返回值返回状态,而是返回一个 Boolean 值,指示调用是否成功。在成功的情况下,实际的状态值是通过第二个参数返回的。它的工作方式是调用程序向该函数传递指向一个 DWORD 变量的指针,而该 API 函数用状态值填充指向的内存位置。以下代码片段显示了一个调用非托管 FileEncryptionStatus 函数的可能外部方法定义:

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
out UInt32 status);

该定义使用 out 关键字来为 UInt32 状态值指示 by-ref 参数。这里我也可以选择 ref 关键字,实际上在运行时会产生相同的机器码。out 关键字只是一个 by-ref 参数的规范,它向 C# 编译器指示所传递的数据只在被调用的函数外部传递。相反,如果使用 ref 关键字,则编译器会假定数据可以在被调用的函数的内部和外部传递。


托管代码中 out 和 ref 参数的另一个很好的方面是,地址作为 by-ref 参数传递的变量可以是线程堆栈中的一个本地变量、一个类或结构的元素,也可以是具有合适数据类型的数组中的一个元素引用。调用程序的这种灵活性使得 by-ref 参数成为封送缓冲区指针以及单数值指针的一个很好的起点。只有在我发现 ref 或 out 参数不符合我的需要的情况下,我才会考虑将指针封送为更复杂的 CLR 类型(例如类或数组对象)。


如果您不熟悉 C 语法或者调用 Windows API 函数,有时很难知道一个方法参数是否需要指针。一个常见的指示符是看参数类型是否是以字母 P 或 LP 开头的,例如 LPDWORD 或 PINT。在这两个例子中,LP 和 P 指示参数是一个指针,而它们指向的数据类型分别为 DWORD 或 INT。然而,在有些情况下,可以直接使用 C 语言语法中的星号 (*) 将 API 函数定义为指针。以下代码片段展示了这方面的示例:

void TakesAPointer(DWORD* pNum);

可以看到,上述函数的唯一一个参数是指向 DWORD 变量的指针。


当通过 P/Invoke 封送指针时,ref 和 out 只用于托管代码中的值类型。当一个参数的 CLR 类型使用 struct 关键字定义时,可以认为该参数是一个值类型。Out 和 ref 用于封送指向这些数据类型的指针,因为通常值类型变量是对象或数据,而在托管代码中并没有对值类型的引用。相反,当封送引用类型对象时,并不需要 ref 和 out 关键字,因为变量已经是对象的引用了。


如果您对引用类型和值类型之间的差别不是很熟悉,请查阅 2000 年 12 月 发行的 MSDN® Magazine,在 .NET 专栏的主题中可以找到更多信息。大多数 CLR 类型都是引用类型;然而,除了 System.String 和 System.Object,所有的基元类型(例如 System.Int32 和 System.Boolean)都是值类型。


封送不透明 (Opaque) 指针:一种特殊情况


有时在 Windows API 中,方法传递或返回的指针是不透明的,这意味着该指针值从技术角度讲是一个指针,但代码却不直接使用它。相反,代码将该指针返回给 Windows 以便随后进行重用。


一个非常常见的例子就是句柄的概念。在 Windows 中,内部数据结构(从文件到屏幕上的按钮)在应用程序代码中都表示为句柄。句柄其实就是不透明的指针或有着指针宽度的数值,应用程序用它来表示内部的 OS 构造。


少数情况下,API 函数也将不透明指针定义为 PVOID 或 LPVOID 类型。在 Windows API 的定义中,这些类型意思就是说该指针没有类型。


当一个不透明指针返回给您的应用程序(或者您的应用程序期望得到一个不透明指针)时,您应该将参数或返回值封送为 CLR 中的一种特殊类型 — System.IntPtr。当您使用 IntPtr 类型时,通常不使用 out 或 ref 参数,因为 IntPtr 意为直接持有指针。不过,如果您将一个指针封送为一个指针,则对 IntPtr 使用 by-ref 参数是合适的。


在 CLR 类型系统中,System.IntPtr 类型有一个特殊的属性。不像系统中的其他基类型,IntPtr 并没有固定的大小。相反,它在运行时的大小是依底层操作系统的正常指针大小而定的。这意味着在 32 位的 Windows 中,IntPtr 变量的宽度是 32 位的,而在 64 位的 Windows 中,实时编译器编译的代码会将 IntPtr 值看作 64 位的值。当在托管代码和非托管代码之间封送不透明指针时,这种自动调节大小的特点十分有用。


请记住,任何返回或接受句柄的 API 函数其实操作的就是不透明指针。您的代码应该将 Windows 中的句柄封送成 System.IntPtr 值。


您可以在托管代码中将 IntPtr 值强制转换为 32 位或 64 位的整数值,或将后者强制转换为前者。然而,当使用 Windows API 函数时,因为指针应是不透明的,所以除了存储和传递给外部方法外,不能将它们另做它用。这种“只限存储和传递”规则的两个特例是当您需要向外部方法传递 null 指针值和需要比较 IntPtr 值与 null 值的情况。为了做到这一点,您不能将零强制转换为 System.IntPtr,而应该在 IntPtr 类型上使用 Int32.Zero 静态公共字段,以便获得用于比较或赋值的 null 值。


封送文本


在编程时经常要对文本数据进行处理。文本为 interop 制造了一些麻烦,这有两个原因。首先,底层操作系统可能使用 Unicode 来表示字符串,也可能使用 ANSI。在极少数情况下,例如 MultiByteToWideChar API 函数的两个参数在字符集上是不一致的。


第二个原因是,当需要进行 P/Invoke 时,要处理文本还需要特别了解到 C 和 CLR 处理文本的方式是不同的。在 C 中,字符串实际上只是一个字符值数组,通常以 null 作为结束符。大多数 Windows API 函数是按照以下条件处理字符串的:对于 ANSI,将其作为字符值数组;对于 Unicode,将其作为宽字符值数组。


幸运的是,CLR 被设计得相当灵活,当封送文本时问题得以轻松解决,而不用在意 Windows API 函数期望从您的应用程序得到的是什么。这里是一些需要记住的主要考虑事项:













是您的应用程序向 API 函数传递文本数据,还是 API 函数向您的应用程序返回字符串数据?或者二者兼有?


您的外部方法应该使用什么托管类型?


API 函数期望得到的是什么格式的非托管字符串?


我们首先解答最后一个问题。大多数 Windows API 函数都带有 LPTSTR 或 LPCTSTR 值。(从函数角度看)它们分别是可修改和不可修改的缓冲区,包含以 null 结束的字符数组。“C”代表常数,意味着使用该参数信息不会传递到函数外部。LPTSTR 中的“T”表明该参数可以是 Unicode 或 ANSI,取决于您选择的字符集和底层操作系统的字符集。因为在 Windows API 中大多数字符串参数都是这两种类型之一,所以只要在 DllImportAttribute 中选择 CharSet.Auto,CLR 就按默认的方式工作。


然而,有些 API 函数或自定义的 DLL 函数采用不同的方式表示字符串。如果您要用到一个这样的函数,就可以采用 MarshalAsAttribute 修饰外部方法的字符串参数,并指明一种不同于默认 LPTSTR 的字符串格式。有关 MarshalAsAttribute 的更多信息,请参阅位于 MarshalAsAttribute Class 的 Platform SDK 文档主题。


现在让我们看一下字符串信息在您的代码和非托管函数之间传递的方向。有两种方式可以知道处理字符串时信息的传递方向。第一个也是最可靠的一个方法就是首先理解参数的用途。例如,您正调用一个参数,它的名称类似 CreateMutex 并带有一个字符串,则可以想像该字符串信息是从应用程序向 API 函数传递的。同时,如果您调用 GetUserName,则该函数的名称表明字符串信息是从该函数向您的应用程序传递的。


除了这种比较合理的方法外,第二种查找信息传递方向的方式就是查找 API 参数类型中的字母“C”。例如,GetUserName API 函数的第一个参数被定义为 LPTSTR 类型,它代表一个指向 Unicode 或 ANSI 字符串缓冲区的长指针。但是 CreateMutex 的名称参数被类型化为 LTCTSTR。请注意,这里的类型定义是一样的,但增加一个字母“C”来表明缓冲区为常数,API 函数不能写入。


一旦明确了文本参数是只用作输入还是用作输入/输出,就可以确定使用哪种 CLR 类型作为参数类型。这里有一些规则。如果字符串参数只用作输入,则使用 System.String 类型。在托管代码中,字符串是不变的,适合用于不会被本机 API 函数更改的缓冲区。


如果字符串参数可以用作输入和/或输出,则使用 System.StringBuilder 类型。StringBuilder 类型是一个很有用的类库类型,它可以帮助您有效地构建字符串,也正好可以将缓冲区传递给本机函数,由本机函数为您填充字符串数据。一旦函数调用返回,您只需要调用 StringBuilder 对象的 ToString 就可以得到一个 String 对象。


GetShortPathName API 函数能很好地用于显示什么时候使用 String、什么时候使用 StringBuilder,因为它只带有三个参数:一个输入字符串、一个输出字符串和一个指明输出缓冲区的字符长度的参数。


图 3 所示为加注释的非托管 GetShortPathName 函数文档,它同时指出了输入和输出字符串参数。它引出了托管的外部方法定义,也如图 3 所示。请注意第一个参数被封送为 System.String,因为它是一个只用作输入的参数。第二个参数代表一个输出缓冲区,它使用了 System.StringBuilder。


小结


本月专栏所介绍的 P/Invoke 功能足够调用 Windows 中的许多 API 函数。然而,如果您大量用到 interop,则会最终发现自己封送了很复杂的数据结构,甚至可能需要在托管代码中通过指针直接访问内存。实际上,本机代码中的 interop 可以是一个将细节和低级比特藏在里面的真正的潘多拉盒子。CLR、C# 和托管 C++ 提供了许多有用的功能;也许以后我会在本专栏介绍高级的 P/Invoke 话题。


同时,只要您觉得 .NET Framework 类库无法播放您的声音或者为您执行其他一些功能,您可以知道如何向原始而优秀的 Windows API 寻求一些帮助。





Figure 1 MessageBeep, Interop Done Well

namespace Wintellect.Interop.Sound{
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;

sealed class Sound{
public static void MessageBeep(BeepTypes type){
if(!MessageBeep((UInt32) type)){
Int32 err = Marshal.GetLastWin32Error();
throw new Win32Exception(err);
}
}

[DllImport("User32.dll", SetLastError=true)]
static extern Boolean MessageBeep(UInt32 beepType);

private Sound(){}
}

enum BeepTypes{
Simple = -1,
Ok = 0x00000000,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
IconExclamation = 0x00000030,
IconAsterisk = 0x00000040
}
}

Figure 2 Non-Pointer Data Types

Win32 TypesSpecificationCLR Type
char, INT8, SBYTE, CHAR†8-bit signed integerSystem.SByte
short, short int, INT16, SHORT16-bit signed integerSystem.Int16
int, long, long int, INT32, LONG32, BOOL†, INT 32-bit signed integerSystem.Int32
__int64, INT64, LONGLONG64-bit signed integerSystem.Int64
unsigned char, UINT8, UCHAR†, BYTE8-bit unsigned integerSystem.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR†, __wchar_t16-bit unsigned integerSystem.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT32-bit unsigned integerSystem.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG64-bit unsigned integerSystem.UInt64
float, FLOATSingle-precision floating pointSystem.Single
double, long double, DOUBLEDouble-precision floating pointSystem.Double
†In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.

Figure 3 GetShortPathName Declarations
// ** Documentation for Win32 GetShortPathName() API Function
// DWORD GetShortPathName(
// LPCTSTR lpszLongPath, // file for which to get short path
// LPTSTR lpszShortPath, // short path name (output)
// DWORD cchBuffer // size of output buffer
// );

[DllImport("Kernel32", CharSet = CharSet.Auto)]
static extern Int32 GetShortPathName(
String path, // input string
StringBuilder shortPath, // output string
Int32 shortPathLength); // StringBuilder.Capacity



SQL Server 中系统表的作用

sysaltfiles             主数据库 保存数据库的文件
syscharsets         主数据库 字符集与排序顺序
sysconfigures      主数据库 配置选项
syscurconfigs      主数据库 当前配置选项
sysdatabases     主数据库 服务器中的数据库
syslanguages     主数据库 语言
syslogins             主数据库 登陆帐号信息
sysoledbusers    主数据库 链接服务器登陆信息
sysprocesses     主数据库 进程
sysremotelogins主数据库 远程登录帐号
syscolumns        每个数据库 列
sysconstrains     每个数据库 限制
sysfilegroups      每个数据库 文件组
sysfiles                每个数据库 文件
sysforeignkeys   每个数据库 外部关键字
sysindexs             每个数据库 索引
sysmenbers        每个数据库 角色成员
sysobjects           每个数据库 所有数据库对象
syspermissions 每个数据库 权限
systypes               每个数据库 用户定义数据类型
sysusers              每个数据库 用户




关于SQL Server事务日志的问题汇总

 

1、用BACKUP LOG database WITH NO_LOG清除日志
把数据库属性中的故障还原模型改为“简单”可以大大减慢日志


增长的速度。
   如果把还原模型调到简单,这样就不支持时间点还原了,但


是日志文件会很小,如果数据比较重要
推荐还是把数据库的还原模型调为完全


用BACKUP LOG database WITH NO_LOG命名后,会截断不活动日


志,不减小物理日志文件的大小,
但逻辑日志会减小,收缩数据库后会把不活动虚拟日志删除来释


放空间,不会损坏数据。


如果日志被截断并收缩数据库后,就不能直接用最近的一个全库


备份做时间点还原,建议立即备份
数据库,以防万一。


2、sql server运行中,是否能删除主数据库事务日志文件
步骤如下:(1)、分离数据库
企业管理器--数据库--右击你要删除日志的数据库--所有


任务--分离数据库
(2)、然后删除日志文件
(3)、然后再附加数据库
企业管理器--数据库--右击数据库--所有任务--附加数


据库
这时候只附加。mdf就可以了。


3、压缩SQL数据库及日志的详细方法


SQL Server 2000基础教程——压缩数据库
数据库在使用一段时间后,时常会出现因数据删除而造成数据库


中空闲空间太多的情况,这时就需要减少分配给数据库文件和事


务日志文件的磁盘空间,以免浪费磁盘空间。当数据库中没有数


据时,可以修改数据库文件属性直接改变其占用空间,但当数据


库中有数据时,这样做会破坏数据库中的数据,因此需要使用压


缩的方式来缩减数据库空间。可以在数据库属性选项中选择“


Auto shrink”选项,让系统自动压缩数据库,也可以用人工的


方法来压缩。人工压缩数据库有以下两种方式:
1、用Enterprise Manager 压缩数据库
在Enterprise Manager 中在所要压缩的数据库上单击右键,从


快捷菜单中的“所有任务(All Tasks)”中选择“Shrink


Database(压缩数据库)”选项,就会出现如图6-10 所示的对


话框。可以在图6-10 所示的对话框中选择数据库的压缩方式,


也可以选择使用压缩计划或压缩单个文件
 
单击图6-10 中的“Files”按钮,会出现如图6-11 所示的压缩


数据库文件对话框,可以针对每个数据库文件进行不同的压缩设


置。
 
单击图6-10 中的“Change” 按钮,会出现如图6-12 所示的压


缩计划编辑对话框,可以指定压缩计划的执行方式。单击图6-12


中的“Change” 按钮,会出现如图6-13 所示的循环工作计划编


辑对话框,可以编辑计划执行的周期或时间点。设置完成后单击


“OK” 按钮就开始压缩数据库,在压缩结束后会显示一个压缩


情况信息框。
 
 
2、用Transact-SQL 命令压缩数据库
可以使用DBCC SHRINKDATABASE 和DBCC SHRINKFILE 命令来压缩


数据库。其中DBCC SHRINKDATABASE 命令对数据库进行压缩,


DBCC SHRINKFILE 命令对数据库中指定的文件进行压缩。
(1) DBCC SHRINKDATABASE
DBCC SHRINKDATABASE 命令语法如下:
DBCC SHRINKDATABASE (database_name [, target_percent]
[, {NOTRUNCATE | TRUNCATEONLY}] )
各参数说明如下:
·target_percent 指定将数据库压缩后,未使用的空间占数据


库大小的百分之几。如果指定的百分比过大,超过了压缩前未使


用空间所占的比例,则数据库不会被压缩。并且压缩后的数据库


不能比数据库初始设定的容量小。
·NOTRUECATE
将数据库缩减后剩余的空间保留在数据库,中不返还给操作系统


。如果不选择此选项,则剩余的空间返还给操作系统。
·TRUNCATEONLY
将数据库缩减后剩余的空间返还给操作系统。使用此命令时SQL


Server 将文件缩减到最后一个文件分配,区域但不移动任何数


据文件。选择此项后,target_percent 选项就无效了。
例6-14: 压缩数据库mytest 的未使用空间为数据库大小的20%



dbcc shrinkdatabase (mytest, 20)
运行结果如下:
DBCC execution completed. If DBCC printed error


messages, contact your system administrator.
(2) DBCC SHRINKFILE
DBCC SHRINKFILE 命令压缩当前数据库中的文件。其语法如下:
DBCC SHRINKFILE ( {file_name | file_id }
{ [, target_size] |
[, {EMPTYFILE | NOTRUNCATE | TRUNCATEONLY}] } )
各参数说明如下:
·file_id
指定要压缩的文件的鉴别号(Identification number, 即ID)


。文件的ID 号可以通过 FILE_ID()函数或如本章前面所讲述


的Sp_helpdb 系统存储过程来得到。
·target_size
指定文件压缩后的大小。以MB 为单位。如果不指定此选项,SQL


Server 就会尽最大可能地缩减文件。
·EMPTYFILE
指明此文件不再使用,将移动所有在此文件中的数据到同一文件


组中的其它文件中去。执行带此参数的命令后,此文件就可以用


ALTER DATABASE 命令来删除了。
其余参数NOTRUNCATE 和TRUNCATEONLY 与DBCC SHRINKDATABASE


命令中的含义相同。
例6-15: 压缩数据库mydb 中的数据库文件mydb_data2 的大小到


1MB。 use mydb dbcc shrinkfile (mydb_data2, 1)
 



企业管理器里面的方法:
1、打开企业管理器
2、打开要处理的数据库
3、点击最上面菜单>工具>SQL查询分析器,打开SQL查询分析器
4、在输入窗口里面输入:


Code:
DUMP TRANSACTION [数据库名] WITH  NO_LOG
BACKUP LOG [数据库名] WITH NO_LOG
DBCC SHRINKDATABASE([数据库名])



点击绿色的小三角(或按F5)执行查询,等状态栏提示处理完成


即可!



程序里面的方法:
压缩数据库日志
--1.清空日志
exec('DUMP TRANSACTION ['+@dbname+'] WITH  NO_LOG')
--2.截断事务日志:
exec('BACKUP LOG ['+@dbname+'] WITH NO_LOG')
--3.收缩数据库文件(如果不压缩,数据库的文件不会减小
exec('DBCC SHRINKDATABASE(['+@dbname+'])')



4、减小日志的方法:


一、用如下步做了:
1、DUMP TRANSACTION 庫名 WITH no_log
2、dbcc shrinkfile(logfilename)
3、收縮數據庫
4、設定自動收縮。


二、
分离数据库,删除日志文件,再附加,OK!
右击数据库--所有任务--分离or 附加


三、


1、backup log 庫名 WITH no_log
2、dbcc shrinkfile(logfilename)
3、收縮數據庫
4、設定自動收縮。



身份证号码15位升18位(C#)

身份证18位验证


     18位身份证标准在国家质量技术监督局于1999年7月1日实施的GB11643-1999《公民身份号码》中做了明确的规定。 GB11643-1999《公民身份号码》为GB11643-1989《社会保障号码》的修订版,其中指出将原标准名称"社会保障号码"更名为"公民身份号码",另外GB11643-1999《公民身份号码》从实施之日起代替GB11643-1989。GB11643-1999《公民身份号码》主要内容如下:
一、范围
该标准规定了公民身份号码的编码对象、号码的结构和表现形式,使每个编码对象获得一个唯一的、不变的法定号码。
二、编码对象
公民身份号码的编码对象是具有中华人民共和国国籍的公民。
三、号码的结构和表示形式
1、号码的结构
公民身份号码是特征组合码,由十七位数字本体码和一位校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
2、地址码
表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按GB/T2260的规定执行。
3、出生日期码
表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日代码之间不用分隔符。
4、顺序码
表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。
5、校验码
(1)十七位数字本体码加权求和公式
S = Sum(Ai * Wi), i = 0, ... , 16 ,先对前17位数字的权求和
Ai:表示第i位置上的身份证号码数字值
Wi:表示第i位置上的加权因子
Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

(2)计算模
Y = mod(S, 11)


(3)通过模得到对应的校验码
Y:         0 1 2 3 4 5 6 7 8 9 10
校验码: 1 0 X 9 8 7 6 5 4 3 2
四、举例如下:
北京市朝阳区: 11010519491231002X
广东省汕头市: 440524188001010014


15位升18的方法

根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。


地址码表示编码对象常住户口所在县(市、旗、区)的行政区划代码。生日期码表示编码对象出生的年、月、日,其中年份用四位数字表示,年、月、日之间不用分隔符。顺序码表示同一地址码所标识的区域范围内,对同年、月、日出生的人员编定的顺序号。顺序码的奇数分给男性,偶数分给女性。校验码是根据前面十七位数字码,按照ISO 7064:1983.MOD 11-2校验码计算出来的检验码。下面举例说明该计算方法。


15位的身份证编码首先把出生年扩展为4位,简单的就是增加一个19,但是这对于1900年出生的人不使用(这样的寿星不多了)


某男性公民身份号码本体码为34052419800101001,首先按照公式⑴计算:


∑(ai×Wi)(mod 11)……………………………………(1)


公式(1)中:
i----表示号码字符从由至左包括校验码在内的位置序号;
ai----表示第i位置上的号码字符值;
Wi----示第i位置上的加权因子,其数值依据公式Wi=2(n-1)(mod 11)计算得出。


i 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1


ai  3 4 0   5 2 4 1 9 8 0 0 1 0   1 0 0 1 a1


Wi 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 1


ai×Wi 21 36 0 25 16 16 2 9 48 0 0 9 0 5 0 0 2 a1


根据公式(1)进行计算:


∑(ai×Wi) =(21+36+0+25+16+16+2+9+48++0+0+9+0+5+0+0+2) = 189


189 ÷ 11 = 17 + 2/11


∑(ai×Wi)(mod 11) = 2


然后根据计算的结果,从下面的表中查出相应的校验码,其中X表示计算结果为10:


∑(ai×WI)(mod 11) 0 1 2 3 4 5 6 7 8 9 10
校验码字符值       ai 1 0 X 9 8 7 6 5 4 3 2
根据上表,查出计算结果为2的校验码为所以该人员的公民身份号码应该为 34052419800101001X。

C#代码:



  private string per15To18(string perIDSrc)
  {
   int iS = 0;
 
   //加权因子常数
   int[] iW=new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
   //校验码常数
   string LastCode="10X98765432";
   //新身份证号
   string perIDNew;
 
   perIDNew=perIDSrc.Substring(0,6);
   //填在第6位及第7位上填上‘1’,‘9’两个数字
   perIDNew += "19";
 
   perIDNew += perIDSrc.Substring(6,9);
 
   //进行加权求和
   for( int i=0; i<17; i++)
   {
    iS += int.Parse(perIDNew.Substring(i,1)) * iW[i];
   }
    
   //取模运算,得到模值
   int iY = iS%11;
   //从LastCode中取得以模为索引号的值,加到身份证的最后一位,即为新身份证号。
   perIDNew += LastCode.Substring(iY,1);


   return perIDNew;
  }



C#如何在Form中嵌入并且操作Excel表格

 

网上比较多讲述如何操作Excel表的文章,但都是启动Excel的窗口来打开Excel数据文件。有时候需要把Excel表嵌入到自己程序的Form中,给客户一个不用切换窗口的操作界面,似乎更好。这在VC中用OLE技术很容易实现,但是在C#中方法就不一样啦。下面将就此进行阐述。


一、首先简要回顾一下如何操作Excel表


  先要添加对Excel的引用。选择项目-〉添加引用-〉COM-〉添加Microsoft Excel 9.0。(不同的office讲会有不同版本的dll文件)。
   using Excel;
   using System.Reflection;
  
   //产生一个Excel.Application的新进程
   Excel.Application app = new Excel.Application();
   if (app == null)
   {
    statusBar1.Text = "ERROR: EXCEL couldn't be started!";
    return ;
   }
  
   app.Visible = true; //如果只想用程序控制该excel而不想让用户操作时候,可以设置为false
   app.UserControl = true;
  
   Workbooks workbooks =app.Workbooks;
 
   _Workbook workbook = workbooks.Add(XlWBATemplate.xlWBATWorksheet); //根据模板产生新的workbook
//  _Workbook workbook = workbooks.Add("c:\\a.xls"); //或者根据绝对路径打开工作簿文件a.xls


   Sheets sheets = workbook.Worksheets;
   _Worksheet worksheet = (_Worksheet) sheets.get_Item(1);
   if (worksheet == null)
   {
    statusBar1.Text =  "ERROR: worksheet == null";
    return;
   }


   // This paragraph puts the value 5 to the cell G1
   Range range1 = worksheet.get_Range("A1", Missing.Value);
   if (range1 == null)
   {
    statusBar1.Text =  "ERROR: range == null";
    return;
   }
   const int nCells = 2345;
   range1.Value2 = nCells;
 
二、将Excel用户界面嵌入到自己的Windows Form中


      由于目前,C#和vb.net都不支持OLE技术(参见微软支持中心Info:304562),,所以只有使用WebBrowser控件来完成此功能。(以下方法参见微软支持中心Howto:304662)
      1、右击工具箱,选择自定义工具箱,添加COM组件,选择“Microsoft Web 浏览器”(对应文件是\winnt\system32\shdocvw.dll),确定。在工具箱中将会出现文本为Explorer的WebBroser控件图标。
      2、在Form1中添加WebBrowser控件。(对象名却省是axWebBrowser1)
      3、假定要打开的excel文件是: c:\a.xls。
       string strFileName = @"c:\a.xls";
     Object refmissing = System.Reflection.Missing.Value;
     axWebBrowser1.Navigate(strFileName, ref refmissing , ref refmissing , ref refmissing , ref refmissing);
    值得注意的是用WebBrowser控件不支持菜单合并,也就是说无法把Excel表的菜单带入到我们的程序中。这是相对于OLE实现方法的一大缺点。幸好提供了可以把工具栏添加进来的功能,通过工具栏可以进行许多Excel专有的操作。
     //下面这句可以将excel本身的工具调添加进来
    axWebBrowser1.ExecWB(SHDocVw.OLECMDID.OLECMDID_HIDETOOLBARS, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,ref refmissing , ref refmissing);
   
三、回到本文提出的问题,如何操作嵌入的Excel?


   首先需要明白,用WebBrowser“装载”Excel"表,实际上仍然是在新的进程空间中运行Excel.exe。可以用任务管理器观察。因此,只要我们能够获取Excel.Application对象,就能像上文一中所说的那样操作Excel数据。
   幸好可以通过WebBrowser的方法NavigateComplete提供的事件参数e来访问Excel.Application。
  public void axWebBrowser1_NavigateComplete2(object sender, AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
  {
   Object o = e.pDisp;
   Object oDocument = o.GetType().InvokeMember("Document",BindingFlags.GetProperty,null,o,null);
   Object oApplication = o.GetType().InvokeMember("Application",BindingFlags.GetProperty,null,oDocument,null);
      //Object oName = o.GetType().InvokeMember("Name",BindingFlags.GetProperty ,null,oApplication,null);


   //由于打开的是excel文件,所以这里的oApplication 其实就是Excel.Application
   Excel.Application eApp =(Excel.Application)oApplication;//这样就可以象上文中所述来操作Excel了。
  }


四、包含该WebBrowser的Form退出时候,如何确保Excel进程也退出?(参见Microsoft帮助中心KB317109)


  由于WebBrowser只不过是对Excel表的浏览,而Excel在单独的进程中运行。所以要保证对该Excel对象eApp及其相应的所有成员变量都释放引用,才能保证在Form退出时excel进程跟着退出。这一点在一个程序需要多次打开关闭excel表时尤为重要。
      Excel.Application oApp;
      Excel.Workbooks oBooks;
      Excel.Workbook oBook;
      Excel.Worksheet oSheet;
      ...........
    private void ExcelExit()
    {
       NAR(oSheet);
       oBook.Close(False);
       NAR(oBook);
       NAR(oBooks);
       oApp.Quit();
       NAR(oApp);


       Debug.WriteLine("Sleeping...");
       System.Threading.Thread.Sleep(5000);
       Debug.WriteLine("End Excel");
    }
  private void NAR(Object o)
  {
        try{System.Runtime.InteropServices.Marshal.ReleaseComObject(o);}
        catch{}
        finally{o = null;}
    }
  经过试验,我发现除了释放这些变量以外,必须把该axWebBroswer1也销毁掉,Excel进程才退出。否则的话,即使让axWebBroser1去Navigate空内容"about:blank", excel进程仍然不会退出。因此应该将axWebBroser1所在的Form关闭掉,或者直接调用axWebBrowser1.Dispose();
  如果还是其它问题导致不能正常退出,就只有调用垃圾回收了。
  GC.Collect();



'压缩文件函数,dataS为源文件,dataz为目标文件,传出为一个布尔值





Private Function DataZip(ByVal Datas As String, ByVal Dataz As String) As Boolean





On Error GoTo Compact_Error





    Dim JRO As JRO.JetEngine





    Set JRO = New JRO.JetEngine





    Dim fso As New FileSystemObject





    If fso.FileExists(Dataz) = True Then





        If MsgBox("此压缩文件已存在是否将其覆盖?", vbYesNo + vbQuestion, "压缩工程数据文件") = vbYes Then





            Kill Dataz





        Else





            Exit Function





        End If





    End If





    '压缩工程文件





    JRO.CompactDatabase "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Datas, _





    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Dataz & ";Jet OLEDB:Engine Type=5"





    DataZip = True





    MsgBox "工程数据压缩成功!", vbInformation + vbOKOnly, "压缩数据文件"





    Exit Function





Compact_Error:





    DataZip = False





    If Err.Number = -2147467259 Then





        MsgBox "数据压缩失败!(可能你的数据库正被其他程序使用,请将重新运行系统!)", vbOKOnly + vbInformation, "错误"





        Exit Function





    End If





    dbEncrypt.SaveError "MDIForm1-DataZip"





End Function





 





'解压缩函数,datas未压缩文件,dataz为已压缩文件





Private Function Zipext(ByVal Dataz As String, ByVal Datas As String) As Boolean





On Error GoTo Compact_Error





    Dim JRO As JRO.JetEngine





    Set JRO = New JRO.JetEngine





    Dim fso As New FileSystemObject





    If fso.FileExists(Datas) = True Then





        If MsgBox("此工程文件已存在是否将其覆盖?", vbYesNo + vbQuestion, "解压缩工程数据文件") = vbYes Then





            Kill Datas





        Else





            Exit Function





        End If





    End If





    '解压缩工程文件





    JRO.CompactDatabase "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Dataz, _





    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Datas & ";Jet OLEDB:Engine Type=5"





    Zipext = True





    MsgBox "工程数据解压缩成功!", vbOKOnly + vbInformation, "解压缩数据文件"





    Exit Function





Compact_Error:





    Zipext = False





    If Err.Number = -2147467259 Then





        MsgBox "数据压缩失败!(可能你的数据库正被其他程序使用,请将重新运行系统!)", vbOKOnly + vbInformation, "错误"





        Exit Function





    End If





    dbEncrypt.SaveError "MDIForm1-DataZip"





End Function

       Word的“文件”菜单最下面列出了最近使用过的文件名及路径,文件名及路径会随着用户的使用不断地发生变化,这符合统计学中“最近使用”最大可能是“经常使用”的原则,方便了用户,提高了效率。它的实现方法有很多,我举一例,供大家参考。







       一、在工作目录下创建一个LastFile.ini文件,其中第一行为历史文件的总数,以下行是历史文件的全路径。当然您也可以使用数据表存储,那样编程时也许更方便一些。







       LastFile.ini文件内容如:4







"D:\程序实例\slzj\slzj源代码\2004.11.18水利造价\示例.mdb"







"C:\WINDOWS\Desktop\111\111.mdb"







"D:\程序实例\slzj\slzj源代码\2004.11.18水利造价\示例.mdb"







"D:\程序实例\slzj\slzj源代码\2004.11.3\2004.11.3\2004.11.3\2004.11.3\示例(审查).mdb"







    二、在Form_Load中编写如下代码,达到在文件菜单中显示历史文件的效果







'**************显示以往打开的文件记录***************************







    '对配置文件不存在的情况下,作出操作。







    If Dir(App.Path & "\lastfile.ini") = "" Then







        Open App.Path & "\lastfile.ini" For Output As #1







        Write #1, 0







        Close #1







End If







'打开lastfile.ini文件







    Open App.Path & "\lastfile.ini" For Input As #1







    Dim strLastfile2 As String







    '获取历史文件的数目







    Line Input #1, strLastfile2







    iMaxLastfile = Int(strLastfile2)







    Dim i As Integer







    '添加历史文件到ActiveBar菜单,先在ActiveBar中预设4各Command和一个分割线。并把他们的Visible=False







    For i = 1 To iMaxLastfile







        Line Input #1, strLastfile2







        strLastfile(i - 1) = Mid(strLastfile2, 2, Len(strLastfile2) - 2)‘去引号







        AABar.Bands("MenuFile").Tools.item(i + 10).Caption = strLastfile(i - 1)







        AABar.Bands("MenuFile").Tools.item(i + 10).Visible = True







    Next







    '关闭文件







    Close #1







    '设置分隔条







    If iMaxLastfile <> 0 Then







        AABar.Bands("MenuFile").Tools.item(15).Visible = True







End If







三、在Form_Unload中添加如下代码,将打开文件记录写入配置文件。







    Open App.Path & "\LastFile.ini" For Output As #1







    Dim i As Integer







    Write #1, iMaxLastfile‘写入历史文件总数







    For i = 0 To iMaxLastfile - 1







        Write #1, strLastfile(i)‘写入历史文件路径







    Next







    Close #1







四、在需要更新菜单中文件历史记录的地方使用下面函数(如:打开一个文件,新建并打开一个文件等)







Private Sub UpdateLastFile(ByVal strPath As String)







    On Error GoTo SaveErr:







    Dim strDuan As String







    strDuan = strPath







    '判断要添加的文件是否时列表中的第一个文件







    If strDuan <> AABar.Bands("MenuFile").Tools.item(11).Caption Then







        '将列表中的文件依次下移一位,空出第一位







        Dim i As Integer







        For i = 3 To 1 Step -1







            strLastfile(i) = strLastfile(i - 1)







            AABar.Bands("MenuFile").Tools.item(11 + i).Caption = AABar.Bands("MenuFile").Tools.item(10 + i).Caption







        Next







        '将头一位设置为当前操作的文件路径







        strLastfile(0) = strDuan







        AABar.Bands("MenuFile").Tools.item(11).Caption = strDuan







        '如果列表文件数小于最大文件数则加一







        If iMaxLastfile < 4 Then







            iMaxLastfile = iMaxLastfile + 1







        End If







        '设置新移动的列表项可见







        AABar.Bands("MenuFile").Tools.item(iMaxLastfile + 10).Visible = True







    End If







    '如果列表不为空则下方的分隔条可见







    If iMaxLastfile <> 0 Then







        AABar.Bands("MenuFile").Tools.item(15).Visible = True







    Else







        AABar.Bands("MenuFile").Tools.item(15).Visible = False







    End If







    Exit Sub







SaveErr:







    dbEncrypt.SaveError "MDIForm1-UpdateLastFile"







End Sub







五、单击文件历史记录时调用如下函数。







Private Sub MenuLastfile(ByVal strName As String, Index As Integer)







    On Error GoTo SaveErr:







    '如果文件已不存在则提示







    If Dir(strName) = "" Then







        MsgBox "文件不存在,请确认后再次打开!", vbOKOnly + vbInformation, "打开文件"







        Exit Sub







    End If







    '设置当前打开文件为列表中的选择文件







    strConnection = strName







    '**************重新设置历史文件列表顺序*****************







    Dim i As Integer







    For i = Index To 12 Step –1 '把列表中选择文件的位置之上的文件依次下移







        strLastfile(i - 11) = strLastfile(i - 12)







     AABar.Bands("MenuFile").Tools.item(i).Caption= AABar.Bands("MenuFile").Tools.item(i - 1).Caption







    Next







    strLastfile(0) = strName







    '将选择的文件的放在列表中的首位







    AABar.Bands("MenuFile").Tools.item(11).Caption = strConnection







    CloseWnd‘自定义过程,用于关闭系统中打开的除MDI窗口外的所有窗口







  







 







    strConnection = strName







    Me.Caption = "水利造价管理系统" & "-" & strConnection







    ShowMenu







    Exit Sub







SaveErr:







    dbEncrypt.SaveError "MDIForm1-MenuLastFile"







End Sub







Private Sub CloseWnd()







    On Error GoTo SaveErr:







    Dim i As Integer







    For i = Forms.count - 1 To 1 Step -1







        If Forms(i).Name <> "FrmDaoHang1" And Forms(i).Name <> "FrmDaoHang2" And Forms(i).Name <> "FrmToolSearch" Then







            Unload Forms(i) '关闭到倒数第二个窗体







        End If







    Next







    Exit Sub







SaveErr:







    dbEncrypt.SaveError "MDIForm1-CloseWnd"







End Sub

    Data Dynamics ActiveBar是由Data Dynamics开发的一款设计用户界面必不可少的 ActiveX 控件,如果你想让自己的程序变得更专业,就必须用它。使用它可以制作出像 Word 一样的个性化菜单、“自定义”对话框;像 Outlook 的快捷按钮栏;像 CorelDRAW 的入坞式窗口;像 Delphi 的控件选择页面。并且使用十分简单,保存设计图和加载设计图都十分简单,只用几个方法既可。SP3 中全面支持了 XP 样式,可以在非 XP 操作系统中实现 XP 的介面。这个控件可以在 VB6 、 VC++ 、Delphi、PB等开发工具上使用。





1、对象ActiveBar





          Tools





         Tool


        Bands


         Band


  Tools





     Tool





  ChildBands





     Band





             Tools


       Tool





    CustomizeListBox


       2、添加主菜单:





       Form中添加一个ActiveBar―>对其右单击选中快捷菜单中的属性―>弹出ActiveBar设计器界面(左侧的TreeView中显示当前ActiveBar的资源,右侧显示选中资源的属性->右单击资源栏中的Bands添加一个MenuBar为工程的主菜单项->右单击刚刚添加的主菜单添加若干Button作为主菜单的各个显示项。





       其中:AlignToForm确定ActiveBar是否填充整个Form





       3、添加一级菜单





       右单击资源栏中的Bands添加一个PopUpMenu为工程的一级子菜单项->右单击一级子菜单添加若干Button作为一级子菜单的各个显示项。->将主菜单的相应显示项的SubBands值设置为此一级子菜单的Name值。





       4、添加二级菜单





       右单击资源栏中的Bands添加一个PopUpMenu为工程的二级子菜单项->右单击二级子菜单添加若干Button作为二级子菜单的各个显示项。->将一级子菜单的相应显示项的SubBands值设置为此二级子菜单的Name值。





       5、实现菜单的Check选项





       在程序相应的菜单中使用ABar.Bands("MenuTool").Tools.item("…").Checked = Not AABar.Bands("MenuTool").Tools.item("…").Checked来实现Check选项的乒乓。





       6、工具栏





       工具栏的实现方法与菜单相似





       CaptionPostion――工具栏中的文字相对于图标的位置





       Style――工具栏中显示文字或图标还是文字图标都显示





       DisplayMoreToolsButton――Determines if the More Tools button is displayed on the band.

       大部分软件的主窗体启动之前,都有一个图标首窗体的启动,并且图标窗体与主窗体共存一段时间,您可以打开EXCEL亲身感受一下。



Option Explicit



Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long



Const HWND_TOPMOST = -1        '窗口置顶



Const SWP_SHOWWINDOW = &H40    ‘显示窗体



Const SWP_NOSIZE = &H1         '不允许改变大小



Private Sub Form_Load()



    '设置窗体永远置顶



    SetWindowPos Me.hwnd, HWND_TOPMOST, Me.Top, Me.Left, Me.Width, Me.Height, SWP_NOSIZE Or SWP_SHOWWINDOW



End Sub



 



'Time1 and Time2 配合达到延时效果



Private Sub Timer1_Timer()



    Timer2.Enabled = True



    MDIForm1.Show



End Sub



 



Private Sub Timer2_Timer()



    Unload Me



End Sub



Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)



    Unload Me



    MDIForm1.Show



End Sub



 



Private Sub Image1_Click()



    Unload Me



    MDIForm1.Show



End Sub

Microsoft公司在Windows NT3.51开始提出了注册表(Registry)的概念。注册表是Windows的一个重要组成部分,它保存了Windows中的各种配置参数。Windows的各个功能模块和安装的应用模块,在启动时都要读取注册表的信息,并根据这些参数来设置自己的运行环境,我们就可以把注册信息写入注册表来实现程序的共享注册。



VB中,有两种方法可以访问注册表:第一种是使用VB提供的函数:



Private Sub Command1_Click()



Dim KeyName as String’创建的项目名称



Dim SubKeyName as String’项目下的子项



Dim ValueName as String’子项下的值项名称



Dim Value1 as String’值项的值



Keyname=”MyKey”



SubKeyName=”MySubKey”



ValueName=”MyValueName”



Value1=”My New Key!”



      ’查询是否存在MyValueName值项,如果不存在,返回NOEXIT



Ret=GetSetting(KeyName,SubKeyName,ValueName,”NOEXIT”)



If ret=”NOEXIT”then



   ’如果不存在MyValueName值项,则创建该值项



   SaveSetting KeyName,SubKeyName,ValueName,value1



Else



   ’如果存在,则删除这个程序项MyKey



  DeleteSetting(KeyName)



End if



End Sub



创建的默认位置为HKEY_CURRENT_USER\Software\VB and VBA Program Settings下



第二种使用Windows API函数处理注册表



Global Const HKEY_CLASSES_ROOT=&H80000000



Global Const HKEY_CURRENT_USER=&H80000001



Global Const HKEY_LOCAL_MACHINE=&H80000002



Global Const HKEY_USERS=&H80000003



Global Const ERROR_SUCCESS=0&



Global Const ERROR_NO_MORE_ITEMS=259&



 



    Public Const REG_SZ=1



Public Const REG_BINARY=3



Public Const REG_DWORD=4



Public Const STANDARD_RIGHTS_ALL=&H1F0000



Public Const KEY_QUERY_VALUE=&H1



Public Const KEY_SET_VALUE=&H2



Public Const KEY_CREATE_SUB_KEY=&H4



Public Const KEY_ENUMERATE_SUB_KEYS=&H8



Public Const KEY_NOTIFY=&H10



Public Const KEY_CREATE_LINK=&H20



Public Const SYNCHRONIZE_ =&H100000



Public Const KEY_ALL_ACCESS=((STANDARD_RIGHTS_ALL OR KEY_QUERY_VALUE OR KEY_SET_VALUE OR KEY_CREATE_SUB_KEY OR KEY_ENUMERATE_SUB_KEYS OR KEY_NOTIFY OR KEY_CREATE_LINK) AND (NOT SYNCHRONIZE))



Public Const REG_OPTION_NON_VOLATILE=0



 



private Declare Function RegCreateKey Lib "advapi32.dll" Alias "RegCreateKeyA" (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long



private Declare Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpValueName As String, lpcbValueName As Long, ByVal lpReserved As Long, lpType As Long, lpData As Byte, lpcbData As Long) As Long



private Declare Function RegCloseKey Lib "advapi32.dll" Alias "RegCloseKey" (ByVal hKey As Long) As Long



private Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, lpSecurityAttributes As SECURITY_ATTRIBUTES, phkResult As Long, lpdwDisposition As Long) As Long



private Declare Function RegDeleteKey Lib "advapi32.dll" Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey As String) As Long



private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long



private Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Any, lpcbData As Long) As Long        



private Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long        



private Declare Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal lpValueName As String) As Long



cmdcCreateKey 创建项



cmdCreateValue 创建值项



cmdDeleteValue 删除项和值项



cmdExit 退出



Private Sub cmdCreateKey_Click()



    Dim lResult As Long



    Dim hKeyMyKey As Long



    Dim dwFlag As Long



    Dim msg, response



    lResult = RegCreateKey(HKEY_CURRENT_USER, "Software\MyKey", hKeyMyKey)



If lResult = ERROR_SUCCESS Then



    msg = "成功创建项"



Else



    msg = "创建项失败"



End If



        response = MsgBox(msg, vbOKOnly, "提示信息")



        RegCloseKey (hKeyMyKey)



End Sub



Private Sub cmdCreateValue_click()



    Dim lResult As Long



    Dim hKeyMyKey As Long



    Dim szValue As String



    Dim msg, response



    '打开注册表项



    lResult = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\mykey", 0, KEY_ALL_ACCESS, hKeyMyKey)



    If lResult = ERROR_SUCCESS Then



        '写入值项



        szValue = "Hello Workd!"



        lResult = RegSetValueEx(hKeyMyKey, "TestValue", 0, REG_SZ, ByVal szValue, Len(szValue))



        msg = "成功创建值项TestValue!"



        '关闭注册表项



        RegCloseKey (hKeyMyKey)



    Else



        msg = "创建值项TestValue失败!"



End If



Response=msgbox(msg,vbokonly)



End Sub



Private sub cmdQueryValue_Click()



    Dim lResult As Long



    Dim msg, response



    lResult = RegDeleteKey(HKEY_CURRENT_USER, "Software\MyKey")



    If lResult = ERROR_SUCCESS Then



        msg = "成功删除项MyKey!"



    Else



        msg = "删除项MyKey失败!"



    End If



    response = MsgBox(msg, vbOKOnly)



Endif



利用程序加密的安全性稍微低一些,如果您的软件价值很高的话,推荐使用加密狗(目前主流已经到了第三代),它的可靠性要高很多。如果您对加密解密非常感兴趣的话可以拜读一下段刚老大的《加密与解密》一书,或登陆看雪论坛交流。


(本篇完)

Option Explicit



Private Declare Function GetVolumeInformation Lib "kernel32" _



  Alias "GetVolumeInformationA" (ByVal lpRootPathName As String, _



  ByVal lpVolumeNameBuffer As String, ByVal nVolumeNameSize As Long, _



  lpVolumeSerialNumber As Long, lpMaximumComponentLength As Long, _



  lpFileSystemFlags As Long, ByVal lpFileSystemNameBuffer As String, _



  ByVal nFileSystemNameSize As Long) As Long '等到某一磁盘分区的信息



‘************注册窗体*****************



’运用另一个***.mdb来控制软件是否超出试用期



Private Sub Form_Load()



 '根据C盘序列号得到原ID



  Dim Driver, VolName, Fsys As String



  Dim volNumber, MCM, FSF As Long



  Driver = "c:\"



  Dim res As Long



  Dim localid As Long



  res = GetVolumeInformation(Driver, VolName, 127, volNumber, MCM, FSF, Fsys, 127)



  '将c盘序列号加密并显示在注册窗体的本机码中



  localid = *****volNumber***** ‘加密算法



  Text1.Text = localid‘显示经加密后的本机码



End Sub



Private Sub cancel_Click()



        On Error GoTo error



        '检测系统文件夹是否有***.mdb文件,如果没有,则是系统第一次安装,建立此数据库文件



        If Dir(sPath & "\***.**") = "" Then



            Dim ws As Workspace



            Dim db As Database



            Dim tdf As TableDef



            Dim fld As Field



            Dim rst As Recordset



            'DBEngine对象相当于Jet数据库引擎,不需要创建该对象,CreateWorkspace创建一个工作区对象



            'Workspace对象为用户定义一个会话,通过与之关联的用户名和口令建立一个安全级别。当不需要安全级别时可使用缺省的工作区DBEngine.Workspace(0)



            Set ws = DBEngine.Workspaces(0)



            '创建一个空的数据库文件,dbLangGeneral参数用来确定数据驱动程序支持的参数



            Set db = ws.CreateDatabase(sPath & "\***.mdb", dbLangGeneral)



            '创建一张新表



            Set tdf = db.CreateTableDef("***")



            '创建first_time字段



            Set fld = tdf.CreateField("first_time", dbDate, 8)



            tdf.Fields.Append fld '把first_time字段添加到表中



            '创建last_time字段



            Set fld = tdf.CreateField("last_time", dbDate, 8)



            tdf.Fields.Append fld '把last_time字段添加到表中



            '创建times字段



            Set fld = tdf.CreateField("times", dbInteger, 2)



            tdf.Fields.Append fld '把times字段添加到表中



            db.TableDefs.Append tdf '将***表添加到***.mdb中



            db.Close '关闭***.mdb



            Set db = ws.OpenDatabase(sPath & "\***.mdb") '以可读写方式打开***.mdb



            Set rst = db.OpenRecordset("***") '打开一个记录集



            With rst



                .AddNew '向记录集增加一条新记录



                '写入一条记录



                .Fields("first_time") = Date



                .Fields("last_time") = Date



                .Fields("times") = 1



                .Update '将记录写入数据库



            End With



            rst.Close



            db.Close '关闭***.mdb



            ws.Close



           '**********更改系统时间,来实现隐藏注册库的修改时间***************



………………………………………



…………………………………………



…………………………………………………



            dbEncrypt.dbEncrypt (sPath & "\***.mdb") '数据库加密



            Name sPath & "\***.mdb" As sPath & "\***.**"



            '********************将时间改会原来时间************************



            …………………………



…………………………



            MsgBox "这是你首次启动本系统!你的试用期为30天,今天是第一天,谢谢使用!", vbOKOnly + vbInformation, "欢迎!"



            ***.Show '启动主窗体



        Else '系统有***.mdb文件,则不是第一次运行,就不用建立数据库文件了.



            Dim ws2 As Workspace



            Dim db2 As Database



            Dim rst2 As Recordset



            Dim num As Integer



            dbEncrypt.dbExplain (sPath & "\***.**")



            Set ws2 = Workspaces(0)



            Set db2 = ws2.OpenDatabase(sPath & "\***.**")



            Set rst2 = db2.OpenRecordset("***") '开始检测用户是否修改了系统日期



            rst2.MoveFirst



            If rst2.Fields("last_time") > Date Or rst2.Fields("times") > 100 Then



               MsgBox "对不起,你在本软件的试用期不可以修改系统日期,否则将取消您的系统试用权,如果你想继续使用本软件。请您恢复系统日期,谢谢合作!", vbOKOnly + vbInformation, "提示"



                End



            End If



            If Date - rst2.Fields("first_time") >= 30 Then '设定试用期为30天



                MsgBox vbCrLf & "你已经启动本系统" & rst2.Fields("times") & "次,但已超过了软件30天的试用期。" & vbCrLf & vbCrLf & "如果您愿意继续使用本系统,请将“本机码”以打电话(***-********)" & vbCrLf & vbCrLf & "或发邮件(mi6236@tom.com)的形式与***联系来得到注册码!", vbOKOnly + vbInformation, "提示"



            Else



                '仍在试用期内



                num = rst2.Fields("times")



                rst2.Edit



                rst2.Fields("last_time") = Date



                rst2.Fields("times") = num + 1



                rst2.Update



                MsgBox "这是你第" & rst2.Fields("times") & "次使用本系统,你还有" & 30 - (Date - rst2.Fields("first_time")) & "天的试用期,祝你今天工件愉快!", vbOKOnly + vbInformation, "提示"



                ***.Show '启动你的主窗体



            End If



           rst2.Close



           db2.Close



           ws2.Close



    '***************更改系统时间,来实现隐藏注册库的修改时间***************



            ……………………………………



    '*****************************************************************************



        dbEncrypt.dbEncrypt (sPath & "\***.mi") '加密数据库



        Name sPath & "\***.**" As sPath & "\***.**" '因在前面改动时间会影响库中的时间,故在这里做一下假改动来达到修改时间的目的。



    '********************将时间改会原来时间************************



           ………………………………………



    '**************************************************************



     End If



        Unload register '关闭注册窗口



    Exit Sub



error:



    dbEncrypt.SaveError "Register-cancel_Click"



End Sub



 



Private Sub enter_Click()



On Error GoTo SaveErr:



'进行注册,验证注册ID



  Dim ws As Workspace



  Dim db As Database



  Dim tdf As TableDef



  Dim rst As Recordset



  Dim fld As Field



  Dim Driver, VolName, Fsys As String



  Dim volNumber, MCM, FSF As Long



  Driver = "c:\"



  Dim res As Long



  res = GetVolumeInformation(Driver, VolName, 127, volNumber, MCM, FSF, Fsys, 127) '得到硬盘序列号



  Dim Tid As Long



  Dim regid As String



  Tid = Val(Text1.Text)



  regid = Trim(Text2.Text)



  If regid = ******************* Then '判断输入的密码是否同解密算法得到的密码一致



    '***********************更改系统时间,来实现隐藏注册库的修改时间***************



            ………………………………



    '*****************************************************************************



    MsgBox "恭喜您已经注册成功,欢迎使用水利工程投资控制与评审系统", vbOKOnly + vbInformation, "提示"



    '*****将注册信息写入密码注册库*****



    dbEncrypt.dbExplain (sPath & "\***.**") '数据库解密



    Set ws = DBEngine.Workspaces(0)



    Set db = ws.OpenDatabase(sPath & "\***.**")



    Set rst = db.OpenRecordset("***")



    rst.MoveFirst



    rst.Edit



    rst.Fields("***") = 1



    rst.Update



    db.Close



    dbEncrypt.dbEncrypt (sPath & "\***.**") '数据库加密



    '********************将时间改会原来时间************************



             ………………………



    '**************************************************************



    Unload ***



    ***.Show '进入登录窗体



  Else



    MsgBox vbCr & "注册码不正确,请重新输入。" & vbCrLf & vbLf & "如果您想试用本软件可单击“取消”按钮", vbOKOnly + vbInformation, "提示"



    Exit Sub



  End If



  Exit Sub



SaveErr:



    dbEncrypt.SaveError "Register-enter_Click"



End Sub


(未完待续)

    程序员开发一款共享软件除打算用自己的一点点智慧给大众提供服务外,也需要大众给与的一点点精神与物质鼓励。私欲是人的本质,财富难免不愿转手他人,人们希望所有的软件都能将免费进行到底。共享软件作者没有了精神与物质的鼓励,服务大众的激情不可避免地消退。为了能够保持一如既往的动力,程序员们想尽办法让用户被动的支付一些鼓励,虽然这不是共享软件作者的初衷,但为了能够继续生存永远为人民服务,这也是出于被迫。



    加密与解密是一把双刃刀,程序员最大的痛苦莫过于自己的软件还没收到支付的一文钱,网络上便随处可见它的破解版,而一部分用户的理想就是建立在程序员痛苦之上,所谓的道高一尺魔高一丈。在这里我只是分析一种简单实现软件注册的实例,起一丝抛砖引玉的作用。



Option Explicit



’运用***.mdb来控制软件的注册



'GetWindowDirectory()返回Windows系统路径字符串的长度,lpBuffer存放系统路径字符串,nsize系统路径字符串的长度



Private Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long



Public sPath As String '用于存放系统目录



Sub Main()



    Dim ws As Workspace‘工作区



    Dim db As Database‘数据库



    Dim tdf As TableDef‘数据表



    Dim fld As Field‘数据字段



    Dim rst As Recordset‘数据记录



    '*************锁定系统目录************************



    Dim PathSize As Long



    Dim API_sPath As String * 256 '必需256长



    PathSize = GetWindowsDirectory(API_sPath, Len(API_sPath))



    sPath = Left$(API_sPath, PathSize) '从字符串API_sPath的左侧开始,取PathSize个字符(去除API_sPath的右侧的空格)



    '************更改系统时间,来实现隐藏注册库的修改时间**********



            Dim DateTemp



            Dim MyDate



            Dim TimeTemp



            Dim MyTime



            MyDate = #**/**/****#



            MyTime = #**:**:** PM#



            DateTemp = Date



            TimeTemp = Time



            Date = MyDate



            Time = MyTime



    '****查找是否存在***库,如不存在创建***库,启动注册窗体************



    If Dir(sPath & "\***.***") = "" Then



        Set ws = DBEngine.Workspaces(0)



        Set db = ws.CreateDatabase(sPath & "\***.mdb", dbLangGeneral) 'dbLangGenral是一个常数,用来确定数据驱动程序支持的语言类别



        Set tdf = db.CreateTableDef("***")



        Set fld = tdf.CreateField("***", dbInteger)



        tdf.Fields.Append fld



        db.TableDefs.Append tdf



        Set db = ws.OpenDatabase(sPath & "\***.mdb")



        Set rst = db.OpenRecordset("***")



        rst.AddNew



        rst.Fields("***") = 0



        rst.Update



        rst.Close



        db.Close



        ws.Close



       



        dbEncrypt.dbEncrypt (sPath & "\***.mdb") '加密数据库



        'SetAttr sPath & "\***.mdb", vbHidden '更改数据库的属性,当数据库设置为隐藏DIR找不到此文件,因此没有通用性,vbsystem数据库设置为系统文件时因win2k中不存在系统文件属性文件找不到,所以也没用通用性。



        Name sPath & "\***.mdb" As sPath & "\***.**" '重命名数据库



        register.Show



    Else



     '**注册库存在,判断是否已经注册,如已注册启动主窗体,如未注册启动注册窗体**



        dbEncrypt.dbExplain (sPath & "\***.***")  '数据库解密



        Set ws = DBEngine.Workspaces(0)



        Set db = ws.OpenDatabase(sPath & "\***.**")



        Set rst = db.OpenRecordset("***")



        rst.MoveFirst



        If rst.Fields("***") = 1 Then



            ***.Show‘启动主窗体



        Else



            ***.Show‘启动注册窗体



        End If



        rst.Close



        db.Close



        ws.Close



        dbEncrypt.dbEncrypt (sPath & "\***.**") '加密数据库



    End If



       '********************将时间改会原来时间************************



             Date = DateTemp + (Date - MyDate)



             Time = TimeTemp + (Time - MyTime)



    '**************************************************************



End Sub


(未完待续)

    1、什么是软件配置管理







    软件配置管理是指通过执行版本控制、变更控制的规程,以及使用合适的配置管理软件,来保证所有配置项的完整性和可跟踪性。配置管理是对工作成果的一种有效保护。







    2、为什么需要配置管理







   如果没有软件配置管理,最大的麻烦是工作成果无法回溯。随着工作的进展新的程序覆盖了老的程序,当突然发现新程序有问题而老程序正确时怎么办?那只能重写老的程序来覆盖新的程序。过一段时间又发现原来的老程序有问题,而解决方法在原来的新程序中……您是不是快要发疯了。







    为了避免成果被覆盖,包括我自己在内的很多人早期采用手工管理版本的方式,例如当一个新版本产生时用当时的日期来命名文件夹,然后再复制一下以后的修改在复制的文件夹内进行,这样上一个版本就被保存下来了,周而复始不同的版本不会被覆盖。虽然这种方式可以从某种程度上解决版本的回溯问题,但他存在的缺点是显而易见的:第一点如果保留结果过于频繁,将会导致产生大量的有着重复内容的文件夹,庞大的物理空间,管理起来很麻烦;如果保留旧版本的时间间隔太长,可能产生某些有用的老程序无法回溯。拿我最近开发的一个程序来说程序只有几十兆,经过一年的开发各版本累计到1G。第二容易产生版本的混乱,如果是团队开发软件,这种简单的方法更难解决问题的本质了。







    3、人的问题







    配置管理的方法是成熟的,而且相应的软件工具也是成熟的,基本上不存在看不懂、不会用的问题。配置管理的执行效果如何,完全是事在人为。妨碍配置管理的主要问题是人们嫌麻烦和侥幸心理作怪。







    在没出乱子的情况下,执行版本控制看起来有些麻烦。每次修改工作的时候总是要Get Latest Version,接着Check Out,修改完后又要Check In,多做了三步。其实这三步加起来也就十几秒钟,而且不费脑子,根本没有添加多少麻烦,仅仅是个人感觉不爽而以。然而不执行版本控制的话,万一发生工作成果被覆盖或丢失等问题,麻烦就大了。







    4、软件配置管理规范







    软件研发和管理过程中会产生许许多多的工作成果,例如文档、程序和数据等,他们都应当妥善地保管起来,以便查阅和修改。如果把所有文件一股脑的塞进计算机里,那么使用起来很麻烦。







    凡是纳入配置管理范畴的工作成果统称为配置项配置项主要有两大类:一类是属于产品的组成部分,例如需求文档、设计文档、源代码、测试用例等等;另一类是在管理过程中产生的文档,例如各种计划、报告等。







    每个配置项的主要属性有名称、标识符、文件状态、版本、作者、日期等。配置项及历史纪录反映了软件的演化过程。







    基线由一组配置项组成,这些配置项构成了一个相对稳定的逻辑实体。基线中的配置项被冻结后,不能在被任何人随意更改。基线通常对应于开发过程中的里程碑。通常将交付该客户的基线称为一个Release,为内部开发用的基线称为一个Build。







版本控制的目的是按照一定的规则保存配置项的所有版本,避免发生版本丢失或混乱等现象。配置项的状态有三种:“草稿”、“正式发布”和“正在修改”







配置项的版本号与配置项的状态紧密相关:







(1)    处于“草稿”状态的配置项的版本号格式为:0.YZ







(2)    处于“正式发布”状态的配置项的版本号格式为:X.Y。







 一般只是Y值递增,当Y值到达一定的范围时X值才发生变化。







(3)    处于“正在修改”状态的配置项的版本号格式为:X.YZ。







一般只增大Z值,当配置项修改完毕,状态重新变成“正式发布”时,将Z值变为0,增加X.Y值。







    5、常用的配置管理软件







    A)、自从20世纪80年代后期研制并完善了“增量存储算法”后配置管理工具的春天便开始了,目前国内常用的配置管理工具大概有SourceSafe、CVS和ClearCase。







    SourceSafe是Micrsoft公司推出的一款支持团队协同开发的配置管理工具,是Visual Studio的套件之一。因为其短小精悍,又继承了微软集成销售的一贯作风用户可以相对于免费的价格得到,用户量绝对是第一位。







SourceSafe简单易用人们在使用配置管理工具时候,80%的时间只是用Add,Check in,Check out等区区几个功能。







SourceSafe 的主要局限性:只支持WINDOWS不支持异构环境下的配置管理;对INTERNET支持不够完善。







B)、在详细介绍SourceSafe首先简单概述一下它的基本机制。SourceSafe是使用服务器、本地机的概念来进行操作的,它认为所有需要操作的文件都存在服务器版本文件和本地机版本文件,无论您的SourceSafe的架构是服务器客户机形式还是个人单机版形式,它的机制都是这样。用户所用的修改都是在本地机上完成的,修改完成后再上传服务器。单机版也是这样操作。我们一定要明确两个版本后再来分析。







服务器版本文件是一个绝对受配置管理软件限制的文件,用户只能通过SourceSafe的规定的权限和操作方法修改它,因为它并不是您一个人的,它是大家的。本地文件是一个基本不受限制的文件,您可以象操作本地文件一样操作它。







SourceSafe由Visual SourceSafe 6.0 Admin、Microsoft Visual SourceSafe 6.0、Analyze VSS DB、Analyze & Fix VSS DB四部分组成。







C)、Analyze VSS DB、Analyze & Fix VSS DB两个工具不是很常用,前者用于检查SourceSafe数据库文件的完整性,后者主要是修正SourceSafe数据库文件存在的错误。







D)、Visual SourceSafe 6.0 Admin的功能类似于win2k的用户管理器,软件配置管理人员用它来分配用户和设定相应的权限。







管理员的管理操作一般都集中在Visual SourceSafe 6.0 Admin中,系统中只有一个系统管理员Admin可以登陆到此程序中进行管理工作,一般刚刚安装的系统中此用户的密码缺省为空。而且系统为Admin这个用户保留的一切权力,不可更改。







数据库的创建这个操作必须在服务器上执行,因为通过客户端创建数据库的操作,只是在客户端的机器上创建的数据库,这个数据库往往只能单机使用。同时要必须注意,由于VSS是通过WINDOWS的网络共享来完成服务器端受控版本文件的共享,因此VSS服务端的数据库必须建立在服务器的一个完全共享的目录之中。否则,客户端将无法获得数据库中的文件。







数据库的备份与恢复,备份数据库或者其中的一个项目,点击tools-archive projects…菜单弹出对话框,根据提示一步步进行备份,最后会形成一个扩展名为*.ssa的备份档案文件。







如果要从档案文件中恢复VSS数据库中的文件数据,点击tools-restore projects菜单根据提示一步步完成数据恢复工作。其中,在恢复过程中,可以选择恢复为原有工程,也可改变恢复成其他工程目录。







E)、Microsoft Visual SourceSafe 6.0是SourceSafe的主要使用平台,样子象Windows的文件管理器,它所显示的路经是以‘$’符号为根节点的相对路径,在这里介绍一下此平台的主要使用方法。







1)添加项目







您可以在根节点下添加项目,方法是:File—Add File出现Add File对话框后选中相关文件,按Add即可。







你可以继续通过File-Create Project在根目录下创建一个项目后在此项目节点下添加文件。







添加完文件后,您所添加源文件的属性自动变为只读,并在所添加文件的文件夹下生成一个vssver文件 ,以后对文件的操作基本与原文件没有关系了。







2)浏览Source Safe Server中的文件







Visual SourceSafe Explore中双击要打开的文件,会弹出一个对话框,您直接点击OK即可。这时SourceSafe Explore会将文件拷贝一份到本地机的临时文件夹中(临时文件夹路径在tools-options-general下设置),因原文件前面提到已经变了只读,所以临时文件也是只读属性,而且文件名会通过系统自动更改。







3)设置工作文件夹







 SourceSafe 的文件夹需要在本地计算机上指定一个“working folder”。当“check out”时,相应文件会下载到这个本地工作文件夹中。我们在本地的文件中修改文件,然后把修改后的文件“check in”回服务器的source safe中。







我们可以利用“set working folder”这条命令来建立source safe的文件夹和本地“working folder”的对应关系。方法在source safe的文件目录树中选中要建立对应关系的文件夹-右单击-选择“set working folder”即可。







4)、下载最新版本文件到本地机







get latest version”命令可以将一个文件、一组文件或整个文件夹的最新版本从source safe中拷贝到本地的计算机中,并用只读的形式保存起来。方法如下:







在左侧的文件树中选择相应的文件夹右单击后,选择“get latest version”







这时会弹出一个对话框,它包括三个复选框:三个复选框全空时,只将source safe文件夹根目录下的文件拷贝到本地计算机,如同DOS中的COPY命令;recursive选项选中时,会将source safe文件夹下的所有文件夹及文件都拷贝到本地计算机,如同DOS中的DISKCOPY,make writable选中,拷贝到本地的文件是可写的。







    如果我们单击“advance”按钮,就会出现更多的选择项。在“set file”中的四个选项第一如下:current为拷贝操作发生时的当前时间;Modification为文件最近一次修改的时间;check in为文件最后一次check in是的时间;default同current.







    在replace writable中的四个选项作用是,当本地机有一个和要下载的文件同名时,且本地机的文件是可写的同名文件时,设置系统如何执行拷贝:ask系统提示是否覆盖本地的同名文件;replace自动覆盖本地的同名文件;skip不覆盖本地的同名文件;merge将两个文件合并。







    一定要养成先Get Latest Version的习惯,否则如果别人更新了代码,VC会提示你存在版本差异并问您是否覆盖、整合、保留等,如果选错了就会把别人的代码Cancel掉,所以一定小心。







    (5)下载文件到本地操作







    当我们要修改一个文件时,首先要把文件从source safe中复制到“working folder”中,并且以可写的形式保存,这一系列动作的命令就是check out。具体使用方法如下:选择要下载到本地机的文件,右单击后选择check out,这时会弹出一个对话框。缺省的状态下“don’t get local copy”这个选项是不选的,他的意义是这样的:如果不选保持缺省状态,当本地的同名文件是只读时,则系统首先用source safe的文件更新本地的文件,本地的文件变为可写。当本地的文件是可写时,则会出现另一提示框其中的选项leave this file:本地文件保留当前状态,source safe中的文件也保留当前状态,这样有可能两个文件不一致。选项Replace your local file with this version from source safe:用source safe中的文件更新本地的文件。如果您选择don’t get local copy选项:则不把source safe的文件拷贝到本地。







    文件check成功后,您可以看到文件上有红色标记,这时您的本地文件是可写的,您就可以修改文件了。上面的选项也许让您心乱,为了操作更简便,我们推荐一种check out 方法:







    当本地的文件比source safe中的文件内容新时,选择don’t get local copy选项。然后check in使本地机与服务器内容同步;







    当source safe中的文件比本地机的文件内容新时,则在source safe中选择此文件,然后get latest version命令,然后按照默认选项进行check out;







    当两者内容相同时,按照默认选项操作。







    注意:source safe中使用了文件锁的概念当一个文件被别人check out时,其他人不能check out 此文件;如果文件锁是无效的,您可以查看Visual SourceSafe 6.0 Admin-tools-general-allow multiple chechouts选项是否被选中。只有当check out 修改文件完毕后,一定要check in,来保证source safe中的文件最新。







    谨记check out时将是使得代码对自己可写,对别人只读,请仅仅Check Out自己需要修改的部分,不然你工作的时候同组成员只能休息了。







    (6)上传文件到服务器操作







    我们必须利用 check in命令保证source safe本地的文件同步,check in与check out 成对出现,它的作用是用本地的文件更新source safe中被check out 的文件。







具体操作在source safe选中处于check out状态的文件,右单击选择check in即会出现一个对话框:默认状态下它的两个复选框处于非选状态,Keep checked out选项,可以在check in 后自动的再次check out,等于是省略了下一步check out操作;remove local copy选项,可以在check in的同时,删除本地机上working folder中的同名文件。







一般按照缺省选项就可以了。Check in成功后,source safe和本地的文件是完全相同的,本地的文件变成了只读文件。要再次修改文件时,再执行check out操作,此时本地机的文件属性自动变为可写状态。一定记住check out 后要check in,不然导致的后果就如同写完了文件不保存差不多。







一定要保证你的文档正确、可编译后再Check In不然会使得其他人也无法通过编译,整个工程没法调试了。







7)undo check out 操作







当一个文件被check out 后,您如果想要撤销这项操作,可以使用undo check out命令,操作步骤:选中处于check out 状态的文件,右单击后选择undo check out.







    当source safe中的文件和本地的文件完全相同时,则不出现提示信息,文件恢复为普通状态。







    当source safe中的文件和本地的这个文件不完全相同时,则出现提示窗口,对话框local copy中包括三个选项:







replace选项选中后会出现,系统询问是否覆盖的信息,如果单击yes则是用source safe上的文件最后一个版本覆盖本地机上的文件,如果选择no保留本地计算机上文件的内容,source safe上的文件是上次check in后的内容。此时,两个文件可能出现不同;







leave选项保留当前计算机上的内容,source safe上的文件是上次check in后的内容,两个文件可能出现不同;







delete 选项删除本地计算机上的这个文件。







选择一个选项后,单击OK后,文件回到普通状态。







8)edit操作







edit命令是一个组合命令,是先check out再修改的命令的组合。应当注意的是,执行edit命令后,我们修改了文件,但是source safe中的文件并没有同步的修改,我们还是要check in完成本地文件与source safe上文件的同步。   







9)查看文件的历史内容







方法选中此文件,右单击选择show history,出现一对话框后,经选择OK后弹出一窗体,我们可以看到这个文件的所有版本,要查看某个版本可以选中VIEW按钮。如果想下载某个先前的版本可以点击get按钮。







10)关于source safe的权限







缺省状况下,项目安全管理是以简单模式来运行,即用户对工程的操作的权限只有两种,一种只读权限,一种读写权限。要启用高级模式,可以在Visual SourceSafe 6.0 Admin-tools-project security-enable project security将此选项选选中。







source safe的权限分为5级:







无权限级:看不到文件







read级:自能浏览文件,可以使用get latest version命令







check in/check out级:可以更新文件,但不能对文件进行删除







delete级:可以删除文件,但通过某些命令这些文件还能恢复。







Destroy级:可以彻底的删除文件,删除之后无法恢复。







为用户设定权限的工作一般由软件配置管理员在Visual SourceSafe 6.0 Admin中完成。







权限管理就是管理用户和工程目录之间的操作权限的关系。因此,有两种管理方式。一种就是以工程目录为主线来管理权限,一种是以用户为主线来管理权限。







以目录为主线管理用户权限则点击tools-right by project…菜单,弹出对话框来管理项目的用户访问权限。







如果以用户为主线来用户权限,则应先在主界面的下方的用户列表中选中一个用户,再点击rights assignments for user…菜单,弹出对话框,对话框下方列出了该用户对数据库各项目目录的访问权限,如果访问某个项目在列表上没有列出,则说明该项目的权限是继承上级目录的访问权限。只要您点选一个目录,就可以编辑该用户对该项目目录的访问权限。







权限复制就是将一个用户的权限直接复制给另外一个用户,管理员可以通过copy user right…菜单来实现。







11)关于password的更改







password一般是由软件配置管理员分配的,如果我们需要修改密码,可以在tools-change password 下修改。






需要说明的一点是当你的source safe密码和windows密码相同时,启动source safe,不会出现提示您输入密码的对话框。这是微软的的一贯作风,在SQL server数据库管理系统下也能找到这个影子,因为微软认为windows的密码应该比其他软件的密码级别要高,既然您能用相同的用户名和密码进入windows那么您也有权使用相同的用户名进入其他的软件。

开发手记(四)——实战破解ACCESS密码

    ACCESS与EXCEL都是OFFICE套件产品,但他们的加密方式完全不同。EXCEL利用的是DES加密技术,而ACCESS只是简单运用异或运算修改头文件中的若干位来达到加密的目的。







     异或运算特性:一数经两次异或可以回到原值,特性举例:(A)XOR(B)=(C)同时(C)XOR(B)=(A),同理可以推出(C)XOR(A)=(B)。A代表ACCESS头文件中的若干位,B代表用户密码,C为经过加密的头文件的若干位的值。我们可以用ultraedit或VC以二进制方式打开原始ACCESS,记录下相关头文件的值,再打开加密后的ACCESS记录下相关头文件的值,两者再异或结果就是ACCESS的密码了。明白了原理后破解ACCESS的密码就很容易了。ACCESS在不同的版本中它的加密位也是不同的:ACCESS97的最大密码长度为13位,加密位是从头文件的67位开始至79位结束,未加密的这13位十六进制值为"86,FB,EC,37,5D,44,9C,FA,C6,5E,28,E6,13"ACCESS2000的最大密码长度为20位,加密位也是从头文件的67位开始至106位结束,未加密的这40位十六进制值为20 6D EC 37 FB D2 9C FA 60 C8 28 E6 B5 20 8A 60 F2 02 7B 36 53 E4 DF B1 D1 62 13 43 69 39 B1 33 92 F7 79 5B 34 23 7C 2A ”,ACCESS2000采用的是40位中的低字节,如前两位二进制值为‘20 7D’,那么它的加密位为‘20’位。







  解密最直接的方法是用原始头文件的相关位覆盖加密文件的相关位;也可以得用国外有名的免费软件ACCKEY,它只有361KB;但我们更愿意自己编写一个破解程序达到目的,这样更有成就感。







‘*****ShowPassWord.vbp----破解ACCESS密码*****







************(c)mi6236,2005,vb6.0  win2k*************







Option Explicit







Private Sub ShowPassWord_Click()







    Dim password As String







    Dim temp As Byte







    Dim source97(12) As Byte







    Dim source2000(39) As Byte







    Dim i As Integer







    '**********************************************************







    '将未加密ACCSEE97中的67-79位的值并赋给数组source97()







    '**********************************************************







    source97(0) = &H86







    source97(1) = &HFB







    source97(2) = &HEC







    source97(3) = &H37







    source97(4) = &H5D







    source97(5) = &H44







    source97(6) = &H9C







    source97(7) = &HFA







    source97(8) = &HC6







    source97(9) = &H5E







    source97(10) = &H28







    source97(11) = &HE6







    source97(12) = &H13







    '**********************************************************







    '将未加密ACCSEE2000中的67-106位的值并赋给数组source2000()







    '**********************************************************







    source2000(0) = &H20







    source2000(1) = &H6D







    source2000(2) = &HEC







    source2000(3) = &H37







    source2000(4) = &HFB







    source2000(5) = &HD2







    source2000(6) = &H9C







    source2000(7) = &HFA







    source2000(8) = &H60







    source2000(9) = &HC8







    source2000(10) = &H28







    source2000(11) = &HE6







    source2000(12) = &HB5







    source2000(13) = &H20







    source2000(14) = &H8A







    source2000(15) = &H60







    source2000(16) = &HF2







    source2000(17) = &H2







    source2000(18) = &H7B







    source2000(19) = &H36







    source2000(20) = &H53







    source2000(21) = &HE4







    source2000(22) = &HDF







    source2000(23) = &HB1







    source2000(24) = &HD1







    source2000(25) = &H62







    source2000(26) = &H13







    source2000(27) = &H43







    source2000(28) = &H69







    source2000(29) = &H39







    source2000(30) = &HB1







    source2000(31) = &H33







    source2000(32) = &H92







    source2000(33) = &HF7







    source2000(34) = &H79







    source2000(35) = &H5B







    source2000(36) = &H34







    source2000(37) = &H23







    source2000(38) = &H7C







    source2000(39) = &H2A







    '读取命令对话框中所选的文件







    CommonDialog1.ShowOpen







    CommonDialog1.DialogTitle = "打开ACCESS数据库文件"







    If (CommonDialog1.FileName = "" Or Mid(CommonDialog1.FileName, Len(CommonDialog1.FileName) - 2, 3) <> "mdb") Then







       i = MsgBox("您未选择文件或选择的文件不是ACCESS数据库文件", vbOKOnly + vbCritical, "注意")







       Exit Sub







    End If







    Open CommonDialog1.FileName For Binary As #1







    Get #1, 21, temp







    If temp = &H0 Then‘判断ACCESS数据库的版本号







        For i = 0 To 12







            Get #1, 67 + i, temp '逐次读取头文件中67-79位放入temp中,并与source97中各元素异或返回密码







            If temp = source97(i) Then Exit For '加密位读取完毕







            password = password & Chr((temp Xor source97(i)))







        Next







        Close #1







        If Len(password) = 0 Then







            Text1.Text = "该数据库没有加密!"







        Else







            Text1.Text = "该数据库的密码为:" + password







        End If







    Else







        If temp = &H1 Then







            For i = 0 To 39 Step 2







            Get #1, 67 + i, temp '逐次读取头文件中67-79位放入temp中,并与source2000中各元素异或返回密码







            If temp = source2000(i) Then Exit For '加密位读取完毕







            password = password & Chr((temp Xor source2000(i)))







            Next







            Close #1







            If Len(password) = 0 Then







                Text1.Text = "该数据库没有加密!"







            Else







                Text1.Text = "该数据库的密码为:" + password







            End If







        End If







    End If







End Sub







是不是ACCESS真的这么脆弱呢,其实你完全可以在数据库头文件的其他固定位与已存储的固定数据位上做文章,如何做这个丰富的想象空间就留给朋友们了!

    人们常问:“需求、设计、编程、测试四者究竟哪个重要?”





    这个问题不好回答。四者都是软件开发过程中必不可少的环节,光做好其中一个环节并不能产生好的系统,但是做坏了其中任何一个环节,必定对系统产生坏的影响。若站在风险管理的角度讲,也许需求开发与管理是最重要的环节。因为需求是产品的根源,对产品需求的认识是否彻底对产品的影响最大。就像一条河流,如果源头被污染了,那么整条河流也就被污染了。





庆幸的是我本人既是软件的开发者又是这套系统的最终用户,个人亲身在造价管理部门、投标部门、项目法人结算部门工作过,对系统的大部分需求比较了解,这也给需求分析工作减轻了负担。也许是对需求的理解过于自负,因此对水利设计单位的需求调查深度不够,导致了目前系统针对设计单位用户功能的提供存在一个缺陷,而且是不容易弥补的,这一点的需求与许多模块相关联,牵一发而动全军。正如Frederick Brooks在他1987年的经典文章“No Silver Bullet”中阐述需求的重要性:“开发软件系统最困难的部分就是准确说明开发什么。最困难的概念性工作是编写出详细的需求,包括所有面向用户、面向机器和其他软件系统的接口。此工作一旦有失误,将会给系统带来极大的损害,并且以后对他修改也极为困难。”





软件需求是系统设计之源,详细的需求分析完成后接下来的工作便要进入系统的设计阶段。软件系统设计核心内容包括:体系结构设计、用户界面设计、数据库设计、模块设计、数据结构与算法设计等几方面。





根据这套软件的具体开发情况,系统设计准备了两套方案:一个是继承市场上现有水利造价软件的主流设计思路,对其不完善的部分改进并加入一些先进的设计思想。另一种方案是抛弃主流设计思路,另辟新径开发一个面目一新的产品。我对两种方案的优缺点进行了对比:





第一种方案的优点:





1、主流水利造价系统结构设计的可实现性具有先例,开发风险系数低。





2、有大量的老用户习惯了以前的软件操作模式,主流的用户界面和模块画分会给老用户以熟悉感,用户不用重新学习软件的使用方法。





3、可以吸收市场上较成熟的同类软件的数据库设计思想,缩短了开发时间。





第二种方案的优点:





1、全新模式设计,可以彻底抛弃现有同类软件在需求、设计方面的不足。





2、给用户以全新的感觉,避免用户产生新软件是某一款老软件升级版的错觉。





3、避免了因与某一同类软件的部分结构相似而产生的版权纠纷问题。





我对两套方案进行了详细分析对比,权衡利弊决定采用第一个方案。并运用以下方式对第一套设计方案中的不足进行弥补:





1、彻底分析市场现有同类软件的不足,从需求、设计方面入手,增强系统的功能与操作的人性化,让软件更体贴用户需要。改进计算与导出模块的数据结构算法,提高系统的整体性能。因水利造价管理的需求是随时代发展而变化的,预留软件接口,提高可扩展性。对部分模块全新设计,提高系统的兼容性。





2、只吸收设计思想,不抄袭整个软件,尽量避免版权争议。





3、本人在天津水利造价管理领域有一些人脉,因此软件设计主要吸收天津某单位开发的一套系统的优秀思想。即使将来产生版权争议也容易摆平,再不行还可以合作吗。





根据《计算机软件保护条例》第六条的规定,除计算机的程序和文档外,著作权法不保护计算机软件开发所用的思想、概念、发现、原理、算法、处理过程和运算方法。也就时说,利用以有的上述内容开发软件,并不构成侵权。因为开发软件时所采用的思想、概念等均属计算机软件基本理论的范围,是设计开发软件不克缺少的理论依据,属于社会公有领域,不能为个人专有。





   所以市面上的同类型的软件结构布局、使用方法都有几分相似,这给用户的使用也提供了很大的方便。但他们之间肯定是有差异的,各有长短,一些后者吸收前人的思想并发展继承后异军突起了。


因为已经完成了系统需求详细分析,对业务流程十分熟悉,而这种数据处理系统的技术含量不很高,所以对市场现有水利造价软件的体系结构、用户界面、模块划分的分析相对容易一些。软件所依赖的数据结构与算法可结合程序实现时一同考虑。在对现有软件的剖析过程中最让人头痛的是数据库结构的分析过程。需要搞清每个库的作用,每个库之间的联系;库中每个表的作用,他们之间的联系;表中的每个字段的作用,他们之间的联系;之后他们所有互相之间的联系。我耗时一个月对软件的6个库、120张表、3000多个字段进行了详细的功能及关系描述,描述文档约有10万字。算起来比全新设计库结构所要耗费的精力一点不少,这样做只是为了增加软件的成功系数。





至此软件的分析工作全部完成,接下来的工作就是要真刀真枪的编码了。

   当立项建议经评审通过后,软件公司的机构领导接着会任命项目经理,由项目经理去筹备项目。财务部门、人力资源部门为新团队的组建提供必要的财力和人力资源。





       作为以合作的形式开发共享软件,人力和财力的筹备就成为项目组织者一个人要操心的事了。粗略计算一下如果以聘用的形式组建开发团队,普通成员需3人,每人每月工资2000,开发时间为1年,软件的人力成本超过7万元,总体开支预算超过10万。虽然将来软件的版权容易划分,但风险性太大,弄不好就闹成人财两空的悲剧,这也是软件迟迟停留在分析阶段的主要原因。寻找志同道合的合作伙伴,便成了我朝思暮想的事情。天,你掉下来的馅饼在那里!津城丢车事件让我得到了这个馅饼。





       老自行车是我上学时卖得,现在到处摇摇晃晃,破破烂烂。今个儿漏气,明个儿爆胎,后个儿刹车掉了……不过在一般情况下,只要不影响它能够被我骑起来,掉钢丝也好,丢刹车也好,我是照骑不误的。不过有一天晚上可把我吓了一大跳,那天很晚了,我和同事结束加班,两个人有说有笑的骑着。突然自行车的把不能动了,纹丝不动,自行车连同车上的我一起驶向马路中央。迎面来的出租车使劲向我按喇叭,我已经听到了,可自行车的把根本不听我的使唤,人都蒙了。好在马路上车少,出租车及时躲开了我,而路中间正好没有跟上的车,这时我也清醒了一些从车上跳下来,着牙瞅着已经开远但仍回过头向我嘟囔着的出租车司机傻笑。一路惊魂未定,得出结论:出门在外,安全第一赶快买辆新车。





       周五新车搞定,日式车比我那俩破杂牌车好骑N倍。周六去北大青鸟办些事,外面风不小而且有些顶。心里一个劲的琢磨是骑车还是坐车,经过三十秒的考虑选择了前者。天津的公交太有特色,车总是那样少,人总是那样多,终点三站以后基本没座,车慢得不如自行车,还有另一个重要原因就是想体会一下骑新车的快乐。新车确实好骑,路上自己不住表扬自己的决策英明,免受在公交车上站一个小时的痛苦。脑子不停的开小差:都说在天津人人都丢车,可我已经骑车五年也没丢一辆,继续表扬自己幸运。到了目的地特意把车锁在可以透过玻璃窗看到的位置,见到熟人叙叙旧,中午吃顿饭,下午离开时竟然找不到车,左找右找前找后找,发现没了。问问是不是被人扳到别处,问后才知道这里月丢车在10辆以上。我的车可是里程数不足15KM,属于我的时间不足24小时呀,没办法自认倒霉,后悔无限,诅咒偷我车的贼无数遍,事已至此看来回去时只能挤公交车了。北大青鸟的张老师非要拉我去报案,起初我不愿再浪费时间,明白即使报案车也找不到,后来执不过他只能去了。好在距离不远走路也就十分钟,在路上无意中说起了我开发软件的想法,他也非常支持,表示帮我找学校李主任谈一下看看是不是可以合作,我也没太在意,丢车后的失落交加着对小偷的愤恨已经充实了我的整个大脑。到了派出所见到了人民警察,备案记录五分钟了事,我儿时的偶像并没有对我的不幸表示更多同情,也许这种小事在他们眼力已司空见惯,倒是我的耳朵里充满了他们对工作劳累的倾诉,告诉我他们的年龄平均不会超过55岁,对于这一数据我没有调查也就没有发言权,只得默许了他的言论,最后留下联系电话,报案事件画一句号。





        灰色的心情随着时间的推移渐渐变淡。一日突然接到北大青鸟的电话,内容是关于项目合作的,张老师希望我能和李主任谈一下,这对于我来说可是意外收获,不能放过,马上见面。李主任看起来是个生意人,他向我表明了用意:学校希望能够引入一些有意义的项目来提高学生的软件设计经验,如果将来软件有商业价值可以合作推广。他是一举两得,对于我来说也不错:学校可以推荐给我三名即将毕业的优秀学生,并提供开发场所及设备。不管太多了,将来怎么样以后再说,先答应下来,机不可失,最起码开发资金难题解决了。和推荐的同学见面,我们年龄相仿,就是不知他们水平怎么样,大家相互谦虚的客气一下进入正题。因为软件的设计阶段基本完成,剩下的主要任务是实现,我粗略的介绍了一下设计概要,大家熟悉熟悉,这样开发团队初步建立起来了。





       321日是项目进展的一个里程碑,场所、人员、设备全部到位。环境不错是一栋写字楼的八层,大约有30平米,电脑三台可以上宽带。学校还为屋里贴了一个牌子“***公司“,名字现在记不清了。项目的筹备以这种我从来没有想到的方式完成,余下时间就可以全身心编码了,这才是程序员最喜欢做的事情,激动的心情朋友们一定能够意会到

  立项是软件开发的首要步骤,但常常被开发者轻视。立项主要目的是开发正确的产品,是软件开发成功的第一前提。“良好的开端是成功的一半”,那么错误的开端将是什么样的结局呢?结果可想而知了。我在立项过程中很多思想来源于林锐先生的“软件工程与项目管理解析”一书,在此向我的偶像表示敬意。


    一、产品构思、立项调查与形成立项建议书


    产品构思、立项调查是形成立项建议书的前提准备,立项建议书是产品构思、立项调查的最终结果。在撰写正式的《立项建议书》之前,开发者首先要在宏观层面上搞清楚“开发什么”、“怎样开发”、“怎样产生价值”等重大问题即产品构想。立项调查的目的是为产品构思和可行性分析提供充分的、有价值的信息。如果不做调查的话,那么产品构思和可行性分析建立在空想之上,主管臆断的成分就很多。我把软件的立项建议书拿出来一起评估,大家也可以作为参考。


    1.产品介绍


       1.1产品定义


       “水利造价管理软件包”其中包括水利造价管理系统及水利工程投资控制与评审系统。水利造价管理系统主要用于水利工程的投资估算、投资概算、投资预算、招标标底、投标报价、施工结算的编制,水利工程投资控制与评审系统主要用于各级主管单位对下级管理部门的投资估算、投资概算、投资预算、招标标底、投标报价、施工结算进行审核与审批。


        1.2产品开发背景


        (1)
             为了提高自己的程序开发水平,增强合作开发共享软件方面的经验,将几年的想法付诸实
践。


        (2)
             工程造价目前趋势已从手工计算时代过渡到了造价师控制下的信息化时代,在日常工作中
造价工程师需要处理大量数据,对其进行收集、整理、统计、计算得到有用的信息。由于这些工作相当枯燥与烦琐,如果基础数据发生一丝变化,所有数据都要重新计算。手工对其管理工作量大,且易出错,不能满足当今社会的需要。数据库系统作为当前比较流行且成熟的数据管理方式,可以方便、迅速、准确地对信息进行分类整理、查询、计算从中取得信息建立报表。目前造价管理信息化还处于过渡期尤其是水利造价行业,虽然大家已经看到了信息化的优势,但一部分单位还处于半手工工作状态,而且整个行业的信息链结并不完善,市场潜力很大,这就为开发一款集成式、多功能的水利造价管理系统提供了必要性。

        1.3 产品主要功能和特色


       (1)
             本系统涵盖了水利造价管理中计划与规划部门的投资估算、设计部门的投资概算、造价管
理部门的投资预算、招标管理部门的招标标底、施工企业的投标报价、施工单位与项目法人之间的施工结算的编制与辅助处理各相关部门之间的投资审查。


       (2)
             软件特色


          [1]、全Windows界面,编辑操作便利、快捷,具有很高的稳定性和容错性;


         [2]、集项目投资控制与评审于一体;


          [3]、编制阶段可直接对定额模糊查询、快速定位,无须再翻阅定额书本;


        [4]、工程编辑可直接输入工程项目和工程量,自动进行项目的合计,无须编号,方便直观;


          [5]、与“Excel”软件无隙连接,表格可输入到“Excel”中,用户可指定报表输出的格式,方便了投标工作;


          [6]、可扩展性强,多种方式进行定额补充;


          [7]、造价数据输入、输出文件均保存在一个文件中,为项目的管理提供了极大的方便,提高了安全性;


          [8]、材料自动统计,无须人工输入,,商品砼可直接进价,不须做补充定额;


          [9]、系统内部实现动态错误捕捉技术,将软件中存在的问题自动记录到错误库中,方便了软件的升级;


        1.4 产品范围 


  产品适用于水利部2002年8月颁布的新版水利定额,包括《水利建筑工程概算定额》、《水利建筑工程预算定额》、《水利水电施工机械台时费定额》、《水利水电设备安装工程预算定额》、《水利水电设备安装工程概算定额》和相应配套的编制办法水总[2002]116号,不适用于工民建、市政等行业的造价管理。


  产品适用于水利部2002年8月颁布的新版水利定额,包括《水利建筑工程概算定额》、《水利建筑工程预算定额》、《水利水电施工机械台时费定额》、《水利水电设备安装工程预算定额》、《水利水电设备安装工程概算定额》和相应配套的编制办法水总[2002]116号,不适用于工民建、市政等行业的造价管理。


    2、市场概述


  2.1 客户需求描述


   (1)   本产品的客户群主要是水利造价工程师,客户的计算机操作水平不高。


   (2)   客户对产品的要求除了实现造价业务外,软件还要具备数据结果精确性、易操作性和稳定性


   (3)   本软件严格按照造价业务流程开发,充分考虑到人性化操作。


    2.2 市场规模与发展趋势


   (1)    目前市场处于未成熟阶段,本产品在市场同类产品中处于领先水平。


   (2)    本产品价格****元/套,市场同类产品****元/套。


   (3) 目前市场总额****万元,水利设计总院所开发所软件占市场总份额15%,各地水利设计分院或造价管理站所开发相关软件占市场总份额70%,各软件公司相关产品占市场总额15%。本产品预计最终占市场总额**%。


  3、产品发展目标


   1-2年占领本省**%需求市场,3-5年实现占领全国**%市场。


    4、产品技术方案


    4.1 产品体系结构


     前台VB控制,后台数据库存储。


        4.2 关键技术


    (1)    主程序的算法保证计算速度,实现难度大。


    (2)    vsprint、vsflexgrid、activebar控件的全部使用方法掌握,实现难度一般。


    (3)    结果以EXCEL形式输出,实现难度一般。



    5.项目开发计划


      5.1 项目团队建设


      角色        知识技能要求                 建议人选、人数工作时间



     项目经理    精通业务流程、熟悉项目管理    1人、3小时/日


     程序员      精通VB、T-SQL               3人、8小时/日


      5.2 成本估计


     成本类型            金额               备注


     人力资源         0           合作开发、不发工资


     软硬件资源      3000           自备电脑、电脑升级


     房屋租赁费      5000 


     伙食、交通费       6000


     其他           1000


      5.3 进度表


      ~2.12              需求分析


   2004.2.12-2004.3.21      体系结构设计、数据库设计
 
   3.21~4.21         输入模块设计、实现、测试


   4.21~8.11         主模块、计算模块设计、实现、测试


   8.11~9.11         输出模块设计、实现、测试。


   9.11~11.11        软件整体测试


   11.11~           软件发布、维护


   注:由于诸多原因,部分模块边设计边实现。


    6、市场营销计划


      6.1产品盈利模式


      销售模式采用本省直销、外省代理。


      6.2促销和渗透模式


     (1)    申请科技进步奖。


     (2)    建立专题网页


     (3)    各网站上宣传、并提供试用版下载


     (4)    将产品试用版赠送潜在用户


     (5)    通过造价管理方面朋友推广


     (6)    与造价管理部门合作。


      6.3 销售方式和渠道


     (1)    直销。与软件开发者直接联系,销售产品


     (2)    代理。寻找其他城市代理商。
 
     (3)    联盟。与行业管理部门合作。


    7、总结


     (1)、软件的市场需求大。


     (2)、同类产品不成熟、本软件的市场竞争力强。


     (3)、软件开发成本低。


     (4)、软件开发技术较易实现。


     (5)、身为使用者的开发者对需求具有更充分的把握。


    项目建议书大功告成,建议能否真的实现,成功或失败的可能性有多大,   可从一下几个方面对其进行可行性分析:


  1、市场可行性
  2、政策可行性
  3、技术可行性
  4、成本收益
  5、SWOT


http://blog.csdn.net/mi6236/services/trackbacks/441596.aspx

C#设计模式系列1-简单工厂模式

 

        简单工厂模式及时根据它提供的数据,返回几个可能类中的一个类的实例。通常它返回的类都有一个共同的父类和共同的方法,但每个方法执行的任务都不同,而且根据不同的数据进行了优化。该设计模式实际上并不属于23个GoF模式。 下面的例子就说明了这个道理:
       这个例子主要是用来根据用户录入的“FirstName LastName”和“LastName,FirstName”两种可能的姓名来获得FirstName和LastName。父类是Namer,子类是FirstFirst,LastFirst,简单的工厂类是NamerFact,具体的代码如下:



namespace GoFClass
{
 //父类
 public class Namer
 {
  protected string strFirstName;
  protected string strLastName;


  public string getFirstName()
  {
   return strFirstName;
  }
  public string getLastName()
  {
   return strLastName;
  }
 }


//以下是两个派生类
 
 public class FirstFirst:Namer
 {
  public FirstFirst(string strName)
  {
   int i=strName.Trim().IndexOf(" ");
   if(i>0)
   {
    strFirstName=strName.Substring(0,i).Trim();
    strLastName=strName.Substring(i+1).Trim();
   }
   else
   {
    strFirstName="";
    strLastName=strName;
   }
  }
 }


 public class LastFirst:Namer
 {
  public LastFirst(string strName)
  {
   int i=strName.Trim().IndexOf(",");
   if(i>0)
   {
    strLastName=strName.Substring(0,i).Trim();
    strFirstName=strName.Substring(i+1).Trim();
   }
   else
   {
    strFirstName="";
    strLastName=strName;
   }
  }
 }


//简单工厂类


 public class NameFactory
 {
  public NameFactory()
  {
  }
  public Namer getName(string strName)
  {
   int i=strName.Trim().IndexOf(" ");
   if(i>0)
    return new FirstFirst(strName);
   else
    return new LastFirst(strName);
  }
 }
}


//使用方法


   NameFactory nameFac=new NameFactory();
   Namer namer=nameFac.getName(tbName.Text);
   tbFirstName.Text=namer.getFirstName();
   tbLastName.Text=namer.getLastName();



"ASP.NET 管理实用工具"使用方法

 

问题:


Windows2000系统,先装的  VS.NET,然后装IIS,这时出现的问题是:
(1)IIS中的 ASP.NET标签页打不开.说内存为只读。
(2)用VS.NET建ASP.NET项目时,说本机的WEB服务器运行的ASP.NET不是1.1版本。


--------------------------------------------------------------------------------


解决办法:


重新注册iis


运行C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_regiis.exe -i


不要把iis和ip绑定,写个批处理文件  每次出现问题就执行一次。


(cd\
    C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_regiis.exe -i
    pause) 


--------------------------------------------------------------------------------


相关说明:


在单个计算机上管理 ASP.NET 的多个版本的安装和卸载的管理实用工具(1.1.4322.0)
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
用法:
    aspnet_regiis.exe [-i[r] [-enable] | -u[a] | -r | -s[n] <path> | -k[n] <path
> | -lv | -lk | -c | -e[a] | -?]


 -i         - 安装 ASP.NET 的此版本,并更新 IIS 元数据库根处的
              脚本映射和根以下的所有
              脚本映射。现有的低版本脚本映射
              升级到此版本。
 -ir        - 安装 ASP.NET 的此版本,仅注册。不
              更新 IIS 中的脚本映射。
 -enable    - 带 -i 或 -ir 指定 -enable 时,还将
              在 IIS 安全控制台(IIS 6.0 或更高版本)中启用 ASP.NET。
 -s <path>  - 在指定的路径以递归方式安装此版本
              的脚本映射。现有的低版本脚本映射
              升级到此版本。
              例如 aspnet_regiis.exe -s W3SVC/1/ROOT/SampleApp1
 -sn <path> - 在指定的路径以非递归方式安装此版本的
              脚本映射。现有的低版本脚本映射
              升级到此版本。
 -r         - 为 IIS 元数据库根位置的此版本
              以及根以下的所有脚本映射安装脚本映射。不论当前版本是什么,
              所有现有的脚本映射都
              更改为此版本。
 -u         - 卸载 ASP.NET 的此版本。到此版本的
              现有脚本映射重新映射到此计算机上安装的
              其余的最高 ASP.NET 版本。
 -ua        - 卸载计算机上的所有 ASP.NET 版本
 -k <path>  - 从指定的路径中以递归方式移除到任何 ASP.NET 版本的所有
              脚本映射。
              例如 aspnet_regiis.exe -k W3SVC/1/ROOT/SampleApp1
 -kn <path> - 从指定的路径中以非递归方式移除到任何 ASP.NET 版本的所有
              脚本映射。
 -lv        - 列出计算机上安装的所有
              ASP.NET 版本(包括状态和安装路径)。
              Status: Valid[ (Root)]|Invalid
 -lk        - 列出包含 ASP.NET 脚本映射的所有 IIS 元数据库项的所有路径
              (连同版本一起)。不显示从父项
              继承 ASP.NET 脚本映射的项。
 -c         - 将客户端脚本的此版本安装到
              每个 IIS 站点目录的 aspnet_client 子目录中。
 -e         - 从每个 IIS 站点目录的 aspnet_client 子目录中
              移除客户端脚本的此版本。
 -ea        - 从每个 IIS 站点目录的 aspnet_client 子目录中
              移除客户端脚本的所有版本。
 -?         - 打印此帮助文本。



1、通配符的使用


 在ADO。NET中允许使用通配符进行数据查询。如下面语句查询表中EmployeeID以A开头的所有数据
                Select EmployeeID,EmployName,Tel,Salary .....where EmployeeID='A%';


    ADO。NET允许在字符串的开头或结尾使用%或*通配符。如下面语句查询表中所有单号尾为S的单据
 Select productcode,productname,productsum,productprice ........where ordercode='%S'


    ADO.NET不允许使用单独的符号如"?","-"等


2、分隔符的使用
  a.引号
  要注意ADO.NET中单引号的使用,例如在查询搜索用户姓名时,用户可能会查找姓名为K'Leey的数据,此时,数据查询语句将变为
  name='K'Leey'
  在查询时,出现单引号时,应将之替换为两个单引号,即name='K''Leey',我们在实际操作中,当数据查询语句中出现单引号时,可以使用String类的Replace方法进行替换将“'”换成“''”,如
  condition = "name='"+tempname.Replace("'","''")+"'"


    b.日期
  可以使用#符号来处理ADO.NET中涉及日期格式的查询,如下示例
  condition = "endDate<#2005/09/07# and endDate>#2005/08/07#"
 
   c.列分隔符
  当数据表中的某列由于某些原因含有列分隔符时,如sale order,可以使用[]将此列区分开来,如下示例
  condition = "[sale order] = S845647"
   此种情况下,如果数据列中已含有列分隔符做为列名的一部分时sale] order[,需要在列分隔符结束部分]前加入\符号,即
  condition = "[sale\] order[] = S845647"
  如果在C#中来处理这种ADO.NET操作时,需要注意符号转义问题,示例将变为
  condition = "[sale\\] order[] = S845647"



C#设计模式之简单工厂篇

| 1 Comment

案例】公司准备开发一套产品订单系统,客户强烈要求该系统能适应不同的数据库,即能让客户十分方便的决定到底是用SqlServer数据库还是Oracle数据库,或者其它数据库,而且数据库切换应该简单,决不能让客户麻烦得手忙脚乱。



遇到这种情况,最愚蠢的办法就是开发不同数据库版本的系统,即一套SqlServer版的订单系统,一套Oracle版的订单系统,但要真是这样干的话,我相信项目经理一定会获得千古蠢名。  



“具体情况具体分析”,此时如果设计模式运用得恰到好处,省时、省力的高效软件工程就会立马出炉,且看我如何过招。



UML图如下:



首先定义一个接口,具体名为Idatabase,在这个接口中,定义好数据库操作的方法名和参数,以及返回值,本案例中我定义如下方法:



public interface IDatabase

    bool Connect(string ConnectString); 
    bool Open(); 
    bool Command(string SQL); 
    void Close();
}



重要提醒:“接口一生唯谨慎,定义大事不糊涂”,编写接口时一定要考虑周全,并对参数、返回值进行反复推敲,为什么?因为所有的实现类都是要根据该接口的规范进行代码具体编写,也即接口的定义是公用的,一旦改动了接口,后果就是所有的实现类也都必须相应调整。  



然后就是编写具体的实现类了,客户要求多少不同类型的数据库,你就定义多少个Idatabase的实现类,虽然工作量大了点,可当你看到客户满意的笑容时,你心里也就会有一种由衷的幸福感,好了,SqlServer实现类代码如下:



public class SqlServer : IDatabase 
   
        SqlConnection conn; 
        SqlCommand command;  



        public bool Connect(string ConnectString) 
       
            try 
           
                conn = new SqlConnection(ConnectString); 
                return true
           
            catch(SqlException) 
           
                return false
                        
        }






        public bool Open() 
       
            try 
           
                conn.Open(); 
                return true
           
            catch(SqlException) 
            
                return false
           
         



        public bool Command(string SQL) 
       
            try 
           
                command = new SqlCommand(SQL,conn); 
                command.ExecuteNonQuery(); 
                return true
           
            catch(SqlException) 
           
                return false
           
         



        public void Close() 
       
            conn.Close(); 
            conn.Dispose(); 
       
     



呵呵,有点长,咬着牙读完,心里明白了就会很舒服的,如果你现在有这种感觉了,好,再接再厉,再为Oracle实现类编写具体代码吧,依葫芦画瓢,大家有空就画一下吧,我就画个雏形了:



public class Oracle : IDatabase 
   
        public Oracle() 
        {           
        }



        public bool Connect(string ConnectString) 
       
            return true
         



        public bool Open() 
       
            return true
         



        public bool Command(string SQL) 
       
            return true
         



        public void Close() 
         



       
    }






嗯,不错,你有多少种数据库就编写不同的实现类代码吧,这里就不赘述了,接下来呢?聪明的读者一定会想到这个问题:这个接口和这么多的实现类怎么用啊?我们再定义一个称之为工厂的类,由它来决定选用哪种数据库为进行操作,这个类比较简单:



public class Factory 
   
        public static IDatabase SelectDatabase(string DatabaseType) 
       
            switch(DatabaseType) 
           
                case "SqlServer": 
                    return new SqlServer();                   
                case "Oracle": 
                    return new Oracle(); 
                default
                    return new SqlServer(); 
           
       
    }

c#调用API显示内部局域网内的主机

 

//写这个程序,为了是显示局域网内的主机,然后通过用户点击选中的机器,检查是否该主机上安装有SQLSERVER,是为一个配置程序所写的
//这里提供CODE,给有需要的朋友


//设置DLL必须使用该命名空间
using System.Runtime.InteropServices;


  #region 动态链接库定义
  [DllImport("mpr.dll",CharSet=CharSet.Auto)] private static extern int WNetEnumResource(IntPtr hEnum, ref int lpcCount,IntPtr lpBuffer, ref int lpBufferSize );
  [DllImport("mpr.dll",CharSet=CharSet.Auto)] private static extern int WNetOpenEnum(RESOURCE_SCOPE dwScope,RESOURCE_TYPE dwType,RESOURCE_USAGE dwUsage,[MarshalAs(UnmanagedType.AsAny)][In] Object lpNetResource,out IntPtr lphEnum);
  [DllImport("mpr.dll",CharSet=CharSet.Auto)] private static extern int WNetCloseEnum( IntPtr hEnum );
  #endregion


  #region 定义枚举常量
  public enum RESOURCE_SCOPE
  {
   RESOURCE_CONNECTED = 0x00000001,
   RESOURCE_GLOBALNET = 0x00000002,
   RESOURCE_REMEMBERED = 0x00000003,
   RESOURCE_RECENT= 0x00000004,
   RESOURCE_CONTEXT= 0x00000005
  }


  public enum RESOURCE_TYPE
  {
   RESOURCETYPE_ANY= 0x00000000,
   RESOURCETYPE_DISK= 0x00000001,
   RESOURCETYPE_PRINT = 0x00000002,
   RESOURCETYPE_RESERVED = 0x00000008,
  }


  public enum RESOURCE_USAGE
  {
   RESOURCEUSAGE_CONNECTABLE =0x00000001,
   RESOURCEUSAGE_CONTAINER=0x00000002,
   RESOURCEUSAGE_NOLOCALDEVICE =0x00000004,
   RESOURCEUSAGE_SIBLING=0x00000008,
   RESOURCEUSAGE_ATTACHED=0x00000010,
   RESOURCEUSAGE_ALL =(RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
  }


  public enum RESOURCE_DISPLAYTYPE
  {
   RESOURCEDISPLAYTYPE_GENERIC= 0x00000000,
   RESOURCEDISPLAYTYPE_DOMAIN= 0x00000001,
   RESOURCEDISPLAYTYPE_SERVER= 0x00000002,


   RESOURCEDISPLAYTYPE_SHARE= 0x00000003,
   RESOURCEDISPLAYTYPE_FILE = 0x00000004,
   RESOURCEDISPLAYTYPE_GROUP= 0x00000005,
   RESOURCEDISPLAYTYPE_NETWORK= 0x00000006,
   RESOURCEDISPLAYTYPE_ROOT = 0x00000007,
   RESOURCEDISPLAYTYPE_SHAREADMIN = 0x00000008,
   RESOURCEDISPLAYTYPE_DIRECTORY = 0x00000009,
   RESOURCEDISPLAYTYPE_TREE = 0x0000000A,
   RESOURCEDISPLAYTYPE_NDSCONTAINER = 0x0000000B
  }


  public struct NETRESOURCE
  {
   public RESOURCE_SCOPE dwScope;
   public RESOURCE_TYPE dwType;
   public RESOURCE_DISPLAYTYPE dwDisplayType;
   public RESOURCE_USAGE dwUsage;
   [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] public string lpLocalName;
   [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] public string lpRemoteName;
   [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] public string lpComment;
   [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] public string lpProvider;
  }


  public enum NERR
  {
   NERR_Success = 0,/* Success */
   ERROR_MORE_DATA = 234, // dderror
   ERROR_NO_BROWSER_SERVERS_FOUND = 6118,
   ERROR_INVALID_LEVEL = 124,
   ERROR_ACCESS_DENIED = 5,
   ERROR_INVALID_PARAMETER = 87,
   ERROR_NOT_ENOUGH_MEMORY = 8,
   ERROR_NETWORK_BUSY = 54,
   ERROR_BAD_NETPATH = 53,
   ERROR_NO_NETWORK = 1222,
   ERROR_INVALID_HANDLE_STATE = 1609,
   ERROR_EXTENDED_ERROR= 1208
  }
  #endregion



  #region 开始列举网络函数
  public TreeNode pNode1;
  public TreeNode pNode2;
  public TreeNode pNode3;
  private void WNETOE(Object o)
  {
   int iRet;
   IntPtr ptrHandle = new IntPtr();
   try
   {
    iRet =WNetOpenEnum(RESOURCE_SCOPE.RESOURCE_GLOBALNET,RESOURCE_TYPE.RESOURCETYPE_ANY,RESOURCE_USAGE.RESOURCEUSAGE_ALL,o,out ptrHandle );
    if( iRet != 0 )return;


    int entries;
    int buffer = 16384;
    IntPtr ptrBuffer = Marshal.AllocHGlobal(buffer);
    NETRESOURCE nr;
    
    for(;;)
    {
     entries = -1;
     buffer = 16384;
     iRet =WNetEnumResource(ptrHandle,ref entries,ptrBuffer,ref buffer);
     if((iRet != 0) || (entries<1))break;


     Int32 ptr = ptrBuffer.ToInt32();
     for(int i=0;i<entries;i++ )
     {
      nr = (NETRESOURCE)Marshal.PtrToStructure( new IntPtr(ptr), typeof(NETRESOURCE) );
      if(RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER == (nr.dwUsage & RESOURCE_USAGE.RESOURCEUSAGE_CONTAINER))
      {
       
       ptr += Marshal.SizeOf( nr );
       if(nr.dwDisplayType == RESOURCE_DISPLAYTYPE.RESOURCEDISPLAYTYPE_NETWORK)
       {
        pNode1 = tView.Nodes.Add(nr.lpRemoteName);//TreeView
        pNode1.ImageIndex = 0;
        pNode1.SelectedImageIndex = 0;
       }
       else if(nr.dwDisplayType == RESOURCE_DISPLAYTYPE.RESOURCEDISPLAYTYPE_DOMAIN)
       {
        pNode2 = pNode1.Nodes.Add(nr.lpRemoteName);
        pNode2.ImageIndex = 1;
        pNode2.SelectedImageIndex =1;
       }
       else if(nr.dwDisplayType == RESOURCE_DISPLAYTYPE.RESOURCEDISPLAYTYPE_SERVER)
       {
        string sPCName = nr.lpRemoteName;
        pNode3 = pNode2.Nodes.Add(sPCName.Substring(2,sPCName.Length-2));\\此处为去处机器名称前的两个"\\"字符
        pNode3.ImageIndex = 2;
        pNode3.SelectedImageIndex = 2;
       }


       WNETOE(nr);
      }
      
     }
    }
    Marshal.FreeHGlobal( ptrBuffer );
    iRet =WNetCloseEnum( ptrHandle );
   }
   catch(Exception ex)
   {
    MessageBox.Show("查找本地网络错误:"+ex.Message,"错误",MessageBoxButtons.OK,MessageBoxIcon.Error);
   }
  }


  #endregion


  //窗体LOAD事件中,使用函数
  private void frmDBConnConfig_Load(object sender, System.EventArgs e)
  {
   WNETOE(null);
  }


作者主页:itbaby.jss.cn


欢迎访问我的主页



C# 学习资源列表


名称:快速入门
地址:http://chs.gotdotnet.com/quickstart/
描述:本站点是微软.NET技术的快速入门网站,我们不必再安装.NET Framework中的快速入门示例程序,直接在网上查看此示例即看。


名称:微软官方.NET指导站点
地址:http://www.gotdotnet.com/
描述:上面的站点是本站的一个子站点,本站点提供微软.NET官方信息,并且有大量的用户源代码、控件下载,微软.NET开发组的人员也经常在此站点发表一些指导性文章。


名称:SourceForge
地址:http://www.sourceforge.net
描述:世界上最大的Open Source项目在线网站,上面已经有.NET的各种大型Open Source项目上千件,包括SharpDevelop、NDoc、Mono等都是在此站点发布最新源代码信息。


名称:CodeProject
地址:http://www.codeproject.com
描述:很多非官方的中小型示例源代及文章,相当全面,基本上我们想要的各种方面的资料都可以在此处查找。


名称:Fabrice's weblog
地址:http://dotnetweblogs.com/FMARGUERIE/Story/4139.aspx
描述:这是一个WebLog形式的在线日志网站,定期更新,包括.NET相关的工具、混淆器、反编译器等各种信息,十分值得收藏。


名称:
地址:http://www.aspalliance.com/aldotnet/examples/translate.aspx
描述:c#翻译为vb.net,提供一个文本框,将你的C#源代码贴进去,就可以帮你翻译成VB.NET语法。


名称:CSharpHelp
地址:http://www.csharphelp.com
描述: 专业的C#语言在线帮助网站,主要提供C#语言方面的技术文章。专业性很强。


名称:DotNet247
地址:http://www.dotnet247.com
描述:最好的索引网站,分别按照门类及命名空间的索引,也提供了Microsoft KB知识库。


名称:ASP.NET
地址:http://www.asp.net
描述:微软.NET webform的老巢,资料和实例代码都非常难得。


名称:微软.NET Winform
地址:http://www.windowsforms.net/
描述:微软.NET Winform的老巢。


名称:微软 KnowledgeBase
地址:http://support.microsoft.com/
描述:微软知识库,开发的时候遇到的怪问题,可能会在这里找到答案。


名称:MSDN
地址:http://msdn.microsoft.com/
描述:这个就不用多说了吧,虽然出了中文MSDN,但是资料还是不够全,英文的就什么都有了。


名称:HotScripts
地址:http://www.hotscripts.com/
描述:Welcome to HotScripts.com, the net’s largest PHP, CGI, Perl, javascript and ASP script collection and resource web portal. We currently have 24,004 scripts across 11 different programming languages and 1,240 categories, as well as links to books, articles, as well as programming tips and tutorials.


名称:ASPAlliance
地址:http://www.aspalliance.com/
描述:提供相当丰富的文章和示例代码,思路匮乏的时候可以找找思路


名称:CSDN文档中心
地址:http://dev.csdn.net/
描述:中文的,资料还算丰富,可以作为国内首选。


名称:DOTNET中华网
地址:http://www.aspxcn.com/
描述:2002-2003年的时候这个站点很不错的,不过现在好像管理不得力,有点疲软,资料更新也不过及时,论坛里人也不够热心了,因为希望它好起来,所以列出来。资料都比较老,不过有些D版的东西还可以。提供很多学习代码。


名称:中国DotNet俱乐部
地址:http://www.chinaspx.com/
描述:有点公司背景的网站,很健壮,资料更新及时,比较丰富。论坛解答也不错。


名称:【孟宪会之精彩世界】
地址:http://dotnet.aspx.cc/
描述:MS-MVP的个人站点,包括了他所有的经验文章,还是很值得一看的。


名称:dotNET Tools.org
地址:http://www.dotnettools.org
描述:ccboy,也就是CSDN的小气的神的站点,里面有很多关于.NET等的好东东。


名称:博客堂
地址:http://blog.joycode.com/
描述:半官方性质的MS-MVP汇集blog,大家可以在这里接触到最新的技术,了解发展趋势,对技术的探索等等,优秀的文章。


名称:DotNetBips.com - Applying .NET
地址:http://www.dotnetbips.com/
描述:该站点的文章,涉及到了整个.NET,从底层的IL到语言到架构,文章很多,质量还不错。


名称:C# Frequently Asked Questions
地址:http://blogs.msdn.com/csharpfaq/
描述:The C# team posts answers to common questions


名称:正则表达式
地址:http://www.regexplib.com/
描述:  正则表达式学习站点


名称:WINDOW formS FAQ
地址:http://www.syncfusion.com/FAQ/Winforms/
描述:常见的forms faq问题,很多问题都可以在这里找到答案。


名称:ASP.NET 常用类库说明
地址:http://www.123aspx.com/rotor/default.aspx
描述:不用多说,看标题就知道是关于asp.net的名称空间的


名称:ASP.NET System.Web.Mail
地址:http://www.systemwebmail.com/faq/3.8.aspx
描述:邮件发送常见问题解决方法


名称:VB.NET & C# 比较
地址:http://www.harding.edu/USER/fmccown/WWW/vbnet_csharp_comparison.html
描述:VB.NET跟C#语法区别


名称:VB.NET架构师 BLOG
地址:http://panopticoncentral.net/
描述:不用多说,想了解VB.NET的朋友不可不去的站点(PS,不知道我有没有记错是不是这个地址)


名称:索克论坛
地址:http://www.sorke.com/bbs/Boards.asp
描述:我想应该是国内最好的第三方.NET控件的下载基地



 

作者: John Lau
http://spaces.msn.com/members/yollelaw


在开发多语言界面的Windows应用程序,经常会用到下面几句代码:


Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
ResourceManager rsMng =  new ResourceManager("MyForm.MessageResource", Assembly.GetExecutingAssembly());
string text = rsMng.GetString("test");


在一般的情况下程序都能正确地取出操作系统区域设置所对应的资源文本, 但有些情况下就另当别论了.请看以下代码:


 public class Form1: System.Windows.Forms.Form
 {
 private ResourceManager rsMng;
 public Form1()
 {
  Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
  rsMng =  new ResourceManager("MyResource", 
   Assembly.GetExecutingAssembly());
  string text1 = rsMng.GetString("test");
  MessageBox.Show(this, text1);
 }
 ...
 private void myMethod()
 {
  string text2 = rsMng.GetString("test");
  MessageBox.Show(this, text2);
 }
 private void button1_Click(object sender, System.EventArgs e)
 {
  Thread thread = new Thread(new ThreadStart(this.myMethod));
  thread.Start();
 }
 ...
}


如果以上程序中正确包含英文和中文两个语言资源(英文为预设), 然后让程序在运行在英文操作系统中(区域设置为中国), 那么你将发现取出的text1和text2值并不一样,前者为中文,后者为英文.
产生这个问题的原因是因为使用了多线程.
Thread的CurrentUICulture属性是和界面相关的Culture,处理资源读取、标签本文,提示信息等内容,它的预设值与操作系统的界面语言相同。而CurrentCulture是和格式相关的Culture,处理日期格式,时间,货币,日历等内容, 它的预设内容与操作系统的区域设置相同。所以当程序运行在区域设置为中国的英文操作系统中时,CurrentUICulture预设置为en, CurrentCulture的预设值为zh-CN.
代码 Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; 只是把程序的启动线程(处理界面加载、更新显示等工作)的CurrentUICulture设为中文, 新增线程的CurrentUICulture属性仍将保持是英文的预设值。所以CurrentThread线程取出的文本会是中文(text1),而新线程thread处理中取出的文本却是英文(text2).
有两个办法可以解决以上问题:
1. 修改新线程的CurrentUICulture属性:
 Thread thread = new Thread(new ThreadStart(this.myMethod));
 thread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
 thread.Start();
2. 每次取资源文本时都指定文化区域:
 private uiCulture =  Thread.CurrentThread.CurrentCulture;  //定义全局的区域变量


 rsMng.GetString("test", uiCulture);


经过以上的处理后,多语言界面程序就可以很准确地取出资源文本了。



在做一些网站(特别是BBS之类)时,经常会有充许用户输入html样式代码,却禁止脚本的运行的需求, 以达到丰富网页样式,禁止恶意代码的运行。
当然不能用 HtmlEncode 和 HtmlDecode 方法,因为这样连基本的html代码会被禁止掉。
我在网上搜索,也没有找到好的解决办法,倒是收集了一些脚本攻击的实例:
1. <script>标记中包含的代码
2. <a href="/oblog4/javascript:..."中的代码
3. 其它基本控件的 on...事件中的代码
4. iframe 和 frameset 中载入其它页面造成的攻击

有了这些资料后,事情就简单多了,写一个简单的方法,用正则表达式把以上符合几点的代码替换掉:
  public string wipeScript(string html)
  {
       System.Text.RegularExpressions.Regex regex1 = new System.Text.RegularExpressions.Regex(@"<script[\s\S]+</script *>",System.Text.RegularExpressions.RegexOptions.IgnoreCase);   
       System.Text.RegularExpressions.Regex regex2 = new System.Text.RegularExpressions.Regex(@" href *= *[\s\S]*script *:",System.Text.RegularExpressions.RegexOptions.IgnoreCase);   
       System.Text.RegularExpressions.Regex regex3 = new System.Text.RegularExpressions.Regex(@" on[\s\S]*=",System.Text.RegularExpressions.RegexOptions.IgnoreCase);   
       System.Text.RegularExpressions.Regex regex4 = new System.Text.RegularExpressions.Regex(@"<iframe[\s\S]+</iframe *>",System.Text.RegularExpressions.RegexOptions.IgnoreCase);   
       System.Text.RegularExpressions.Regex regex5 = new System.Text.RegularExpressions.Regex(@"<frameset[\s\S]+</frameset *>",System.Text.RegularExpressions.RegexOptions.IgnoreCase);   
       html = regex1.Replace(html, ""); 
//过滤<script></script>标记
       html = regex2.Replace(html, ""); 
//过滤href="/oblog4/javascript:" (<A>) 属性
       html = regex3.Replace(html, " _disibledevent="); 
//过滤其它控件的on...事件
       html = regex4.Replace(html, ""); //过滤iframe
       html = regex5.Replace(html, ""); //过滤frameset
       return html;
  }
此方法输入可能包含脚本的html代码,返回则就是干净的代码了。
我做过一些简单的测试,可以满中要求,只是还存在几个疑问:
以上考滤的情况是否比较完善, 还存在其它的脚本攻击手段吗?
是否会有其它更好的解决办法?


自己动手写屏保

屏保程序非常简单,它只是一个扩展名为“.scr”,全屏运行,并符合一定规则的应用程序,我们可以用任何语言来进行开发。
屏保程序放置在系统的System32目录下面,被系统自动调用执行。当系统执行屏保程序时,它会要求带上一个不同的命令参数,以实现不同的功能,如正常运行,预览,设置等。我们只要在程序中为不同参数实现不同的功能,我们的程序就可以被系统正常地调用了。以下列出全部的命令参数及其意义:


















命令行参数 意义详解
/s 正常运行屏保程序(到时间系统自动调用), 一般要求为接收到键盘事件时退出,屏蔽鼠标事件及其显示
/a 允许在用户结束屏保运行时,显示一个密码输入对话框(WIN98需要自已实现对话框,WIN2000及以后可自动返回锁定时的登录对话框,此参数可以不必使用)
/c 在屏保选取配置时,显示本屏保程序的参数设置对话框
/p 在屏保选取配置时,预览屏保程序,一般要求为可接收键盘或鼠标事件后返返

在C#中我们可以这样实现:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

public class MyScreenSaver : System.Windows.Forms.Form
{
 public static void Main(string arg)
 {
  Application.Run(new MyScreenSaver(arg)); //把参数传到Form中去
 }

 public MyScreenSaver(string arg)
 {
  //实现全屏显示
  this.Width = Screen.PrimaryScreen.Bounds.Width; 
  this.Height = Screen.PrimaryScreen.Bounds.Height;
  this.Left = 0;
  this.Top = 0;
  switch(arg.ToLower())
  {
   case "/s":
    /*...正常运行实现代码*/
    break;
   case "/a":
    /*...密码对话框实现代码*/
    break;
   case "/c":
    /*...参数设置实现代码*/
    break;
   case "/p":
    /*...预览实现代码*/
    break;
   default:
    Application.Exit(); //其它情况退出
    break;
  }
  /*...其它代码*/
 }
 /*...它代码*/
}

程序成后,把生成的EXE扩展名改变“.scr”,再复制到System32下便可。然后你就可以在桌面-->“显示属性”-->“屏幕保护程序”去选取,并进行其它相关的操作(设置,预览等)了.

windows消息大全

       消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做TMsg,


它在Windows单元中是这样声明的:
type
      TMsg = packed record
      hwnd: HWND; / /窗口句柄
      message: UINT; / /消息常量标识符
      wParam: WPARAM ; // 32位消息的特定附加信息
      lParam: LPARAM ; // 32位消息的特定附加信息
      time: DWORD; / /消息创建时的时间
      pt: TPoint; / /消息创建时的鼠标位置
end;


消息中有什么?
       是否觉得一个消息记录中的信息像希腊语一样?如果是这样,那么看一看下面的解释:
hwnd 32位的窗口句柄。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。
       message 用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。lParam 通常是一个指向内存中数据的指针。由于W P a r a m、l P a r a m和P o i n t e r都是3 2位的, 因此,它们之间可以相互转换。


WM_NULL = $0000;
WM_CREATE = $0001;
应用程序创建一个窗口
WM_DESTROY = $0002;
一个窗口被销毁
WM_MOVE = $0003;
移动一个窗口
WM_SIZE = $0005;
改变一个窗口的大小
WM_ACTIVATE = $0006;
一个窗口被激活或失去激活状态;
WM_SETFOCUS = $0007;
获得焦点后
WM_KILLFOCUS = $0008;
失去焦点
WM_ENABLE = $000A;
改变enable状态
WM_SETREDRAW = $000B;
设置窗口是否能重画
WM_SETTEXT = $000C;
应用程序发送此消息来设置一个窗口的文本
WM_GETTEXT = $000D;
应用程序发送此消息来复制对应窗口的文本到缓冲区
WM_GETTEXTLENGTH = $000E;
得到与一个窗口有关的文本的长度(不包含空字符)
WM_PAINT = $000F;
要求一个窗口重画自己
WM_CLOSE = $0010;
当一个窗口或应用程序要关闭时发送一个信号
WM_QUERYENDSESSION = $0011;
当用户选择结束对话框或程序自己调用ExitWindows函数
WM_QUIT = $0012;
用来结束程序运行或当程序调用postquitmessage函数
WM_QUERYOPEN = $0013;
当用户窗口恢复以前的大小位置时,把此消息发送给某个图标
WM_ERASEBKGND = $0014;
当窗口背景必须被擦除时(例在窗口改变大小时)
WM_SYSCOLORCHANGE = $0015;
当系统颜色改变时,发送此消息给所有顶级窗口
WM_ENDSESSION = $0016;
当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,
通知它对话是否结束
WM_SYSTEMERROR = $0017;
WM_SHOWWINDOW = $0018;
当隐藏或显示窗口是发送此消息给这个窗口
WM_ACTIVATEAPP = $001C;
发此消息给应用程序哪个窗口是激活的,哪个是非激活的;
WM_FONTCHANGE = $001D;
当系统的字体资源库变化时发送此消息给所有顶级窗口
WM_TIMECHANGE = $001E;
当系统的时间变化时发送此消息给所有顶级窗口
WM_CANCELMODE = $001F;
发送此消息来取消某种正在进行的摸态(操作)
WM_SETCURSOR = $0020;
如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口
WM_MOUSEACTIVATE = $0021;
当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给当前窗口
WM_CHILDACTIVATE = $0022;
发送此消息给MDI子窗口当用户点击此窗口的标题栏,或当窗口被激活,移动,改变大小
WM_QUEUESYNC = $0023;
此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序
分离出用户输入消息
WM_GETMINMAXINFO = $0024;
此消息发送给窗口当它将要改变大小或位置;
WM_PAINTICON = $0026;
发送给最小化窗口当它图标将要被重画
WM_ICONERASEBKGND = $0027;
此消息发送给某个最小化窗口,仅当它在画图标前它的背景必须被重画
WM_NEXTDLGCTL = $0028;
发送此消息给一个对话框程序去更改焦点位置
WM_SPOOLERSTATUS = $002A;
每当打印管理列队增加或减少一条作业时发出此消息
WM_DRAWITEM = $002B;
当button,combobox,listbox,menu的可视外观改变时发送
此消息给这些空件的所有者
WM_MEASUREITEM = $002C;
当button, combo box, list box, list view control, or menu item 被创建时
发送此消息给控件的所有者
WM_DELETEITEM = $002D;
当the list box 或 combo box 被销毁 或 当 某些项被删除通过LB_DELETESTRING, LB_RESETCONTENT, CB_DELETESTRING, or CB_RESETCONTENT 消息
WM_VKEYTOITEM = $002E;
此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息
WM_CHARTOITEM = $002F;
此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息
WM_SETFONT = $0030;
当绘制文本时程序发送此消息得到控件要用的颜色
WM_GETFONT = $0031;
应用程序发送此消息得到当前控件绘制文本的字体
WM_SETHOTKEY = $0032;
应用程序发送此消息让一个窗口与一个热键相关连
WM_GETHOTKEY = $0033;
应用程序发送此消息来判断热键与某个窗口是否有关联
WM_QUERYDRAGICON = $0037;
此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序能返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标
WM_COMPAREITEM = $0039;
发送此消息来判定combobox或listbox新增加的项的相对位置
WM_GETOBJECT = $003D;
WM_COMPACTING = $0041;
显示内存已经很少了
WM_WINDOWPOSCHANGING = $0046;
发送此消息给那个窗口的大小和位置将要被改变时,来调用setwindowpos函数或其它窗口管理函数
WM_WINDOWPOSCHANGED = $0047;
发送此消息给那个窗口的大小和位置已经被改变时,来调用setwindowpos函数或其它窗口管理函数
WM_POWER = $0048;(适用于16位的windows)
当系统将要进入暂停状态时发送此消息
WM_COPYDATA = $004A;
当一个应用程序传递数据给另一个应用程序时发送此消息
WM_CANCELJOURNAL = $004B;
当某个用户取消程序日志激活状态,提交此消息给程序
WM_NOTIFY = $004E;
当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口
WM_INPUTLANGCHANGEREQUEST = $0050;
当用户选择某种输入语言,或输入语言的热键改变
WM_INPUTLANGCHANGE = $0051;
当平台现场已经被改变后发送此消息给受影响的最顶级窗口
WM_TCARD = $0052;
当程序已经初始化windows帮助例程时发送此消息给应用程序
WM_HELP = $0053;
此消息显示用户按下了F1,如果某个菜单是激活的,就发送此消息个此窗口关联的菜单,否则就
发送给有焦点的窗口,如果当前都没有焦点,就把此消息发送给当前激活的窗口
WM_USERCHANGED = $0054;
当用户已经登入或退出后发送此消息给所有的窗口,当用户登入或退出时系统更新用户的具体
设置信息,在用户更新设置时系统马上发送此消息;
WM_NOTIFYFORMAT = $0055;
公用控件,自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构
在WM_NOTIFY消息,使用此控件能使某个控件与它的父控件之间进行相互通信
WM_CONTEXTMENU = $007B;
当用户某个窗口中点击了一下右键就发送此消息给这个窗口
WM_STYLECHANGING = $007C;
当调用SETWINDOWLONG函数将要改变一个或多个 窗口的风格时发送此消息给那个窗口
WM_STYLECHANGED = $007D;
当调用SETWINDOWLONG函数一个或多个 窗口的风格后发送此消息给那个窗口
WM_DISPLAYCHANGE = $007E;
当显示器的分辨率改变后发送此消息给所有的窗口
WM_GETICON = $007F;
此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄;
WM_SETICON = $0080;
程序发送此消息让一个新的大图标或小图标与某个窗口关联;
WM_NCCREATE = $0081;
当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送;
WM_NCDESTROY = $0082;
此消息通知某个窗口,非客户区正在销毁
WM_NCCALCSIZE = $0083;
当某个窗口的客户区域必须被核算时发送此消息
WM_NCHITTEST = $0084;//移动鼠标,按住或释放鼠标时发生
WM_NCPAINT = $0085;
程序发送此消息给某个窗口当它(窗口)的框架必须被绘制时;
WM_NCACTIVATE = $0086;
此消息发送给某个窗口 仅当它的非客户区需要被改变来显示是激活还是非激活状态;
WM_GETDLGCODE = $0087;
发送此消息给某个与对话框程序关联的控件,widdows控制方位键和TAB键使输入进入此控件
通过响应WM_GETDLGCODE消息,应用程序可以把他当成一个特殊的输入控件并能处理它
WM_NCMOUSEMOVE = $00A0;
当光标在一个窗口的非客户区内移动时发送此消息给这个窗口 //非客户区为:窗体的标题栏及窗
的边框体
WM_NCLBUTTONDOWN = $00A1;
当光标在一个窗口的非客户区同时按下鼠标左键时提交此消息
WM_NCLBUTTONUP = $00A2;
当用户释放鼠标左键同时光标某个窗口在非客户区十发送此消息;
WM_NCLBUTTONDBLCLK = $00A3;
当用户双击鼠标左键同时光标某个窗口在非客户区十发送此消息
WM_NCRBUTTONDOWN = $00A4;
当用户按下鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONUP = $00A5;
当用户释放鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONDBLCLK = $00A6;
当用户双击鼠标右键同时光标某个窗口在非客户区十发送此消息
WM_NCMBUTTONDOWN = $00A7;
当用户按下鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONUP = $00A8;
当用户释放鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONDBLCLK = $00A9;
当用户双击鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_KEYFIRST = $0100;
WM_KEYDOWN = $0100;
//按下一个键
WM_KEYUP = $0101;
//释放一个键
WM_CHAR = $0102;
//按下某键,并已发出WM_KEYDOWN, WM_KEYUP消息
WM_DEADCHAR = $0103;
当用translatemessage函数翻译WM_KEYUP消息时发送此消息给拥有焦点的窗口
WM_SYSKEYDOWN = $0104;
当用户按住ALT键同时按下其它键时提交此消息给拥有焦点的窗口;
WM_SYSKEYUP = $0105;
当用户释放一个键同时ALT 键还按着时提交此消息给拥有焦点的窗口
WM_SYSCHAR = $0106;
当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后提交此消息给拥有焦点的窗口
WM_SYSDEADCHAR = $0107;
当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后发送此消息给拥有焦点的窗口
WM_KEYLAST = $0108;
WM_INITDIALOG = $0110;
在一个对话框程序被显示前发送此消息给它,通常用此消息初始化控件和执行其它任务
WM_COMMAND = $0111;
当用户选择一条菜单命令项或当某个控件发送一条消息给它的父窗口,一个快捷键被翻译
WM_SYSCOMMAND = $0112;
当用户选择窗口菜单的一条命令或当用户选择最大化或最小化时那个窗口会收到此消息
WM_TIMER = $0113; //发生了定时器事件
WM_HSCROLL = $0114;
当一个窗口标准水平滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
WM_VSCROLL = $0115;
当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口也,发送给拥有它的控件 WM_INITMENU = $0116;
当一个菜单将要被激活时发送此消息,它发生在用户菜单条中的某项或按下某个菜单键,它允许程序在显示前更改菜单
WM_INITMENUPOPUP = $0117;
当一个下拉菜单或子菜单将要被激活时发送此消息,它允许程序在它显示前更改菜单,而不要改变全部
WM_MENUSELECT = $011F;
当用户选择一条菜单项时发送此消息给菜单的所有者(一般是窗口)
WM_MENUCHAR = $0120;
当菜单已被激活用户按下了某个键(不同于加速键),发送此消息给菜单的所有者;
WM_ENTERIDLE = $0121;
当一个模态对话框或菜单进入空载状态时发送此消息给它的所有者,一个模态对话框或菜单进入空载状态就是在处理完一条或几条先前的消息后没有消息它的列队中等待
WM_MENURBUTTONUP = $0122;
WM_MENUDRAG = $0123;
WM_MENUGETOBJECT = $0124;
WM_UNINITMENUPOPUP = $0125;
WM_MENUCOMMAND = $0126;
WM_CHANGEUISTATE = $0127;
WM_UPDATEUISTATE = $0128;
WM_QUERYUISTATE = $0129;
WM_CTLCOLORMSGBOX = $0132;
在windows绘制消息框前发送此消息给消息框的所有者窗口,通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色


WM_CTLCOLOREDIT = $0133;
当一个编辑型控件将要被绘制时发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置编辑框的文本和背景颜色
WM_CTLCOLORLISTBOX = $0134;
当一个列表框控件将要被绘制前发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置列表框的文本和背景颜色
WM_CTLCOLORBTN = $0135;
当一个按钮控件将要被绘制时发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置按纽的文本和背景颜色
WM_CTLCOLORDLG = $0136;
当一个对话框控件将要被绘制前发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置对话框的文本背景颜色
WM_CTLCOLORSCROLLBAR= $0137;
当一个滚动条控件将要被绘制时发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置滚动条的背景颜色
WM_CTLCOLORSTATIC = $0138;
当一个静态控件将要被绘制时发送此消息给它的父窗口;通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置静态控件的文本和背景颜色
WM_MOUSEFIRST = $0200;
WM_MOUSEMOVE = $0200;
// 移动鼠标
WM_LBUTTONDOWN = $0201;
//按下鼠标左键
WM_LBUTTONUP = $0202;
//释放鼠标左键
WM_LBUTTONDBLCLK = $0203;
//双击鼠标左键
WM_RBUTTONDOWN = $0204;
//按下鼠标右键
WM_RBUTTONUP = $0205;
//释放鼠标右键
WM_RBUTTONDBLCLK = $0206;
//双击鼠标右键
WM_MBUTTONDOWN = $0207;
//按下鼠标中键
WM_MBUTTONUP = $0208;
//释放鼠标中键
WM_MBUTTONDBLCLK = $0209;
//双击鼠标中键
WM_MOUSEWHEEL = $020A;
当鼠标轮子转动时发送此消息个当前有焦点的控件
WM_MOUSELAST = $020A;
WM_PARENTNOTIFY = $0210;
当MDI子窗口被创建或被销毁,或用户按了一下鼠标键而光标在子窗口上时发送此消息给它的父窗口
WM_ENTERMENULOOP = $0211;
发送此消息通知应用程序的主窗口that已经进入了菜单循环模式
WM_EXITMENULOOP = $0212;
发送此消息通知应用程序的主窗口that已退出了菜单循环模式
WM_NEXTMENU = $0213;
WM_SIZING = 532;
当用户正在调整窗口大小时发送此消息给窗口;通过此消息应用程序可以监视窗口大小和位置也可以修改他们
WM_CAPTURECHANGED = 533;
发送此消息 给窗口当它失去捕获的鼠标时;
WM_MOVING = 534;
当用户在移动窗口时发送此消息,通过此消息应用程序可以监视窗口大小和位置也可以修改他们;
WM_POWERBROADCAST = 536;
此消息发送给应用程序来通知它有关电源管理事件;
WM_DEVICECHANGE = 537;
当设备的硬件配置改变时发送此消息给应用程序或设备驱动程序
WM_IME_STARTCOMPOSITION = $010D;
WM_IME_ENDCOMPOSITION = $010E;
WM_IME_COMPOSITION = $010F;
WM_IME_KEYLAST = $010F;
WM_IME_SETCONTEXT = $0281;
WM_IME_NOTIFY = $0282;
WM_IME_CONTROL = $0283;
WM_IME_COMPOSITIONFULL = $0284;
WM_IME_SELECT = $0285;
WM_IME_CHAR = $0286;
WM_IME_REQUEST = $0288;
WM_IME_KEYDOWN = $0290;
WM_IME_KEYUP = $0291;
WM_MDICREATE = $0220;
应用程序发送此消息给多文档的客户窗口来创建一个MDI 子窗口
WM_MDIDESTROY = $0221;
应用程序发送此消息给多文档的客户窗口来关闭一个MDI 子窗口
WM_MDIACTIVATE = $0222;
应用程序发送此消息给多文档的客户窗口通知客户窗口激活另一个MDI子窗口,当客户窗口收到此消息后,它发出WM_MDIACTIVE消息给MDI子窗口(未激活)激活它;
WM_MDIRESTORE = $0223;
程序 发送此消息给MDI客户窗口让子窗口从最大最小化恢复到原来大小
WM_MDINEXT = $0224;
程序 发送此消息给MDI客户窗口激活下一个或前一个窗口
WM_MDIMAXIMIZE = $0225;
程序发送此消息给MDI客户窗口来最大化一个MDI子窗口;
WM_MDITILE = $0226;
程序 发送此消息给MDI客户窗口以平铺方式重新排列所有MDI子窗口
WM_MDICASCADE = $0227;
程序 发送此消息给MDI客户窗口以层叠方式重新排列所有MDI子窗口
WM_MDIICONARRANGE = $0228;
程序 发送此消息给MDI客户窗口重新排列所有最小化的MDI子窗口
WM_MDIGETACTIVE = $0229;
程序 发送此消息给MDI客户窗口来找到激活的子窗口的句柄
WM_MDISETMENU = $0230;
程序 发送此消息给MDI客户窗口用MDI菜单代替子窗口的菜单
WM_ENTERSIZEMOVE = $0231;
WM_EXITSIZEMOVE = $0232;
WM_DROPFILES = $0233;
WM_MDIREFRESHMENU = $0234;
WM_MOUSEHOVER = $02A1;
WM_MOUSELEAVE = $02A3;
WM_CUT = $0300;
程序发送此消息给一个编辑框或combobox来删除当前选择的文本
WM_COPY = $0301;
程序发送此消息给一个编辑框或combobox来复制当前选择的文本到剪贴板
WM_PASTE = $0302;
程序发送此消息给editcontrol或combobox从剪贴板中得到数据
WM_CLEAR = $0303;
程序发送此消息给editcontrol或combobox清除当前选择的内容;
WM_UNDO = $0304;
程序发送此消息给editcontrol或combobox撤消最后一次操作
WM_RENDERFORMAT = $0305;


WM_RENDERALLFORMATS = $0306;
WM_DESTROYCLIPBOARD = $0307;
当调用ENPTYCLIPBOARD函数时 发送此消息给剪贴板的所有者
WM_DRAWCLIPBOARD = $0308;
当剪贴板的内容变化时发送此消息给剪贴板观察链的第一个窗口;它允许用剪贴板观察窗口来
显示剪贴板的新内容;
WM_PAINTCLIPBOARD = $0309;
当剪贴板包含CF_OWNERDIPLAY格式的数据并且剪贴板观察窗口的客户区需要重画;
WM_VSCROLLCLIPBOARD = $030A;
WM_SIZECLIPBOARD = $030B;
当剪贴板包含CF_OWNERDIPLAY格式的数据并且剪贴板观察窗口的客户区域的大小已经改变是此消息通过剪贴板观察窗口发送给剪贴板的所有者;
WM_ASKCBFORMATNAME = $030C;
通过剪贴板观察窗口发送此消息给剪贴板的所有者来请求一个CF_OWNERDISPLAY格式的剪贴板的名字
WM_CHANGECBCHAIN = $030D;
当一个窗口从剪贴板观察链中移去时发送此消息给剪贴板观察链的第一个窗口;
WM_HSCROLLCLIPBOARD = $030E;
此消息通过一个剪贴板观察窗口发送给剪贴板的所有者 ;它发生在当剪贴板包含CFOWNERDISPALY格式的数据并且有个事件在剪贴板观察窗的水平滚动条上;所有者应滚动剪贴板图象并更新滚动条的值;
WM_QUERYNEWPALETTE = $030F;
此消息发送给将要收到焦点的窗口,此消息能使窗口在收到焦点时同时有机会实现他的逻辑调色板
WM_PALETTEISCHANGING= $0310;
当一个应用程序正要实现它的逻辑调色板时发此消息通知所有的应用程序
WM_PALETTECHANGED = $0311;
此消息在一个拥有焦点的窗口实现它的逻辑调色板后发送此消息给所有顶级并重叠的窗口,以此来改变系统调色板
WM_HOTKEY = $0312;
当用户按下由REGISTERHOTKEY函数注册的热键时提交此消息
WM_PRINT = 791;
应用程序发送此消息仅当WINDOWS或其它应用程序发出一个请求要求绘制一个应用程序的一部分;
WM_PRINTCLIENT = 792;
WM_HANDHELDFIRST = 856;
WM_HANDHELDLAST = 863;
WM_PENWINFIRST = $0380;
WM_PENWINLAST = $038F;
WM_COALESCE_FIRST = $0390;
WM_COALESCE_LAST = $039F;
WM_DDE_FIRST = $03E0;
WM_DDE_INITIATE = WM_DDE_FIRST + 0;
一个DDE客户程序提交此消息开始一个与服务器程序的会话来响应那个指定的程序和主题名;
WM_DDE_TERMINATE = WM_DDE_FIRST + 1;
一个DDE应用程序(无论是客户还是服务器)提交此消息来终止一个会话;
WM_DDE_ADVISE = WM_DDE_FIRST + 2;
一个DDE客户程序提交此消息给一个DDE服务程序来请求服务器每当数据项改变时更新它
WM_DDE_UNADVISE = WM_DDE_FIRST + 3;
一个DDE客户程序通过此消息通知一个DDE服务程序不更新指定的项或一个特殊的剪贴板格式的项
WM_DDE_ACK = WM_DDE_FIRST + 4;
此消息通知一个DDE(动态数据交换)程序已收到并正在处理WM_DDE_POKE, WM_DDE_EXECUTE, WM_DDE_DATA, WM_DDE_ADVISE, WM_DDE_UNADVISE, or WM_DDE_INITIAT消息
WM_DDE_DATA = WM_DDE_FIRST + 5;
一个DDE服务程序提交此消息给DDE客户程序来传递个一数据项给客户或通知客户的一条可用数据项
WM_DDE_REQUEST = WM_DDE_FIRST + 6;
一个DDE客户程序提交此消息给一个DDE服务程序来请求一个数据项的值;
WM_DDE_POKE = WM_DDE_FIRST + 7;
一个DDE客户程序提交此消息给一个DDE服务程序,客户使用此消息来请求服务器接收一个未经同意的数据项;服务器通过答复WM_DDE_ACK消息提示是否它接收这个数据项;
WM_DDE_EXECUTE = WM_DDE_FIRST + 8;
一个DDE客户程序提交此消息给一个DDE服务程序来发送一个字符串给服务器让它象串行命令一样被处理,服务器通过提交WM_DDE_ACK消息来作回应;
WM_DDE_LAST = WM_DDE_FIRST + 8;
WM_APP = $8000;
WM_USER = $0400;
此消息能帮助应用程序自定义私有消息;
/////////////////////////////////////////////////////////////////////
通知消息(Notification message)是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows 95公共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。
按扭
BN_CLICKED //用户单击了按钮
BN_DISABLE //按钮被禁止
BN_DOUBLECLICKED //用户双击了按钮
BN_HILITE //用户加亮了按钮
BN_PAINT按钮应当重画
BN_UNHILITE加亮应当去掉
组合框
CBN_CLOSEUP组合框的列表框被关闭
CBN_DBLCLK用户双击了一个字符串
CBN_DROPDOWN组合框的列表框被拉出
CBN_EDITCHANGE用户修改了编辑框中的文本
CBN_EDITUPDATE编辑框内的文本即将更新
CBN_ERRSPACE组合框内存不足
CBN_KILLFOCUS组合框失去输入焦点
CBN_SELCHANGE在组合框中选择了一项
CBN_SELENDCANCEL用户的选择应当被取消
CBN_SELENDOK用户的选择是合法的
CBN_SETFOCUS组合框获得输入焦点
编辑框
EN_CHANGE编辑框中的文本己更新
EN_ERRSPACE编辑框内存不足
EN_HSCROLL用户点击了水平滚动条
EN_KILLFOCUS编辑框正在失去输入焦点
EN_MAXTEXT插入的内容被截断
EN_SETFOCUS编辑框获得输入焦点
EN_UPDATE编辑框中的文本将要更新
EN_VSCROLL用户点击了垂直滚动条消息含义
列表框
LBN_DBLCLK用户双击了一项
LBN_ERRSPACE列表框内存不够
LBN_KILLFOCUS列表框正在失去输入焦点
LBN_SELCANCEL选择被取消
LBN_SELCHANGE选择了另一项
LBN_SETFOCUS列表框获得输入焦点

前言
我的第三篇文章,关于恢复的文章终于出来了,由于最近比较忙,都是断断续续写出来的,所以就费了不少的时间。
我的意图是把与恢复有关的东西整理一下,当然,这篇文章并还没有包含所有与恢复有关的知识,仅仅是常用,如果大家还有什么好的案例,可以贴出来,大家一起讨论。
因为时间仓促,文章中肯定还有很多失误之处,望大家指正。
此文已在chinese oracle user group and itput发表
最后声明,次文章不经许可,不得转载。


第一章. 理解什么是数据库恢复
当我们使用一个数据库时,总希望数据库的内容是可靠的、正确的,但由于计算机系统的故障(硬件故障、软件故障、网络故障、进程故障和系统故障)影响数据库系统的操作,影响数据库中数据的正确性,甚至破坏数据库,使数据库中全部或部分数据丢失。因此当发生上述故障后,希望能重构这个完整的数据库,该处理称为数据库恢复。恢复过程大致可以分为复原(Restore)与恢复(Restore)过程。
数据库恢复可以分为以下两类:
1.1实例故障的一致性恢复
当实例意外地(如掉电、后台进程故障等)或预料地(发出SHUTDOUM ABORT语句)中止时出现实例故障,此时需要实例恢复。实例恢复将数据库恢复到故障之前的事务一致状态。如果在在线后备发现实例故障,则需介质恢复。在其它情况ORACLE在下次数据库起动时(对新实例装配和打开),自动地执行实例恢复。如果需要,从装配状态变为打开状态,自动地激发实例恢复,由下列处理:
(1) 为了解恢复数据文件中没有记录的数据,进行向前滚。该数据记录在在线日志,包括对回滚段的内容恢复。
(2) 回滚未提交的事务,按步1重新生成回滚段所指定的操作。
(3) 释放在故障时正在处理事务所持有的资源。
(4) 解决在故障时正经历一阶段提交的任何悬而未决的分布事务。
1.2介质故障或文件错误的不一致恢复
介质故障是当一个文件、一个文件的部分或磁盘不能读或不能写时出现的故障。
文件错误一般指意外的错误导致文件被删除或意外事故导致文件的不一致。
这种状态下的数据库都是不一致的,需要DBA手工来进行数据库的恢复,这种恢复有两种形式,决定于数据库运行的归档方式和备份方式。
(1) 完全介质恢复可恢复全部丢失的修改。一般情况下需要有数据库的备份且数据库运行在归档状态下并且有可用归档日志时才可能。对于不同类型的错误,有不同类型的完全恢复可使用,其决定于毁坏文件和数据库的可用性。
(2) 不完全介质恢复是在完全介质恢复不可能或不要求时进行的介质恢复。重构受损的数据库,使其恢复介质故障前或用户出错之前的一个事务一致性状态。不完全介质恢复有不同类型的使用,决定于需要不完全介质恢复的情况,有下列类型:基于撤消、基于时间和基于修改的不完全恢复。
基于撤消(CANCEL)恢复:在某种情况,不完全介质恢复必须被控制,DBA可撤消在指定点的操作。基于撤消的恢复地在一个或多个日志组(在线的或归档的)已被介质故障所破坏,不能用于恢复过程时使用,所以介质恢复必须控制,以致在使用最近的、未损的日志组于数据文件后中止恢复操作。
基于时间(TIME)和基于修改(SCN)的恢复:如果DBA希望恢复到过去的某个指定点,是一种理想的不完全介质恢复,一般发生在恢复到某个特定操作之前,恢复到如意外删除某个数据表之前。


第二章. 数据库恢复案例测试环境
2.1 数据库环境
以下的所有案例都是通过测试经过,环境为:
OS:Windows 2000 Server
DB:Oracle 816
DBNAME:TEST
数据文件:
SQL> select file#,status,enabled,name from v$datafile;


FILE# STATUS ENABLED NAME
---------- ------- ---------- --------------------------------------------------------------------------------
1 SYSTEM READ WRITE D:\ORACLE\ORADATA\TEST\SYSTEM01.DBF
2 ONLINE READ WRITE D:\ORACLE\ORADATA\TEST\RBS01.DBF
3 ONLINE READ WRITE D:\ORACLE\ORADATA\TEST\USERS01.DBF
4 ONLINE READ WRITE D:\ORACLE\ORADATA\TEST\TEMP01.DBF
5 ONLINE READ WRITE D:\ORACLE\ORADATA\TEST\TOOLS01.DBF
6 ONLINE READ WRITE D:\ORACLE\ORADATA\TEST\INDX01.DBF
控制文件:
SQL> select * from v$controlfile;


STATUS NAME
------- --------------------------------------------------------------------------------
D:\ORACLE\ORADATA\TEST\CONTROL01.CTL
D:\ORACLE\ORADATA\TEST\CONTROL02.CTL
D:\ORACLE\ORADATA\TEST\CONTROL03.CTL
联机日志:
SQL> select * from v$logfile;


GROUP# STATUS MEMBER
---------- ------- --------------------------------------------------------------------------------
1 STALE D:\ORACLE\ORADATA\TEST\REDO01.LOG
2 D:\ORACLE\ORADATA\TEST\REDO02.LOG
3 STALE D:\ORACLE\ORADATA\TEST\REDO03.LOG
2.2 数据库备份脚本
冷备份脚本
rem script:coldbak.sql
rem creater:chenjiping
rem date:5.8.2003
rem descffline full backup database


--connect database
connect internal/password;
--shutdown database
shutdown immediate;
--Copy Data file
!xcopy d:\oracle\oradata\test\*.dbf d:\database/H/R;
--Copy Control file
!xcopy d:\oracle\oradata\test\*.ctl d:\database/H/R;
--Copy Log file
!xcopy d:\oracle\oradata\test\*.log d:\database/H/R;
--startup database
startup;


说明:
1、以上脚本在数据库关闭状态下备份数据库所有的数据文件,联机日志,控制文件(在一个目录下),如果成功备份,所有文件是一致的。
2、没有备份参数文件,参数文件可以另外备份,没有必要每次都备份,只需要在改变设置后备份一次。
3、如果以上命令没有成功依次执行,那么备份将是无效的,如连接数据库不成功,那么肯定关闭数据库也不成功,那么备份则无效
4、冷备份建议下人工干预下执行。


数据库OS热全备份脚本
rem script:hotbak.sql
rem creater:chenjiping
rem date:5.8.2003
rem desc:backup all database datafile in archive


--connect database
connect internal/password;


--archive
alter system archive log current;
--start


alter tablespace system begin backup;
!xcopy d:\oracle\oradata\test\system01.dbf d:\databak/H/R;
alter tablespace system end backup;


alter tablespace rbs begin backup;
!xcopy d:\oracle\oradata\test\rbs01.dbf d:\databak/H/R;
alter tablespace rbs end backup;


alter tablespace users begin backup;
!xcopy d:\oracle\oradata\test\users01.dbf d:\databak/H/R;
alter tablespace users end backup;


alter tablespace tools begin backup;
!xcopy d:\oracle\oradata\test\tools01.dbf d:\databak/H/R;
alter tablespace tools end backup;


alter tablespace indx begin backup;
!xcopy d:\oracle\oradata\test\indx01.dbf d:\databak/H/R;
alter tablespace indx end backup;
--end


--bak control file
--binary
alter database backup controlfile to 'd:\databak\controlbinbak.000';
--ascii
alter database backup controlfile to trace;


alter system archive log current;
说明:
1、热备份必须在数据库归档方式下才可以运行
2、以上脚本可以在数据库运行状态下备份数据库所有的数据文件(除了临时数据文件),没有必要备份联机日志。
3、归档日志至少需要一次完整备份之后的所有日志。
4、如果以上命令没有成功依次执行,那么备份也是无效的,如连接数据库不成功,那么备份则无效


RMAN备份只讲叙有恢复目录的情况,如果没有恢复目录,情形大致相似。以下是RMAN的热备份全备份的脚本:
# script:bakup.rcv
# creater:chenjiping
# date:5.8.2003
# desc:backup all database datafile in archive with rman


# connect database
connect rcvcat rman/rman@back;
connect target internal/virpure;


# start backup database
run{
allocate channel c1 type disk;
backup full tag 'dbfull' format 'd:\backup\full%u_%s_%p' database
include current controlfile;
sql 'alter system archive log current';
release channel c1;
}
# end


说明:
1、 数据库必须运行在归档模式下
2、 RMAN将自动备份数据文件,运行可靠
3、 归档日志另外备份处理,但至少需要保存一次备份来的日志
4、 没有必要用RMAN做冷备份,效果不好


以上举例说明了数据库的恢复案例的测试环境与部分备份测试脚本,其它的备份脚本可以根据以上脚本演变而来或在案例中加以说明。
数据库的自动实例将不加以说明,这里只举例说明媒体错误或人为错误造成的恢复可能。
以上包括以下案例都是在WINDOWS+ORACLE816上测试验证的,在不同的操作系统与不同的数据库版本中略有差别。

第三章. 了解与恢复相关的信息
1、理解报警日志文件
报警日志文件一般记载了数据库的启动/关闭信息,归档信息,备份信息,恢复信息,常见错误信息,部分数据库修改记录等。一般令名规则为<SID>Alrt.log或Alrt<SID>.log,如我的测试数据库的报警日志文件的名称为testalrt.log。
报警日志文件的路径是根据初始化参数background_dump_dest来决定的,如在我的机器上,该参数值为 D:\Oracle\admin\test\bdump,那么,你就可以在该路径下找到该文件
2、后台进程跟踪文件
后台进程跟踪文件的路径与报警日志文件的路径一致,在某些情况下,你可以通过后台跟踪文件的信息了解更多的需要恢复的信息。如在数据库需要恢复的时候,报警日志文件中常有这样的语句:
Errors in file D:\Oracle\admin\test\bdump\testDBW0.TRC:
ORA-01157: cannot identify/lock data file 1 - see DBWR trace file
通过提示的DBWR跟踪文件,可以查询到更详细的信息。
3、v$recover_file与v$recovery_log
这是两个动态性能视图,可以在mount下查看,通过这两个视图,你可以了解详细的需要恢复的数据文件与需要使用到的归档日志。

第四章. 数据库恢复案例
4.1非归档模式下的备份与恢复
备份方案:采用OS冷备份
1.连接数据库并创建测试表
SQL*Plus: Release 8.1.6.0.0 - Production on Tue May 6 13:46:32 2003
(c) Copyright 1999 Oracle Corporation. All rights reserved.
SQL> connect internal/password as sysdba;
Connected.
SQL> create table test(a int);
Table created
SQL> insert into test values(1);
1 row inserted
SQL> commit;
Commit complete


2.备份数据库
SQL> @coldbak.sql 或在DOS下 svrmgrl @coldbak.sql


3.再插入记录
SQL> insert into test values(2);
1 row inserted
SQL> commit;
Commit complete
SQL> select * from test;
A
---------------------------------------
1
2
4.关闭数据库
SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.


5.毁坏一个或多个数据文件,如删除user01.dbf
C:\>del D:\ORACLE\ORADATA\TEST\USERS01.DBF
模拟媒体毁坏


6.重新启动数据库,会发现如下错误
SQL> startup
ORACLE instance started.


Total System Global Area 102020364 bytes
Fixed Size 70924 bytes
Variable Size 85487616 bytes
Database Buffers 16384000 bytes
Redo Buffers 77824 bytes
Database mounted.
ORA-01157: cannot identify/lock data file 3 - see DBWR trace file
ORA-01110: data file 3: 'D:\ORACLE\ORADATA\TEST\USERS01.DBF'


在报警文件中,会有更详细的信息
Errors in file D:\Oracle\admin\test\bdump\testDBW0.TRC:
ORA-01157: cannot identify/lock data file 3 - see DBWR trace file
ORA-01110: data file 3: 'D:\ORACLE\ORADATA\TEST\USERS01.DBF'
ORA-27041: unable to open file
OSD-04002: unable to open file
O/S-Error: (OS 2) 系统找不到指定的文件。


7.拷贝备份复原到原来位置(restore过程)
C:\>xcopy d:\database\*.* d:\oracle\oradata\test/H/R/S


8.打开数据库,检查数据
SQL> alter database open;
Database altered.
SQL> select * from test;
A
---------------------------------------
1


这里可以发现,数据库恢复成功,但在备份之后与崩溃之前的数据丢失了。
说明:
1、非归档模式下的恢复方案可选性很小,一般情况下只能有一种恢复方式,就是数据库的冷备份的完全恢复,仅仅需要拷贝原来的备份就可以(restore),不需要recover。
2、这种情况下的恢复,可以完全恢复到备份的点上,但是可能是丢失数据的,在备份之后与崩溃之前的数据将全部丢失。
3、不管毁坏了多少数据文件或是联机日志或是控制文件,都可以通过这个办法恢复,因为这个恢复过程是Restore所有的冷备份文件,而这个备份点上的所有文件是一致的,与最新的数据库没有关系,就好比把数据库又放到了一个以前的“点”上。
4、对于非归档模式下,最好的办法就是采用OS的冷备份,建议不要用RMAN来作冷备份,效果不好,因为RMAN不备份联机日志,restore不能根本解决问题。
5、如果没有备份联机日志,如RMAN的备份,就需要利用不完全恢复(until cancel)的方法来重新创建联机日志文件

ORACLE备份策略(ORACLE BACKUP STRATEGY)

| 2 Comments

前言
    等了这么多天,我的第二篇文章《ORACLE备份策略》终于可以与大家见面了,建议大家在读这篇文章之前,一定要理解ORACLE的构架,可以参考第一篇文章:http://expert.csdn.net/Expert/topic/1601/1601315.xml?temp=.2924768,因为ORACLE的备份与恢复,都是与ORACLE的构架紧密相关的,特别是ORACLE的SCN。
    关于备份与恢复的文章,网上也有不少,进入Google,输入ORACLE备份,点击搜索,我相信搜索出来的记录没有一个人能读完,但是大部分不是太老,也就是太不完全,很早我就想总结一下了,我的这篇文章,主旨并不是说大家读了这篇文章,就会了备份的相关知识,它仅仅也是一个提示,希望大家能从中得到益处。


概要
1、了解什么是备份
2、了解备份的重要性
3、理解数据库的两种运行方式
4、理解不同的备份方式及其区别
5、了解正确的备份策略及其好处


一、了解备份的重要性
可以说,从计算机系统出世的那天起,就有了备份这个概念,计算机以其强大的速度处理能力,取代了很多人为的工作,但是,往往很多时候,它又是那么弱不禁风,主板上的芯片、主板电路、内存、电源等任何一项不能正常工作,都会导致计算机系统不能正常工作。当然,这些损坏可以修复,不会导致应用和数据的损坏。但是,如果计算机的硬盘损坏,将会导致数据丢失,此时必须用备份恢复数据。
其实,在我们的现实世界中,已经就存在很多备份策略,如RAID技术,双机热备,集群技术发展的不就是计算机系统的备份和高可用性吗?有很多时候,系统的备份的确就能解决数据库备份的问题,如磁盘介质的损坏,往往从镜相上面做简单的恢复,或简单的切换机器就可以了。
但是,上面所说的系统备份策略是从硬件的角度来考虑备份与恢复的问题,这是需要代价的。我们所能选择备份策略的依据是:丢是数据的代价与确保数据不丢失的代价之比。还有的时候,硬件的备份有时根本满足不了现实需要,假如你误删了一个表,但是你又想恢复的时候,数据库的备份就变的重要了。ORACLE本身就提供了强大的备份与恢复策略,这里我们只讨论ORACLE备份策略,以下的备份都是指ORACLE数据库备份,恢复将放到下一讲中。
所谓备份,就是把数据库复制到转储设备的过程。其中,转储设备是指用于放置数据库拷贝的磁带或磁盘。
能够进行什么样的恢复依赖于有什么样的备份。作为 DBA,有责任从以下三个方面维护数据库的可恢复性:
·使数据库的失效次数减到最少,从而使数据库保持最大的可用性;
·当数据库不可避免地失效后,要使恢复时间减到最少,从而使恢复的效率达到最高;
·当数据库失效后,要确保尽量少的数据丢失或根本不丢失,从而使数据具有最大的可恢复性。
灾难恢复的最重要的工作是设计充足频率的硬盘备份过程。备份过程应该满足系统要求的可恢复性。例如,如果数据库可有较长的关机时间,则可以每周进行一次冷备份,并归档重做日志,对于24*7的系统,或许我们考虑的只能是热备份。 如果每天都能备份当然会很理想,但要考虑其现实性。企业都在想办法降低维护成本,现实的方案才可能被采用。只要仔细计划,并想办法达到数据库可用性的底线,花少量的钱进行成功的备份与恢复也是可能的。
二、了解ORACLE的运行方式
ORACLE数据库有两种运行方式:一是归档方式(ARCHIVELOG),归档方式的目的是当数据库发生故障时最大限度恢复数据库,可以保证不丢失任何已提交的数据;二是不归档方式(NOARCHIVELOG),只能恢复数据库到最近的回收点(冷备份或是逻辑备份)。我们根据数据库的高可用性和用户可承受丢失的工作量的多少,对于生产数据库,强烈要求采用为归档方式;那些正在开发和调试的数据库可以采用不归档方式。
 如何改变数据库的运行方式,在创建数据库时,作为创建数据库的一部分,就决定了数据库初始的存档方式。一般情况下为NOARCHIVELOG方式。当数据库创建好以后,根据我们的需要把需要运行在归档方式的数据库改成ARCHIVELOG方式。
1、改变不归档方式为为归档方式
a.关闭数据库,备份已有的数据,改变数据库的运行方式是对数据库的重要改动,所以要对数据库做备份,对可能出现的问题作出保护。
b. 修改初试化参数,使能自动存档
修改(添加)初始化文件init[SID].ora参数:
log_archive_start=true           #启动自动归档
log_archive_format=ARC%T%S.arc   #归档文件格式
log_archive_dest=/arch12/arch        #归档路径
在8i中,可以最多有五个归档路径,并可以归档到其它服务器,如备用数据库(standby database)服务器
c.启动Instance到Mount状态,即加载数据库但不打开数据库:
$>SVRMGRL
SVRMGRL >connect internal
SVRMGRL >startup mount
   d.发出修改命令
SVRMGRL >alter database archivelog;
SVRMGRL>alter database open;
2、改变归档状态为不归档状态
   与以上步骤相同,但有些操作不一样,主要是在以上的b操作中,现在为删除或注释该参数,在d操作中,命令为
SVRMGRL >alter database noarchivelog;
    注意,从归档方式转换到非归档方式后一定要做一次数据库的全冷备份,防止意外事件的发生。

三、ORACLE备份的分类
简单的按照备份进行的方式,可以分为逻辑备份、冷备份(脱机备份)、热备份(联机备份),其实冷备份与热备份又可以合称为物理备份
按照备份的工具,可以分为EXP/IMP备份、OS拷贝、RMAN、第三方工具,如VERITAS
以下我们将从多个角度来说明以上的各种备份方式
1、EXP/IMP逻辑备份
导入/导出是ORACLE幸存的最古老的两个命令行工具了,其实我从来不认为Exp/Imp是一种好的备份方式,正确的说法是Exp/Imp只能是一个好的转储工具,特别是在小型数据库的转储,表空间的迁移,表的抽取,检测逻辑和物理冲突等中有不小的功劳。当然,我们也可以把它作为小型数据库的物理备份后的一个逻辑辅助备份,也是不错的建议。
对于越来越大的数据库,特别是TB级数据库和越来越多数据仓库的出现,EXP/IMP越来越力不从心了,这个时候,数据库的备份都转向了RMAN和第三方工具。下面我们还是简要介绍一下EXP/IMP的使用。
i、使用方法
Exp parameter_name=value
Or Exp parameter_name=(value1,value2……)
只要输入参数help=y就可以看到所有帮助
如:
C:\>set nls_lang=simplified chinese_china.zhs16gbk
C:\>exp -help
Export: Release 8.1.6.0.0 - Production on 星期四 4月 10 19:09:21 2003
(c) Copyright 1999 Oracle Corporation.  All rights reserved.


通过输入 EXP 命令和用户名/口令,您可以
在用户 / 口令之后的命令:


实例: EXP SCOTT/TIGER
或者,您也可以通过输入跟有各种参数的 EXP 命令来控制“导出”
的运行方式。要指定参数,您可以使用关键字:


格式: EXP KEYWORD=value 或 KEYWORD=(value1,value2,...,valueN)
实例: EXP SCOTT/TIGER GRANTS=Y TABLES=(EMP,DEPT,MGR)
或 TABLES=(T1: P1,T1: P2),如果 T1 是分区表
USERID 必须是命令行中的第一个参数。
关键字  说明(默认)        关键字      说明(默认)
--------------------------------------------------------------------------
USERID   用户名/口令            FULL         导出整个文件 (N)
BUFFER   数据缓冲区的大小          OWNER        所有者用户名列表
FILE     输出文件 (EXPDAT.DMP)    TABLES       表名列表
COMPRESS 导入一个范围 (Y)   RECORDLENGTH  IO 记录的长度
GRANTS  导出权限 (Y)            INCTYPE      增量导出类型
INDEXES 导出索引 (Y)           RECORD       跟踪增量导出 (Y)
ROWS    导出数据行 (Y)         PARFILE      参数文件名
CONSTRAINTS 导出限制 (Y)    CONSISTENT   交叉表一致性
LOG      屏幕输出的日志文件    STATISTICS  分析对象 (ESTIMATE)
DIRECT   直接路径 (N)              TRIGGERS     导出触发器 (Y)
FEEDBACK 显示每 x 行 (0) 的进度
FILESIZE 各转储文件的最大尺寸
QUERY    选定导出表子集的子句


下列关键字仅用于可传输的表空间
TRANSPORT_TABLESPACE 导出可传输的表空间元数据 (N)
TABLESPACES 将传输的表空间列表
在没有警告的情况下成功终止导出。
C:\>
帮助已经很详细的说明了参数的意义和使用方法,并列举了几个简单的例子,注意的是,从8i开始,已经开始支持数据子集的方法,就是可以指定自己的Where条件,可以从表中导出一行或多行数据。
注意上面的set nls_lang=simplified chinese_china.zhs16gbk,通过设置环境变量,可以让exp的帮助以中文显示,如果set nls_lang=American_america.字符集,那么你的帮助就是英文的了。
增量和累计导出必须在全库方式下才有效,而且,大多数情况下,增量和累计导出并没有想象中的那么有效。ORACLE从9i开始,不再支持增量导出和累计导出。
ii、表空间传输
表空间传输是8i新增加的一种快速在数据库间移动数据的一种办法,是把一个数据库上的格式数据文件附加到另外一个数据库中,而不是把数据导出成Dmp文件,这在有些时候是非常管用的,因为传输表空间移动数据就象复制文件一样快。
关于传输表空间有一些规则,即:
·源数据库和目标数据库必须运行在相同的硬件平台上。
·源数据库与目标数据库必须使用相同的字符集。
·源数据库与目标数据库一定要有相同大小的数据块
·目标数据库不能有与迁移表空间同名的表空间
·SYS的对象不能迁移
·必须传输自包含的对象集
·有一些对象,如物化视图,基于函数的索引等不能被传输
可以用以下的方法来检测一个表空间或一套表空间是否符合传输标准:
exec sys.dbms_tts.transport_set_check(‘tablespace_name’,true);
select * from sys.transport_set_violation;
如果没有行选择,表示该表空间只包含表数据,并且是自包含的。对于有些非自包含的表空间,如数据表空间和索引表空间,可以一起传输。
以下为简要使用步骤,如果想参考详细使用方法,也可以参考ORACLE联机帮助。
a.设置表空间为只读(假定表空间名字为APP_Data 和APP_Index)
  alter tablespace app_data read only;
  alter tablespace app_index read only;
b.发出EXP命令
  SQL>host exp userid=”””sys/password as sysdba”””
transport_tablespace=y tablespace=(app_data, app_index)
 以上需要注意的是
·为了在SQL中执行EXP,USERID必须用三个引号,在UNIX中也必须注意避免“/”的使用
·在816和以后,必须使用sysdba才能操作
·这个命令在SQL中必须放置在一行(这里是因为显示问题放在了两行)
   c.拷贝数据文件到另一个地点,即目标数据库
    可以是cp(unix)或copy(windows)或通过ftp传输文件(一定要在bin方式)
   d.把本地的表空间设置为读写
   e.在目标数据库附加该数据文件
imp file=expdat.dmp userid=”””sys/password as sysdba”””
     transport_tablespace=y
     “datafile=(c:\temp\app_data,c:\temp\app_index)”
  f.设置目标数据库表空间为读写
alter tablespace app_data read write;
    alter tablespace app_index read write;
iii、导出/导入与字符集
明白ORACLE的多国语言设置,ORACLE多国语言设置是为了支持世界范围的语言与字符集,一般对语言提示,货币形式,排序方式和CHAR,VARCHAR2,CLOB,LONG字段的数据的显示等有效。ORACLE的多国语言设置最主要的两个特性就是国家语言设置与字符集设置,国家语言设置决定了界面或提示使用的语言种类,字符集决定了数据库保存与字符集有关数据(如文本)时候的编码规则。正如刚才上面的一个小例子,环境变量NLS_LANG的不同,导致EXP帮助发生变化,这就是多国语言设置的作用(NLS_LANG包含国家语言设置与字符集设置,这里起作用的是国家语言设置,而不是字符集)。
ORACLE字符集设定,分为数据库字符集和客户端字符集环境设置。在数据库端,字符集在创建数据库的时候设定,并保存在数据库props$表中,对于8i以上产品,已经可以采用“Alter database character set 字符集”来修改数据库的字符集,但也仅仅是从子集到超集,不要通过update props$来修改字符集,如果是不支持的转换,可能会失去所有与字符集有关的数据,就是支持的转换,也可能导致数据库的不正常工作。字符集分为单字节字符集与多字节字符集,US7ASCII就是典型的单字节字符集,在这种字符集中length=lengthb,而ZHS16GBK就是常用的双字节字符集,在这里lengthb=2*length。
在客户端的字符集环境比较简单,主要就是环境变量或注册表项NLS_LANG,注意NLS_LANG的优先级别为:参数文件à注册表à环境变量àalter session。NLS_LANG的组成为“国家语言设置.字符集”,如nls_lang=simplified chinese_china.zhs16gbk。客户端的字符集最好与数据库端一样(国家语言设置可以不一样,如zhs16gbk的字符集,客户端可以是nls_lang =simplified chinese_china.zhs16gbk或Ameircan_America.zhs16gbk,都不影响数据库字符的正常显示),如果字符集不一样,而且字符集的转换也不兼容,那么客户端的数据显示与导出/导入的与字符集有关的数据将都是乱码。
使用一点点技巧,就可以使导出/导入在不同的字符集的数据库上转换数据。这里需要一个2进制文件编辑工具即可,如uedit32。用编辑方式打开导出的dmp文件,获取2、3字节的内容,如00 01,先把它转换为10进制数,为1,使用函数NLS_CHARSET_NAME即可获得该字符集:
SQL> select nls_charset_name(1) from dual;
NLS_CHARSET_NAME(1)
-------------------
US7ASCII
可以知道该dmp文件的字符集为US7ASCII,如果需要把该dmp文件的字符集换成ZHS16GBK,则需要用NLS_CHARSET_ID获取该字符集的编号:
SQL> select nls_charset_id('zhs16gbk') from dual;
NLS_CHARSET_ID('ZHS16GBK')
--------------------------
              852
把852换成16进制数,为354,把2、3字节的00 01换成03 54,即完成了把该dmp文件字符集从us7ascii到zhs16gbk的转化,这样,再把该dmp文件导入到zhs16gbk字符集的数据库就可以了。(注意,十进制数与十六进制之间的转换,想明白其中的道理)
Iv、跨版本使用Exp/Imp
Exp/Imp很多时候,可以跨版本使用,如在版本7与版本8之间导出导入数据,但这样做必须选择正确的版本,规则为:
  ·总是使用IMP的版本匹配数据库的版本,如果要导入到816,则使用816的导入工具。
  ·总是使用EXP的版本匹配两个数据库中低的那个版本,如在815与816之间互导,则使用815的EXP工具。

前言
    其实,很早以前,大楷就是我任斑竹的时候,我就有个想法,找一些或写一些文章出来,贴给大家共享,避免大家走过多的弯路,可是我的心却安静不下来,所以就一直搁浅了。
    现在,我想我暂时可能会稳定一点了,所以想静点心下来写点东西,把自己的知道的东西,怎么方便的快捷的告诉大家,其实,好多时候,我上论坛一看,整个论坛充斥着这样的那样的错误的思想,想纠正,却无从下手,这个时候,我就想,要是有一些系统性的东西给他们,那多好啊!
    很多人和我一样,大楷都是从SQL Server转过来的,可能是受MS的影响太深,老想着用SQL Server的方法来解决ORACLE的问题,这就是一个极大的失误,比如有人就老喜欢用那个OEM,那个我一直认为是ORACLE的糟粕的一个代表,ORACLE的精髓是代码的管理,任何管理、备份、恢复都可以通过代码或脚本实现。比如还有的人就喜欢SQL Server过程中能直接写select语句,认为ORACLE必须要能做,有人认为SQL SERVER的过程能执行DDL语句,那ORACLE也必须要这样……诚然,我不是承认不可以,但是很多的很多的这一切,就是你实现了,却是ORACLE强烈反对的,因为它们对ORACLE的性能可能有很大的影响。
    我想,要学好ORACLE,就必须脚踏实地,一步一步来,你可以拿两个数据库来比较,但是不要想着关系型数据库都是一样的,它们是有差别的,或多或少,一个简单的例子,在SQL Server中,一个表的修改是以页面级来锁定的,对表的修改是阻塞读操作的(如Select),所以SQL Server的锁是非常昂贵的,如果不是特别指定,一个语句就是一个事务。在ORACLE中,这一切都变了,ORACLE可以提供最小的行级锁,所有对表的锁定不影响Select查询,在ORACLE中,锁的资源占用是非常小的,所以ORACLE默认开启事务,直到你提交或回滚。
    我学ORACLE也就两年时间,到现在专职做ORACLE管理和ORACLE数据仓库,也是自己慢慢走过来的,我其实也没有得到高人指点,就是自己慢慢啃书,慢慢查资料。论坛可以用来交流,但是想让它让你有很大进步,可能性不大,毕竟,论坛上面系统性的东西太少。要想做一个好的DBA,是不容易的,有人总结了DBA 10点该做的和不该做的。
> #1 - Do Maintain your Expertise
> #2 - Do Use the DBMS_STATS Package to Collect Statistics
> #3 - Do Use Bind Variables
> #4 - Do Put your Production Database in ARCHIVELOG Mode
> #5 - Do Use Locally Managed Tablespaces
> #6 - Do Monitor Your Database
> #7 - Do Practice Recoveries
> #8 - Do Get Involved with User Groups and Other Resources
> #9 - Do Establish Standards and Change Control Processes
> #10 - Do Think Ahead


> Oracle Database Top 10 Don'ts
> #1 - Don't Waste Time Re-Organizing Your Databases
> #2 - Don't Use .Log or Other Common Extensions For Your Database File Names
> #3 - Don't Leave Your Database Open To Attack
> #4 - Don't Decide Against Hot Backups
> #5 - Don't Use ASSM
> #6 - Don't Forget the 80/20 Rule
> #7 - Don't Stack Views
> #8 - Don't Be a Normalization Bigot
> #9 - Don't Forget to Document Everything
> #10 - Do Not Use Products You are Not Licensed For.
     CSDN上的专职从事数据库管理的可能很少,也就导致了这里很多人的问题都是在开发上面,当然,我不是要求大家学管理,就是开发,也需要对数据库理解,半懂不懂的搞开发,最可能的后果,可能就是项目的失败,而这是一个程序员的耻辱。
     正因为CSDN的这种特殊情况,很多高手都不愿意来这里,我问其原因,回答很简单,CSDN只能让我退步,我去干什么,大家也都清楚,现在是一个信息化的时代,不是不进则退了,是进步慢也是退步的时代了。
     今天就写这么多,下面开始转入正文,因为时间仓促,多少可能有些笔误或错误,也就希望大家指正,在以后的时间里,我会定期的写这么一些文章,希望大家有所收获,我的下一篇文章计划是“ORACLE的备份策应”。
      此文不经许可,不可转载,如有必要,也需要注明出处!
-------------------------------------------------------------------------


                                  概要
在本章里你可以了解以下内容
1、 ORACLE 实例——包括内存结构与后台进程
2、 ORACLE 数据库——物理操作系统文件的集合
3、 了解内存结构的组成
4、 了解后台进程的作用
5、 了解数据库的物理文件
6、 解释各种逻辑结构

修改 SID

| 2 Comments

nid utility:
http://download-west.oracle.com/docs/cd/B13789_01/server.101/b10825/dbnewid.htm#i1004683


测试如下:
[oracle@WWW2 oracle]$ nid
DBNEWID: Release 9.2.0.1.0 - Production
Copyright (c) 1995, 2002, Oracle Corporation.  All rights reserved.

Keyword     Description                    (Default)
----------------------------------------------------
TARGET      Username/Password              (NONE)
DBNAME      New database name              (NONE)
LOGFILE     Output Log                     (NONE)
REVERT      Revert failed change           NO
SETNAME     Set a new database name only   NO
APPEND      Append to output log           NO
HELP        Displays these messages        NO

[oracle@WWW2 oracle]$ nid target=system/system dbname=xzh
DBNEWID: Release 9.2.0.1.0 - Production
Copyright (c) 1995, 2002, Oracle Corporation.  All rights reserved.

Connected to database ESAL (DBID=1442095281)

NID-00121: Database should not be open


Change of database name failed during validation - database is intact.
DBNEWID - Completed with validation errors.

说明:
数据库不能在打开时修改id,关闭数据库并启动到mount状态

SQL> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL> startup mount;
ORACLE instance started.

Total System Global Area  420548948 bytes
Fixed Size                   450900 bytes
Variable Size             201326592 bytes
Database Buffers          218103808 bytes
Redo Buffers                 667648 bytes
Database mounted.
SQL> exit
Disconnected from Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.1.0 - Production
[oracle@WWW2 oracle]$ nid target=system/system dbname=xzh
DBNEWID: Release 9.2.0.1.0 - Production
Copyright (c) 1995, 2002, Oracle Corporation.  All rights reserved.

Connected to database ESAL (DBID=1442095281)

Control Files in database:
    /home/oracle/oradata/esal/control01.ctl
    /home/oracle/oradata/esal/control02.ctl
    /home/oracle/oradata/esal/control03.ctl

Change database ID and database name ESAL to XZH? (Y/[N]) => Y

Proceeding with operation
Changing database ID from 1442095281 to 26073845
Changing database name from ESAL to XZH
    Control File /home/oracle/oradata/esal/control01.ctl - modified
    Control File /home/oracle/oradata/esal/control02.ctl - modified
    Control File /home/oracle/oradata/esal/control03.ctl - modified
    Datafile /home/oracle/oradata/esal/system01.dbf - dbid changed, wrote new name
    Datafile /home/oracle/oradata/esal/undotbs01.dbf - dbid changed, wrote new name
    Datafile /home/oracle/oradata/esal/item01.dbf - dbid changed, wrote new name
    Datafile /home/oracle/oradata/esal/temp01.dbf - dbid changed, wrote new name
    Control File /home/oracle/oradata/esal/control01.ctl - dbid changed, wrote new name
    Control File /home/oracle/oradata/esal/control02.ctl - dbid changed, wrote new name
    Control File /home/oracle/oradata/esal/control03.ctl - dbid changed, wrote new name

Database name changed to XZH.
Modify parameter file and generate a new password file before restarting.
Database ID for database XZH changed to 26073845.
All previous backups and archived redo logs for this database are unusable.
Shut down database and open with RESETLOGS option.
Succesfully changed database name and ID.
DBNEWID - Completed succesfully.

下面要做的就是修改init.ora文件,
然后新创建口令文件,然后用resetlogs选项打开数据库

SQL> alter database open resetlogs;   
Database altered.


SQL> show parameter db_name

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_name                              string      xzh
SQL> show parameter instance

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
active_instance_count                integer
cluster_database_instances           integer     1
instance_groups                      string
instance_name                        string      xzh
instance_number                      integer     0
open_links_per_instance              integer     4
parallel_instance_group              string
parallel_server_instances            integer     1

很简单的,修改完毕之后,要重新冷备数据库,
还有就是修改tnsnames.ora与listener.ora

计算表与表空间所占用的空间

| 1 Comment

selecT SUM(BYTES)
FROM DBA_SEGMENTS
WHERE OWNER='XXX'
AND segment='TABLE'
and segment_name='XXX' ;


SELECT a.tablespace_name,SUM(bytes) FROM dba_data_files a GROUP BY a.tablespace_name;

查询系统中某表常用操作

SELECT SQL_TEXT, DECODE(COMMAND_TYPE,2,'INSERT',6,'UPDATE',7,'DELETE'),EXECUTIONS
FROM V$SQL
WHERE COMMAND_TYPE IN (2,6,7)
ORDER BY EXECUTIONS;

COMMAND_TYPE的含义
~~~~~~~~~~~~
ROLLBACK : 45
SAVEPOINT: 46
ALTER : 42
COMMIT : 44
TRUNCATE : 85 (!)
INSERT : 2
SELECT : 3
UPDATE : 6
DELETE : 7

About this Archive

This page is an archive of entries from September 2005 listed from newest to oldest.

August 2005 is the previous archive.

October 2005 is the next archive.

Find recent content on the main index or look in the archives to find all content.