<![CDATA[召苏博客 - 编程开发]]> http://www.3exware.com/home/ zh-cn PBlog2 v2.4 召苏博客 http://www.3exware.com/home/images/logos.gif http://www.3exware.com/home/ 召苏博客 http://www.3exware.com/home/article.asp?id=80 <![CDATA[[REAL Studio]保存文件位置信息]]> info@toringo.com(kmzs) Sun,15 Aug 2010 17:18:54 +0800 http://www.3exware.com/home/default.asp?id=80 有时候我们需要保存文件的位置信息以便在程序下次运行时找到这个文件,比如文件菜单中的“最近打开的文件”之类。如果这个文件需要让各种程序都能找到,那么只能保存文件的绝对路径。如果只要被REAL Studio程序使用,那么应该保存SaveInfo。

SaveInfo需要通过FolderItem类的GetSaveInfo方法来获取,这个方法有两个参数,第一个是相对路径(也是一个FolderItem实例),后一个是个可选的模式参数。

举例如下(假设有个全局字符串变量s):

  Dim f,d as FolderItem
  f=Volume(0).Child("Doc").Child("abc.txt")
  d=Volume(0).Child("Doc")
  s=f.GetSaveInfo(d)
  
在Windows上,f是C:\Doc\abc.txt,d是其所在的文件夹,但其实d可以是任何一个文件或文件夹。上述代码执行后,s中就保存了文件f的SaveInfo,可以将其写入程序设置文件、某个数据库、注册表或defaults中,以便于以后读取。

要从SaveInfo中解析出文件,只要这样:

  Dim f As New FolderItem
  f=f.GetRelative(s)

那么使用SaveInfo有什么好处呢?主要有两点:

1)用于通过相对路径找到文件。比如某个文件夹A中有文件b,在调用GetSaveInfo时,将A传入,即s=b.GetSaveInfo(A);随后如果你整体移动了文件夹A(b仍在A内),在试图解析出b时,通过b=A.GetRelative(s),仍然可以找到b,当然这里的A必须是指向移动后的文件夹。

2)即便不需要上面这个好处,也应该使用GetSaveInfo,它不光保存了相对、绝对路径,还保存了其他信息,使得更名或移动过的文件(主要在Unix系统上)仍能被找到。比如在Mac上,使用s=f.GetSaveInfo(Volume(0))并将s的值保存起来,以后即使文件f被改名或移动(严格的说是在当前磁盘分区,或者说卷内移动,当然在Mac上跨磁盘移动不容易,要Command+Shift+拖曳文件),仍然能通过所保存的SaveInfo解析到更名或移动后的文件。对这种用法,向GetSaveInfo中传入什么都没问题,此处传入Volume(0),是因为Volume(0)是系统盘根目录,只要不是无盘工作站之类,这个FolderItem都不会为Nil,而且它的“位置”通常极少可能会改变。

因此在REAL Studio中首选应该保存SaveInfo而不是AbsolutePath。这样至少在Mac上,不会由于文件移动或更名而找不到。大部分Mac软件的Open Recent子菜单都不会由于文件被移动而无效,但一些只追求跨平台而忽视native特性的公司的产品或许会这样,比如Adobe的一些软件。]]>
http://www.3exware.com/home/article.asp?id=77 <![CDATA[通过编程访问Google账户和服务]]> info@toringo.com(kmzs) Sat,31 Jul 2010 21:38:24 +0800 http://www.3exware.com/home/default.asp?id=77 本文介绍通过桌面程序访问Google账户和Google服务的方法。以Google Calendar服务为例,使用Google Calendar API 2.0版。

1、Google账户登录
通过程序登录Google账户的方法有很多,最简单的方法是使用ClientLogin这个API,在桌面程序中一般可以使用这种方式。为了测试,可以直接在浏览器中调用这个API来看结果,URL类似这样:

https://www.google.com/accounts/ClientLogin?accountType=HOSTED_OR_GOOGLE&Email=YourAccount@gmail.com&Passwd=PASSWORD&service=xapi&source=YourCompany-YourApp-Version

其中Email=YourAccount@gmail.com是你的Google账户Email地址,Passwd=PASSWORD是账户的密码。service是要访问的服务名,对于Google Calendar服务,要传递cl。source是登录来源,也就是你的程序的名称之类。注意上面的关键字是区分大小写的,比如“Email=”中的“E”一定要大写。

以REALbasic语言为例,来看看怎么用程序来处理。在这里我们使用同步模式的HTTPSecureSocket来发送请求。
Dim https As New HTTPSecureSocket
https.Yield=True // 同意让渡CPU时间,以防在发送请求过程中界面长时间停止响应
Const PostURL="https://www.google.com/accounts/ClientLogin"
Dim form As New Dictionary // 使用字典组装表单,这比用“+”运算符连接字符串来组装URL要方便
form.Value("accountType")="HOSTED_OR_GOOGLE"
form.Value("Email")=“YourAccount@gmail.com"
form.Value("Passwd")="PASSWORD"
form.Value("service")="cl" // 指定访问Google Calendar服务
form.Value("source")="REALSoftware-REALStudio-"+RBVersionString
https.SetFormData(form) // 将表单指派给https,会对特殊字符编码,比如空格会被转为“%20”。
Dim ResultValue As String
ResultValue=https.Post(PostURL,20) // POST表单到指定地址,20秒无返回则自动超时

