安装部署工程模板使用教程

[原文作者]Mary Lee
假定你已经使用免费的Visual Studio Express Edition开发了一个程序,现在你想对公众部署这个程序。
我们的话题就此开始!
创建一个新的部署工程:
1.      在“File”菜单里,点击“Add”,并单击“New Project”;
2.      在弹出“Add New Project”对话框的“Project Type”面板中,展开“Other Project Types”结点,然后选择“Setup and Deployment Projects”;
3.      在“Templates”面板中,选择你想要创建的部署工程类型;
在这个例子中,我使用的版本是Visual Basic 2008 Express Edition,所以当我打开“New Project”对话框的时候,没有“Other Project Types”这个结点。
只有在标准版(Standard Edition)或者更高版本(比如Professional Edition 或 Team System Edition)中才有“Other Projects Types”这个结点。同样地,你也不能下载Setup Project模板并添加到Visual Studio Express Edition版本中。
说到这里,你发现Visual Studio Express Edition虽然是免费,但功能受限了吧!下面列出的所有版本的功能都受到同样限制:Visual Basic 2005 Express Edition,Visual C# 2005 Express Edition, Visual C++ 2005 Express Edition, Visual Basic 2008 Express Edition,Visual C# 2008 Express Edition, and Visual C++ 2008 Express Edition。
希望总是有的!使用ClickOnce就可以轻松地与大伙共享你的程序。它的发布向导能够轻松地创建一个安装程序来核查并安装所需的初始环境,比如.NET Framework 或 SQL Server Express Edition。发布的文件可以拷贝到CD光盘,本地Web服务器或者文件分享网络,以便用户们可以下载并安装你的程序。如果你总结客户的使用反馈后给程序增加了新的功能特性,ClickOnce还可以轻松地更新你的程序!
我制作了一个网络浏览器,现在我要教你使用Visual Basic 2008 Express Edition发布向导来部署这个程序到文件分享网络上去。
1.      展开Solution Explorer,右击解决方案名字并点击 “Publish”。
接着你就看到发布向导Publish Wizard 打开了。
2.      输入你想要Visual Studio拷贝文件的位置,并单击“next”。
3.      输入你的最终用户的起始安装位置,并单击“next”。
 
这个位置叫做安装位置。这两个位置是分开的,以防你可能没有对文件共享网络的写入权限。在那种情况下,你只好将文件拷贝到本地,管理员会拷贝这些文件到文件共享网络中去。
4.      选择程序开始运行的路径,并单击“Next”。

如果你选择的是“available online or offline”,该程序将要被安装到最终用户的计算机。如果你选择“only available online only”,该程序只能从安装位置运行。
5.      在发布向导的最后一页,单击“Finish”完成即可。
6.      打开网络文件夹,核查所有文件已经拷贝。最终用户可以打开Browser. application, publish.htm,或 setup.exe files 来开始安装。
好,我们现在对安装过程进行测试。
7.      打开程序安装文件所在的位置。

8.      在 publish.htm文件中,单击“ launch” 或“Install”。
稍后你就看到Launching Application对话框
9.      在安全警告窗口中, 单击 “Install”。 
 
10.      测试你的程序。

在我创建的浏览器里我输入了http://www.microsoft.com并单击Go!!!,结果我的程序运行良好。

上面讲的对 Visual Basic and Visual C# Express Editions都适用。  The Visual C++ Express Edition 开发环境并不具备ClickOnce 发布向导,但是你可以在 Visual Studio 控制台模式下完成。更多信息请参阅:ClickOnce Deployment for Visual C++ Applications
愿你享受开发程序的乐趣!

什么时候可以捕捉到StackOverflowException(堆栈溢出)异常?

[原文作者]Mary Lee
       答案是:当你抛出它的时候
       从CLR 2.0开始,触发StackOverflowException异常的条件就改变了,用户不能触发这个异常(备注1),如果发生异常的话取而代之的是CLR会直接结束掉发生异常的进程。
