前面介绍了实现自己的搜索模块,我们已经能够从互联网上搜索到自己想要的音乐的地址了,所以这一篇文章主要介绍实现自己的下载的模块。对于下载模块,也没有做的很复杂,毕竟对于象迅雷那种很强劲的下载工具也不是很了解,所以只能在功能上模仿一个形似。

 

一 下载模块功能

 

上图右侧显示了下载模块的基本功能,支持多任务下载,并且支持断点续传。所以可以进行暂停,取消等控制。而下载并没有使用多线程下载,因为相对要复杂一些,所以目前只是单线程下载。对于MP3这种小文件来说速度还是不错的。

对于下载模块多任务实现,没有使用异步方法,或者是建立线程池去控制。而是使用了基于事件的异步模型。在.NET中基于此模型实现的控件就是BackGroundWork,他能很好的解决子线程和UI主线程间的通信,并且使用起来非常方便。但是他最大的问题是,他是以一个控件实现的,并且不支持多任务,所以我们想要使用他的话,每一个任务都需要使用一个控件,并且如果像编写一个与UI无关的下载模块,却引人一个UI控件,总是不太好,所以我们下载的核心就是自己实现一个可以支持多任务的BackGroundWork模块,用来管理下载。除了下载管理模块之外,我们还需要具体的下载功能以及下载后的文件管理功能。

继续阅读

上一篇文章已经写了有10个月之久了,那之后出差半年,都一直没时间继续。最近不太忙,终于有时间继续做下去,花了1周半的时间,把后续的下载功能实现了。这篇文章开始主要就介绍一下具体的实现。

 

一 程序结构

 

 

前一篇文章只是一个开头,介绍了搜索部分的大概结构,现在主要看一下整个程序的结构。程序主要的功能目前是搜索和下载。他们都已DLL的形式存在,供主程序调用。

以上是整个工程的结构。主要功能有两大块,搜索音乐和下载音乐。

  • ISearh:定义了一套接口,指定了搜索时的编码方式,搜索歌词的方式和搜索音乐文件的方式。如果工程引入并实现了这些接口,那么编译出来的DLL就能被我们的主程序直接使用了。这样可以方便的开发搜索插件。
  • MusicPiugin:这个目录下的工程就是搜索插件。我们搜索来源于百度网页,soso网页以及百度提供的快搜接口(只返回5首歌曲);而歌词是来源于千千静听提供的接口,所以我们一共有4个插件,他们都实现了ISearh下定义的接口。
  • MusicCrawler:这个项目中主要是封装了搜索时的网络请求操作,并且对ISearh下定义的接口进行编程,提供了搜索音乐和歌词方法。
  • MusicRunner:这个项目的主要作用就是负责加载搜索插件并提供搜索方法。其中引入了MusicCrawler工程的DLL,在加载了搜索插件DLL后,调用他的方法来实现具体的应约搜索。
  • MusicDownload:这个项目主要功能是负责文件的下载,其中对下载的网络请求进行封装,并且提供了基于事件驱动的多任务的调度和管理,并且包含了文件管理功能。
  • MusicCommon:这个项目定义了一些公用的方法,实体类,常量,枚举等类型。
  • Test :是一个测试DEMO,他只引用了MusicRunner、MusicDownload、MusicCommon这三个工程,而其他工程对他都是不可见的。并且在其中提供了简单的播放功能。

继续阅读

两篇文件介绍了.NET平台下Drag and Drop操作的原理以及整个拖拽的过程,还分析了拖拽过程中的数据的格式。本篇是这个小系列的最后一篇,主要是通过列子介绍.NET程序如何与Windows Shell之间进行双向的文件传递,以及如何修改拖动时的图标样式。

 

一 Windows Shell

 

可能有点奇怪,介绍Drag and Drop 怎么介绍到Shell上去了。虽然拖拽的数据对象可以是任意格式的,但是我们平时拖拽的最多的还是文件,文件夹这样的对象。打开文件,发送文件,移动文件,这样的操作我们在Windows中使用的太多了。而这些都和Shell有着密切的关系。这里就简单介绍一下,详细可以参见MSDN :Windows Shell

 

1.什么是Shell

 

Shell其实也是一种程序,如果接触过unix或Linux或许比较好理解。准确的说Shell是一个命令解析器,在Windows上我们输入Cmd,在出来的窗体中可以进行一些列的系统操作,启动程序、管理文件、设置系统服务等等;而同样我们也可以在Windows提供的图形界面中操作,比如打开我的电脑管理文件、打开控制面板设置计算机。这就是我们常见的两种Shell:图形界面Shell和命令行Shell。 Shell实际是介于操作系统内核与用户之间的一个接口。

 

 