如果成功(https.HTTPStatusCode=200),ResultValue中将得到类似这样的3行返回结果(失败的话https.HTTPStatusCode=403并会返回错误信息):
SID=DQAAAH4AAADifYYKy7p9Lg7KdT4BW3zb7RvFmKJm7iJgQ2hjpTG6CLhVnQPFybD8t_odSpU5ZgODxcXnGi50cc-Ky8u-O0KaQiCEydE9RurCkoLEywY7a6trnN6mQW5SmcgjiNCwqiIzhAvQcu-0RFrvKQNJX78GQE4fFE_fs4uX2GTYleQGQg
LSID=DQAAAIAAAABWZhQlKvg2elXVWVttR23V2AWMu57XXvOiJJbGrHlUZW-96nsaokBwCzxs8_edWSDZOU3lU71R14u8QntrODC7WAFlBMkG04GZzIND7WPqX73t--dUAkYVlDoyfwP4N7bTNrg5VsOeeYervrxs_ADg8MBpqlnw-bbjIcyIub8org
Auth=DQAAAIAAAABWZhQlKvg2elXVWVttR23V2AWMu57XXvOiJJbGrHlUZW-96nsaokBwCzxs8_edWSC5iZ0YUERmdfIogOo05uk-YFGm0na7lYS_yT-_BNOLSBZeDcwtOOp9osxbv_7oUdGHbfhOoDza6G-ISUZM4dYgkDNmWyOqjTUwwbWAbbjU4w

这里我们只需要使用最后一行“Auth=”后面的内容(称为验证令牌)。将它解析出来保存到字符串变量AuthToken中(解析过程略),然后要设置一下后续的Google账户访问时需要发送的HTTP报头:
https.SetRequestHeader("Authorization","GoogleLogin auth="+AuthToken) // 将验证令牌放入Authorization请求头中
https.SetRequestHeader("GData-Version","2") // 如本文开头所说,我们要使用2.0版的API,所以设置GData-Version头

2、调用Google Calendar API
下面这个API用于获取已登录的用户的所有日历:
https://www.google.com/calendar/feeds/default/allcalendars/full
要使用这个API,只要继续用上面那个设置好验证令牌的HTTPSecureSocket实例向这个地址发送GET命令即可。
Const AllCalendarURL="https://www.google.com/calendar/feeds/default/allcalendars/full"
dim v As String=https.Get(AllCalendarURL,20) // 这次用GET(不要用POST),超时仍然选20秒

但得到的返回值v中的多半不是XML格式的日历列表,而是像这样一个HTML页面:
- <HTML>
- <HEAD>
- <TITLE>Moved Temporarily</TITLE>
- </HEAD>
- <BODY BGCOLOR="#FFFFFF" TEXT="#000000">
- <H1>Moved Temporarily</H1>
The document has moved <A HREF="https://www.google.com/calendar/feeds/default/allcalendars/full?gsessionid=gC7NAlsQASjnE_wXPF1kxQ">here</A>.
- </BODY>
- </HTML>

此时,如果检查https的HTTPStatusCode属性,会发现它的值是302,而不是200(OK,正常)。 302在HTTP协议里是Redirect(重定向)的意思,重定向到了上面那个HTML中显示的地址。我们不需要从HTML里解析它,一则这样太麻烦,二则Google可以自己决定对于302这个状态应该返回什么样的正文文本,如果Google做了些改动,我们解析方法可能就失效了。那怎么办呢?应该想到HTTP协议返回的内容不光只有页面正文,还有页面报头。我的经验是,进行通过HTTP访问某个网站开放的API一类的开发,经常检查一下返回的页面的报头是个好习惯。可以通过HTTPSecureSocket的PageHeaders方法,或在调试器中看_pageHeaders属性来查看报头,对于上面的GET操作,我们会看到这样一组报头:
Expires: Sat, 31 Jul 2010 12:43:48 GMT
Date: Sat, 31 Jul 2010 12:43:48 GMT
Set-Cookie: S=calendar=gC7NAlsQASjnE_wXPF1kxQ;Expires=Sun, 31-Jul-2011 12:43:48 GMT;Secure
Location: https://www.google.com/calendar/feeds/default/allcalendars/full?gsessionid=gC7NAlsQASjnE_wXPF1kxQ
Content-Type: text/html; charset=UTF-8
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE

