月度存档: 十一月 2009

一个自动还原短网址的 Chrome 插件 AutoExpander

上 twitter 时,为了节省消息内容的长度,通常都会把网址用一些网址缩短服务进行简化,例如 bit.ly、is.gd 等,但是在有些时 候可能会碰到打不开缩短后网址的情况,例如连接 bit.ly 失败等,这个时候可以通过第三方的服务来将地址还原,但是这样会比较麻烦,要五个步骤才能 看到原来的地址,现在使用 AutoExpander 插件后,只需要直接输入短地址,插件会自动调用第三方的服务来进行短地址的还原,将“复制短地址、 粘贴到还原短地址的服务网站的输入框、点击还原、拷贝原地址到 Chrome 地址栏、回车”这样五个步骤直接缩短为“粘贴短地址到 Chrome 地址 栏、回车”这样两个步骤。当然,如果你的默认浏览器就是 Chrome,那么可以更方便地,直接点击短地址就行了,连“回车”这一步都省了。

插 件项目地址:

http://autoexpander.googlecode.com/

直接安装:

http://autoexpander.googlecode.com/files/autoexpander-0.3.crx

请 使用 Chrome 4.0 测试版安装:)

慎用 script 节点的 src 属性来传递参数

在有些使用 javascript 来渲染数据的时候,为了能动态获取不同的数据,并且保持 javascript 代码的可扩展性,会将 javascript 代码中获取数据的部分需要的参数提取出来,做为参数放在 script 节点的外部。

一般来说,传递参数到 javascript 文件内部的方法有两种,一种是将参数写在一个 script 节点中,写成全局变量的方式的传递给紧接着这个 script 节点的外部 javascript 中,Google Analytics 就是使用这样的方式:

<script type="text/javascript">
var p1 = "v1", p2 = "v2";
</script>
<script type="text/javascript" src="foo.js"></script>

另外一种是将参数直接写在 script 节点的 src 属性中,相当于一个页面的查询字符串一样:

<script type="text/javascript" src="foo.js?p1=v1&p2=v2"></script>

不过,使用 script 节点的 src 属性来传递参数需要注意一个很重要的问题,那就是动态变化的 src 属性会导致缓存失效

现在,为了网站性能的需要,一般都会将 javascript 文件放在独立的服务器上,并设置一个较长的过期时间,这样客户端只会在第一次访问网站时需要去下载这个 javascript 文件。但是,如果使用 src 来传递参数,就可能会使这种缓存策略失效。特别是 src 中存在动态参数的情况,例如统计脚本中如果有一个 ip 参数,那么访客每次连上线时,可能 ip 都会不同,就会导致 javascript 缓存失效了。

解决这个问题的方法也很简单,简单地的将 src 属性中的参数放到 script 节点的一个自定义属性中就可以了,例如 data-args,而 src 属性只需要保留一个时间戳就可以了。因为使用 src 属性来传递参数本来就需要定位 script 节点,所以改由 data-args 自定义属性来传递参数并不会增加额外的代码。只不过页面会通不过 w3c 的验证罢了 :)

&lt;script type="text/javascript" src="foo.js" data-args="p1=v1&amp;p2=v2"&gt;&lt;/script&gt;

再次提醒,慎用 script 节点的 src 属性来传递参数 :)

REALbasic 中使用结构体作为 Win32 API 的参数及使用 Win32 API 停止服务

在之前,我使用 ShellExecute 这个 API 来执行命令(《REALbasic 中使用 ShellExecute 执行命令》),然后通过这个方法来停止某个服务,但是今天想加服务运行状态检测,这样就可以在服务没有运行的情况下不再询问用户是否需要停止某个服务。

为了省事,我一开始决定同样使用 cmd 去执行一个命令,将服务状态输出到一个临时文件中,再通过读取这个临时文件,查找特征字符串来判断服务是否运行:

// 伪代码
ShellExecute("cmd.exe /c sc query service_name &gt; tmpfile")
dim serviceStatus as string
serviceStatus = TextInputStream.Open(tmpfile).ReadAll()
if instr(serviceStatus, "STOPPED") &lt; 1 then
  // 提示用户是否停止服务
end if

但是这样做不太靠谱,因为 sc 这个命令执行是要时间的,而 ShellExecute 是异步的,这就导致了在调用 ShellExecute 执行完 sc query 之后,临时文件 tmpfile 里并不是马上就有服务状态的内容了。

