慎用 script 节点的 src 属性来传递参数
Nov 13th
在有些使用 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 的验证罢了 :)
<script type="text/javascript" src="foo.js" data-args="p1=v1&p2=v2"></script>
再次提醒,慎用 script 节点的 src 属性来传递参数 :)
REALbasic 中使用结构体作为 Win32 API 的参数及使用 Win32 API 停止服务
Nov 10th
在之前,我使用 ShellExecute 这个 API 来执行命令(《REALbasic 中使用 ShellExecute 执行命令》),然后通过这个方法来停止某个服务,但是今天想加服务运行状态检测,这样就可以在服务没有运行的情况下不再询问用户是否需要停止某个服务。
为了省事,我一开始决定同样使用 cmd 去执行一个命令,将服务状态输出到一个临时文件中,再通过读取这个临时文件,查找特征字符串来判断服务是否运行:
// 伪代码 ShellExecute("cmd.exe /c sc query service_name > tmpfile") dim serviceStatus as string serviceStatus = TextInputStream.Open(tmpfile).ReadAll() if instr(serviceStatus, "STOPPED") < 1 then // 提示用户是否停止服务 end if
但是这样做不太靠谱,因为 sc 这个命令执行是要时间的,而 ShellExecute 是异步的,这就导致了在调用 ShellExecute 执行完 sc query 之后,临时文件 tmpfile 里并不是马上就有服务状态的内容了。
为了防止这个情况,我再加了一个临时文件内容的检测,如果为空的话,sleep 100ms,再继续读取,如果超过 10 次仍没有内容,直接当作服务正在运行来对待。
// 伪代码 ShellExecute("cmd.exe /c sc query service_name > tmpfile") dim serviceStatus as string dim tryCount as integer while tryCount < 10 and serviceStatus = "" tryCount = tryCount + 1 App.SleepCurrentThread(100) serviceStatus = TextInputStream.Open(tmpfile).ReadAll() wend if instr(serviceStatus, "STOPPED") < 1 then // 提示用户是否停止服务 end if
但是这样仍然不能百分百保证正确,于是想到可不可以继续用 win32 api 来做这些,Google 了一下,找到一篇博客[1],看了之后发现如果只需要停止服务的话,还是蛮简单的,决定使用 win32 api 来实现我想要的功能了。
在停止服务这个过程中需要用到的 win32 api 有:OpenSCManagerW、OpenServiceW、QueryServiceStatus、ControlService,还有一个结构体:SERVICE_STATUS。
win32 api 的参数都很好对应,数值、句柄类型的参数直接使用 Integer 即可,字符串类型使用 CString,而 SERVICE_STATUS 这个结构体也可以直接通过工程菜单添加。
在传参的时候需要注意两点:
- 如果是字符串类型的参数并且调用的接口是 Unicode 类型的,那么需要在传入参数之前,将参数值转换为 Unicode 编码。可以通过 ConvertEncoding(text, Encodings.UTF16) 来转换。
- 如果参数是结构体,那么参数前的传参方式就要写 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 特性的应用程序也不是难事:)
参数资料
- VC++启动和停止服务, huangchonghai
- QueryServiceStatus, MSDN
Chromium Updater for Mac
Nov 7th
为了随时更新到 Chromium 的最新 nightly build,又不想每次去 Chromium 的网站下,还有解压,移动,麻烦……为了省事,直接用 REALbasic 写了一个 Chromium Updater,专门用来更新 Chromium。目前只支持 Mac :)
下载(使用右键下载):ChromiumUpdater (3.5M)