但是事实并不是100%如此,用户代码仍然能够人为抛出StackOverflowException异常,当然不是通过真正的溢出错误,而是用户手动调用。这和文档上说得不一样,这里我们用一个不太规范但是很简单的程序来说明这种情况。(见备注)
      当然,这种用法没什么特别的,但是我还是觉得有必要提到这点,因为最近我看了一个新讨论组的谈话记录,有人发布了些简单的异常触发代码,碰巧里面提到StackOverflowException。他的代码里直接抛出了这个异常,而且他也有足够的理由相信这个异常在产品代码中也可以被抛出,对此我真的感到非常惊讶。
      请不要认为这篇文章提倡用户来抛出StackOverflowException异常(你当然不应该这么做),所有介绍都只是我的一点兴趣。坦白说我希望任何情形这个异常都是不能被捕捉的。
      public static void CatchStackOverflow1() {
    try {
        throw new StackOverflowException();
    } catch (StackOverflowException ex) {
        // Executes and handles the exception.  User code continues
        Console.WriteLine(ex.Message);
    }
   }
 
   static int CreateRealOverflow(int p1) {
    return 42 + CreateRealOverflow(p1 + 1);
    }
 
   public static void CatchStackOverflow2() {
    try {
        CreateRealOverflow(42);
    } catch (StackOverflowException ex) {
        // Will not execute
        Console.WriteLine(ex.Message);
    }
   }
 
   static void Main(string[] args) {
    CatchStackOverflow1();
    CatchStackOverflow2();
  
}
      [1]除非你可以在CLR托管的情况下执行一些回收机制,这当然是一个例外而不是规则规定的
 
 

正确操作windows窗体的Timer事件

[原文作者]Jared Parsons                                       
 
     Windows窗体的时间(Timer)类允许用户在一个时间段内执行特定的操作。时间对象会在设定的时间内开始一个Tick事件,用户可以容易的响应这个事件。如果软件开发者想在在一个指定的时间段内检查一个特定的条件(比如2秒钟,我会在这篇文章中用两秒作为范例),这时这个事件就会非常有用。
 
     偶尔用户会惊奇的发现Tick事件会比期望当中的更快地被触发。比较在两个调用期间等待两秒钟而言,取而代之的是Tick事件几乎是在一个被调用的过程完成后就被立即被触发了。
 
     这里所发生的是这个事件在事件循环机制作用下所产生的状态变化。时间事件的间隔期是由现实世界的时间来计算的。所以直白的讲,每隔两秒钟windows将认为时间间隔结束,然后发送一个新的Tick事件消息。下一时间段的windows窗体事件就不会再执行代码,一个tick事件会被触发【1】。
 
     现在我们可以想象一下我们已经有如下的代码。
 
     Private Sub OnTimerTick () Handles m_timer.Tick
         RunSomeOperation ()
     End Sub
 
     考虑一下如果 RunSomeOperation 用时超过2秒钟将发生什么。Tick事件会在RunSomeOperation过程当中被实时的触发,另外一个Tick事件会等候处理。一旦我们离开OnTimerTick过程,我们会回到Windows窗体代码,这些代码会巡视到Tick事件并且促使触发它,让我们重新回到OnTimerTick过程。
 
     这是与大多数人的期望矛盾的。大多数人会期望Tick事件应该在窗体代码完成后的两秒被触发。
 
     要想解决这个矛盾,开发者可以在处理一个timer事件的时候停止时间计数器的计数。然后再退出事件处理程序后从新激活计数器。这会使windows从激活计数器时才开始计算时间间隔。这样做的结果是使得timer事件在代码停止执行后的两秒钟才被触发。请参考以下代码:
 
    Private Sub OnTimerTick () Handles m_timer.Tick
      M_timer.stop ()
      Try
             RunSomeOperation ()
       Finally
             M_timer.Start ()
       End Try
    End Sub
 
 
 
      【1】这不是100%正确。不过这确实是无论什么时候应用程序开始又一次的循环事件时的情况。循环事件(更加确切的说是当循环事件发生或没有发生的时候)也与我们的这篇讨论相关联。

