乐者为王

Do one thing, and do it well.

使用Win32备份Foxmail的邮件

Foxmail是一个非常不错的邮件客户端,可惜在对邮箱内邮件的备份上做的不是很好,只能手工一封一封地将邮件导出,在邮件比较少时这样处理还可以应付,但假如有成百上千封邮件时还要这样处理显然是相当郁闷的。因此写了一个可以自动将某个邮箱中所有邮件导出为独立的eml格式邮件文件(该格式可以被OutLook邮件程序识别并打开)的小程序。

在Foxmail的安装目录下有一个mail文件夹,该文件夹中的每个子目录分别对应着一个邮件账号。进入某个帐号对应的文件夹,可以发现文件夹中有以in.BOX、out.BOX、send.BOX、spam.BOX和trash.BOX等文件,它们分别对应于收件箱、发件箱、已发送邮件箱、垃圾邮件箱和废件箱。根据VC++实现Foxmail邮件的批量导出一文可以知道每个邮件头以下面的16个字符开始:

1
2
3
0x10 0x10 0x10 0x10 0x10 0x10 0x10
0x11 0x11 0x11 0x11 0x11 0x11
0x53 0x0D 0x0A

知道了邮箱所对应存储文件的格式后,以下的事情就变的简单了。

  1. 获取要备份的文件夹路径;
  2. 遍历该文件夹,找出以BOX为后缀名的邮箱存储文件;
  3. 分析存储文件,将邮件导出为eml格式的邮件文本。

下面是需要用到的一些Win32 API函数:

创建非模态对话框

  • CreateDialog

获取目录信息

  • BROWSEINFO
  • LPITEMIDLIST
  • SHBrowseForFolder
  • SHGetPathFromIDList

遍历某个目录下的文件

  • SetCurrentDirectory
  • FindFileFirst
  • FindNextFile
  • FindClose

备份邮件

  • CreateDirectory
  • CreateFile
  • ReadFile
  • WriteFile
  • CloseHandle

还有,如果在对话框模版里用到了不一般的控件(比如说进度条),那么还需要

  1. 引用头文件commctrl.h;
  2. 并链接comctl32.lib;
  3. 在WinMain中创建对话框前调用InitCommonControls()方法。

代码下载

Windows Server 2008配置技巧

如何取消登录时要按Ctrl+Alt+Delete组合键

开始 -> 运行 -> gpedit.msc -> 计算机配置 -> Windows设置 -> 安全设置 -> 本地策略,点击“安全选项”,在右侧的框内找到“交互式登录:不要按CTRL+ALT+DEL” -> 启用。

如何取消关机时出现的关机理由选择项

开始 -> 运行 -> gpedit.msc -> 计算机配置 -> 管理模板,点击“系统”,在右侧的框内找到“显示关机事件跟踪” -> 禁用。

如何禁止远程用户的匿名访问

开始 -> 运行 -> regedit,定位到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa,修改右侧框内restrictanonymous的值为1。

如何在退出系统时清除最近打开的文档的历史

开始 -> 运行 -> gpedit.msc -> 用户配置 -> 管理模板 -> 开始菜单和任务栏 -> 在右侧的框内找到“退出系统时清除最近打开文档的历史” -> 启用。

如何取消必须输入密码登录系统

开始 -> 运行 -> rundll32 netplwiz.dll,UsersRunDll,选中要自动登陆的账户,取消“要使用本机,用户必须输入用户名密码”,输入该帐户的密码即可(前提是要关闭UAC)。

如何关闭UAC

控制面板 -> 用户帐户 -> 打开或关闭用户账户控制 -> 取消“使用用户账户控制(UAC)帮助保护您的计算机”。

如何关闭IE增强安全配置

控制面板 -> 管理工具 -> 服务器管理器 -> 在右侧的框内找到“配置 IE SEC”并打开 -> 禁用。

如何在Web应用的HTTP与HTTPS间的相互切换

对于HTTPS和HTTP的不同请求,Web容器会生成两个不同的session对象。因此,如果在同一个Web应用中只有部分页面使用SSL,要保证使用SSL的页面与不使用SSL的页面间的相互切换(也就是HTTPS请求与HTTP请求间的切换)会话保持连续,那么可以通过在访问的URL中传递sessionId来实现,也就是说在进入或退出HTTPS的URL上绑定一个sessionId。比如从HTTP切换到HTTPS时,URL为https://example.com/login.do;jsessionid=<%=session.getId()%>,从HTTPS切换到HTTP时为http://example.com/welcome.do;jsessionid=<%=session.getId()%>。这样Web容器会根据这个sessionId获取session对象,而不是生成新的session对象。就可以保证HTTP和HTTPS切换时会话不变。