2.Windwos Shell

 

这里我们主要了解的是图形界面的Shell。Windows UI为用户提供了访问各种对象、运行程序以及管理系统的能力。在访问的众多对象中,我们最熟悉的就是文件和目录,他们都是存放在硬盘上的;但是还有一些并不是真实存在的对象,比如远程打印机和回收站,他们并不是真正的存在于硬盘之上。Shell把这些对象组织为一套层次结构,提供给用户和程序使用和管理。

 

 

3. Shell编程

 

Windows Shell最常见的部分就是桌面和任务栏,Shell所管理的对象我们可以称之为Shell Object。我们前面提到过Shell Object,但是他并不是仅仅包含文件和目录,还包含那些虚拟的对象。桌面是所有Shell Object的根,也就是层次结构中最顶层的。

对于桌面来说,它也是一个窗体,实际就是一个ListView控件,所以在窗口拖动文件,和我们在自己的程序中拖动是没有本质区别的。而资源管理器Explorer也是一个程序,通过API获得Shell 的层次结构并显示,然后提供给用户进行操作。所以我们完全可以通过使用API,在自己的程序中实现简单的Sehll功能。也可以通过对Shell编程,实现自己的功能。

关于Shell编程可以参见:Windows Shell 编程

而在CodeProject上有一个C#实现的资源管理器:http://www.codeproject.com/KB/miscctrl/FileBrowser.aspx

继续阅读

在上一篇文章介绍了在.NET中进行Drag和Drop操作的方法,以及底层的调用实现过程。实际是通过一个DoDragDrop的WIN32 API来监视拖拽过程中的鼠标,根据鼠标的位置获得IDropTraget和IDropSource接口,对拖拽源和目标进行操作。但是拖拽的目的是进行数据的交换,在上一篇文章中对于发送和接受数据都是一笔带过,所以这一篇主要介绍Drag和Drop操作中的数据。

 

 

一 .NET中Drag和Drop时的数据传输

 

 

Drag和Drop的过程其实就是一个数据交换的过程,比如我们把ListView中的一条数据拖放到另一个ListView中;或者是把一个MP3拖放到播放器中;或者是拖动一段文字到输入框;甚至windows的资源管理器中,从C盘拖动一个文件到D盘,其实都是这样一个Drag and Drop的过程。

我们先来看看我们上一篇文章中ListView直接拖动的例子

//ListView1 拖动
private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
        {
            ListViewItem[] itemTo = new ListViewItem[((ListView)sender).SelectedItems.Count];
            for (int i = 0; i < itemTo.Length; i++)
            {
                itemTo[i] = ((ListView)sender).SelectedItems[i];
            }
            ((ListView)(sender)).DoDragDrop(itemTo, DragDropEffects.Copy);
        }


//ListView2 接收
private void listView2_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(ListViewItem[])))
            {
                ListViewItem[] files = (ListViewItem[])e.Data.GetData(typeof(ListViewItem[]));
                foreach (ListViewItem s in files)
                {
                    ListViewItem item = s.Clone() as ListViewItem;
                    listView2.Items.Add(item);
                }
            }
        }

我们看到ListView1动数据时,DoDragDrop方法的第一个参数就是一个Object型的,用来传送任何类型的数据;而listView2_DragDrop方法则用来接收数据,我们注意到typeof(ListViewItem[]),接收时指定了要接收的数据类型。可以看到我们例子中,DataSource和DataTarget之间传送和接受的数据时都是Object型。如果我们发送时的原始类型和接收时指定的类型不相符,就无法得到数据。

上面是比较好理解的,和我们定义方法中,使用Object类型传递各种类型的数据,方法中在进行数据类型的转换道理是一样的。不过这只是在我们自己的程序中,我们清楚数据源和数据目标之间要传递的数据类型,所以不存在问题。而对于两个程序之间进行数据交换就没有这么简单了,首先系统并不认识Object这样一个类型,其实就是即便有了一种通用的类型,接收方并不知道传送的数据原始类型,如果对仍和数据都进行转换,并不是一个好的办法。

继续阅读