VB XML 手册6:在VB中写XSLT转换

 
      大多数XSLT程序员对于下面这段复制一个XML文件的XSLT转换都非常熟悉。
<?xmlversion="1.0"encoding="utf-8"?>
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:outputmethod="xml"indent="yes"/>
 
    <xsl:templatematch="@* | node()">
        <xsl:copy>
            <xsl:apply-templatesselect="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
        这种XSLT身份转换的应用十分普遍,因为它可以让你复制整个XML文档,并且会“访问”到每一个XML节点和属性。如果你添加一个模板,那么它就会对那些存在匹配的节点或属性进行转换,而那些不存在匹配的节点和属性只会被简单的拷贝。
        我们也可以用Visual Basic 中的XML Literals(包括LINQXMLXML的轴属性)来实现XSLT的转换,VB代码可以通过基于以下伪码的XML文档以递归的方式“访问”到每个节点或元素:
Starting with the root element, perform the following whenever you encounter a node
If the node is an element
If the element has attributes, transform or copy each attribute
If the element has child nodes, transform or copy each node
If the node is text, transform or copy the text
If the node is CData, transform or copy the CData
If the node is a comment, transform or copy the comment
If the node is a processing instruction, transform or copy the processing instruction
 
        对于手册中的这一方法,我们将创建一个抽象(MustInherit)基类来执行上面XML文档中的这段递归调用的伪代码。然后再创建一个继承于这个基类的子类来执行具体的转换。首先,我们将创建那个抽象基类和一个作为“起始点”的Transform函数,它以要被转换的XML文档(XDocument)作为输入,然后将转换后的文档返回。
 
Public MustInherit Class VBXmlTransform
 
 Public Overridable Function Transform(ByVal xmlDoc As XDocument) As XDocument
    Return <?xml version="1.0" encoding="utf-8"?>
           <%= ProcessElement(xmlDoc.Root) %>
 End Function
 
End Class
 
        接着,我们来添加那段要调用到每个XML节点(XNode)的逻辑代码,它包含了elements, text, CData等等。在代码中我们需要判断XML节点的类型从而调用相应的函数来进行转换或者对节点类型进行拷贝,并且返回结果,这个结果可能是一个拷贝而来的节点,也可能是一个经过转换的节点。这个方法即ProcessNode,下面是它的代码:
 
 Public Overridable Function ProcessNode(ByVal xmlNode As XNode) As XNode
‘ This method ignores DTD (XDocumentType) content.
 
    Dim nodeType = xmlNode.GetType()
 
    ‘ Because XCData inherits from XText, check for the XCData type before checking