由于在URL上绑定的sessionId容易被窃取,为了保证用户安全,会话认证时需要结合客户端IP地址。即当用户登录后,通过session.setAttribute("clientIp", request.getRemoteAddr())保存客户端的IP地址,在后继认证会话的合法性时判断客户端的IP是否是原先存储在session对象的clientIP属性的IP地址,如果不是则该会话是非法会话。

希望能在Java 7中看到的特性

1、支持@前缀的字符串,这样就不必再为到底是写几个反斜杠绞尽脑汁了。

2、支持String类型的switch。

3、支持静态equals方法的String类或扩展方法(Extension methods)。不明白为什么Java总是使劲地支持一些丑陋又复杂的语法。像比较两个字符串就偏偏要写成这种样式:

1
if (str1 != null && !str1.equals(str2))

难道就不能写成下面的格式吗?

1
if (str1 != str2)

1
if (!String.equals(str1, str2))

其中str1或str2中任何一个值为null时返回false。

4、对泛型进行一些适当的修正。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GenericsTest {

//    public void output(List<String> list) {
//        System.out.println("List<String>");
//    }

    public void output(List<Object> list) {
        System.out.println("List<Object>");
    }

    public static void main(String[] args) {
        GenericsTest gt = new GenericsTest();

        gt.output(new ArrayList<Object>());
        gt.output(new ArrayList<String>());    // 竟然不能通过编译,想不通!
    }
}

5、可以通过[]来访问List和Map。然后,下面的代码

1
2
3
4
List content = new LinkedList(10);
content.add(0, "Fred");
content.add(1, "Barney");
String name = content.get(0);

就可以写成

1
2
3
4
List content = new LinkedList(10);
content[0] = "Fred";
content[1] = "Barney";
String name = content[0];

还有可能的就是允许为列表使用数组初始化程序语法。例如:

1
LinkedList content = {"Fred", "Barney", "Wilma", "Betty"};

6、添加decimal或money关键字作为原生类型,或者使BigDecimal支持+,-,*,/等运算符。处理金融相关的数据时总是要使用到BigDecimal类型。可由于BigDecimal不支持运算符,在比较大小时就会很繁琐。例如,比较两个金额的大小:

1
2
3
4
BigDecimal zero = new BigDecimal(0);
if (amount.compareTo(zero) > 0) {
  return true;
}

7、支持ref/out关键字或支持返回Tuples。例如:

1
2
3
4
(int, int, int) transform(int x, int y, int z)

(r, g, b) = transform(x, y, z);
(l, a, b) = transform(transform(x, y, z));

当某个方法中有多个返回值或方法需要修改传递进来的String参数并返回修改后的值时,当前的Java语法处理起来就会比较麻烦。

8、重新整理简化I/O和Collection中的API,使之更易使用。

9、简单而强大的Date和Time实现,将java.util包中的Date和Calendar类合并成一个。

如何使文字与图标垂直居中对齐

在做网页的时候,经常会需要在某段文字前加上一个图标。然后就会发现增加的图标和文字的位置不齐,文字总是比图标低点。

微笑

解决的方法有两个:一个是设置图标的vertical-align为top;还有就是将margin-bottom设为-3px。

1
2
<img style="vertical-align: top" src="/uploads/smiley-smile.gif" border="0" />微笑
<img style="margin-bottom: -3px" src="/uploads/smiley-smile.gif" border="0" />微笑

实现自己的Java Annotation

Annotation类型的声明与一般的接口声明极其相似,区别只在于它在interface关键字前面使用“@”符号。下面就是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface Event {
}

public class EventAnonationTest {
    @Event
    public void clicked() {
    }
}

@Target指定可以应用Annotation类型的程序元素,以防止在其它程序元素中误用Annotation类型:

1
2
3
4
5
6
7
8
9
10
public enum java.lang.annotation.ElementType {
    TYPE,               // Class, interface, or enum (but not annotation)
    FIELD,              // Field (including enumerated values)
    METHOD,             // Method (does not include constructors)
    PARAMETER,          // Method parameter
    CONSTRUCTOR,        // Constructor
    LOCAL_VARIABLE,     // Local variable or catch clause
    ANNOTATION_TYPE,    // Annotation Types (meta-annotations)
    PACKAGE             // Java package
}