最近可能有一个和DragDrop有关的项目,目前还没确定用C++还是C#做,我也在进行一些学习调查。所以就边学边写,来谈一谈在C#中的拖拽操作。这篇文章主要是介绍.NET中拖拽的实现。

 

 

 一 C#中Drap and Drop的用法

 

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            listView1.View = View.List;
            listView2.View = View.List;
        }


        private void listView1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.Copy;
        }

        private void listView1_DragLeave(object sender, EventArgs e)
        {

        }

        private void listView1_DragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.Copy;
        }

        private void listView1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                String[] files = (String[])e.Data.GetData(DataFormats.FileDrop);
                foreach (String s in files)
                {
                    ListViewItem item = new ListViewItem(s);
                    listView1.Items.Add(item);
                }
            }
        }

        private void listView1_ItemDrag(object sender, ItemDragEventArgs e)
        {
            ListViewItem[] itemTo = new ListViewItem[((ListView)sender).SelectedItems.Count];
            for (int i = 0; i < itemTo.Length; i++)
            {
                itemTo[i] = ((ListView)sender).SelectedItems[i];
            }


            //System.Runtime.InteropServices.ComTypes.IDataObject obj;
            //System.Runtime.InteropServices.ComTypes.FORMATETC formatEtc;
            //System.Runtime.InteropServices.ComTypes.STGMEDIUM stgMedium;
            //formatEtc = new System.Runtime.InteropServices.ComTypes.FORMATETC()
            //{
            //    cfFormat = 15,
            //    dwAspect =  System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT,
            //    lindex = 1,
            //    ptd = IntPtr.Zero,
            //    tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL
            //};

            //stgMedium = new System.Runtime.InteropServices.ComTypes.STGMEDIUM()
            //{
            //    pUnkForRelease = null,
            //    tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_HGLOBAL,
            //    unionmember = IntPtr.Zero
            //};
            //obj.SetData(formatEtc, stgMedium, true);
            ((ListView)(sender)).DoDragDrop(itemTo, DragDropEffects.Copy);
        }


        private void listView2_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(ListViewItem[])))
                e.Effect = DragDropEffects.Copy;
        }

        private void listView2_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(typeof(ListViewItem[])))
            {
                ListViewItem[] files = (ListViewItem[])e.Data.GetData(typeof(ListViewItem[]));
                foreach (ListViewItem s in files)
                {
                    ListViewItem item = s.Clone() as ListViewItem;
                    listView2.Items.Add(item);
                }
            }
        }
    }

上面是一段使用两个ListView显示信息的代码,其中ListView1接受我们拖动一个文件到他的窗体上,并显示文件路径;而ListView2是接受我们从ListView1中拖动文件路径,显示在自己的view中。程序的运行结果入下。

继续阅读

之前写过一篇介绍字符和编码的文章:<程序中的字符和编码> http://blog.csdn.net/cc_net/archive/2008/09/07/2896317.aspx,主要是从理论上介绍了,当时对于unicode介绍的比较简单,而在c#中用的很多,写程序经常碰到编码问题,所以这次主要介绍一下编码规则。

 

 

 一 编码和编码规则

 

这是一个比较不好理解的东西。我们都知道计算机只认识2进制的数字0和1,任何字符在电脑中都是以2进制的形式存储的。把字符转换为计算机认识的过程就叫做编码,经过编码的字符才能被计算机处理,这时经过编码后的字符称之为【内码】。从前面一篇文章可以看到字符编码经历了ASCII、ANSI、UNICODE这几个阶段。所以字符编码就是指定文字和内码之间转换的一种规则,更简单的可以理解为用几个字节去标识字符。而所以编码规则就是,字符在字节中的存储方式。 这个就好比邮政编码,有的国家是6位数,有的是5为数标识。对于6位数,那一位是表示省,那一个是表示市这是需要规定的,这也就是所以的编码规则。

 

 

二 BOM

 

 

在介绍UNICODE编码规则之前,需要先介绍一个概念,BOM——Byte Order Mark,就是字节序标记。因为USC-2和USC-4都是用多个字节来表示一个字符,所以就存在一个表示顺序的问题。是字节1+字节2表示,还是字节2+字节1来表示一个字符。

在UCS编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。

看了上面的介绍,我们就可以在UNICODE编码前加上一个BOM,就可以知道用那一种方法解码字符。

 

继续阅读

扯淡

 

WPF是下一代WINDOWS桌面程序开发的主流,而Silverlight也是微软在RIA上的重要一步,而且随着WIN PHONE7一起,也成了以后微软手机开发的主要工具。现在Silverlight也是火的不得了。加上FLEX,HTML5,现在的程序员也是越来越难当,每天都要被新技术,新概念,新名次不断的轰炸。虽然很多东西对于我们这菜鸟级的程序员根本用不上,也没有太多公司会跟风而上。毕竟技术只是为了更好的创造利润。但作为对新鲜事务有着强烈好奇心的我们来说,没空多了解小也是挺好的。

 