‘ for XText.
 
    If nodeType Is GetType(XCData) Then Return ProcessCData(xmlNode)
    If nodeType Is GetType(XText) Then Return ProcessText(xmlNode)
    If nodeType Is GetType(XElement) Then Return ProcessElement(xmlNode)
    If nodeType Is GetType(XComment) Then Return ProcessComment(xmlNode)
    If nodeType Is GetType(XProcessingInstruction) Then Return _
      ProcessProcessingInstruction(xmlNode)
 
    Return xmlNode
 End Function
 
        接下来,我们添加一个强类型函数来处理每种节点类型以及属性。由于处理element的函数和其他的不一样,所以我们将稍后给出。处理其他的节点类型和属性的函数相当简单,因为基类的行为就只是拷贝文档,即每一个函数仅仅将其输入值返回。所以我们创建这段代码的目的也就是为了提供一个强类型函数,可以让我们在继承于它的子类的具体方法中来进行覆盖。下面是那些强类型函数(除ProcessElement函数):
 
     Public Overridable Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
        Return xmlAttribute
     End Function
 
     Public Overridable Function ProcessCData(ByVal xmlCData As XCData) As XCData
        Return xmlCData
     End Function
 
     Public Overridable Function ProcessText(ByVal xmlText As XText) As XText
        Return xmlText
     End Function
 
     Protected Overridable Function ProcessComment(ByVal xmlComment As XComment) As XComment
        Return xmlComment
     End Function
 
     Public Overridable Function ProcessProcessingInstruction( _
       ByVal pi As XProcessingInstruction) As XProcessingInstruction
 
        Return pi
      End Function
 
 
        现在让我们来看看那个ProcessElement函数,处理element的方法之所以独特是因为它不仅有属性还有child节点,这些属性和child节点都需要被转换或者拷贝,所以我们必须为每个属性提供ProcessAttribute函数,并且为每个child节点提供ProcessNode函数。我们将把这些代码放入一个名为CopyElement的方法中,这样ProcessElement函数看起来就和其他的强类型函数一样,但是它将返回一个对CopyElement函数的调用,而不仅仅返回其输入值。CopyElement函数使用了XML Literals,嵌入式表达以及LINQXML来实现对XML element的拷贝,代码如下:
 
     Public Overridable Function ProcessElement(ByVal xmlElement As XElement) As XElement
         Return CopyElement(xmlElement)
     End Function
 
     Public Overridable Function CopyElement(ByVal xmlElement As XElement) As XElement
         Return <<%= xmlElement.Name %>
             <%= From attribute In xmlElement.Attributes() _
                 Select ProcessAttribute(attribute) %>>
             <%= From node In xmlElement.Nodes() _
                 Select ProcessNode(node) %>
           </>
     End Function
 
        上面就是我们所说的抽象类了,现在我们可以利用它来进行一些简单或复杂的身份转换了。让我们来看个例子吧:
     创建一个转换
 
        我们的例子将使用和手册1中相同的XML文件,它混合了来自几个不同命名空间的内容,从而提供了一个非常好的例子。这些示例文件中的内容在
< AdditionalContactInfo > element中可以找到: http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo schema。在contact info文件中有address信息,它包含了三种不同的元素:homePostalAddress physicalDeliveryOfficeName registeredAddressaddressType类型含有一个必需的元素PostalCode,下面我们可以创建一个简单的类来对<PostalCode>进行转换并将它重命名为<ZipCode>
 
        首先,我们需要导入那些源文档中不同的schemas,这些源文档中特定的schemas,对于抽象的VBXmlTransform类而言并不需要,但对我们继承而来的子类却是必需的。
 
    Imports <xmlns="http://SampleSchema/AWContacts">
    Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
    Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
    Imports <xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord">
 
 
         接下来,我们创建一个继承于VBXmlTransform的类,这个子类叫做AWTransform,在这个子类中,我们覆盖了基类并且添加了新的代码,从而可以执行我们想要的任何转换。这样,我们就必须覆盖掉基类的ProcessElement方法,因为我们需要寻找所有名为PostalCode的元素,如果找到了就进行转换,如果没有找到,才会用基类的ProcessElement方法来处理。
 
   Class AWTransform
    Inherits VBXmlTransform
 