@Retention设置Java编译器处理Annotation类型的方式:

1
2
3
4
5
public enum java.lang.annotation.RetentionPolicy {
    SOURCE,     // Annotation is discarded by the compiler
    CLASS,      // Annotation is stored in the class file, but ignored by the VM
    RUNTIME     // Annotation is stored in the class file and read by the VM
}

@Documented指明需要在Javadoc中包含Annotation(缺省是不包含的)。使用@Documented的一个技巧就是指定Retention为RetentionPolicy.RUNTIME。这样,Annotation就会保留在编译后的类文件中并且由虚拟机加载,然后Javadoc就可以抽取出Annotation并添加到类的HTML文档中。

@Inherited定义了Annotation类型的修饰是否可以由被修饰类的子类继承。

wxWidgets Wizard for Visual Studio 2005/2008

花了点时间为VS2005/VS2008写了一个简单的wxWidgets Wizard。本来想写的更完善一些再发布出来的,只是实在没有多少心思再继续地写下去了。而且该向导程序已经基本可以满足我自己的要求了。不过,如果以后有时间的话还是会继续完善它的。以下是我原本打算要实现的一些功能:

  1. 支持创建对话框程序;
  2. 可以直接安装wxWidgets Wizard到VS2005中的安装程序;
  3. 集成wxWidgets Help文档到VS2005中;
  4. 实现代码智能提示功能;
  5. 可以在VS2005中直接编辑和编译XRC资源;
  6. 在向导过程中可以设置一些wxWidgets的高级特性(比如Menu Bar,Status Bar等)。

截图:

代码下载:https://github.com/dohkoos/wxwizard

如何调试Win32程序

方法一:使用OutputDebugString函数

函数的原型如下:

1
void OutputDebugString(LPCTSTR lpOutputString);

该函数会输出信息到系统的DEBUGER,输出结果可以使用工具DebugView观察。因为OutputDebugString的参数是字符串,而我们在实际使用过程中通常希望能像printf一样支持变参。下面的方法实现了这个效果:

1
2
3
4
5
6
7
8
9
10
void DebugString(LPCTSTR lpszFormat, ...)
{
    va_list args;
    TCHAR szText[1024];

    va_start(args, lpszFormat);
    wvsprintf(szText, lpszFormat, args);
    OutputDebugString(szText);
    va_end(args);
}

方法二:输出调试信息到Console上

1
2
3
4
FILE *stream;
AllocConsole();
freopen_s(&stream, "CONOUT$", "w", stdout);
printf("Hello, world!\n");

这里AllocConsole()用来打开Console,而freopen_s则把标准输出和Cosole关联。“CONOUT$”这个很关键。

C#开发BHO插件UrlTrack

最近忽然突发奇想,想统计一下我最经常上的网站是哪些,并且在这些网站上都停留了多久。为此决定写一个BHO插件来做这件事。

BHO(Browser Help Objects)是实现了特定接口(IObjectWithSite)的COM组件。开发好的BHO插件除了要在注册表中注册为COM Server外,还必须将它的CLSID在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects下注册为子键。每当浏览器[1]启动时,首先会在上述注册表位置查看是否有注册的BHO CLSID,如果有则分别创建一个实例,并对BHO实例进行初始化。BHO实例运行在浏览器的地址空间内,能对可访问的对象(如窗口、模块等等)执行任何操作,且因为它依附于浏览器的主窗口,所以其生命周期与浏览器实例的生命周期一致。下图演示了BHO的创建过程:

下面就来介绍一下如何开发BHO插件。首先创建一个C#项目,类型为Class Library。然后将Class1.cs改名为IObjectWithSite.cs,还要给IObjectWithSite添加两个功能:GetSite和SetSite。

1
2
3
4
5
6
7
Public Interface Iobjectwithsite
{
    [Preservesig]
    Int Setsite([Marshalas(Unmanagedtype.Iunknown)]Object Site);
    [Preservesig]
    Int Getsite(Ref Guid Guid, Out Intptr Ppvsite);
}

添加一个UrlTrack.cs文件,并且实现IObjectWithSite接口。使用BHO还需要添加两个引用SHDocVw.dll和MSHTML.dll,可以在Windows\System32目录下找到。

