2012年2月24日 星期五

[objc] 被沒收的 UTF8String

最近協助同事開發 webkit plugin,我提供的 Player 能使用 playWithUrl 啟動播放:

-(void)playWithUrl: (const char*) aUrl;

但在 plugin 由外層 javascript 傳入的參數是 NSString* 型式的,直覺上我們會利用 UTF8String 方法,獲得 c String:

[theUrl UTF8String]

但試了幾次後,發現轉進來的 c String 偶爾會變成奇怪的內容,俗稱『亂碼』。這種偶發性的不確定因素,有股熟悉感。先翻閱手冊,在 UTF8String 中提到:


The returned C string is automatically freed just as a returned object would be released; you should copy the C string if it needs to store it outside of the autorelease context in which the C string is created.

簡單地說,這個 c String 是生命週期會跟著原來的 NSString 跑,所以,要確保它被使用的時候,是正確的內容,我們應該先 retain 它,最後,我們討論了一下,決定提供一個新的 method:

-(void)playWithNSStringUrl: (NSString*) theUrl
{
[theUrl retain];
memset(inputUrl, 0, sizeof(char) * 1024);
strncpy(inputUrl, [theUrl UTF8String], 1024);
[theUrl release];
[self playWithUrl: inputUrl];
}

確實宣示我們持有 theUrl,並保存其內容。亂碼就不再來亂了 :D

2012年2月17日 星期五

XCode 編譯 i386 架構時的 "illegal text reloc to XXX" 問題

最近在協助同事編寫 Webkit Plugin。經驗上如同 iOS 實作時相仿:為了同時支援不同的架構(arm6, arm7)得將用到的 library 分別編譯出來,將利用 lipo 將它們 merge 成一個同時支援多種架構的 library,即為俗稱的 universal library。
在一開始我忘了需要不同架構的 library,因為某些 osx 需要 32bit 的 library,在我電腦上,預設編出來的是 x86_64 架構,本來以為單純編譯出 i386 的版本再合併起來就好。沒想到發生了新的問題:

ld: illegal text reloc to cstring from ../FFMPEG.i386/lib/libavformat.a(aea.o) in _aea_read_header for architecture i386
collect2: ld returned 1 exit status
Command /Developer/usr/bin/llvm-g++-4.2 failed with exit code 1


雖然不太清楚細節是什麼,總之這錯誤看起來是 linker 在進行 relocation 時發生了問題。為何這個問題只在 i386 編譯時會發生呢?剛好找到在 apple maillist 上有說明:

http://lists.apple.com/archives/unix-porting/2008/Jan/msg00027.html

*** EXPLANATION ***
The two assembly commands load the absolutes address of _trail into R15. Doing so is fine if _trail is ultimately in the same linkage unit. _trail is in libmodule.dylib.
For this to work, at runtime the dynamic loader (dyld) would have to rewrite the two instructions. Normally dyld only updates data pointers.
One work around is to make libdyalog an archive (e.g. libdyalog.a) and link that with pere.s. Then all the code would be in the same linkage unit, so there would be no need for runtime text relocs.
The runtime (dyld) does support text relocs (updating instructions) for i386, but you need to link with -read_only_relocs suppress.

相信大部分的人跟我一樣對於底層能掌握的知識不多,至少我們知道:

The runtime (dyld) does support text relocs (updating instructions) for i386, but you need to link with -read_only_relocs suppress.

索性在 other link flags 加上:

-read_only_relocs suppress

程式似乎就能正常編譯成功了。

PS. 這個 flag 不能加在 x86_64 的架構,請設定在條件式編譯選項。

2012年2月7日 星期二

MySQL 資料檔移轉 (on osx)

由於某台測試 Server 的一些設定檔毁損讓 LVM 無法正常掛載,經 MIS 緊急修復後我也參與了復原工作。從掛回去的檔案系統上,MIS 備份出了 MySQL data 路徑的資料庫檔案。由於 MySQL Server 是 5.0 版本的,要直接將檔案丟到新版的 MySQL Server 是不可行的。最好的情況是能轉變成 mysqldump 匯出的 .sql 檔。在 MySQL 官網看了半天,找不到合用的 5.0 binary 檔,要弄個合適的 linux 環境可能又要花不少時間,乾脆就抓 source code 自己來編了。這樣就可以不需要 linux 環境,能編出來放在 osx 上執行。

source code 的編譯上沒有遇到什麼困難,一如往常地 ./configure --prefix=/opt/mysql && make install 就能搞定。而接下來遇到的問題才是值得我筆記下來的部分!

將備份出來的 data 放至 MySQL 指定到的 data 路徑(自編的情況是放在 $MYSQL/var),啟動 MySQL 看起來一切正常。show tables 也都有看到資料表出現,但有些是採用 innodb engine,因為忘了放 ibdata1 檔而只有看到名字,沒有實際的內容。在關閉 MySQL Server 放回檔案就能正確讀到那些使用 innodb engine 的資料表。

我開始了第一次 mysqldump 的嘗試,但它跟我說找不到資料表:

mysqldump: Got error: 1146: Table .agentlog doesn't exist when using LOCK TABLES

這個資料表在 show tables 有出現,但名稱為 AgentLog。我也查了檔案是有 AgentLog.frm 的。於是我推測問題應該是出在 osx 的檔案系統,osx 安裝時選用的檔案系統預設為不分大小寫的。要在 osx 上建立出區分大小寫的檔案系統很容易,這步驟在 Android Soruce 編譯的指引中有包含:請參考『Setting up a Mac OS X build environment』。

於是我重新將資料放在新建立的、有區分大小寫的檔案系統中,再度執行 mysqldump 就不會找不到檔案。但我遇到新的問題:mysqldump 要匯出某些表格時說它無法支援 big5_chinese_ci。於是我查了 ./configure --help

--with-charset=CHARSET
Default character set, use one of:
binary
armscii8 ascii big5 cp1250 cp1251 cp1256 cp1257
cp850 cp852 cp866 cp932 dec8 eucjpms euckr gb2312 gbk geostd8
greek hebrew hp8 keybcs2 koi8r koi8u
latin1 latin2lab latin5 latin7 macce macroman
sjis swe7 tis620 ucs2 ujis utf8

在預設的情況下沒有包含 big5_chinese_ci!不過我們有另一個參數可以使用:

--with-extra-charsets=CHARSET,CHARSET,...
Use charsets in addition to default (none, complex,
all, or a list selected from the above sets)

於是我指定了 --with-extra-charsets=all,最後終於將資料匯成 .sql 的型式

2012年2月2日 星期四

以 OpenGL ES 1.x 在 iOS 上貼圖(texture) 的限制

最近為了要製作 Media Player 開始研究怎麼在 iOS 上畫圖,其中一種畫圖的方式是使用 OpenGL ES。OpenGL ES 對於我這初學者來說難度不小,而在先前『試作』Media Player 的經驗,我們能運用 Texture 來貼圖。參考了 iPhone 遊戲自作入門,我已經知道如何畫出一個方形的圖片。

More about iPhone遊戲自作入門


值得注意的是在書上的 93 頁提到:

讀入材質中的圖像尺寸的長與寬必須分別都是 2 的倍數。若沒有遵守這個條件,圖像可能無法正常描繪,必須多加注意。

同樣的限制在著名的 OpenGL ES From the Ground Up, Part 6: Textures and Texture Mapping 也有提到:

Images used for textures must be sized so that their width and height are powers of 2, so both the width and height should be 2, 4, 8, 16, 32, 64, 128, 256, 512, or 1024. An image could be, for example, 64x128, or 512x512.


所以,我們能知道書上的翻譯不太正確,是 2 的次方,而非 2 的倍數。在這限制大約在半年前就有遇過,那時只是先對於在 iOS 上貼圖的方法進行調查與撰寫雛型,隨著時間過去與忙碌於其他專案而將程式碼弄丟了(也忘了筆記下來)。在重新開始這個工作前,我試著在 StackOverflow 提問:How do I render frames from FFmpeg to the screen in an iOS app? 在網友的回覆中,得到一些重要的 keyword!其中最有用的資訊就是知道 NPOT。並且由另一篇提問得到畫出非 2 次方邊長是可行的 Rendering to non-power-of-two texture on iPhone

因此,對於畫出非 2 次方邊長的 texture 的條件在於:

一、你的裝置必需支援 GL_APPLE_texture_2D_limited_npot Extension,可以利用官方文件提到的 CheckForExtension 測試

二、必需滿足 APPLE_texture_2D_limited_npot 的使用條件

In the absence of OES_texture_npot, which lifts these restrictions, neither mipmapping nor wrap modes other than CLAMP_TO_EDGE are supported in conjunction with NPOT 2D textures. A NPOT 2D texture with a wrap mode that is not CLAMP_TO_EDGE or a minfilter that is not NEAREST or LINEAR is considered incomplete. If such a texture is bound to a texture unit, it is as if texture mapping were disabled for that texture unit
這句話在實作上的意義就是:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

完整的範例如下:
https://s3.amazonaws.com/qrtt1.articles/xcode.sample/texture.npot.tgz

2012年2月1日 星期三

讀取 iOS 專案內的資源檔

去年也開始接觸了 iOS 的開發,有些小技巧還是不熟悉啊!像簡單地讀取專案內的資源檔就查了半天。決定開始記錄這些小事情。為了練習使用 opengles 畫出單一的圖形,我放了一個 RGB565 的資料檔(rgb320x240.raw) 在專案內,打算採用 fopen 將它開啟讀入記憶體上操作。但不知道『路徑』該填些什麼。一開始只填檔名,想碰一下運氣,但 FILE* 始終是 NULL。後來在 stackoverflow 找到了解答:

NSString *path = [[NSBundle mainBundle] pathForResource:@"rgb320x240" ofType:@"raw"];

這麼一來我就取得了 rgb320x240.raw 在 iPhone 上的實際路徑:

/var/mobile/Applications/DAC73AB8-79F8-4B6F-9AC0-099C564C8ADA/GLES.app/rgb320x240.raw