可以看到Location头的值正是跳转目标的URL,和HTML中显示的URL一致。
那么我们也进行“重定向”,向新地址发送GET命令:
Dim headers As InternetHeaders=https.PageHeaders // 读出所有InternetHeaders
Dim Result As String
If headers<>Nil Then
  Dim RedirectTo As String=headers.Value("Location") // 读取Location头,以得到重定向地址
  If RedirectTo<>"" Then Result=https.Get(RedirectTo,20) // 继续GET,向新地址请求数据
End If

但是结果很可惜,我们得到了又一个类似上面那样的HTML和一个新的302,如果继续下去你会得到永无止境的大体相同的HTML和302状态。为什么会这样呢?比较一下各次得到的正文内容或Location头,你会发现差异在URL里的gsessionid=xxxxxxxx一段。这段内容给出了会话ID(或者叫会话种子,Seed),这意味着我们必须使用这个会话才能继续执行后续的API。

要注意,HTTP协议是一种无连接协议,客户端发送请求时建立连接,服务器响应请求返回了数据后,连接立即断开。对于Google API,为了避免每执行一个API函数就进行一次登陆验证,服务器端会以会话ID为索引保存一个上下文。相应地,在客户端向服务器发送请求时也必须同时发送这个会话ID,以使服务器知道它所保存的哪个上下文和这个客户端对应。因此客户端也要保存这个会话ID和一些信息,以便在发送请求时使用,客户端所保存的东西被称为Cookie。

在REALbasic语言的HTTPSecureSocket类中怎么使用这些“曲奇饼干”呢?回过头来再看上面的那组报头,发现有一个名为Set-Cookie报头,可以看到其中也包含gsessionid的内容。这就是我们要保存并在后续访问中使用的Cookie了。所谓的“在后续访问中使用”,意思就是在后续的请求中作为报头发送给服务器。因此可以不必使用额外的变量,只要像上面保存验证令牌和GData版本号那样将Cookie缓存到https对象的RequestHeader里面就行了。现在修改上面的代码如下:
Dim headers As InternetHeaders=https.PageHeaders // 读出所有InternetHeaders
Dim Result As String
If headers<>Nil Then
  Dim Cookie As String=headers.Value("Set-Cookie") // 从Set-Cookie头读出Google要求我们使用的Cookie
  Dim RedirectTo As String=headers.Value("Location") // 从Location读出重定向地址
  If RedirectTo<>"" Then
    If Cookie<>"" Then https.SetRequestHeader("Cookie",Cookie) // 将Cookie写入请求报头中
    Result=https.Get(RedirectTo,20) // 向新地址请求数据
  End If
End If

这回Result中将收到含有日历列表的一个XML格式的字符串(实际上是Google Data格式的,也可以是RSS或Atom格式)。接下来只要用XMLDocument和相关的类进行解析,就能获取我们想要的数据了。

(转载请注明来源:http://www.3exware.com/home/default.asp?id=77)]]>
http://www.3exware.com/home/article.asp?id=52 <![CDATA[将REALbasic与TortoiseSVN一同使用的注意事项]]> info@toringo.com(kmzs) Wed,02 Dec 2009 23:11:08 +0800 http://www.3exware.com/home/default.asp?id=52

[转自上海棠盈阁软件咨询有限公司内部Wiki]

就内容而言,本文可能会包含:将RB与TortoiseSVN一同使用的注意事项、使用RB版本控制系统工程的注意事项和使用TortoiseSVN的注意事项 :-)

基本原则:使用TortoiseSVN的基本原则之一,就是所有对已由版本控制系统管理的文件的删除、重命名、移动等操作应一律使用TortoiseSVN相关的菜单,而不是资源管理器本身提供的功能来完成。

1. 新增文件或文件夹
在RB的版本控制系统(rbvcp)工程中添加了新工程项后,RB会自动在磁盘上添加一个与工程项对应的文件。如果在工程中添加了文件夹,那么RB会自动创建一个文件夹。因此在递交前要注意将这些新增文件或文件夹添加到版本控制系统中(或者说工作拷贝的仓库中),做法有二:

  • 右键单击资源管理器中的合适的目录(即含有新增文件或文件夹的目录),在弹出的右键菜单中使用TortoiseSVN子菜单中的Add命令添加所有新增项目到版本控制系统中
  • 或者,右键单击资源管理器中的合适的目录(比如整个项目的顶级目录),在弹出的右键菜单中使用Commit命令,在随后弹出的对话框中勾选左下角的“Select / deselect All”复选框,这样在递交时新增文件或文件夹会被自动先添加到版本控制系统中

注意,在使用Commit时,不要急于按下对话框上的提交按钮,要先填写好日志,检查是否要勾选“Select / deselect All”复选框,全好了之后再递交。