关于 REALbasic 中使用 AutoDiscovery 时发生错误 40 的问题
Nov 2nd
今天在使用 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 来传递一些控制信息。
在 AIR 中突破同源策略访问 iframe 中的内容
Oct 26th
本篇所讲关于 AIR 的内容是基本 HTML+JS,而不是 Flex 或其他。
在 AIR 中,如果当前页面有一个 iframe,并且我们需要获取这个 iframe 的内容,但是如果 iframe 里包含的是远程页面,在默认情况下,浏览器策略并不允许这种情况,这就是同源策略的限制。
在 Titanium 这个类似于 AIR 的平台中,默认情况下是可以直接从应用的页面中去读取 iframe 中的内容而没有任何限制,但是在 AIR 中不可以。在网上搜索了一番,再看了好几遍 AIR 的文档之后,终于找到解决问题的方法了。
在 AIR 中,iframe 标签拥有额外的几个属性:sandboxRoot、documentRoot、allowCrossDomainXHR 及 ondominitialize。这里为了解决读取 iframe 内容所用到的属性就是 sandboxRoot 及 documentRoot。
通过 sandboxRoot 及 documentRoot,可以将本地的页面映射为远程页面,并在沙盒中运行,另外,在沙盒中运行的页面就会拥有映射域名的域。例如,我们可以将本地的 proxy.html 映射为 http://example.com/local/proxy.html,这样,在实际运行时,proxy.html 中如果用 document.domain 获取页面所在的域,就会得到 example.com,这个时候如果在 proxy.html 中添加一个 iframe,这个 iframe 指向我们实际需要获取内容的地址,因为域相同,就可以直接获取 iframe 的内容了。
例如,我们要在 AIR 中获取 http://example.com/member/login.html 的内容,或者操作这个页面的元素,就可以先用一个 proxy.html,用来实现将本地文件运行在沙盒中,再用一个 login.html 来对实际网页进行操作。
<!– proxy.html –>
<iframe src=”http://example.com/local/login.html” id=”frame” width=”100%”
sandboxRoot=”http://example.com/local/”
documentRoot=”app:/” name=”frame” height=”100%”></iframe>
<!– login.html –>
<iframe id=’f' frameborder=”0″ scrolling=”no”
src=”http://example.com/member/login.html”
width=”100%” height=”100%”></iframe>
<script type=”text/javascript”>
alert(document.domain);
var f = document.getElementById(‘f’);
f.onload = function() {
alert(f.contentWindow.document.documentElement.innerHTML);
};
</script>
在 proxy.html 中,虽然 iframe 的 src 属性是 http://example.com/locale/login.html,但是由于这个 iframe 具有 sandboxRoot 属性,所以实际请求会被重定向到相对于 sandboxRoot,并且使用 documentRoot 进行定位的实际地址 app://login.html,但是这个时候 login.html 中具有 document.domain=”example.com” 这个属性,所有可以直接使用 iframe.contentWindow 来访问 iframe 中的内容。
虽然解决方法有些麻烦,但终归可以实现突破同源策略的限制来读取 iframe 的内容了。
当然,如果你有更好的方法,请不吝赐教:)
使用 arguments.caller 实现自动回调
Oct 25th
这是一篇介绍类似于 js hack 的文章,只是说明了一种可行的途径,并且可能会增加代码复杂程度,具体项目中是否可以使用还请自辨:)
在前端开发过程中,有许多业务流程可能是需要用户进行登录的,并且登录过程是放在弹出层中,这样就可以不用刷新页面,增强用户体验。在登录时,用户的操作就会被打断,为了进一步增强用户体验,我们可能需要在登录完成后自动继续进行用户在登录前想进行的操作。
假设有这样一个场景,用户需要发表一个留言,但是发表留言是需要登录的,而发表留言的输入框是一直显示的,这也就要求在用户点击了发表按钮时对用户登录状态进行验证,传统的做法是将在用户登录状态检查封装在一个函数之中,这个函数接收一个回调参数,如果登录验证通过,则执行回调函数。这样的逻辑可以用以下代码表示:
function doAction() {
checkLogin(function() {
// 处理业务逻辑
});
}
function checkLogin(callback) {
if (isLogin) {
callback();
} else {
showLogin(callback);
}
}
function showLogin(callback) {
document.getElementById(“login-btn”).onclick = function() {
isLogin = true;
callback();
};
}
总觉得这样的方式会将业务逻辑放到一个匿名函数中,而不是放在了按钮的事件响应函数中,感觉上不是那么好。这对整个事件响应函数的改动比较大,主要的业务逻辑都放在了登录验证函数的回调中。而希望可以是下面这样:
function doAction() {
// 直接在函数开始进行登录验证,将业务逻辑独立出来
if (!doLogin()) {
return;
}
// 处理业务逻辑
}
function doLogin() {
if (isLogin) {
return true;
} else {
var callback = function() {}; // 自动组装 callback
showLogin(callback);
return false;
}
}
function showLogin(callback) {
document.getElementById(“login-btn”).onclick = function() {
isLogin = true;
callback();
};
}
这里的关键在于怎么“自动组装 callback”,很幸运的是,JavaScript 的 Function 提供了一个属性 caller 可以用来获取调用当前函数的函数是什么。并且还可以用 caller 的 arguments 属性来获取 caller 执行时的参数是什么,这也就使我们自动组装 callback 并恢复 caller 的原参数成为可能。
<div id=”result”></div><input type=”button” value=”Sumit” onclick=”sendPost()” id=”post” /><input type=”button” value=”Clear login” onclick=”isLogin=false” id=”reset” /><div id=’login’ style=”border:1px solid #000;padding:10px;margin:10px;display:none”><strong>Login</strong><br /><input type=”button” id=’loginbtn’ value=”Login” /></div><script type=”text/javascript”>// 是否登录var isLogin = false;// 检查登录状态var checkLogin = function(cfg) {if (isLogin) {return true;}cfg = cfg || {};var callback = null;// 自动执行回调if (cfg.autoCallback && arguments.callee.caller) {try {var caller = arguments.callee.caller,args = caller.arguments || [],scope = cfg.callbackScope || {},newArgs = [];for (var i = 0; i < args.length; ++i) {newArgs.push(args[i]);}callback = caller ? function() {try {caller && caller.apply(scope, newArgs);} catch (e) {//alert(e.message);}} : null;} catch (e) {callback = null;}}showLogin(callback);return false;};function sendPost() {if (!checkLogin({autoCallback:true})) {return;}document.getElementById(‘result’).innerHTML += ‘Hello<br />’;}function showLogin(callback) {document.getElementById(‘login’).style.display = ‘;document.getElementById(‘loginbtn’).onclick = function() {document.getElementById(‘login’).style.display = ‘none’;isLogin = true;callback && callback();}}</script>
其实弄出这么个方法来实现登录自动回调也是为了方便写代码,不用每次去将一堆业务逻辑放到 callback 中,直接在函数开始回一个 if (!checkLogin({autoCallback:true})) 就行。
caller 属性在 MDC 中被标记为“非标准”的,但是在主流浏览器中,都是支持这个属性的,也就是说,在浏览器兼容性上使用 caller 是没有问题的。
不过,使用奇技淫巧容易伤身体,慎用:)
REALbasic 中使用 ShellExecute 执行命令
Oct 16th
在 REALbasic 中,如果需要执行 cmd 命令,可以直接使用 Shell 类,但是这样的话,编译成 Windows 程序时会额外需要一个 Shell.dll 的动态链接库,这对于我这样的 1exe 爱好者是不能忍受的。但是对于 Mac OS X 和 Linux 的生成目标来说,是不存在这个问题的,因为 Mac OS X 的应用程序本身就是一个文件夹,而 Linux 的目标不会生成额外的链接库。因此,需要针对 Windows 进行特殊处理。于是在网上搜索解决方案,找到了 VB 中执行程序的几种方法:
1. 使用 CreateProcess
通过 CreateProcess 以及使用管道,可以执行外部程序并获取输出,但是这个方法过于烦琐,并且我也不需要外部进程执行完毕后的输出结果,因此不采用。
2. 使用 Shell 方法
VB 里有一个 Shell 方法,但是在 RB 中并没有,所以此路不通。
3. 使用 ShellExecute
这个方法同样是一个系统 API,可以直接通过 RB 的 declare 声明并调用它,在测试之后,使用 declare 来使用系统 API 不会生成额外的 dll,正是我需要的。
首先在 RB 的某个模块中添加一个方法 ShellExecute,用来封装对系统 API 的请求:
function ShellExecute(hWnd as Integer, lpOperation as String, _
lpFile as String, lpParameters as String, lpDirectory as String, nShowCmd as Integer)
注意,在 RB 中添加方法时,参数列表中的 _ 需要去掉,这里是为了排版的需要而加上的。
这个 API 是定义在 shell32.dll 中的,在 ShellExecute 方法中先需要声明:
soft declare function ShellExecuteA Lib ”shell32.dll” _
(ByVal hWnd As Integer, ByVal lpOperation As CString, _
ByVal lpFile As CString, ByVal lpParameters As CString, _
ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integersoft declare function ShellExecuteW Lib ”shell32.dll” _
(ByVal hWnd As Integer, ByVal lpOperation As CString, _
ByVal lpFile As CString, ByVal lpParameters As CString, _
ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer
然后来调用这个 API:
dim ret as Integer Try ret = ShellExecuteW(hWnd, ConvertEncoding(lpOperation, Encodings.UTF16), _ ConvertEncoding(lpFile, Encodings.UTF16), _ ConvertEncoding(lpParameters, Encodings.UTF16), _ ConvertEncoding(lpDirectory, Encodings.UTF16), nShowCmd) Catch ret = ShellExecuteA(hWnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd) End return ret
把 ShellExecuteW 放在 Try … Catch 中是为了兼容的需要,因为像 Windows 98 这样比较老的系统中,系统内部编码还不是 Unicode,还没有 ShellExecuteW 这样结尾是 W 的 API。
另外,因为 RB 内部是使用的 UTF-8 编码,而系统 API ShellExecuteW 使用的编码是 Unicode,所以参数在传递前需要进行转码,将字符串参数值转换为 Unicode 编码,否则在执行一个明明正确的命令时,会出现“找不到文件”的错误。
好了,这样就可以使用 ShellExecute 方法来执行外部程序了,例如:
#if TargetWin32
dim ret as Integer
ret = Win32Helper.ShellExecute(hWnd, "open", "cmd.exe", _
"/c shutdown -t 10", "", 0)
#endif上面的代码就调用 cmd.exe 执行了命令 shutdown -s -t 10,也就是 10 秒后关机。
其实这个方法也可以用来打开文档或者网址,具体的用法可以去 MSDN 找一找。
当然,如果不在乎生成的 Windows 目标还有额外的 dll 的话,完全可以使用 RB 自带的 Shell 类,功能也强上不少。
解决 Finder 中挂载 Samba 出现“输入的文本似乎不是可识别的 URL 格式”错误
Oct 15th
在公司里为了让笔记本和台式机共享文档,决定用内部的 Samba 做中转,但是在 Finder 中直接使用“连接到服务器”时,会出现“输入的文本似乎不是可识别的 URL 格式”错误,但是我输入的地址明明是 smb:// 开头的。
用这个错误信息在网上找了找,没有找到任何解决文案,遂放弃。
今天决定再尝试一下,换了个关键字,直接用“iDeneb samba”作为关键字来搜索,慢慢发掘之后还真找到了有用的信息:http://www.hackint0sh.org/f179/81233.htm
按照文中说明,到 /System/Library/Filesystems 目录下,把 afpfs.fs 删除,并重新创建到 /System/Library/Filesystems/AppleShare/afpfs.kext 的软链接,但是操作完之后还是会提示“输入的文本似乎不是可识别的 URL 格式”。
再找了找,找到了这篇:http://www.insanelymac.com/forum/i … opic=92989&st=580,似乎说是系统安装完成时 afpfs.fs 到 /System/Library/Filesystems/AppleShare/afpfs.kext 的软链接少了开头的斜杠,也就是说它的软链接地址是 System/Library/Filesystems/AppleShare/afpfs.kext。
好吧,我在之前操作的时候为了省事,直接进入 Filesystems 目录用相对路径来创建软链接的,看了文章之后,老老实实的用全路径再次创建软链接,Command+K,双击,成功连上 Samba 服务器:)
完整的操作步骤也只有两步:
sudo rm /System/Library/Filesystems/afpfs.fs
sudo ln -s /System/Library/Filesystems/AppleShare/afpfs.kext /System/Library/Filesystems/afpfs.fs
注意:一定不能省略了路径最开始的斜杠(/)或者使用相对路径。
所以,如果你也是用黑苹果的,也碰到了这个问题,不妨试试这个解决方法吧。
1000E 升级到 2G 内存时安装 OSX 需要用到的 dsdt.aml
Sep 6th
上周去把 EPC 的内存升级到了 2G,直接发现开机进不了 OSX 了。
在两天没有电脑用之后,下定决心重装系统。重装之后,能进系统,但是重新启动之后键盘和触摸板就不能用了,电池状态指示挂了,郁闷。
在网上找了找,发现可能是 dsdt 的问题。由于内存大小变了,dsdt 中的信息不正确,继而导致 ACPI 失效,然后就整个玩完了。
一个老外找到了解决方法,通过修改 dsdt.aml 文件中的内存大小信息,就可以正确进入系统了。直接下载他提供的压缩包中的 dsdt.aml,开机直接四国了……
好吧,这个老外蛮厚道的,给出了手动处理的方法:
For the 1GB:
…
Name (SMBS, 0×0400)
OperationRegion (BIOS, SystemMemory, 0×3F7AE064, 0xFF)
Field (BIOS, ByteAcc, NoLock, Preserve)
{
…
For the 2GB:
…
Name (SMBS, 0×0400)
OperationRegion (BIOS, SystemMemory, 0×7F7AE064, 0xFF)
Field (BIOS, ByteAcc, NoLock, Preserve)
{
手动修改步骤
1. 首先要下载一个 dsdt.aml 反编译的工具:ACPICA - Windows Binary Tools
2. 解压后有一个 iasl.exe,把它拷贝到 dsdt.aml 所在的目录或者把 dsdt.aml 拷贝到 iasl 的目录都行,然后在命令行中运行:iasl -d dsdt.aml
3. 按照上面的说明,使用文本编辑器打开 dsdt.aml.dsl,根据你的内存大小修改 SystemMemory 后面的数值,1G 就用 0×3F7AE064,2G 就用 0×7F7AE064。
4. 再到命令行中,运行 iasl dsdt.aml.dsl,它会生成一个 dsdt.aml.aml,把它拷贝到 OSX 所在分区的根目录,覆盖原来的 dsdt.aml 即可。或者也可以把原来的文件备份一下,方便出错的还原。
参考资料
