

Code Soil
CODE SOIL - コードソイル [by Smalldust]
There are 36 Posts and 19 Comments so far.
Subscribe to Posts (Powered by feedburner)


CODE SOIL - コードソイル [by Smalldust]
There are 36 Posts and 19 Comments so far.
Subscribe to Posts (Powered by feedburner)
针对Microsoft Office系列的编程一直是比较麻烦的领域;这主要是因为Office里面大量的COM对象构成的庞大的体系结构让人望而生畏。
编写Outlook插件也是如此。虽然.Net的到来使得深入系统底层的RAD开发成为了可能,但是由于Outlook仍然是COM接口,要写Outlook插件,从根本上来说仍旧是编写Outlook COM插件的过程。凭借.Net和COM的互操作能力,才使得C#/VB.Net等语言编写的插件能够被Outlook等Office程序调用。
首先,无论是Outlook也好,Word也好,所有的Office应用程序都使用了共同的COM接口_IDTExtensibility2来与插件进行通信。因此,要开发一个Outlook插件(当然其他Office插件也一样),必须实现这个接口。这个接口定义在AddInDesigner Object Library当中,位于<drive>\Program Files\Common Files\DESIGNER\MSADDNDR.DLL文件中。在Visual Studio.Net当中,对这个COM接口进行了包装,包装后的Assembly名为Extensibility,该接口名为IDTExtensibility2。
下面就通过一个演示项目来说明一下。
在Outlook当中有一个BUG/设计缺陷,就是当回复、转发一封邮件的时候,这封回复/转发邮件的文字编码会自动变得和收到的邮件相同。例如,我经常从美国收到编码为US-ASCII的邮件,当我用中文添加一些评注,转发给中国人的朋友时,如果我忘记了手动把编码改成UTF-8或GB2312,邮件内容就会变成乱码。即便我在“新邮件选项”里面把新邮件的默认编码设置为UTF-8也没有用。
这种时候,如果有一个Outlook插件能够自动将所发送邮件的编码改成UTF-8有多好啊。于是,就有了下面的制作过程。
Step 1 创建Outlook插件项目
首先,在Visual Studio 2005(其他版本也可。这里以2005为例说明)中,新建一个Project,类型为Other Project Types > Extensibility > Shared Add-in,并在Application Host画面选择要使用这个Add-in的Office程序。这样一个Add-in Project就生成了。在这个Project当中,我们可以看到如下两个平时不常见的Reference被添加到了项目当中:
Step 2 实现IDTExtensibility2接口
打开Connect.cs文件,我们就可以看到VS已经帮我们创建好了一个叫做Connect的类,来实现IDTExtensibility2接口。这个接口和一般的Host/Add-in体系结构类似,内容如下:
void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom);
当COM add-in被连接到host应用程序时OnConnection事件将被触发。
简单实现上面几个接口的例子可以参考微软知识库的302901号文章。
Step 3 截获ItemSend事件 更改MailItem属性
实现了上面的接口,我们就要在其中添加代码了。由于我们的目的是在发送邮件时强制改变邮件的编码(encoding),因此只需要在OnConnection当中添加截获发送邮件的事件即可:
之后,我们就可以在事件函数中控制邮件的编码了:
在完成上面的代码之后,我们只需要打开Outlook,就可以测试这个插件的效果了。要确认该插件是否安装,可以在Outlook的菜单上选择Tools > Options > Other,然后按Advances Options按钮,COM Add-ins即可看到插件的列表,以及启用和禁用插件。
0 comments smalldust | .Net Programming | add in, interop, outlook
早在Java到来之际,程序员们都已逐渐接受并乐于接受String的这一特性:immutable。从C/C++转到.Net/C#的程序员们,在最初可能非常不适应把char[]和string分开对待,但是一旦习惯了就会觉得非常方便,尤其是直接以+进行连接,以及支持switch…case等。
这个时候问题来了,string真的是immutable的吗?
cbrumme的blog上给出了一个例子:
该程序的执行结果也许正在你的预料之中,输出的是类似MYCOMPUTERNAMElways immutable之类的字符串,也就是说原字符串的前面一部分被计算机名覆盖掉了。
对上面的程序,我们可以做出如下分析:
1,computerName和otherString 的文本相同,因此由于编译器的Interning的结果,二者其实指向同一个字符串,用Object.ReferenceEquals()可以验证其相等。
2,红色部分标出的Marshal指令,使得该string被marshal为一个unmanaged pointer(LPWSTR)传递给了GetComputerName函数;
3,GetComputerName函数直接改写了computerName指向的缓冲区,string的immutable特性即被破坏。
由此我们可以看到,在与Unamanaged代码进行交互操作时必须额外小心,因为从某种意义上来说Unmanaged代码权限更大,破坏力也就更大,也就更容易引起意想不到的问题。
那么,上面那段使用GetComputerName的代码中,对该函数的包装要如何改进呢?
首先,在使用一个API之前应该注意其各个参数的in, out性质,例如关于GetComputerName,MSDN上有如下一段:
很显然,lpBuffer应该是用来输出的缓冲区,因此不应该用string,而是用byte[],StringBuilder之类的类型与之对应;即便一定要用String,也绝对不能Marshal为LPWSTR/LPTSTR,而是Marshal为VBByRefStr,以确保Managed代码侧string的immutable性质。
此外,使用.Net中unsafe代码也可以打破String的immutable,这和使用Unmanaged代码的本质是相同的。
习惯了.Net编程,尤其是以前几乎没有用过Basic,Pascal,C/C++等“较古老”的语言的程序员,遇到PInvoke,尤其是COM interoperation的时候,往往是一头雾水不得要领。相信在在将来,一方面是从C#,.Net以及Java开始学习编程的人会越来越多,一方面整个Windows也逐渐往Managed平台迁移,懂得如何直接同Win32API打交道的程序员就会越来越少(当然绝对数量肯定还是很多的,至少比用DDK写驱动程序的多吧……)
但是,对于.Net程序员来说,虽然不用直接使用C++调用API,但是PInvoke的知识还是必不可少的,对于Handle,Unmanaged Thread,COM等等最基本的东西仍然是必修课。
前一段写程序时遇到这么一个PInvoke的实际例子:
某个C#写的WindowsForm中,要对资源DLL进行读取(Resource DLL,Win32格式的只含有资源的DLL)。其中要用到这么一个API函数:
其中,ENUMRESTYPEPROC,其定义为:
其中的LPTSTR,在C#里应该怎么写呢?我当时想也没想,既然是LPTSTR (Long Pointer to String),那肯定就是字符串了。于是就有了:
结果,这段代码在执行的时候出错了——用Marshal.GetLastWin32Error()查看,发现是ERROR_NOACCESS(998)。说明,程序访问了禁止访问的地址空间。经测试,其他几个参数:hModule是正确无误的DLL库的Handle,最后的参数也被设置为NULL,那么原因只能是这个strType了。
再回过头来仔细想想……该参数的类型是LPTSTR,在Unicode环境下也即LPWSTR,也即WCHAR * ——没错啊,不就是字符串么?
但是我们仔细看一下MSDN的说明,就会发现,Windows提供了一些标准的资源类型;例如RT_ICON,RT_STRING,RT_VERSION,……仔细看这些类型的定义就会发现它们都是数字。
查阅了相关资料才明白,lpszType这个参数,在值小于某个数值(例如256)的情况下应该被当作数字来处理;而在大于这个数值时,则作为指针,其值是内存中的地址值,也即应该将其当作指针处理。这种情况下,显然就不能用.Net的String类型来一概而论。正确的做法是,使用IntPtr,如果判断其值不是在256以下,再将其Marshal为指向字符串的指针,获取字符串。
结论:在.Net中,数据类型是“强类型”的,不同类型之间无法被强制转换,因此保证了类型安全;但是在C/C++等语言为基础的Win32中,类型却是可以任意变换的,各种Object“一切皆指针”,你可以把一个int* 当作char*,可以把HGDI当作HMODULE……没有人来保证这种变换的安全性,但是也正因此带来了灵活性。Windows的各种数据类型当中,很多类型都是名称不同,但是实际上的数据结构是相同的(例如关于Handle,就有HWND,HMENU,HINSTANCE,HHOOK等等许多种)。相反,同一个数据类型,时而代表数值,时而代表指针的例子也不少见。因此在处理Win32API的时候,必须特别注意这些特点。
有时,我们需在我们的程序中模拟鼠标的移动、点击等动作。——比如,一个再现用户操作的宏,或者一个演示操作方法的Demo程序。那么,我们在.Net中如何实现呢?
.Net并没有提供改变鼠标指针位置、模拟点击操作的函数;但是Windows API提供了。其中一个是SetCursorPos,在.Net里调用形式为:
该函数可以改变鼠标指针的位置。其中X,Y是相对于屏幕左上角的绝对位置。另一个函数是mouse_event:
这个函数更强一些,不仅可以设置鼠标指针绝对的位置,而且可以以相对坐标来设置。另外,该函数还可以模拟鼠标左右键点击、鼠标滚轮操作等。其中的MouseEventFlag是一个基于uint类型的枚举,定义如下:
利用这些这些函数,再配合一些其他的API函数,就可以自动寻找窗口、控件,用程序对其进行鼠标点击、键盘输入等等了。