2. 删除文件或文件夹
从RB工程中删除新工程项(或文件夹)后,如果所删除的工程项对应的文件已被添加到版本控制系统中,那么它并不会被从版本控制系统中去除,也不会被从磁盘 上删除。此时要右键单击相应的文件或文件夹,在弹出的右键菜单中使用TortoiseSVN子菜单中的Delete命令,将它们设置为需要从版本控制系统 中删除的项目。注意,这些文件夹一般不会立即被删除,要在下次Commit后,才会被TortoiseSVN自动从磁盘上删除。

3. 删除以“.obsolete”结尾的文件或文件夹
在 从外部导入工程项(比如拖曳到RB的工程编辑器中) 并保存工程和一些其他情况下,将会产生一个.obsolete结尾的文件。比如将WinMain.rbfrm添加到另一个工程中并保存,将会产生一个 WinMain.rbfrm.obsolete备份文件,在Commit时,如果在Commit对话框上的文件列表框中发现这些文件,可以右键单击相应的 行并从随后弹出的上下文菜单中选择Delete。注意,不要递交任何.obsolete文件或文件夹,不要将它们Add到版本控制系统中。

4. Commit时忘记填log了怎么办?
可 以从TortoiseSVN子菜单中选择Show Log子菜单,在随后弹出的日志查看窗口的列表框中单击刚才递交的ChangeSet对应的行,然后在下面的文本框上右键点击,从上下文菜单中选择 “Edit Log”。TortoiseSVN会弹出日志编辑对话框以便补填日志。

5.“.obsolete”文件夹带来的递交问题的解决办法 ~NEW~
“.obsolete” 文件夹有时会带来额外的递交问题。比如工程中有一个文件夹ABC,其中有几个类或模块,当调整文件夹显示的位置(比如上移、下移)并且修改过其中的内容 后,再保存工程,磁盘上会出现一个ABC.obsolete和一个ABC文件夹。实际上RB的IDE将原来磁盘上的ABC文件夹更名为了 ABC.obsolete并重新用内存中的数据创建了ABC文件夹和其中的各个工程项文件。在递交时会遇到问题。原因分析如下:

  • 原来ABC文件夹中保存工作拷贝数据的“.svn”隐藏文件夹和其中的数据现在跑到ABC.obsolete里面去了,TortoiseSVN无法找到它们,因而ABC文件夹现在处于和尚未添加到版本控制系统的工作拷贝中的那些文件相同的状态。
  • 如果想用TortoiseSVN的Add子菜单重新添加ABC文件夹,也会失败,因为ABC曾经被添加到版本控制系统中,又没有从版本控制系统中删除的记录,因此不可能允许你再添加它

解决方法:将 ABC.obsolete里“.svn”隐藏文件夹移动到ABC文件夹中。如果看不到这个隐藏文件夹,请 先使用资源管理器的【工具】菜单的【文件夹选项】子菜单来显示隐藏文件。移动了“.svn”之后,可以试着对较顶级的文件夹(比如DocuSteward 或DocuSteward\Src)使用【TortoiseSVN】右键菜单的【Clear Up】子菜单(带扫帚图标的那个)清理下。然后删除ABC.obsolete(由于它还没被添加到版本控制系统,因此使用资源管理器或 TortoiseSVN的删除命令均可,用TortoiseSVN的ignore子菜单来忽略它也可以,反正“.obsolete”文件/文件夹里的都是 工程中不再使用的东西)。最后应该就可以正常递交了。

(未完待续)

]]>
http://www.3exware.com/home/article.asp?id=43 <![CDATA[用IDE Script对REAL Studio进行自动化控制一例(答法国客户问)]]> info@toringo.com(kmzs) Sun,19 Apr 2009 02:43:10 +0800 http://www.3exware.com/home/default.asp?id=43 REAL公司欧洲总部的法文客服在REAL公司内部职员邮件列表中发了封邮件,说有个法国的REAL Studio用户问如何用IDE Script将当前工程中某个项目中指定的附注保存为文本文件。
    最近一段时间没少跟应用程序自动化控制打交到,比如在Windows上是许多Windows软件都自带的VBA(MS Office,CorelDraw和AutoCAD等都支持),在Mac上则是能自动化控制所有Cocoa程序的AppleScript。因此对REAL Studio的IDEScript也有些兴趣,便稍微研究了一下。
    REAL Studio要有自己的自动化功能就需要有能跨平台的自动化控制引擎。只能在Mac上用的AppleScript显然是不行的,而且REAL Studio的Mac版暂时还是基于Carbon框架程序而不是Cocoa程序,需要被AppleScript支持就要写个庞大的AppleScript扩展,还不一定能做到完美。VBA本来的确有Mac上的实现,但是由于没法将Mac版VBA代码编译为Intel指令,MS从Office 2008 for Mac开始已经移除了VBA功能,转而支持AppleScript(在大量需要跨平台VBA实现的客户的强列要求下,MS的苹果软件事业部似乎已经动手重新增加VBA对Mac版Office的支持了)。因此REAL Studio基于自己的RBScript引擎设计并开发了专门的IDEScript自动化控制功能。
    IDEScript主要用于编写软件构建脚本,使用户能实现对程序编译、打包,甚至发布的自动化处理。当然也可以做很多别的事情,比如提供这个法国客户所需要的功能。这个客户的需求很清晰,他需要一个自动化脚本,把某个工程中某几个工程项的附注(估计这个用户习惯用附注部分来编写项目文档)导出为文本文件。
    因为他经常修改附注内容,每次手动导出附注比较麻烦,过程大抵是这样的:先进入要导出其附注部分的工程项,接着进入附注中,在代码编辑器里全选,复制,然后打开一个文本编辑器,将复制的内容贴进去,选择文件位置并进行保存,最后关闭文本编辑器。虽然不复杂,每天都要重复两三次也不是件有趣的事情。