‘ Rename <act:PostalCode> to <ZipCode>.
‘ Create an XName object to use for comparisons. This will perform better than comparing
    ‘ xmlElement.Name.LocalName to a string.
 
    Private postalCodeXName As XName = _
      XName.Get("PostalCode", _
                "http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes")
 
    Public Overrides Function ProcessElement(ByVal xmlElement As XElement) As XElement
        Select Case xmlElement.Name
            Case postalCodeXName
                Return TransformPostalCode(xmlElement)
            Case Else
                Return MyBase.ProcessElement(xmlElement)
        End Select
 
        Return Nothing
    End Function
 
    Public Function TransformPostalCode(ByVal postalCodeElement As XElement) As XElement
        Return <ZipCode><%= postalCodeElement.Value %></ZipCode>
    End Function
  End Class
 
     转换文档
 
        为了实现转换文档,我们需要新建一个子类的实例AWTransform,然后将那个XML源文档传给Transform方法,代码如下:
 
        Dim xmlPath = My.Application.Info.DirectoryPath & "\..\..\AWContacts.xml"
        Dim savePath = My.Application.Info.DirectoryPath & "\..\..\TransformSave.xml"
 
        Dim xmlDoc = XDocument.Load(xmlPath)
 
        Dim transform As New AWTransform()
 
        Dim transformedDoc = transform.Transform(xmlDoc)
        transformedDoc.Save(savePath)
     其他示例
 
        让我们来看看一些其他可以添加到AWTransform类的例子。
        以下这段代码实现了我们怎样去转换一个现有属性的内容如果transform找到名为date的属性就将date值转换为一般的日期和时间格式。
 
    Private dateXName As XName = XName.Get("date")
 
    Public Overrides Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
        If xmlAttribute.Name.Equals(dateXName) Then Return TransformDateAttribute(xmlAttribute)
 
        Return MyBase.ProcessAttribute(xmlAttribute)
    End Function
 
    Public Function TransformDateAttribute(ByVal dateAttribute As XAttribute) As XAttribute
        Dim dateValue As New DateTime()
        If DateTime.TryParse(dateAttribute.Value, dateValue) Then _
            dateAttribute.Value = dateValue.ToString("G")
 
        Return dateAttribute
    End Function 
 
        接下来的这段代码实现了我们怎样从转换后的文档中去掉data:如果transform找到
CData section就返回Nothing,这样CData section就不会存在于结果文档中。
 
    Public Overrides Function ProcessCData(ByVal xmlCData As XCData) As XCData
        Return Nothing
    End Function

IntPtr(long)被悄无声息地截短了吗?

 

[原文作者]jaredpr
      一言以蔽之:不会,任何情况都不回被截短。
     我和同事前几天聊到一个有趣的案例,它涉及到IntPtr, Pinvoke以及64bit整体概念的准确性。 最终这场讨论把我们引向对IntPtr处理long类型的构造函数。令我吃惊的是,这个构造函数的是这么写的:
     public unsafe IntPtr(long value) { this.m_value = (void*) ((int) value); }
     问题在于long类型的值被肆意地截短成一个int值。这将直接导致丢失一切越过4G的内存地址 (换言之,没有64bit的寻址)。这个确确实实放在眼前的大漏洞,使我想到这是不是有可能是个反编译器(disassembler)bug。于是我用了.NET ReflectorIL模式。
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: conv.ovf.i4
    L_0003: conv.i
    L_0004: stfld void* System.IntPtr::m_value
    L_0009: ret
     这段IL证实了long值确确实实是被截短了(还在开始处做了一个溢出检测)。但是且慢,mscorlib.dll是个与处理器运行模式紧密相关的特殊的DLL,这一切有可能仅仅是32位操作系统惹得祸。于是我切换到一台64bit的机器,重新打开Reflector,令我气恼的是面对我的依旧是惊人相同的代码。
     几分钟后我想到打开任务管理器(task manager)看看,竟然看到reflector是个运行在WoW64的进程。这意味着reflector还是调用了32位版本的mscorlib.dll。顺利成章地,我用ildasm打开了64位的mscorlib,同时看到在64位模式,内存地址将不再被截短。
 IL_0000: ldarg.0
 IL_0001: ldarg.1
 IL_0002: conv.u
 IL_0003: stfld      void* System.IntPtr::m_value
 IL_0008: ret
      conv.u 是一个非托管的unsigned int的惯用方法,在64位平台上,conv.u将是一个unsigned 8 字节数值 (详见:OpCodes.Conv_U
概括一下,对于开发人员来说,IntPtr(long)的在不同的平台上做着完全正确的事,实现因平台不同而异。在32位操作系统上,如果一个非4GB的内存地址被当做参数传入的话,实现代码将(正确地)抛出一个异常。在64位的世界,实现代码将按照程序员的意愿返回一个正确的地址,不去截短任何值。