WPF/Silverlight已经出了了3年多了,应该说比较成熟了,随着WINDOWS7和.NET4.0的发布,我想以后的桌面应用程序以及WEB程序会想着更绚,更酷,更方便的方向发展。WINDOWS 7的绚丽效果我想用过以后就在也不想换回XP了吧。哈哈。软件已将从如何做软件到如何做好软件,而现在是要到如何做用户喜欢的软件了。我想一个漂亮的等待界面和一个简陋的等待提示相比,用户也不会在乎那1.,2秒的查询速度,而对于WEB软件,能像桌面程序一样操作一直是美好的梦想,随着RIA框架的涌现,现在越来越关注的也是交互和操作了。

 

稍微唠叨了下,WPF/Silverlight 09年就准备开始学习,当时也没什么资料,而且加上工作忙,一出差就给耽误了,不过大致看过一遍SDK的文档,还算有点点基础。现在在一看,3。0,4.0都出来了。而且书籍和相关资料也渐渐多了起来。自己也懒得边学边写,因为网上很多,没有重复的制造轮子啊。 把学习中收集到的一些很好的文章收录在这里,不定期更新:

 

相关资料

 

 

WPF和以往的WINFORM最大的不同就在于它的显示上,WINFORM是使用GDI/GDI+,而WPF则是使用了DirectX,而在WINDOWS7下,系统的图形显示相对于XP也有了很大的改性(其实是VISTA相对XP),在WIN7下有一个DWM(Desktop Windows Manager)。关于DWM的解释可以参见MSDN : http://msdn.microsoft.com/en-us/library/aa969540(VS.85).aspx

下面2个是关于WIN7图形架构和在DWM下GDI和DirectX的工作方式,关于GDI+和DirectX不熟悉的话可以大概了解下,我也只是在上一个项目中有所使用,才有了一定了解。对于学习WPF来说大概了解就行了。

深度解读 – Windows 7核心图形架构细致分析

Redirecting GDI, DirectX, and WPF applications

下面一遍文章介绍了WPF中的图形的呈现过程和内部操作,个人感觉很好,和上面2个文章有一些相关联的东西

一站式WPF–Window(一)

园子里别人写的学习笔记,很全了,快速入门。恩,没有必要在造轮子了。

WPF and Silverlight 学习笔记:索引页

半年没有写东西了。也就出差了半年。半年没有用C#,写了半年VC,呵呵。有时候其实有挺多东西写的,不过一忙起来人就懒了。简单的东西不想写,复杂的又写不了,哈哈。最近不是太忙,大概看了下COM组件模型,然后对DLL工作,库文件有了一些兴趣。因为工作和自己写的程序可以延伸到这些知识上,所以对DLL进行了一定的了解,而且对于直接开始编写C#应用程序的人来说,也很少会去了解这些,所以决定写下来。

 

 

 一 库文件

 

 

库文件是我们在接触编程时经常听到的词。那么什么是库文件。其实库文件是一种重用的思想。比如我们自己编写C#的程序,可能会把公用的一些方法放在一个名为Common的项目中,然后编译成DLL,其他项目引用这个程序集,就可以调用这个里面的方法,而不用自己去实现。在说的大一点,.NET中提供给我们使用的类,也是存放在.NET库中的。而整个WINDOWS系统,都是建立在库文件基础上的。

实在最初的时候是没有库文件的概念,所有功能都是自己实现的。为了提高效率,减少重复工作,就必须对代码重用。重用方法有很对,比如对于一些通用的方法的实现代码让编译器自己生成,但这个问题是,一旦方法变更会添加,编译器也必须修改了。另一个方法是把常用的方法编译成.obj模块文件。这样在编译的时候,去链接这些模块文件,就能实现代码重用。但是所有方法放到一个obj文件中,文件会很庞大,占用内存;如果为每个方法提供一个obj文件,那又会相当的麻烦。

于是就出现了静态库(static library),静态库是一个或多个.obj文件的集合。使用到这些.obj文件对应的函数时,只需要在连接时提供该静态库,而不需要列举用到的.obj文件。而连接器进行连接的时候,只会将程序中用到的函数对应的.obj文件连接到程序中。静态库的形式就是我们WINDOWS上常见的.lib文件。但静态库的确定是,每个程序使用时都会吧需要的obj文件拷贝到内存中,100个程序就有100个拷贝,这样对内存的浪费很大,而且如果静态库文件更新的话,程序也需要重新编译,进行连接。于是共享库(shared library,也叫做动态连接库)就出现了。首先他是被加载到公用的内存空间中,是所有程序共享的,所以对节省了内存;另外他在编译时没有象静态库那样,把代码直接连接到了程序中,只是插入了动态库的一些信息,供调用时使用。这样,我们就有可能直接修改动态库,而不需要重新编译程序。