REALbasic的IDE是没有将工程项中的例程或附注导出为文本的功能的,因此IDEScript的命令集中并没有直接可以调用的命令。我稍微研究了一下,发现要解决这个法国用户的需求并不困难,如果只要支持一个平台的话,只需2行代码(我为了保险起见写了行错误判断)。具体的想法是,先用Location命令跳转到指定的附注项,在用Text命令获取当前代码编辑器中的文本,最后用DoShellCommand调用Unix或Dos的流定向命令来将文本写出到文件中。由于法文客服只需要一个Mac版本做参考,我给就只给出了一个Mac的版本,在Linux上要稍微修改下Unix命令的部分,在Windows上也差不多,Dos的echo命令看来是从Unix上学去的(注意Dos的流输出定位指令是“>>”)。当然使用REALbasic/RBScript语言的预编译指令#if...#elseif...#endif就能编写出支持3个平台的代码了。我的脚本如下:
Location="Window1.Note1"
Dim Result As String=DoShellCommand("echo '"+Text+"' > '/Users/zhujianan/Documents/Note1.txt'",5000)
If Result<>"" Then Print "Failed to save note: "+Result
    第一行代码用于跳转到工程项Window1的名为Note1的代码项/附注项中,第二行代码执行Unix命令echo并用流定向指令符“>”将Text命令返回的文本写入到指定的文件中,第三行命令检查是否正常执行(在Mac中此Unix命令正常执行后不打印任何内容,否则在终端上打印输出错误信息),如果有错误信息就显示给用户(可能的错误比如有目录不存在,文件不可写之类)。
    然后在REAL Studio的脚本编辑器中保存这个脚本,以后就可以直接从文件菜单中选择这个脚本并执行了。]]>
http://www.3exware.com/home/article.asp?id=40 <![CDATA[Is a specific app running?]]> info@toringo.com(kmzs) Sat,10 Jan 2009 00:48:11 +0800 http://www.3exware.com/home/default.asp?id=40 How to know whether an app is running in REALbasic? The common idea is looping through all processes and testing with the name of the app which you want to know if it's opened.

Windows
On Windows, we can do it via declares, the Win32 APIs we need to use are EnumProcesses, EnumProcessModules, GetModuleFileNameEx, etc. I wrote a function a few days ago. You can find the source code of it in Window1 of the RB project attached. I didn't implemented it in a very smart way. It gets the names of all processes, then tests to see if a specific app name can be found. It because I wanted to demonstrate how to list the paths of all processes. You may make some changes on it to exit the For loop when the app is found.

Mac OS X
On Mac, calling AppleScript functions seems the best way. You can loop through all process objects provided by the System Events application in a tell block. However, since AppleScript supports using keyword each or plural of class name to access all objects in the direct parameter of tell statement (in this case, it's the System Events application), so that the script can be simply written like this:

on run {appname}
tell application "System Events"
if (name of each process) contains appname then
return true
else
return false
end if
end tell
end run

OR:

on run {appname}
tell application "System Events"
if (name of processes) contains appname then
return true
else
return false
end if
end tell
end run
 
On Leopard, AppleScript are updated to 2.0, many new features are introduced. As the result, it's able to test whether an app is running without launching it:
on run {appname}
if application appname is running then
return true
else
return false
end if
end run
The code above doesn't work on Tiger and earlier.
 
To use an AppleScript, drag and drop the scpt file to your project, and call it by its name showed in the listbox of project panel, as calling a global function. The AppleScript will be compiled and embedded to app file you built. You will observe that the return value of any AppleScript you get in RB is a String, rather than the date type you expected. So you should compare it with "true" or "false", not a Boolean constant. Refer to the rbp I attached if my description isn't clear to you.
 
Attachment: IsProcRunning.zip

原载本人英文Blog

 

]]>
http://www.3exware.com/home/article.asp?id=39 <![CDATA[Some compression arithmetics implemented in RB.]]> info@toringo.com(kmzs) Thu,08 Jan 2009 23:49:28 +0800 http://www.3exware.com/home/default.asp?id=39 I implemented some common compression arithmetics (LZ77, RLE and Huffman) in RB above a half year ago. I've referred to several open-source C/C++ projects/libraries. None of them has any limit of using, modification and issuance, so they can be used in any project for free.
Download them 
here.