在IObjectWithSite.cs中,还需要为我们的程序指出IE的GUID,使得它可以挂接(attach)到IE上:

1
2
3
4
5
[
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")
]

另外,还需要给BHO程序分配一个GUID,这个可以通过System.Guid.NewGuid()方法得到。

1
2
3
4
5
[
ComVisible(true),
Guid("e90da13b-117a-4178-8111-0f712da09ff9"),
ClassInterface(ClassInterfaceType.None)
]

在UrlTrack.cs中,我们还需要写两个方法用来DLL注册和移除注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static string BHOKEYNAME = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects";

[ComRegisterFunction]
public static void RegisterBHO(Type type)
{
    RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHO_KEY_NAME, true);
    if (registryKey == null)
    {
        registryKey = Registry.LocalMachine.CreateSubKey(BHO_KEY_NAME);
    }

    string guid = type.GUID.ToString("B");
    RegistryKey bhoKey = registryKey.OpenSubKey(guid, true);
    if (bhoKey == null)
    {
        bhoKey = registryKey.CreateSubKey(guid);
    }
    // NoExplorer: dword = 1 prevents the BHO to be loaded by Explorer.exe
    bhoKey.SetValue("NoExplorer", 1);
    bhoKey.Close();

    registryKey.Close();
}

[ComUnregisterFunction]
public static void UnregisterBHO(Type type)
{
    RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHO_KEY_NAME, true);
    string guid = type.GUID.ToString("B");

    if (registryKey != null)
        registryKey.DeleteSubKey(guid, false);
}

接下来就是实现具体的统计功能了。考虑一下,当输入网址后,我们需要记录下网址以及当前的时间;当在同一浏览窗口中切换网址时,不仅需要记录下网址和当前时间,还要设置前一个浏览记录的结束时间;并且在关闭浏览器时,也要记下结束时间。所以在SetSite中需要挂载NavigateComplete2和OnQuit事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void NavigateComplete2(object pDisp, ref object URL)
{
    string url = URL as string;
    if (url.IndexOf("about:blank") >= 0)
    {
        return;
    }
    if (visitHists.Count > 0)
    {
        VisitHist currentHist = visitHists[visitHists.Count - 1];
        if (currentHist.VisitUrl != url)
        {
            currentHist.EndTime = System.DateTime.Now;
        }
        else
        {
            return;
        }
    }
    VisitHist newHist = new VisitHist();
    newHist.StartTime = System.DateTime.Now;
    newHist.VisitUrl = url;
    visitHists.Add(newHist);
}