动态库当然也有他的问题,比如DLL HELL。DLL Hell 是指当多个应用程序共享一个DLL或COM组件时所引发的一系列问题。比如一个应用程序安装一个新版本DLL,而该DLL与机器上的现有版本不向后兼容。这样就可能导致以前的引用程序引发异常。而.NET的强命名程序集就解决了这个问题,它通过程序集(DLL)名称、语言、版本、KEY来区分DLL,而不仅仅是只通过名字。也就能保证对个版本的DLL同时工作。

 

继续阅读

在公司上班,都用QQ音乐听歌,然后一天同事给了个网页听歌的。当然不是百度那些MP3搜索的。页面是一个播放器,边上一个搜索框,很方便。反正最经也不忙,想自己也来做个搜索歌曲的,输入歌名返回地址。毕竟去百度搜索还要多点几下,现在一下就搞定了。

 

一 实现思路

 

 

因为没有自己的音乐库,所以只能到网上去搜索。于是选择了百度和soso两个网站,利用WEB请求,对返回的结果进行分析,得到MP3地址。这就是最基本的实现思路。其实很简单,就是用到了.NET中HttpWebRequest类来操作,然后使用正则表达式分析返回的结果。有了基本思路,就要调查如何实现。如何去网上搜索MP3。我们平时去百度搜索,在地址栏中可以看到URL中带有参数,我们可以通过构造一个URL,提交请求,获得页面的HTML。然后从中得到MP3以及歌词。

以下就是整理的搜索URL的格式。具体结果可以在IE中看到。其中百度和soso是返回和页面HTML,也就是我们在IE中看到的页面;而百度快搜是返回一个XML文件,最多只返回5首歌曲信息;最后是利用千千静听的歌词服务器,获得歌词信息,然后通过歌词信息中的内容,构建URL去获取歌词文件,返回的是LRC文件。

从上面可以看到,尽管搜索页面和类容不同,但是都是:构建URL–请求–分析页面–取得地址 。所以我们可以吧相同的部分提取出来作为一个基类。下图就是最开始的结构:

实现1search

继续阅读

最近在学习多线程方面的东西,打算在自己的小程序中尝试使用。所以也看了不少文章。关于BackgroundWork,其实去年就用Reflector大概看过,但是没有太懂,呵呵,今天看相关文章正好又碰到,所以就仔细看看。

 

一 异步编程

 

在开始介绍BackgroundWork之前还是废话一下,说说异步编程。异步操作通常用于执行完成时间可能较长的任务,如打开大文件、连接远程计算机或查询数据库。异步操作在主应用程序线程以外的线程中执行。应用程序调用方法异步执行某个操作时,应用程序可在异步方法执行其任务时继续执行。

说到异步编程,就不能不说到多线程,他们之间的关系有时总是让人很迷惑。在学习的初期,你会觉得多线程和异步没有什么区别。我自己开一个新线程去执行一个任务和用异步执行一个任务感觉没什么区别。甚至分不清到底用什么。其实多线程是实现异步编程的一种方式,但不是实现异步编程的唯一方式。

如果学习过计算机体系结构就应该知道,CPU和外设之间通信有三种方式。比如请求打印机,当打印机没有准备好,这个时候就等待,直到打印机准备好,在执行操作,这个叫程序查询方式。但因为外设速度慢,浪费CPU时间;后来出现了硬件中断。在等待打印机的过程中,CPU分配给其他进程,当打印机准备好了,则发一个硬件中断,通知CPU打印。这样有效的提高了CPU的效率,但是频繁的中断和进程的切换,也降低了效率;于是就出现了DAM(Direct Memory Access),DMA最明显的一个特点是它不是用软件而是采用一个专门的控制器来控制内存与外设之间的数据交流,无须CPU介入,大大提高CPU的工作效率。在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU。

所以我们有多种实现异步的方式:利用多线程;IOCP 等,而IOCP就是使用了硬件的功能,虽然我们多起了一个线程,但是线程那I/O请求交给硬件驱动后就空闲了,回到线程池被其他调用。比如读取文件使用异步处理,需要把流修改为异步,并且调用Read的异步方法,这时候,线程把请求交给I/O就被回收了,流返回后在使用一个线程进行处理;如果只调用 Read异步方法,而流不支持异步,实际上是在内部用一个线程模拟了所谓的异步;最后就是流使支持异步,但使用同步的read方法,这个时候线程采用轮询的方法,等待操作完成。

继续阅读