原载本人英文Blog

]]>
http://www.3exware.com/home/article.asp?id=38 <![CDATA[How to hide taskbar button of a window?]]> info@toringo.com(kmzs) Sat,03 Jan 2009 20:12:24 +0800 http://www.3exware.com/home/default.asp?id=38
#if TargetWin32 Then
  Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Int32, ByVal nIndex As Int32, ByVal dwNewLong As Int32) As Int32
  Const GWL_EXSTYLE = -20
  Const WS_EX_TOOLWINDOW = &H80
  Call SetWindowLong(Self.Handle,GWL_EXSTYLE,WS_EX_TOOLWINDOW)
#endif


原载本人英文Blog。]]>
http://www.3exware.com/home/article.asp?id=37 <![CDATA[Setting the position of the mouse and so on...]]> info@toringo.com(kmzs) Sat,03 Jan 2009 20:10:25 +0800 http://www.3exware.com/home/default.asp?id=37 From time to time, I see some people ask how to set the mouse position, how to simulate mouse click, and something like these. I have written a sample for Windows and Linux (by calling Win32 and XLib APIs) about two years ago, and it still works fine with the last REALbasic release as I just tested. Click here to download it.

 

原载本人英文Blog

]]>
http://www.3exware.com/home/article.asp?id=35 <![CDATA[REALbasic内幕:插件与程序编译]]> info@toringo.com(kmzs) Sun,07 Dec 2008 00:50:25 +0800 http://www.3exware.com/home/default.asp?id=35 跨平台开发环境REALbasic内幕:插件与程序编译

1、REALbasic插件格式
    REALbasic的插件并非是对IDE功能的扩展,它实际上是对REALbasic框架包的功能扩展。REALbasic的框架包的主要部分本身就是一组RBX格式的插件,它们可以在Resources目录下的Internal Plugins文件夹中找到(Mac OS X版中,Resources目录在应用程序Bundle内部,需要先在Finder中对应用程序Bundle使用“显示包内容”命令)。框架包的另一部分在Resources目录下的Frameworks文件夹中,稍后再介绍,我们先说说插件文件。
    RBX文件是由一组或几组动态库/共享库文件和一些要在IDE中使用的资源打包而成的。其中的每一组动态库通常至少包含一个用于Win32的dll文件,一个用于Mac的UB(通用二进制)格式的dylib或bundle文件,和一个用于Linux x86的so共享库文件。其中UB格式的库能用来为Intel、PowerPC和UB三种目标编译应用程序。但是对于UB格式(也就是既含有Intel CPU指令,又含有PowerPC CPU指令的Mac程序)的支持是从REALbasic 2006第四版开始的,因此许多第三方的插件还提供一个仅用于PowerPC CPU的dylib文件来支持较旧的REALbasic。
    除了PE格式的dll,MachO格式的dylib/bundle,和ELF格式的so之外,有些第三方插件还提供一个PEF格式的库以支持Mac OS 9及以前的操作系统。不过也是在2006年,REALbasic不再提供对Mac OS 9及以前的Mac OS的支持,也不再支持为Mac OS X生成PEF格式的应用程序,因而现有的大部分第三方插件都只含有针对MachO格式的Mac可执行文件的库。
    插件中每一组动态库/共享库文件提供基本相同的功能,有几乎一致的接口,具体功能的实现则因系统系统而异。
    除了这些库文件之外,插件中还有一些其它资源,比如要在REALbasic IDE的控件列表中显示的图标(当然,只有插件提供的是继承自Control类的控件才需要)、要在语言参考窗口中显示的帮助信息,等等。
    所有这些文件是用REALbasic的虚拟宗卷格式封装在一起的。即使您不使用专门处理插件的工具,只要用REALbasic自带的VirtualVolume类编写少量代码,就能一窥RBX文件中的究竟。