为了防止这个情况,我再加了一个临时文件内容的检测,如果为空的话,sleep 100ms,再继续读取,如果超过 10 次仍没有内容,直接当作服务正在运行来对待。

// 伪代码
ShellExecute("cmd.exe /c sc query service_name &gt; tmpfile")
dim serviceStatus as string
dim tryCount as integer
while tryCount &lt; 10 and serviceStatus = ""
  tryCount = tryCount + 1
  App.SleepCurrentThread(100)
  serviceStatus = TextInputStream.Open(tmpfile).ReadAll()
 
wend
if instr(serviceStatus, "STOPPED") &lt; 1 then
  // 提示用户是否停止服务
end if

但是这样仍然不能百分百保证正确,于是想到可不可以继续用 win32 api 来做这些,Google 了一下,找到一篇博客[1],看了之后发现如果只需要停止服务的话,还是蛮简单的,决定使用 win32 api 来实现我想要的功能了。

在停止服务这个过程中需要用到的 win32 api 有:OpenSCManagerW、OpenServiceW、QueryServiceStatus、ControlService,还有一个结构体:SERVICE_STATUS。

win32 api 的参数都很好对应,数值、句柄类型的参数直接使用 Integer 即可,字符串类型使用 CString,而 SERVICE_STATUS 这个结构体也可以直接通过工程菜单添加。

在传参的时候需要注意两点:

  1. 如果是字符串类型的参数并且调用的接口是 Unicode 类型的,那么需要在传入参数之前,将参数值转换为 Unicode 编码。可以通过 ConvertEncoding(text, Encodings.UTF16) 来转换。
  2. 如果参数是结构体,那么参数前的传参方式就要写 ByRef,也就是按引用传值,字符串和数值类型的参数可以不写或者写 ByVal。

以下是 4 个 win32 api 的 REALbasic 定义:

soft declare function OpenSCManagerW Lib "advapi32.dll" _
(ByVal lpMachineName As CString, ByVal lpDatabaseName As CString, _
ByVal dwDesiredAccess As Integer) As Integer
 
soft declare function OpenServiceW lib "advapi32.dll" _
(ByVal hSCManager As Integer, ByVal lpServiceName As CString, _
ByVal dwDesiredAccess As Integer) As Integer
 
soft declare function QueryServiceStatus lib "advapi32.dll" _
(byval hService as integer, byref lpServiceStatus as SERVICE_STATUS) _
as boolean
 
soft declare function ControlService lib "advapi32.dll" _
(byval hService as integer, byval dwControl as integer, _
byref lpServiceStatus as SERVICE_STATUS) as boolean

有了 api 定义之后,就可以直接根据上面提到的 vc++ 流程来停止某个服务了。

完整的服务操作 api 可以参阅微软的 MSDN。

这样看来,REALbasic 要写出支持 Windows 7 特性的应用程序也不是难事:)

参数资料

  1. VC++启动和停止服务, huangchonghai
  2. QueryServiceStatus, MSDN

Chromium Updater for Mac

图片附件为了随时更新到 Chromium 的最新 nightly build,又不想每次去 Chromium 的网站下,还有解压,移动,麻烦……为了省事,直接用 REALbasic 写了一个 Chromium Updater,专门用来更新 Chromium。目前只支持 Mac :)

详细说明

下载(使用右键下载):ChromiumUpdater (3.5M)

图片附件

关于 REALbasic 中使用 AutoDiscovery 时发生错误 40 的问题

今天在使用 AutoDiscovery 发送数据时,发生了错误码为 40 的错误,直接在 UDPSocket 的属性列表里找了一下,没有找到对应的错误码。

在网上搜了一下,找到这篇帖子,里面提到在发送大量数据时,UDPSocket 就会报错误,并且这个错误没有在文档中提到,因此这应该是一个系统级别的错误。

MonkeybreadSoftware 提到了 unix 的错误码定义:

#define    EMSGSIZE    40     /* Message too long */

联想到在程序中是在今天开始有问题的,而且今天添加了一大堆数据,看来的确是同于 UDP 消息过大造成的错误 40。

在网上找了找,使用 UDP 发送消息时,报文的大小最好不要超过 MTU,否则会就容易丢包。我在测试时是使用的 127.0.0.1,包大小为 12.5K,照理说应该是直接走 loopback 而不需要走路由的,就算超过 MTU 也应该能发送,不清楚是不是因为 OS 内部实现机制的问题。

为了彻底解决这个问题,最后还是采用了 TCP 来传递大量数据,只使用 UDP 来传递一些控制信息。