private void OnQuit()
{
    if (visitHists.Count > 0)
    {
        VisitHist currentHist = visitHists[visitHists.Count - 1];
        currentHist.EndTime = System.DateTime.Now;
    }

    // 输出统计记录

开始编译,然后就可以在bin目录下找到项目的dll文件了。在Console中使用regasm /codebase "UrlTrack.dll"注册dll。打开注册表,在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects可以看到多出了一个子项{E90DA13B-117A-4178-8111-0F712DA09FF9}。

需要注意的是,需要将AssemblyInfo.cs文件中的ComVisible属性设为true,否则在注册BHO时会得到这样的信息:

1
RegAsm : warning RA0000 : No types were registered.

更多的BHO资料可以看Browser Extensions

[1] 在Windows操作系统上有两种浏览器:资源浏览器(Explorer.exe,应用于文件系统)和Internet浏览器(IEXPLORE.EXE,应用于互联网资源)。

代码下载:https://github.com/dohkoos/UrlTrack

XRC和动态子菜单

1、什么是XRC

XRC是基于XML的资源系统。它的基本出发点是将界面布局和程序逻辑分离,即将界面布局代码保存在分离的XML文件中,在程序中不涉及控件的创建和布局,只需要加载相应的资源并处理事件绑定即可。

2、XRC文件格式

1
2
3
4
5
6
<?xml version="1.0"?>
<resource version="2.3.0.1">
    <object class="wxFrame" name="ID_MAIN_FRAME">
        <size>200, 300</size>
    </object>
</resource>

3、XRC文件中菜单资源的相关属性

属性 描述
wxMenuBar style Menu bar style: wxMB_DOCKABLE
wxMenu style Menu style: wxMENU_TEAROFF
label Text: label of the menu.
help Text: displayed help string.
wxMenuItem style Menu style: wxMENU_TEAROFF
label Text: label of the menu.
accel Text: accelerator associated to this item ( Alt-X for example ).
help Text: displayed help string.
radio bool value(0/1): 1 if this item is a radio menu item.
checkable bool value(0/1): 1 if this item is a check menu item.
enabled bool value(0/1): 1 if this item is initially enabled.
checked bool value(0/1): 1 if this (check) item is initially checked.
bitmap Text: path to a bitmap to draw at the left of the item.

4、使用XRC创建菜单

创建一个包含菜单布局信息的XML资源文件MenuBar.xrc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8" ?>
<resource version="2.3.0.1" xmlns="http://www.wxwindows.org/wxxrc">
    <object class="wxMenuBar" name="ID_MENUBAR">
        <object class="wxMenu" name="ID_MENU_FILE">
            <label>&File</label>
            <object class="wxMenuItem" name="wxID_CLOSE">
                <label>E&xit</label>
                <accel>Ctrl+Q</accel>
                <help>Quit the application</help>
            </object>
        </object>
        <object class="wxMenu" name="ID_MENU_VIEW">
            <label>&View</label>
        </object>
        <object class="wxMenu" name="ID_MENU_TOOLS">
            <label>&Tools</label>
            <object class="wxMenuItem" name="wxID_OPTIONS">
                <label>&Options...</label>
            </object>
        </object>
        <object class="wxMenu" name="ID_MENU_HELP">
            <label>&Help</label>
            <object class="wxMenuItem" name="wxID_CHECKFORUPDATES">
                <label>Check for &Updates...</label>
            </object>
            <object class="separator" />
            <object class="wxMenuItem" name="wxID_ABOUT">
                <label>&About...</label>
            </object>
        </object>
    </object>
</resource>

加载资源文件:

1
2
3
4
5
6
bool MainApp::OnInit()
{
    wxXmlResource* pResource = wxXmlResource::Get();
    pResource->AddHandler(new wxMenuBarXmlHandler);
    pResource->AddHandler(new wxMenuXmlHandler);
    pResource->Load(wxT("resources/MenuBar.xrc"));

初始化资源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
MainFrame::MainFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
    m_menuBar = NULL;
    InitMenuBar();
}

bool MainFrame::InitMenuBar()
{
    if (m_menuBar)
    {
        SetMenuBar(NULL);
        delete m_menuBar;
    }
    // Initialize the resource system
    m_menuBar = wxXmlResource::Get()->LoadMenuBar(wxT("ID_MENUBAR"));
    if (!m_menuBar)
    {
        wxLogError(wxT("Cannot load main menu from resource file"));
        return false;
    }
    SetMenuBar(m_menuBar);
    return true;
}

5、在XRC菜单上添加动态子菜单

在资源文件中添加一个新的菜单项(wxID_LANGUAGES):

1
2
3
4
5
6
7
8
9
10
11
<object class="wxMenu" name="ID_MENU_VIEW">
    <label>&View</label>
    <object class="wxMenu" name="wxID_LANGUAGE">
        <label>&Language</label>
        <object class="wxMenuItem" name="wxID_LANGUAGES">
            <label>Get Additional/Update language pack</label>
            <help>Downloading Additional/Update language pack</help>
        </object>
        <object class="separator" />
    </object>
</object>

创建动态子菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Initialize the resource system
m_menuBar = wxXmlResource::Get()->LoadMenuBar(wxT("ID_MENUBAR"));
if (!m_menuBar)
{
    wxLogError(wxT("Cannot load main menu from resource file"));
    return false;
}
/*
 * 这里很奇怪,在XRC文件wxID_LANGUAGE的类型明明是wxMenu,可在这里确只能用wxMenuItem。
 * 查看wxWidgets源代码发现XRC系统只会把最上层的class为wxMenu的object创建为wxMenu对象。
 * 其它的则都被创建成了wxMenuItem对象。
 */
// 使用XRCID方法获取控件ID,创建动态子菜单
wxMenuItem* menuItem = m_menuBar->FindItem(XRCID("wxID_LANGUAGE"));
if (menuItem)
{
    wxMenu* subMenu = menuItem->GetSubMenu();
    subMenu->AppendRadioItem(wxID_LANGUAGE_LOWEST + 1, wxT("English"));
    subMenu->AppendRadioItem(wxID_LANGUAGE_LOWEST + 2, wxT("Chinese(Simplified)"));
}
SetMenuBar(m_menuBar);
return true;

代码下载