2、插件的载入
    REALbasic IDE和REALbasic程序都会在启动时载入所需的插件,载入它们的目的却不同。IDE载入插件是为了读取其中的接口信息,以便给出语法提示和在编译时进行语法检查。用REALbasic开发的程序载入它们,则是为了确保这些库中含有所有运行时所需的例程,并获得函数指针以便在需要时能立即调用和执行这些例程的已映射到内存中的CPU指令代码。
    对于不同平台,应用程序载入插件的方法也有差异。比如,对于Mac OS X的MachO程序格式而言。由于MachO可执行文件本身和其它Unix/Linux可执行文件格式类似,是个没有图标没有扩展名,可以从控制台直接调用的实用程序(utility)。Apple出于多种目的——比如为了让它看上去好看些,为了易于本地化和国际化等等——将文本、界面描述、图标、图形图像和其它一些资源与可执行文件本身保存在一个目录结构中。并使这个目录结构在默认情况下显示为单一的程序文件。因此您的工程中所使用的插件里面的MachO格式的共享库,在生成MachO程序时,会被放到这个目录结构中,保存在Frameworks子文件夹下面。这样Mac OS X的加载器(loader)会自动将这些共享库加载到内存中(通常dylib必须在启动时加载,而bundle可以随需加载并且支持在不需要时卸载)。
    在REALbasic 2008第一版以后,Windows上的情况也基本是这样。所需的插件中的用于Windows平台的dll,在程序编译后会被写出并保存在程序所在目录下的“<程序名>  Libs”文件夹中。它们必须与主程序一同安装,且该自文件夹的名称不应被更改。在程序启动时会要求Windows的映像加载器来载入这些动态链接库。
    在REALbasic 2007及以前的版本中,这些dll是作为资源写入Windows程序中的,然后REALbasic程序会调用系统API(应该是LoadLibrary和LoadModule之类的Win32 API)来载入这些dll或其中的例程,也就是自己实现一个简单的载入器。但是这对某些插件在一些特定情况下会产生问题,微软技术支持中心应一些用户的要求曾与数名Windows系统内核和载入器方面的专家花了数个小时跟踪调试,以研究问题的原因,其结论是由于各种原因这些API函数在行为上与系统的映像加载器有些微妙的差异,因此建议REALbasic将这些dll外部化,以便使用系统本身的加载器来加载它们。在运行时再将这些dll写出到临时文件夹也是一种办法,不过考虑到会被一些比较敏感的杀毒软件拦截,因此否决了这个方案。这就是在2008第一版开始,这些dll不再被内置的原因。
    至于Linux,目前没有太多资料,但观察,以前似乎也是将so共享库文件写入到可执行文件本体中,然后调用什么系统功能来载入它们的。不过最新的REALbasic Linux版编译的程序会在启动时将这些so文件写出到一个隐藏的临时文件夹中。这个行为改变在Ubuntu等Linux发行版上没有产生什么问题。但笔者曾遇到过CentOS 5的SELinux阻止了REALbasic 2008第四版所编译的程序,不让它继续运行的问题,在对SELinux进行设置一些后基本予以解决。

3、REALbasic的编译器与RBScript
    既然REALbasic的IDE是用REALbasic语言写的,而用REALbasic语言为自己写编译器似乎不是个好主意,因此RB的编译器是用C/C++(据笔者所知,还有一些Pascal)写成的。C++代码要与REALbasic程序一起工作当然要制作成插件。因此REALbasic的编译器也是个插件。
    这个被称为Spawn Compiler的插件是不公开的,不过其中的部分动态链接库/共享库还是能被找到的。比如在REALbasic 2008及更高版本中,您可以在“REALbasic 200X Libs”文件夹中找到一个名为“Spawn Compiler.dll”的文件。这就是Windows版的编译器。如果将这个文件复制到Plugins文件夹中,并重启REALbasic的IDE,随后尝试输入一些代码,您会看到IDE的关键字自动完成列表会暴露出一些Spawn Compiler的插件中的类和函数的接口。不过这个插件貌似非常复杂,没有文档还真没办法用。
    但是有一个简化定制版的REALbasic编译器是公开的,这就是REALbasic自带的RBScript类(对应于Internal Plugins文件夹中RBScript.rbx文件)。RBScript能在内存中编译REALbasic脚本,并将编译好的CPU指令直接映射到当前的进程空间中执行,并返回执行结果。
    这使得在用REALbasic编写的程序中可以实时编译执行REALbasic代码。REALbasic IDE脚本功能就是使用RBScript使得自动化控制IDE以及自动化实现复杂的编译构建过程成为可能。除此之外,Inspiring Applications公司制作的网页开发工具YUMA也依赖于它。YUMA的主要原理是使用一个用REALbasic编写的后台服务程序从网页中解析出内嵌的REALbasic代码,然后调用RBScript来编译并执行这些代码,再用返回结果生成需要传回给用户的动态页面。除了所支持的是REALbasic语言的一个子集之外,它与PHP、ASP等没有本质上的区别,并且YUMA的企业版也能与Apache等一同使用。与PHP等相比的主要优点在于,网页内嵌的脚本是编译执行,而不是解释执行的。据称,在同等条件下,平均运算速度为PHP的2倍,JavaScript的4倍左右。

