Posts tagged REALbasic
在 REALbasic 中注册 AppleEvent
Jan 31st
之前为了注册一个自定义协议,需要通过注册 AppleEvent 来实现,在 Objective-C 中,可以很方便的使用 NSAppleEventManager 来注册 AppleEvent 句柄,但是在 REALbaisc 中,是没有办法直接去调用 NSAppleEventManager 的,所以需要通过声明然后调用 C API 来实现相应的功能。
与 NSAppleEventManager 中功能相对应的 C API 有 AEInstallEventHandler, NewAEEventHandlerUPP 等,通过这些 API 我们也可以在 REALbasic 中来注册 AppleEvent 了,再配合 Info.plist 中的 URLScheme 声明,即可实现 URL 自定义协议处理句柄。
#if TargetCarbon
soft declare function AEInstallEventHandler Lib CarbonLib ( _
theAEEventClass as Integer, _
theAEEventID as Integer, _
handler as Integer, _
handlerRefcon as Integer, _
isSysHandler as Boolean) as Integer
Soft Declare Function NewAEEventHandlerUPP Lib CarbonLib (userRoutine as Ptr) as Integer
Static CallbackUPP as Integer = 0
If CallbackUPP = 0 then
dim m as MemoryBlock = AddressOf ForwardCarbonAEEventToObject
If m is nil then
Return
End if
CallbackUPP = NewAEEventHandlerUPP(m)
End if
dim v as Variant = me
dim OSError as Integer = AEInstallEventHandler( _
OSTypeToUInt(kInternetEventClass), _
OSTypeToUInt(kAEGetURL), _
CallbackUPP, _
v.Hash, false)
msgbox str(OSError)
#endif先使用 NewAEEventHandlerUPP 来生成一个 AppleEvent 回调函数的句柄,然后调用 AEInstallEventHandler 来注册一个共享函数 ForwardCarbonAEEventToObject 为 AppleEvent 事件处理句柄。
AEInstallEventHandler 所需的 AEEventClass 和 AEEventID 都是一个 4 字节的整型,但是通常我们在调用的时候,是用的一个 4 字符的字符串,因此需要一个函数来将 4 字符转换为 4 字节的整形。
// code from ToolbarSearchField by The ZAZ Studios // http://www.thezaz.com/opensource/realbasic/macosx/searchfield/ static m as new MemoryBlock(4) m.LittleEndian = false m.StringValue(0, 4) = s return m.UInt32Value(0)
在 ForwardCartonAEEventToObject 里,参数 theEvent 和 replyEvent 都量个整形,为了从这两个参数里拿到数据,还需要使用 AEGetParamPtr 来从 AppleEvent 中拿到数据。
soft declare function AEGetParamPtr lib CarbonLib ( _ theAppleEvent as Integer, theAEKeyword as Integer, _ desiredType as Integer, byref actualType as Integer, _ dataPtr as Ptr, maximumSize as Integer, _ byref actualSize as Integer) as Integer
当然还有一系列的 AEGetDataDesc、AEGetDescSize 等函数可以,具体可以查 Xocde 随带的库文档。
关于注册自定义协议,可以参考这篇文章。
通过 Core Foundation 中的一些 C API,在 REALbasic 也可以完成一些平台相关的工作,虽然麻烦了些:)
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 来传递一些控制信息。
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 类,功能也强上不少。