随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

System.Diagnostics.Process: 重定向标准输入、输出和错误

[原文作者]:Lucian Wischik

[原文链接]:System.Diagnostics.Process: redirect StandardInput, StandardOutput, StandardError

    有时我们运行一个外部适用程序,向里面输入数据然后获取它的输出。这往往很容易发生这样的死锁:

     ' BAD CODE

     Using p As New System.Diagnostics.Process

     p.StartInfo.FileName = "cat"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardInput = True

     p.Start()

     p.StandardInput.Write("world" & vbCrLf & "hello")

    ' 这里将发生死锁,如果P写到输出接口的量达到12k

     p.StandardInput.Close()

     Dim op = p.StandardOutput.ReadToEnd()

     p.WaitForExit()

     p.Close()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     End Using

    这段程序将发生死锁,因为“Cat”首先从标准输入接口中读取,然后写到标准输出接口,最后再读取它,这样一直循环下去直到没有任何东西可读取。但是如果它的标准输出接口已经填满了,并且没有对象去读取,这样就不能继续往里面写东西,从而出现阻塞。

    这里的12k仅是一个随机数,我不会关注于它...

     ' BAD CODE

     Using p As New System.Diagnostics.Process

     p.StartInfo.FileName = "findstr"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardError = True

     p.Start()

    ' deadlock here if p needs to write more than 12k to StandardError

     Dim op = p.StandardOutput.ReadToEnd()

     Dim err = p.StandardError.ReadToEnd()

     p.WaitForExit()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     Console.WriteLine("ERROR:") : Console.WriteLine(err)

     End Using

     MSDN文档中说:你可以通过异步读取操作来避免这些依赖和潜在的死锁;或者你可以通过创建两个线程,让其中一个独立线程来读取输出流来避免死锁。因此我们将这样来做:

     使用线程来重定向就不会死锁

     '  GOOD CODE: 这里不会发生死锁.

     Using p As New Diagnostics.Process

     p.StartInfo.FileName = "sort"

     p.StartInfo.UseShellExecute = False

     p.StartInfo.RedirectStandardOutput = True

     p.StartInfo.RedirectStandardInput = True

     p.Start()

     Dim op = ""

     ' do NOT WaitForExit yet since that would introduce deadlocks.

      p.InputAndOutputToEnd("world" & vbCrLf & "hello", op, Nothing)

     p.WaitForExit()

     p.Close()

     Console.WriteLine("OUTPUT:") : Console.WriteLine(op)

     End Using

     ''' <summary>

     ''' InputAndOutputToEnd: a handy way to use redirected input/output/error on a p.

     ''' </summary>

     ''' <param name="p">The p to redirect. Must have UseShellExecute set to false.</param>

     ''' <param name="StandardInput">This string will be sent as input to the p. (must be Nothing if not StartInfo.RedirectStandardInput)</param>

     ''' <param name="StandardOutput">The p's output will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardOutput)</param>

     ''' <param name="StandardError">The p's error will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardError)</param>

     ''' <remarks>This function solves the deadlock problem mentioned at http://msdn.microsoft.com/en-us/library/system.diagnostics.p.standardoutput.aspx</remarks>  

     <Runtime.CompilerServices.Extension()> Sub InputAndOutputToEnd(ByVal p As Diagnostics.Process, ByVal StandardInput As String, ByRef StandardOutput As String, ByRef StandardError As String)

     If p Is Nothing Then Throw New ArgumentException("p must be non-null")

     ' Assume p has started. Alas there's no way to check.

      If p.StartInfo.UseShellExecute Then Throw New ArgumentException("Set StartInfo.UseShellExecute to false")

      If (p.StartInfo.RedirectStandardInput <> (StandardInput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Input only when StartInfo.RedirectStandardInput")

      If (p.StartInfo.RedirectStandardOutput <> (StandardOutput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Output only when StartInfo.RedirectStandardOutput")

     If (p.StartInfo.RedirectStandardError <> (StandardError IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Error only when StartInfo.RedirectStandardError")

 

     Dim outputData As New InputAndOutputToEndData

     Dim errorData As New InputAndOutputToEndData

 

     If p.StartInfo.RedirectStandardOutput Then

     outputData.Stream = p.StandardOutput

     outputData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)

     outputData.Thread.Start(outputData)

     End If

     If p.StartInfo.RedirectStandardError Then

     errorData.Stream = p.StandardError

     errorData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)

     errorData.Thread.Start(errorData)

     End If

 

     If p.StartInfo.RedirectStandardInput Then

     p.StandardInput.Write(StandardInput)

     p.StandardInput.Close()

    End If

'

    If p.StartInfo.RedirectStandardOutput Then outputData.Thread.Join() : StandardOutput = outputData.Output

    If p.StartInfo.RedirectStandardError Then errorData.Thread.Join() : StandardError = errorData.Output

    If outputData.Exception IsNot Nothing Then Throw outputData.Exception

    If errorData.Exception IsNot Nothing Then Throw errorData.Exception

    End Sub

    Private Class InputAndOutputToEndData

    Public Thread As Threading.Thread

    Public Stream As IO.StreamReader

    Public Output As String

    Public Exception As Exception

    End Class

    Private Sub InputAndOutputToEndProc(ByVal data_ As Object)

    Dim data = DirectCast(data_, InputAndOutputToEndData)

    Try : data.Output = data.Stream.ReadToEnd : Catch e As Exception : data.Exception = e : End Try

    End Sub

posted on 2009-02-20 14:19:00 by vbcti  评论(0) 阅读(3625)

PInvoke 和COM对象

 

   我偶尔会觉得有必要把COM的互操作和PInvoke结合起来。在某些场景中,用PInvoke的声明和方法会更容易编码一些。在这些场景中包括进COM对象,并且在签名上加上合适的Marshal标记也是合法的。

    最简单的完成这些场景的方法是有本地的签名只暴露IUnkown实例。在托管代码这边,用一个对象声明并且标记上MarshalAs(UnmanagedType.IUnknown)。例如:

[DllImport("SomeDll.dll")]

[return: MarshalAs(UnmanagedType.IUnknown)]

public static extern object GetSomeComObject();

     有一条需要记住,在这种场景中怎么处理ref这个关键字。在任何情况下,如果一个COM对象被当作来自于PInvoke的签名,CLR会假定他会去调用IUKnown::Release()。 相对的本地代码必须考虑这种情况,适当的对这个对象AddRef()。

    这已经包括了任何的场景,像上面的代码,COM对象返回的值是function [1].

posted on 2009-01-23 14:25:09 by vbcti  评论(0) 阅读(3898)

在调试时怎样禁止JIT优化

 

[原文作者]:Jaredpar

[原文链接]:Disabling JIT optimizations while debugging

 

     如果你正在调试一个托管应用程序,却发现不能查看任何局部变量或者函数参数的值,这是因为

     托管代码在编译时已经被优化了。下面的内容将告诉你怎样解决这个问题。我会教大家一种非常简单的小技巧来利用.ini文件禁止代码优化。它不需要你重新编译你的应用程序并且只要几秒钟就能实现。

     创建一个.ini文件并添加如下内容:

     [.NET Framework Debugging Control]

     GenerateTrackingInfo=1

     AllowOptimize=0

     这个小技巧真的能帮助你节省调试的时间。虽然已经有其他人把它写进博客里,但是在过去几周我总是需要去搜索它,于是我就干脆也把它写进自己博客了以防以后需要。

posted on 2009-01-23 13:59:45 by vbcti  评论(0) 阅读(3400)

功能预览:VS2010中快速查询文件或标签

 

[原文作者]:Lisa Feigenbaum

[原文链接]:Walkthrough: Quick Search for Files and Symbols in Visual Studio 2010

 

    快速查询是2010CTP(Community Technical Preview,社区技术预览)中我喜欢的功能之一,基本上我每时每刻都在使用。我希望读者在开发中也感受到这项功能的魅力。由于开发者需要不时在代码中寻找某些信息的需求,快速查询已经成为当今开发环境最常用的功能,这篇文章深入介绍了VS2010的这项功能。

    此博文是October VS2010 CTP(VS2010社区技术预览十月版)功能预览系列的一部分,该系列旨在深入浅出地介绍VS2010及.NET 4.0中的新功能,即使读者没有下载社区技术预览,也可以在本文结尾处或以下论坛:

    http://social.msdn.microsoft.com/Forums/en-US/vs2010ctpvbcs/thread/1eb74a01-0d50-4a58-b9b3-cdfae5807ef8

    就本文内容及预览发表评论。

    谢谢!

    Lisa(非译者)

    功能预览:针对文件及标签的快速查询

     本文介绍的是VS2010中针对文件及标签的快速查询。快速查询指借助模糊查询技术协助开发者在代码中快速定位至某段代码。当打开项目工程中任意代码文件时,用户可以借助按下CTRL+,(CTRL键和逗号键)快速启动此功能。在快速查询窗口中,用户可以输入任意数量的查询条件,VS会根据输入值寻找项目中符合条件的标签,包括文件名,类型名,成员名。

     此功能预览使用VS中自带的PeopleTrax例子来进行示范,用户可以在任意工程中使用此功能。

    打开示例工程

  1. 示例文件位于:C:\Program Files\Visual Studio 10.0\Samples\1033\TeamDev Samples.zip.
  2. 解压缩示例文件压缩包到自定义路径,找到并打开PeopleTrax 文件夹。
  3. 双击 PeopleTrax.sln在 Visual Studio 2010中打开该工程.

     使用快速查询来寻找标签

     1. Open any file. Click inside the code editor and press CTRL+,. The following window appears. 打开任意文件,点击代码编辑器内部然后按下CTRL+,,将打开如下窗口

       

        

 

 

     2. 在快速查询窗口顶部文本输入框内输入get. 快速查询窗口将列出所有包含单词 "get" 的标签, 如下图所示,输入不分大小写.

   

          

 

      3. 在 "get"之后, 输入空格及单词 name.快速查询窗口会显示所有包含单词"get" 和 "name"的标签, 如下图所示.

 

          

 

       4.按下向下键选中结果列表中的 GetNames 项. 按回车, Visual Studio 将定位到GetNames 方法定义处.

posted on 2009-01-23 13:53:48 by vbcti  评论(0) 阅读(3621)

你知道吗?你可以在异常中解退调用栈

 

[原文作者]:Bill Horst

[原文链接]:Did you know? You can unwind the call stack from exceptions (Bill Horst)

    解退一个异常堆栈的能力是Visual Basic.NET 2005的一个新引进的特性。当调式器触发了一个异常,你可以解退这个堆栈以便于使用代码编辑器修复这个异常并继续调式 修改后的代码。这个异常辅助用户界面有一个“Enable Editing”选项,这个选项可以在当前Solution中展开调式器到代码堆栈的最顶端。

 

  

 

 

    当一个异常未被处理的时候,unwind将会自动发生,但是这个特性可以在Options dialog(在Tools下面)被开启或关闭。(见下图)

  

  

 

    如果你试图在一个异常被触法后去编辑代码,并在解退栈之前,你可以被允许去使用“解退栈并且编辑代码”,“终止代码调试的session”,或“取消编辑”这三个选项。(见下图)

 

    

 

      用户还可以从call stack窗口中进行解退栈,通过在需要的Call stack中单击右键并选择“Unwind To This Frame”。这个特性只是当异常已经被处发并且没有被解退栈时才有效,并且只有当在堆栈的足够靠前的的地方才有效。(见下图)

 

  

     我们希望这个特性可以给您提供更有效率的代码调试,并可以帮助您加强在Visual Basic中使用“Edit and Continue”能力。

posted on 2009-01-23 13:34:31 by vbcti  评论(1) 阅读(3056)

VB Catch …When 为什么这么特别?

 

    [原文作者]:Jared Parsons

    [原文链接]:VB Catch ... When: Why so special?

    VB Catch 的语法有一个独特的特点:When。它允许用户通过表达式筛选一些情况,而不是仅仅是筛选它的类型。 任何的代码都可以添加When去决定是否要处理某个异常。

    Sub Sub1()

    Try

    DoSomeAction()

    Catch ex As Exception When Filter(ex)

    Stop

    End Try

    End Sub

    新闻组通常会问,“为什么这么特别呢?”我也可以通过C#做同样的处理,例如:

static void Sub1()
{
    try
    {
        DoSomeAction();
    }
    catch (Exception ex)
    {
        if (Filter(ex))
        {
            throw;
        }
        HandleException();
    }
}
 
在某种程度上来说,确实是这样的。两种情况下代码都通过调用筛选程序来做决定是否要处理这个异常。细微的不同是在调用筛选的时候。
 
在VB中,When语句是作为IL异常来执行的。当异常被抛出,异常的筛选程序在堆栈展开之前就在进行了。这表示如果筛选方法创建了包括当时的堆栈的错误报告,它就可以展示出异常在什么地方发生的。
 
例如,在上面的代码中,如果DoSomeAction()被抛出,堆栈在筛选程序的表达式里面被检查,下面的堆栈就会被展示出来。
 
 
                
 
 注意,怎么使得DosomeAction这个方法如此清晰可见的?这对错误报告和调查都是很有力的帮助。它也允许在真正出错的语句上设置断点,而不仅仅只是一个验尸报告。
 
 在C#里面执行的代码是发生在堆栈展开之后。只要你不是在执行最优化的代码,仍然可以通过堆栈得到异常的原代码。但是你就不能在错误出现时检查代码了。
 
 
 
                
 
 
 

posted on 2009-01-23 11:23:39 by vbcti  评论(0) 阅读(3062)

新消息:开发者开始趋向于在VB.Net中进行关于XML的工作了

 
 
       Visual Basic 9.0 提供了一个新的功能XML Literals,它将使得对XML的编程更加简单方便,并且在很大程度上的减少了我们的代码量。实际上,XML Literals使得我们用Visual Basic去做XML变得如此简单,以至于很多C#开发者也开始趋向于在VB.NET中来进行XML相关工作!如果想要了解更多信息,请参看下面来自TechEd US的内容:

posted on 2008-12-31 13:47:32 by VBCTI  评论(2) 阅读(3297)

在VB中LINQ To Dataset如何工作

[原文作者]Jonathan Aneja
 
      LINQ的核心是要求可以对任何数据源进行可查询,意味着它必需实现IEnumerable接口。(实际上实现起来远比这个复杂,需要详细了解的请参阅Visual Basic 9.0 Language Specification)。现在当我们使用LINQ to Dataset时遇到了一个问题:DataTable没有实现IEnumerable,我们怎样去建立对它的查询呢?
之前我们了解到Visual Studio 2008中包含一个程序集System.Data.DataSetExtensions.dll,里面定义了一个扩展方法AsEnumerable()。形式如下:
 
                <Extension()> _
                Public Function AsEnumerable(source As DataTable) As EnumerableRowCollection(Of DataRow)
 
       这个函数接受一个DataTable的参数,返回一个实现IEnumerable的对象,从而可以使用LINQ的标准查询操作。你要做的就是导入程序集System.Data(工程模板默认已经做了),然后就可以调用AsEnumerable()来使用针对DatasetLINQ
 
                Dim customers = TestDS.Tables("Customers")
 
                Dim franceCustomers = From cust In customers.AsEnumerable() _
                          Where cust!Country = "France" _
                          Select cust
 
      现在有一个有趣的事是在VBLINQ的工作方法,不需要在代码中明确地调用AsEnumerable()来实现编译通过。其实是这样的,尽管Datatable没有实现IEnumerable接口,然而编译器通过搜寻特定的方法来帮助实现,该方法能够使Datatable转变为某种可查询的类型。当VB编译器遇到基于某个类型的LINQ没有实现IEnumerable, IEnumerable(Of T), IQueryable, 或者IQueryable(Of T),它将按顺序做下面一些事:
1. 看类型中是否有一个一致的查询方法可见。
2. 看类型中是否有一个名字为AsQueryable的实例方法,改方法返回一个可查询的类型。
3. 看类型中是否有一个名字包含AsQueryable的扩展方法,改方法返回一个可查询的类型。
4. 看类型中是否有一个名字为AsEnumerable的实例方法,改方法返回一个可查询的类型。
5. 看类型中是否有一个名字包含AsEnumerable的扩展方法,改方法返回一个可查询的类型。
      其中任何一步,如果编译器发现一个匹配的方法,将插入一个对它的调用。对于Datatable,当命名空间System.Data已经导入时,编译器在第五步为其发现了一个匹配方法,于是建立了一个对AsEnumerable的调用。结果你就能够写如101 LINQ SamplesLINQ to Dataset的代码了:
 
        Dim customers = TestDS.Tables("Customers")
 
        Dim franceCustomers = From cust In customers _
                              Where cust!Country = "France" _
                              Select cust
 
      实际上就是要你提供一个AsEnumerable的扩展方法,该方法返回一个可查询的类型,从而使LINQ起作用。
注意对于强类型的Datasets,你不需要调用AsEnumerable,因为它们继承自TypedTableBase(Of T),该类型实现了IEnumerable,它是VS2008中的一个新类型,在VS2005Dataset设计器将生成继承自DataTable的代码,并且显式地实现IEnumerable
 
 

posted on 2008-12-17 17:30:52 by VBCTI  评论(0) 阅读(2450)

COM对象上的反射

[原文作者]Lucian
[原文链接]Reflection on COM objects
      
     我希望拥有一台这样的完全形态照样机, 当你拍摄一个物体(object)时,它不仅仅把二维图像存储在SD卡上,而是把物体记录在完全形态这个存储卡上,并且了解这个物体和其所构成的所有关系。这样会包括这个物体各个角度的三维图像,关于其历史意义的短文,对其文化和经济中所扮演的角色的描述,详尽的内部图表展现了物体是怎样工作,以及一系列的超链接指向与这个物体有关的主体并且所有的这些都会被保存在维基百科中。
     你准备如何创建这样一个相机呢?
     以上这些是为了引出反射这个主题
     .NET 对象上的反射通过 System.Type 完成,非常简单。比如"Dim type = GetType(System.string)",现在您可以查看所有成员和 System.String 类的继承层次结构。
     如果我们有一个(.Net) interop 程序集,反射COM类型也同样简单。比如,一个project 添加COM引用Microsoft Speech Library,然后可以进行反射做”GetType(SpeechLib.SpVoice)”。其实这样是对(.Net)interop程序集中Runtime Callable Wrapper的反射,Runtime Callable Wrapper”是从COM类型的类库得到的,包括了这个COM类库所有的信息。[译注:Runtime Callble Wrapper(RCW),我们可以生成一个RCW,通过RCW.Net用户就可以使用.Net对象而不是COM组件,为了实现传统的COM程序与.NET程序之间的相互调用,.NET提供了包装类RCW(Runtime Callable Wrapper)CCW(COM Callable Wrapper)。每当一个.NET客户程序调用一个COM对象的方法时就会创建一个RCW对象,每当一个COM客户程序调用一个.NET对象的方法时就会创建一个CCW对象。
     不过有时,你只有COM组件而没有.Net interop程序集。在我为Visual Studio写托管插件时就遇过这样的情况。对于此处反射必须使用ITypeInfo而不是 System.Type 以下是代码以获取该 ITypeInfo然后输出所有成员。我是在 COM 编程的初学者,欢迎所有的建议和改进。(注意:我特意不尝试创建包装 ITypeInfo / TYPEDESC API,虽然那样是比较成熟的) [译注:一般情况下,.Net调用COM组件,我们都会让Visual Studio生成Interop 程序集,这样依然可以用一般的反射,但是让vs.net自动生成一个包装过的.net类库。这种方法虽然方便,但是有很明显的缺点,最致命的就是开发的机器上安装的Com对象的版本比客户机器上安装的高,开发的程序无法正确的运行。
' REFLECTION ON COM OBJECTS. Lucian Wischik, October 2008.
' (with thanks to Eric Lippert and Sonja Keserovic for their help)
'
' CLR允许你通过GetType()进行反射类型
' 对于COM组件,有时你需要通过ITypeInfo/TYPEDESC来进行反射
' * 如果COM组件已经被转换成一个托管的RCW
'   这时可以用RCW进行反射
' * 如果没有RCW可用,还是需要通过ITypeInfo/TYPEDESC
'   ItypeInfo是指向COM组件的指针,可以和System.Type得到一样的信息,Visual Studio对于COM的智能化提示,正是使用这个来反射COM组件
' * 如果没有类库,我们对组件不能做反射
'
' ITypeInfo – class/interface/structure的引用
' TYPEDESC – 表示一些原型(比如,Integer,或者一些复合类型
' 下面显示了怎么使用ItypeInfo来进行反射
'
 
Option Strict On
Imports System.Runtime.InteropServices
 
 
Module Module1
 
    ''' <summary>
    ''' UnmanagedCreateCOM: this is an unmanaged function which calls CoCreateInstance
    ''' to create an instance of CLSID_WebBrowser.
    ''' </summary>
    ''' <returns>returns a new COM object. The caller is expected to AddRef on it.</returns>
    <DllImport("createcom.dll", SetLastError:=False)> _
    Function UnmanagedCreateCOM() As IntPtr
    End Function
 
 
    Sub Main()
        ' .net类型的反射最直接:
        Console.WriteLine("=== REFLECTION ON .NET TYPE VIA .NET REFLECTION ===")
        ReflectOnDotNetType(GetType(System.String))
 
        ' 如果将COM组件加到引用中,反射也是很简单的
        ' 我们将一个COM组件加到引用中,然后反射
        ' 和普通的.net类型一样使用反射:
        Console.WriteLine("=== REFLECTION ON RCW'D COM TYPE VIA .NET REFLECTION ===")
        ReflectOnDotNetType(GetType(SpeechLib.SpVoice))
 
        ' But .net reflection gives pointless results on COM objects which lack an interop assembly:
        ' GetObjectForIUnknown just creates a tiny stub RCW for them with a handful of common functions.
        Console.WriteLine("=== REFLECTION ON NON-RCW'D COM TYPE VIA ITYPEINFO REFLECTION ===")
        ReflectOnDotNetType(Marshal.GetObjectForIUnknown(UnmanagedCreateCOM()).GetType())
 
        ' 这样我们需要使用ITypeInfo来代替:
        Console.WriteLine("=== REFLECTION ON NON-RCW'D COM TYPE VIA COM REFLECTION ===")
        ReflectOnCOMObjectThroughITypeInfo(Marshal.GetObjectForIUnknown(UnmanagedCreateCOM()))
    End Sub
 
 
 
    ''' <summary>
    ''' ReflectOnDotNetType: 反射.net 类型
    ''' </summary>
    ''' <param name="tt">the type to reflect upon</param>
    Sub ReflectOnDotNetType(ByVal tt As System.Type)
        Dim qt As New Queue(Of System.Type)
        qt.Enqueue(tt)
        While qt.Count > 0
            Dim t = qt.Dequeue
            Console.WriteLine("TYPE {0}", t.ToString)
            For Each i In t.GetInterfaces
                Console.WriteLine(" inherits {0}", i.ToString)
                qt.Enqueue(i)
            Next
            For Each m In t.GetMembers
                Console.WriteLine(" member {0}", m.ToString)
            Next
        End While
    End Sub
 
    ''' <summary>
    ''' IDispatch: 托管Idispatch 接口
    ''' </summary>
    ''' <remarks>We don't use GetIDsOfNames or Invoke, and so haven't bothered with correct signatures for them.</remarks>
    <ComImport(), Guid("00020400-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Interface IDispatch
        Sub GetTypeInfoCount(ByRef pctinfo As UInteger)
        Sub GetTypeInfo(ByVal itinfo As UInteger, ByVal lcid As UInteger, ByRef pptinfo As IntPtr)
        Sub GetIDsOfNames_unused()
        Sub Invoke_unused()
    End Interface
 
 
    ''' <summary>
''' ReflectOnCOMObjectThroughITypeInfo: 一个支持Idispatch and attempts COM组件
''' 得到它的ItypeInfor 接口
    ''' 通过这个方法反射COM类型.
    ''' </summary>
    ''' <param name="com">the com object upon which to reflect</param>
    Sub ReflectOnCOMObjectThroughITypeInfo(ByVal com As Object)
        ' How do we get ITypeInfo for a COM object?
        ' It would be nice to use Marshal.GetITypeInfoForType. But that fails when the com object
        ' doesn't have an interop assembly (e.g. when the com object was created for us
        ' by native code). So instead we have to use IDispatch::GetTypeInfo.
        Dim idisp = CType(com, IDispatch)
        Dim count As UInteger = 0 : idisp.GetTypeInfoCount(count)
        If (count < 1) Then Throw New ArgumentException("No type info", "com")
        Dim _typeinfo As IntPtr : idisp.GetTypeInfo(0, 0, _typeinfo)
        If (_typeinfo = IntPtr.Zero) Then Throw New ArgumentException("No ITypeInfo", "com")
        Dim typeInfo = CType(Marshal.GetTypedObjectForIUnknown(_typeinfo, GetType(ComTypes.ITypeInfo)), ComTypes.ITypeInfo)
        Marshal.Release(_typeinfo) ' to release the AddRef that GetTypeInfo did for us.
 
        AddTypeInfoToDump(typeInfo)
        While typeInfosToDump.Count > 0
            DumpTypeInfo(typeInfosToDump.Dequeue())
        End While
    End Sub
 
 
    ''' <summary>
    ''' DumpType: prints information about an ITypeInfo type to the console -- name, inheritance, members
    ''' </summary>
    ''' <param name="typeInfo">the type to dump</param>
    Sub DumpTypeInfo(ByVal typeInfo As ComTypes.ITypeInfo)
 
        ' Name:
        Dim typeName = "" : typeInfo.GetDocumentation(-1, typeName, "", 0, "")
        Console.WriteLine("TYPE {0}", typeName)
 
 
        ' TypeAttr: contains general information about the type
        Dim pTypeAttr As IntPtr : typeInfo.GetTypeAttr(pTypeAttr)
        Dim typeAttr = CType(Marshal.PtrToStructure(pTypeAttr, GetType(ComTypes.TYPEATTR)), ComTypes.TYPEATTR)
 
 
        ' Inheritance:
        For iImplType = 0 To typeAttr.cImplTypes - 1
            Dim href As Integer : typeInfo.GetRefTypeOfImplType(iImplType, href)
            ' "href" is an index into the list of type descriptions within the type library.
            Dim implTypeInfo As ComTypes.ITypeInfo = Nothing : typeInfo.GetRefTypeInfo(href, implTypeInfo)
            ' And GetRefTypeInfo looks up the index to get an ITypeInfo for it.
            Dim implTypeName = "" : implTypeInfo.GetDocumentation(-1, implTypeName, "", 0, "")
            Console.WriteLine(" Implements {0}", implTypeName)
            AddTypeInfoToDump(implTypeInfo)
        Next
 
 
        ' Function/Sub/Property成员:
        ' Note that property accessors are flattened, e.g. for a property "Fred as Integer"
        ' it will be represented as two members "[Get] Function Fred() As Integer", and "[Put] Sub Fred(Integer)"
        ' Each member is uniquely identified by an integer "MEMID".
        ' This memid is what's used e.g. when invoking the member.
        For iFunc = 0 To typeAttr.cFuncs - 1
 
            ' FUNCDESC 是这里的主要结构:
            Dim pFuncDesc As IntPtr : typeInfo.GetFuncDesc(iFunc, pFuncDesc)
            Dim funcDesc = CType(Marshal.PtrToStructure(pFuncDesc, GetType(ComTypes.FUNCDESC)), ComTypes.FUNCDESC)
 
            ' Each function notionally has a list of names associated with it. I'll just pick the first.
            Dim names As String() = {""}
            typeInfo.GetNames(funcDesc.memid, names, 1, 0)
            Dim funcName = names(0)
 
            ' Function 参数:
            Dim cParams = funcDesc.cParams
            Dim s = ""
            For iParam = 0 To cParams - 1
                Dim elemDesc = CType(Marshal.PtrToStructure(New IntPtr(funcDesc.lprgelemdescParam.ToInt64 + Marshal.SizeOf(GetType(ComTypes.ELEMDESC)) * iParam), GetType(ComTypes.ELEMDESC)), ComTypes.ELEMDESC)
                If s.Length > 0 Then s &= ", "
                If (elemDesc.desc.paramdesc.wParamFlags And 2) <> 0 Then s &= "out "
                s &= DumpTypeDesc(elemDesc.tdesc, typeInfo)
            Next
 
            ' 输出函数的其他信息:
            Dim props = ""
            If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYGET) <> 0 Then props &= "Get "
            If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) <> 0 Then props &= "Set "
            If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) <> 0 Then props &= "Set "
            Dim isSub = (funcDesc.elemdescFunc.tdesc.vt = VarEnum.VT_VOID)
            s = props & If(isSub, "Sub ", "Function ") & funcName & "(" & s & ")"
            s &= If(isSub, "", " as " & DumpTypeDesc(funcDesc.elemdescFunc.tdesc, typeInfo))
            Console.WriteLine(" " & s)
            typeInfo.ReleaseFuncDesc(pFuncDesc)
        Next
 
 
        ' Field 成员:
        For iVar = 0 To typeAttr.cVars - 1
            Dim pVarDesc As IntPtr : typeInfo.GetVarDesc(iVar, pVarDesc)
            Dim varDesc = CType(Marshal.PtrToStructure(pVarDesc, GetType(ComTypes.VARDESC)), ComTypes.VARDESC)
            Dim names As String() = {""}
            typeInfo.GetNames(varDesc.memid, names, 1, 0)
            Dim varName = names(0)
            Console.WriteLine(" Dim {0} As {1}", varName, DumpTypeDesc(varDesc.elemdescVar.tdesc, typeInfo))
        Next
 
        Console.WriteLine()
    End Sub
 
 
 
    ''' <summary>
    ''' DumpTypeDesc: given a TYPEDESC, dumps it out into a string e.g. "Ref Int" or
    ''' "Array of MyTypeInfo". Also calls AddTypeInfoToDump for every ITypeInfo encountered.
    ''' </summary>
    ''' <param name="tdesc">the TYPEDESC to dump</param>
    ''' <param name="context">the ITypeInfo that contained this TYPEDESC, for context</param>
    ''' <returns>a string representation of the TYPEDESC</returns>
    Function DumpTypeDesc(ByVal tdesc As ComTypes.TYPEDESC, ByVal context As ComTypes.ITypeInfo) As String
        Dim vt = CType(tdesc.vt, VarEnum)
        Select Case vt
 
            Case VarEnum.VT_PTR
                Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
                Return "Ref " & DumpTypeDesc(tdesc2, context)
 
            Case VarEnum.VT_USERDEFINED
                Dim href = tdesc.lpValue.ToInt32()
                Dim refTypeInfo As ComTypes.ITypeInfo = Nothing : context.GetRefTypeInfo(href, refTypeInfo)
                AddTypeInfoToDump(refTypeInfo)
                Dim refTypeName = "" : refTypeInfo.GetDocumentation(-1, refTypeName, "", 0, "")
                Return refTypeName
 
            Case VarEnum.VT_CARRAY
                Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
                Return "Array of " & DumpTypeDesc(tdesc2, context)
                ' lpValue is actually an ARRAYDESC structure, which also has information on the array dimensions,
                ' but alas .Net doesn't predefine ARRAYDESC.
 
            Case VarEnum.VT_VOID ' e.g. IUnknown::QueryInterface(Ref GUID, out Ref Ref Void)
                Return "Void"
            Case VarEnum.VT_VARIANT
                Return "Object"
            Case VarEnum.VT_UNKNOWN
                Return "IUnknown*"
 
            Case VarEnum.VT_BSTR
                Return "String"
            Case VarEnum.VT_LPWSTR
                Return "wchar*"
            Case VarEnum.VT_LPSTR
               Return "char*"
 
            Case VarEnum.VT_HRESULT
                Return "HResult"
 
            Case VarEnum.VT_BOOL
                Return "Bool"
            Case VarEnum.VT_I1
                Return "SByte"
            Case VarEnum.VT_UI1
                Return "Byte"
            Case VarEnum.VT_I2
                Return "Short"
            Case VarEnum.VT_UI2
                Return "UShort"
            Case VarEnum.VT_I4, VarEnum.VT_INT                
                Return "Integer"
            Case VarEnum.VT_UI4, VarEnum.VT_UINT
                Return "UInteger"
            Case VarEnum.VT_I8
                Return "Long"
            Case VarEnum.VT_UI8
                Return "ULong"
 
            Case Else
                ' 这里还有其他类型,我没有在这里列出
                ' 大家可以根据需要将其他的列出来.
                Return vt.ToString()
        End Select
    End Function
 
 
    Dim typeInfosToDump As New Queue(Of ComTypes.ITypeInfo)
    Dim typeInfosDumped As New HashSet(Of String)
    '
    Sub AddTypeInfoToDump(ByVal typeInfo As ComTypes.ITypeInfo)
        Dim typeName = "" : typeInfo.GetDocumentation(-1, typeName, "", 0, "")
        If typeInfosDumped.Contains(typeName) Then Return
        typeInfosToDump.Enqueue(typeInfo)
        typeInfosDumped.Add(typeName)
    End Sub
 
EndModule

 

 

 

posted on 2008-12-17 17:27:49 by VBCTI  评论(0) 阅读(4879)

LiveRun – 即时查看程序输出的VS插件

         
      [原文作者]Lucian
      [原文链接]LiveRun - a VS plugin to see the output of your program immediately
 
     假设你正在会议室里演示即时编译的程序。有什么最佳的方式来进行演示呢?
     你还在往代码窗口里输入代码?这样,你得依赖听众的想像力――他们在脑海中构造这个程序是怎样运行的。此外,你还得依赖他们相信你的代码真的像和你所说的一样运作。
     或者你要不停的运行你的代码,这样程序的输出窗口会弹出,然后听众可以看到代码的实际工作情况?这是有风险的,因为每次切换代码和输出窗口会中断流程。并且你得依赖听众会记得他们每次看到的输出信息所对应的代码。
     我想这是一个可以被技术所解决的问题!我为Visual Studio 2008 写了一个小插件。它关注的是当前文本缓冲中所包含的代码,并在后台编译它,然后在前端窗口中显示输出。这一系列动作大概每2秒钟重复一次。你甚至不需要保存再重新编译代码就能看到输出了。这个插件特意制做成独立的控制台程序因为它用不着输入。这是一幅截图:
                    
     
     源代码很小而且简单易懂,并且可以从上面的链接中获得。
 
     有两个关键的地方。第一是要使用多线程进行处理。我想让源代码在后台线程中编译因此它并不会影响Visual Studio UI. 但是要获取当前缓冲的文本你必须使用UI线程,而且要显示输出你也得使用它。我使用System.Timers.Timer在后台线程触发它的事件,同时为需要UI线程的任务调用form.Invoke(…)
     我还使用了“non-AutoReset”计时器。我让它获取源码并进行编译+运行+显示,暂停2秒后,再去获取源码并进行编译+运行+显示,如此循环。换句话说,计时器的时间间隔必须设为2 秒在处理完先前的定时事件之后。
 
''' <summary>
''' OnTimer 处理non-autoreset计时器信号。它运行在后台线程中。从当前的缓冲区中获取
''' 源代码并编译它,然后显示输出。
''' </summary>
''' <remarks></remarks>
Sub OnTimer() Handles t.Elapsed
    Try
        Dim oldsrc = src
                '我们在后台线程中。但只能从 UI 线程获取源...
        '此委托将获取源代码并将它存储在"src"字段中
        f.Invoke(New Action(AddressOf GetSource))
        If src <> oldsrc Then
            Dim oldoutput = output
            ' 在后台编译并运行
            output = CompileAndRun(src)
            If output <> "" OrElse oldoutput = "" Then
                ' 在屏幕上显示输出必须在UI线程中完成
                ' 此委托从"output"字段中得到要输出的内容并显示它
                f.Invoke(New Action(AddressOf ShowOutput))
            End If
        End If
    Finally
        t.Start()
    End Try
End Sub
 
 
     另一个关键时刻与如何运行代码并捕获它的输出有关。在“My”命名空间中,VB在这方面有很多有用的函数。我的主要问题是合理的处理异常不留任何遗漏。(注意:获取临时文件名的代码并不完全准确:      事实上你在某个状态之前得到一个未使用的临时文件名并不意味着这个文件名会一直不被使用;同样,它也并不意味着添加了“.vb”扩展名的临时文件名会一直不被使用。但是把这种情况处理的更准确看直来并不十分划算;不管怎样,在异常处理中我们将所有的问题恢复到正常状态。)
 
Function CompileAndRun(ByVal src As String) As String
    Dim fn_exe = ""
    Dim fn_src = ""
    Dim vbc As System.Diagnostics.Process = Nothing
    Dim exe As System.Diagnostics.Process = Nothing
    Try
        ' 准备编译
        fn_src = My.Computer.FileSystem.GetTempFileName() & ".vb"
        My.Computer.FileSystem.WriteAllText(fn_src, src, False)
        fn_exe = My.Computer.FileSystem.GetTempFileName() & ".exe"
        Dim framework = Environment.ExpandEnvironmentVariables("%windir%\Microsoft.Net\Framework")
        Dim latest_framework = (From d In My.Computer.FileSystem.GetDirectories(framework) Where d Like "*\v*" Select d).Last
 
        ' 编译
        vbc = System.Diagnostics.Process.Start(New ProcessStartInfo _
                            With {.CreateNoWindow = True, _
                                  .UseShellExecute = False, _
                                  .FileName = latest_framework & "\vbc.exe", _
                                  .Arguments = String.Format("/out:""{0}"" /target:exe ""{1}""", fn_exe, fn_src)})
        Dim vbc_done = vbc.WaitForExit(3000)
        If Not vbc_done Then Return ""
        If vbc.ExitCode <> 0 Then Return ""
 
        ' 运行
        Dim pinfo = New ProcessStartInfo With {.CreateNoWindow = True, _
                                               .UseShellExecute = False, _
                                               .FileName = fn_exe, _
                                               .RedirectStandardOutput = True}
        exe = New System.Diagnostics.Process With {.StartInfo = pinfo}
        exe.Start()
        Dim output = exe.StandardOutput.ReadToEnd
        Dim exe_done = exe.WaitForExit(3000)
        If Not exe_done Then Return ""
        Return output
    Finally
        ' 我们可以巧妙的关闭VBC进程
        If vbc IsNot Nothing Then
            If Not vbc.HasExited Then
                Try : vbc.Kill() : Catch ex As Exception : End Try
                Try : vbc.WaitForExit() : Catch ex As Exception : End Try
            End If
            Try : vbc.Close() : Catch ex As Exception : End Try
            vbc = Nothing
        End If
 
        ' 我们可以巧妙的关闭EXE
        If exe IsNot Nothing Then
            If Not exe.HasExited Then
                Try : exe.Kill() : Catch ex As Exception : End Try
                Try : exe.WaitForExit() : Catch ex As Exception : End Try
            End If
            Try : exe.Close() : Catch ex As Exception : End Try
            exe = Nothing
        End If
 
        ' 删除剩余的文件
        Try : My.Computer.FileSystem.DeleteFile(fn_exe) : Catch ex As Exception : End Try
        Try : My.Computer.FileSystem.DeleteFile(fn_src) : Catch ex As Exception : End Try
    End Try
End Function
 
 
     一直以来,我都很喜欢听到建议,bug 修复,代码改进以及评论!

posted on 2008-12-17 17:21:59 by VBCTI  评论(0) 阅读(4192)

TableAdapter和多组数据结果

[原文作者]Young Joo                                          
       
     很多人问我TableAdapter是否能够从存储过程里读取多组数据结果。最直接的回答是:不能。你不能通过TableAdapter.Fill()方法来得到一个Dataset。但是我们可以通过另一种简单的方法来实现。
 
DataAdapter.Fill()和多组数据结果
 
     TableAdapter.Fill()方法通过调用DataAdapter.Fill()从数据库中读取数据。DataSet.Fill() 方法可以从存储过程里读取多组数据结果。为了获得多组数据结果,可以应用DataAdapter.Fill()的一个重载方法,它将Dataset作为参数,这样就可以把存储过程的多组数据结果返回给包含有多个表的Dataset
    这里,我们通过一个简单的示例来演示一下这种方法是怎样实现的:
    假设在Northwind数据库里有一个存储过程dbo.spSelectCustomersOrders
    CREATE PROCEDURE spSelectCustomersOrders
    AS
    BEGIN 
       SET NOCOUNT ON
       SELECT * FROM Customers 
       SELECT * FROM Orders
    END
   GO
     下面的代码调用了这个存储过程,并且把2组数据结果存储在Dataset里。
    Dim myConn As New System.Data.SqlClient.SqlConnection
    Dim myAdapter As New System.Data.SqlClient.SqlDataAdapter
    Dim mySelectCommand As New System.Data.SqlClient.SqlCommand
    Dim myDataset As New System.Data.DataSet

    myConn.ConnectionString = "Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated       Security=True"
    mySelectCommand.Connection = myConn
    mySelectCommand.CommandText = "dbo.spSelectCustomersOrders"
    myAdapter.SelectCommand = mySelectCommand
    myAdapter.Fill(myDataset)

    For Each table As System.Data.DataTable In myDataset.Tables
       Console.WriteLine("Table Name:" & table.TableName)
    Next
    代码的输出形式如下:
     Table Name: Table
     Table Name: Table1
    
     我们可以看到,DataAdapter.Fill()方法执行了存储过程,并且把2组数据结果分别存储在2个数据表里。
 
TableAdapter的解决方案
 
     然而,为什么TableAdapter.Fill()方法不能够正确地处理多组数据结果?那是因为TableAdapter.Fill()调用的DataAdapter.Fill()方法是以DataTable作为参数,而不是Dataset。这种情况,我们只需要在TableAdapter里创建一个新的Fill方法,令其调用以Dataset为参数的DataAdapter.Fill()方法。
     假设这里有一个包含CustomersOrdersNorthwindDataset.xsd文件。让我们用上面的存储过程来实现新的Fill方法。把下面的代码加到partial class文件里。(在Dataset Designer上,可以通过双击或者右键选择"View Code"来进入partial class,当然也可以手动创建一个空的class文件。)
    Namespace NorthwindDataSetTableAdapters
        Partial Public Class CustomersTableAdapter
            Public Function FillCustomersOrders(ByVal dataSet As NorthwindDataSet) As Integer 
                 Dim
multiSelectCommand As New System.Data.SqlClient.SqlCommand 
                 Dim returnValue As Integer 

                 
multiSelectCommand.Connection = Me.Connection 
                 multiSelectCommand.CommandText = "dbo.spSelectCustomersOrders" 
                 Me
.Adapter.SelectCommand = multiSelectCommand 
                 '' Map auto-created Table1 that holds the second result-set (Orders rows) to 
                 '' Orders DataTable in our Dataset. 
                 Me.Adapter.TableMappings.Add("Table1", "Orders"
                returnValue = Me.Adapter.Fill(dataSet) 

                Return
returnValue 
            End Function 
         End
Class
     End
Namespace
      有两点需要特别注意:
      首先,新的FillCustomersOrders是以Dataset为参数,这样当我们调用DataAdapter.Fill()方法时,数据结果就会准确地存储到Dataset里。
      第二,注意我们是怎样应用TableMapping将自动生成的数据表映射到Dataset里的Orders表。当应用DataAdapter.Fill()来读取多组数据结果,每一组数据结果都被单独地存储在Dataset的数据表里。默认情况下,这些数据表被命名为Table, Table1, Table2…,为了将这些数据标与Dataset里定义的数据表相对应,我们应用TableMapping。如果你打开NorthwindDataset.xsd后面的代码,在TableAdapter classInitAdapter()方法,你就会看到类似的代码:
    tableMapping.SourceTable = "Table"
    tableMapping.DataSetTable = "Customers"
    '' Colum mapping code skipped
     ...
     Me._adapter.TableMappings.Add(tableMapping)
 
      这段代码是为了保证DataAdapter.Fill方法返回的数据表与Dataset里的数据表相对应。在我们FillCustomersOrders示例里,第二组结果包含的是Orders信息,所以我们在Table1Orders之间创建了映射关系,确保数据FillOrders表中。
     把以上代码添加到partial class后,你就可以调用FillCustomersOrders方法来fill CustomersOrders
     CustomersTableAdapter.FillCustomersOrders(Me.NorthwindDataSet)
 
性能的考虑
 
      有些情况下,这种方法的确很有效。但是这也要看情况,也许你会想到这个方法可以避免多次访问数据库,从而提高性能,但如果仅仅只需要获取一小部分数据,却应用这种方法一次读取了大量的数据,这同样也会降低性能,倒不如一次读取小部分数据,需要其它数据时,与数据库建立另一个连接,再读取。ADO.NET在处理多个数据库连接方面性能优化得还是不错的,很多情况下,都不至于导致性能瓶颈。总之我们只需要遵循最基本的原则:只在需要的时候,才去读取数据。
     但有些情况下,读取多组数据结果还是很有帮助的,所以,应用我在这里所介绍的方法吧,但时刻也不要忘记性能的问题。
 
相关资源

posted on 2008-12-17 17:13:00 by VBCTI  评论(0) 阅读(4037)

Co/Contra-Variance:如何将一个Apple列表转化为Fruit列表

[原文作者]:Lucian
 
    这是我们如何在VB将来某一个版本中实现Co/Contra-Variance的一系列探索的第一项。 这并不是对下一代VB的承诺,而是作为一个提议放在这里,从而能够从我们潜在的用户那儿得到一些反馈。
 
Sub EatFruit(ByVal x As IEnumerable(Of Fruit))
...
 
Dim x As New List(Of Apple)
x.Add(New GrannySmith)
x.Add(New GoldenDelicious)
EatFruit(x)
' ERROR: cannot convert List(Of Apple) to IEnumerable(Of Fruit)
    观察一下以上的代码,或许你觉的它是没问题的。这是个很常见的情景:一个库函数处理一些data类型,但是你的自定义类型继承了这个data类型。如何能够将一个自定义类型的集合传给这个库函数呢?
    我们正在考虑为VB语言增加一个特征来支持这种转变,我们称之为“Co/Contra-Variance”,简称为“Variance”。实际上大概在2005年的时候,CLR已经支持“Variance”了,但并没有一种发布的语言用到它。但是一些其它的语言用到它,这里有一些链接,都是 关于这个主题的:
    我将谈一下VB中如何使用Variance:如何用它让你的代码更简单或清晰,如果我们实现了它能解决什么问题。Variance博大精深,apples转化为fruit只不过是它的寻常一功能而已,以上的文章更是让人觉得它复杂。但是我相信我们提议的语法和例子能够揭去这层神秘的面纱。
    昨天我用Variance解决了一个问题:
Function Call(instance As Expression, method As MethodInfo, arguments As IEnumerable(Of Expression)) As MethodCallExpression
...
 
' Create a new callsite that takes two arguments:
Dim args As New List(Of ConstantExpression)
args.Add(Expression.Constant("x"))
args.Add(Expression.Constant("y"))
'
Dim call1 = Expression.Call(instance, method, args)
' args inherits from IEnumerable(Of ConstantExpression), which
' variance-converts to IEnumerable(Of Expression)
 
    对应于第一段,我们转化为:
' some example classes to get us started
Class Food : End Class
Class Fruit : Inherits Food : End Class
Class Apple : Inherits Fruit : End Class
Class GrannySmith : Inherits Apple : End Class
Class GoldenDelicious : Inherits Apple : End Class
 
' GoldenDelicious < Apple < Fruit < Food
' using < in the mathematical sense of "is smaller than",
' and in the VB sense of "can be converted to"
 
Class AppleBasket
    Implements IReadOnly(Of Apple)
    Implements IWriteOnly(Of Apple)
End Class
 
out”参数
    我们想用关键字“out”和“in”来介绍Variance:
Interface IReadOnly(Of Out T)
    Function Read() As T
End Interface
' "Out" declares that T will only ever be used
' as return type of functions *
 
Dim x As IReadOnly(Of Apple) = New AppleBasket
Dim y As IReadOnly(Of Fruit) = x
 
Dim f As Fruit = y.Read()
' This is guaranteed not to throw InvalidCastException
 
    当接口的参数类型是“out”时,它表明这个类型只能用来返回(其他地方表明传出数据),如果试图调用“Sub(ByVal x As T)”,就会产生一个编译错误。(CLR如何使用Variance限制了很多设计,我们希望能和其他的.NET语言兼容。)
    正是这个“out”保证了CLR能够转化接口:
 
' GoldenDelicions < Apple < Fruit < Food < Object
 
Dim apples As IReadOnly(Of Apple) = New AppleBasket
 
' It is allowed to change to an IReadOnly of something bigger:
Dim fruits As IReadOnly(Of Fruit) = apples
Dim foods As IReadOnly(Of Food) = apples
Dim things As IReadOnly(Of Object) = fruits
 
' It is an ERROR to change to an IReadOnly that is smaller:
Dim golds As IReadOnly(Of GoldenDelicious) = apples
 
' Also an ERROR to change to something unrelated
Dim cars As IReadOnly(Of Car) = apples
 
    通常来说,如果你有一个泛型接口IreadOnly(Of Out T),然后你可以把“Of T”转换为它可以转化的其它类型。很显然,这是类型安全的:
    Variance转换是类型安全的和有效的,它只用一句中间语言指令来实现,不需要运行时Runtime检查。(这区别于数组:每次往数组里放东西,都得进行Runtime检查。)
    参数类型是“out”的接口被成为covariant。
In”参数
Interface IWriteOnly(Of In T)
    Sub Write(ByVal x As T)
End Interface
' "In" declares that T will only ever be used
' as ByVal arguments to functions.
 
Dim x As IWriteOnly(Of Apple) = New AppleBasket
Dim z As IWriteOnly(Of GoldenDelicious) = x
 
z.Write(New GoldenDelicious)
 
    “In”参数正好相反。当接口的参数类型是“In”时,它表明这个类型只能用于ByVal引用(其他地方表明传入数据),如果试图调用“Function f() as T”,就会产生一个编译错误。
    “In”参数保证了反向的类型转换:
' GoldenDelcious < Apple < Fruit < Food < Object
 
Dim apples As IWriteOnly(Of Apple) = New AppleBasket
 
' It is allowed to convert to an IWriteOnly of something smaller:
Dim golds As IWriteOnly(Of GoldenDelicious) = apples
 
' It is an ERROR to convert to something bigger, or unrelated:
Dim foods As IWriteOnly(Of Food) = apples
Dim cars As IWriteOnly(Of Car) = apples
 
 
 
    参数类型是“out”的接口被成为contravariant。
同时有“In”和“Out”
    直到20世纪90年代,人们仍然在为“In”或者“Out”是否是正确的而争论。现在我们知道了他们都是正确的!第一个在这方面有说服力的论据是1995年Giuseppe Castagna的研究论文"Conflict Without A Cause" [PDF]。
    这里有两个例子,说明他们为什么是正确的,以及将他们放在一起:
Class AppleBasket
 Implements IReadOnly(Of Apple)
 Implements IWriteOnly(Of Apple)
 
 Private m_value As Apple
 
 Public Function Read() As Apple Implements IReadOnly(Of Apple).Read
    Return m_value
 End Function
 
 Public Sub Write(ByVal x As Apple) Implements IWriteOnly(Of Apple).Write
    m_value = x
 End Sub
End Class
 
Pipes: 为内部和外部契约(contracts)用“In”和“Out”
' Here we implement a Pipe. Each element in the pipe is an ICollection.
'    IList < ICollection < IEnumerable
'
' When we give out reader ("Out") access to the public, we force it so
' readers can only ever assume that elements are IEnumerable.
' And when we give out writer ("In") access, we force it so
' that writers must always put in IList
'
' This future-proofs our code in TWO directions: it forces the
' implementation to provide IList in case in the future we want
' to expose more to the clients; but it does so without making
' a public commitment to the clients that future implementations
' would have to uphold.
 
Class MyPipe(Of T)
 Implements IWriteOnly(Of T)
 Implements IReadOnly(Of T)
 
 Private contents As New Stack(Of T)
 
 Public Sub Write(ByVal x As T) Implements IWriteOnly(Of T).Write
    contents.Push(x)
 End Sub
 
 Public Function Read() As T Implements IReadOnly(Of T).Read
    Return contents.Pop()
 End Function
End Class
 
 
    我们很希望能得到用户的反馈,从而帮助我们决定是否将这个功能加入VB语言,并且思考让他如何工作。请踊跃评论。
    在以后的几周里,我会新更多关于Variance的东西。
    另外:关于这边文正的标题,这是我们的设想:
Dim x As New List(Of Apple)
Dim y As List(Of Fruit) = x
'
' ERROR: List(Of Fruit) cannot be converted to List(Of Apple)
' Consider using IEnumerable(Of Fruit) instead.

posted on 2008-12-17 17:07:39 by VBCTI  评论(0) 阅读(3684)

往代码中插入Snippet的方法

[原文作者]:Lisa Feigenbaum
 
Code snippets是在Visual Basic 2005中引入的。它提供了一个简单的办法来处理特殊的编码任务或者将一段代码在应用程序中不同的部分重复使用。Code snippets能通过许多种不同的方法来插入。具体哪种方法根据不同的情况而定。 当我们需要浏览code snippet的时候,可以用Code Snippet inserter.
Code Snippet Inserter
      Code Snippet Insert 可以用两种方法触发:
·         在你的代码文件中输入?然后按Tab;
·         点右键,然后从出现的菜单中选择“Insert Snippet..”
      然后你就能根据snippet的目录结构,选择到你需要使用的Snippet。 可以注意到title旁边有tooltips显示对当前选择的snippet的描述和快捷键的。
       对那些你经常使用的或者需要快速获取的snippets,你可以使用snippets的快捷键。一旦你知道了snippet的快捷键,你就能在你的代码中通过键入快捷键+Tab来插入snippet. 要知道一个snippet的快捷键, 可以通过在code snippet inserter中选中一个snippet来查看或者直接在Code Snippets Manager中查看。
   Code Snippets Manager
  
  
     “快捷键+Tab”插入模式也有利于使用关键词来做为快捷键的情况。比如说,在你的代码中键入“Select+Tab”,你能看到如下的snippet被插入了。这种snippets能扩展。
An Expansion Snippet: Select Case
       如果你按了tab键并且完成了你不想要的一个snippet的输入,只要按“CTRL+Z”就能使你的代码回到之前的状态。
       对其他更多的面向任务的snippet,snippet快捷键以相应snippet路径的缩写开头。这样,因为snippet“Send an Email”在“connectivity”目录下,所以它的快捷键是“conEmail。注意:虽然大多数快捷键包含大写字母,为了能正确插入snippet,你输入的类型并不需要完全与相应的大小写类型匹配。 就是说,你输入“conemail”或者“CONEMAIL”都能使snippet正确插入。
       通常你只需要记住一部分的snippet的快捷键。那样的话,你一旦键入快捷键的开头部分,然后按?+Tab,一个完整的快捷键列表会显示出来。比如,为了插入一个Application目录下用“app”为前缀的snippet,你能键入“app+?+Tab”或者“a+?+Tab”来触发完整的快捷键列表。 快捷键列表呈现,当前选中的snippet的title显示在tooltip的最上部。然后你就能双击或者按回车键来将snippet插入你的代码。
     Snippet Shortcut List
       大多数我们讨论的snippet插入方法都涉及到某种键入,但是仍然有一种特殊情况。那就是:拖放。其实code snippet是以.snippet为扩展名的xml文件。一旦你安装了Visual Basic,这种文件就会被自动拷贝到你的电脑。Code Snippets Manager显示了那些snippet文件的路径,这样你就能在磁盘上找到它们。一旦你确定了路径比并且用windows Explorer将你的snippet文件放置于磁盘上,你就能简单地将Snippet拖拽到你的代码中。

posted on 2008-12-17 17:02:00 by VBCTI  评论(0) 阅读(3588)

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

[原文作者]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
愿你享受开发程序的乐趣!

posted on 2008-11-28 10:08:17 by VBCTI  评论(0) 阅读(6700)

正确操作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%正确。不过这确实是无论什么时候应用程序开始又一次的循环事件时的情况。循环事件(更加确切的说是当循环事件发生或没有发生的时候)也与我们的这篇讨论相关联。

posted on 2008-11-07 14:35:41 by VBCTI  评论(0) 阅读(3095)

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

posted on 2008-11-07 14:27:45 by VBCTI  评论(0) 阅读(2624)

Powered by: Joycode.MVC引擎 0.5.2.0