4、REALbasic程序的编译
    从广义上说编译包括两大步骤:
    第一步是将高级语言代码编译成原生的CPU指令(即机器码)。执行这一任务的模块即狭义上的编译器(compiler),或称编译器前端。有时也会有专门用来处理资源的资源编译器,本文不对它进行讨论。既然REALbasic的编译器插件叫做Spawn Compiler,可想而知,生成CPU指令是其主要功能。由于CPU指令不受操作系统影响——也就是说不论是Windows、Intel Mac还是Linux x86,处于相同状况下的RB代码“a=1+2”,理论上可以编译为一组完全相同的CPU指令——因此REALbasic的编译器只要能将REALbasic代码为x86和PowerPC系列CPU编译成两类不同的机器码就可以了。
    第二步是将编译好的CPU指令、各种资源按照操作系统支持的文件格式组合成应用程序文件。执行这一任务的模块就是连接器(Linker),或称编译器后端。MachO格式的Mac程序将资源保存在外部,因此连接工作相对比较简单,在Windows和Linux上则比较复杂。实际上REALbasic并没有Windows和Linux版的连接器,其连接工作依赖于Resources目录下的Frameworks文件夹中的一组文件。
    在Frameworks文件夹中,App Resources Carbon、HXRuntime Carbon Mach-O、HXRuntime Mach-O Console等文件是生成有图形界面(桌面的)或无图形界面(控制台的或后台服务的)Mac程序时所需的资源文件和一些预先编译好的功能。它们相当于静态库,其中的资源或功能是大多数程序都需要的,这也包括初始化程序和载入及处理插件等所需的代码。因此由REALbasic直接提供而无需另外编写或另外附带插件。在编译Mac程序时,它们将与主程序连接在一起。
    而X86RunHoudini.exe、X86HoudiniConsole.exe等文件则是一些预编译连接好的空壳程序文件,可以称之为占位文件。它们基本上是用C/C++写成的,必要的资源或功能也已经连接到其中。REALbasic在为Windows/Linux程序编译好CPU指令后,对这些占位文件进行修改,将CPU指令和各种资源填入到占位文件的代码段和数据段中,最终生成您的Windows/Linux程序。
    因此不必怀疑REALbasic生成的Windows/Linux程序不是编译执行的。可以说,是不是编译执行,主要看得看编译过程的第一大部生成的是不是机器码。对于REALbasic程序来说,不论是里面那些由预编译好的占位文件所提供的、用C++编写的部分,还是后插入的、用REALbasic编写并编译出来的部分,都是原生的CPU指令,都会直接送给CPU去执行,而不是先送给解释器去解释为CPU指令,再由CPU执行。因为REALbasic生成的是编译执行的程序,这一点是毋容置疑的。]]>
http://www.3exware.com/home/article.asp?id=26 <![CDATA[用REALbasic语言写网页程序!]]> info@toringo.com(kmzs) Tue,25 Mar 2008 11:39:03 +0800 http://www.3exware.com/home/default.asp?id=26 用REALbasic语言写网页程序!

    在REALWorld 2008用户大会开幕当天,Inspiring Applications公司宣告了网页开发工具Yuma。使用Yuma能将REALbasic源代码用特定标签嵌入到网页中——是的,与PHP、JavaScript类似——并且在访问前会先被预先编译而不是由服务器解释执行(个人猜测是编译为RBScript代码并在本地执行后返回结果)。Coooooooooooooooool!

    我也是刚刚从一个美国的老客户那边听到的消息,Inspiring Applications公司本身的网站(http://www.inspiringapps.com)好久没更新了,本以为Joseph J. Strout大哥又要宣布创业失败呢,没想到竟然推出了个好东西!

    Yuma产品对实际上是一个服务器程序,提供对相当一部分REALbasic的语言的支持,并允许直接访问REALbasic框架包中的许多功能(不是RB插件SDK中的胶水代码动态访问RB框架就是用RBScript)——真的很多,看一下http://www.yumadev.com/doc——是不是几乎所有非GUI专用的RB框架都有了?!当然也能直接链接到REALSQLDatabase(估计REAL SQL Server也会支持)。

    Yuma将推出专门面向开发者的开发版(免费的哦)和用于部署的企业版(能链接到Apache和IIS什么的)。对于有志于网页开发的Basic程序员,怎么看都是个好消息!

    在此给出关于Inspiring公司创始人JJS老大的介绍。作为一个REALbasic粉丝,我从各种渠道搜集了近几年来曾在REALbasic就职的几乎全部资料,何况我认识REAL的创始人Geoff老大的时候,JJS老大还在REAL供职,因此得到他的资料的途径还是很多的。废话不多说,这是我去年总结的关于Joe老大的信息:

    Joseph J. Strout (JJS) 美国神经系统科学研究所(Neuroscience Lab)硕士学位,编程系自学成材。2000年1月19日加入REAL公司,加入前有Macintosh和Palm开发经验,加入前为REALbasic铁杆用户,此前曾用RB开发一些3D、数据分析和图形图像相关软件。是5.5以前版本的用户界面设计师和许多跨平台API开发者(在我能接触到的少量RB框架及编译器相关代码和自动测试包中也不少代码文件头注释中有JJS字样的内容),2005年12月15日前后离职。离开REAL后成立Verified Express, LLC(现已停业),随后与Brad Weber成立Inspiring Applications。个人主页:http://www.strout.net -- N久没更新了。

]]>