2012/06/26

C compile 常見的錯誤

C 語言是我最熟悉的語言,通常我用它的時候都是講求效能,但顧效能的同時也需要花費更多心力(效率降低)。抓 memory leak、segmentation fault 這些都很要命,當然用對工具可以讓你 debug 速度加快很多,gdb、valgrind、oprofile 也都是必學的。由於沒有 OO 概念,常常需要用 structure 跟 point 去模擬物件、封裝,專案寫大的時候就會很難維護。

下面把我常遇到的 compile error 列出來:

No such file or directory
$ gcc -o test test.c
test.c:2:23: error: json/json.h: No such file or directory
test.c: In function 'main':
test.c:8: warning: assignment makes pointer from integer without a cast
test.c:12: warning: assignment makes pointer from integer without a cast
表示 include 的 header file 找不到,預設會去找 /usr/include、/usr/local/include,若 library 安裝在其他目錄,可以在 compile 時加上 -I/opt/json-c-0.9/include。


Undefined reference
$ gcc -o test test.c -I/opt/json-c-0.9/include
/tmp/ccMCrNFo.o: In function `main':
test.c:(.text+0x9): undefined reference to `json_object_new_object'
test.c:(.text+0x17): undefined reference to `json_object_new_string
...
test.c:(.text+0xc1): undefined reference to `json_object_put'
collect2: ld returned 1 exit status
表示 link library 失敗,compile 時要加上 -ljson


Linking fail
$ gcc -o test test.c -I/opt/json-c-0.9/include -ljson
/usr/bin/ld: cannot find -ljson
collect2: ld returned 1 exit status
表示找不到 libjson.so 的 library 檔案,預設的搜尋路徑 /lib、/lib64、/usr/lib、/usr/local/lib, 若 library 安裝在其他地方,在 compile 時要加上 -L/opt/json-c-0.9/lib 來增加搜尋的路徑。


Implicit declaration of function
$ gcc -Wall -o test test.c test2.o -I/opt/json-c-0.9/include -L/opt/json-c-0.9/lib -ljson
test.c: In function 'main':
test.c:23: warning: implicit declaration of function 'sub_func'
出現此 warning,表示你用的 function 在 .c 檔中有定義,但是没有在對應的 header 檔中宣告。
上例中我在 test.c 去 call sub_func() 這個 function,這個 function 是在 test2.c 中 implement,但卻沒有在 test2.h 宣告。


Error while loading shared libraries
$./test
./test: error while loading shared libraries: libjson.so.0: cannot open shared object file: No such file or directory
表示 runtime 時 load 不到指定的 library,環境變數 $LD_LIBRARY_PATH 要加入 share library 路徑,LD_LIBRARY_PATH=/opt/json-c-0.9/lib

2012/06/25

JSON parser in C language - json-c

C 語言存取 JSON 格式的資料實在不是很方便,不像 python、javascript 原生的物件表示法就跟 JSON 一樣。C 的 implementation 在官網列了好幾套,我自己是選用 json-c

API 使用方式可以參考這裡

這邊就列出基本的操作,以這樣為例:
{
  "name": "Brian",
  "sex": 0,
  "data": {
    "education": "master",
    "profession": "engineer"
  }
}

組成 JSON object,並輸出成 string
struct json_object *root, *data;

root = json_object_new_object();
json_object_object_add(root, "name", json_object_new_string("Brian"));
json_object_object_add(root, "sex", json_object_new_int(0));

data = json_object_new_object();
json_object_object_add(data, "education", json_object_new_string("master"));
json_object_object_add(data, "profession", json_object_new_string("engineer"));
json_object_object_add(root, "data", data);

// Output to string
printf("%s", json_object_to_json_string(root));

// Decrease counter and free object
json_object_put(data);
json_object_put(root);

解開 JSON object
struct json_object *root, *name, *sex, *data, *edu, *prof;

root = json_tokener_parse(json_string);
// Use is_error() to check the result, don't use "j_root == NULL".
if (is_error(j_root)) {
  printf("parse failed.");
  exit(-1);
}

name = json_object_object_get(root, "name");
sex = json_object_object_get(root, "sex");
data = json_object_object_get(root, "data");
// If parse fail, object is NULL
if (data != NULL) {
  edu = json_object_object_get(data, "education");
  prof= json_object_object_get(data, "profession");
}

if (!name || !sex|| !edu || !prof) {
  printf("parse failed.");
  json_object_put(root);
  exit(-1);
}

// Fetch value
printf("name=%s", json_object_get_string(name));
printf("sex=%d", json_object_get_int(sex));
printf("education=%s", json_object_get_string(edu));
printf("profession=%s", json_object_get_string(prof));

// Free
json_object_put(root);

參考資料
json-c-0.9库的json_object_object_get()引发崩溃问题

2012/06/22

Webapp2: Hello world

最近又在摸索 Google app engine,webapp2 基本上就是 GAE 內建的 web framework 啦,提供基本的 URL route、request handler、template、database 存取,相容於 WSGI 架構。

這是官方的 Hello world
helloworld.py
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp2 World!')

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

app.yaml
application: helloworld
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: helloworld.py

若使用 WSGI 來啓動 server,預設的 configuration file 就是 app.yaml,裡面會指明要執行哪個 python code,像上面就是 helloworld.py 這個檔案。執行後會去尋找"app"這個 object,所以物件的命名不能有錯,不然就會出現錯誤:
ERROR    2012-06-20 07:37:47,561 wsgi.py:189] 
Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 187, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 239, in _LoadHandler
    raise ImportError('%s has no attribute %s' % (handler, name))
ImportError:  has no attribute app


如果不想透過 google app launcher 來啓動 project,請參考這一篇 Quick start (to use webapp2 outside of App Engine),需要額外安裝三個 package。
程式碼如下,要自己另外帶起 http server:
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp2 World!')
app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

def main():
    from paste import httpserver
    httpserver.serve(app, host='127.0.0.1', port='8080')

if __name__ == '__main__':
    main()

再從 command line 下達指令就可以了
$ python main.py
serving on http://127.0.0.1:8080

2012/06/20

Python 基本開發環境建置

紀錄 python 開發環境建置過程:

1. 安装 Python-2.7.2
$ tar zxvf Python-2.7.2.tgz
$ cd Python-2.7.2
$ ./configure && make && make install

2. 安装 distribute 0.6.27
$ tar zxvf distribute-0.6.27.tar.gz
$ cd distribute-0.6.27
$ python setup.py build
$ python setup.py install

3. 安裝 pip-1.1
$ tar zxvf pip-1.1.tar.gz
$ cd pip-1.1
$ python setup.py build
$ python setup.py install

4. 安裝 virtualenv 並建制 virtual environment
$ pip install virtualenv
$ virtualenv /opt/env

5. 指定 virtual environment 為預設開發環境
$ source /opt/env/bin/activate
可以看到command line提示改變 (pyenv)brian@localhost:~$
之後用 pip 安裝套件就會放在 /opt/env/lib 之下。


建立獨立的 virtual environment 好處就是比較"乾淨",如果你的系統有多個使用者,大家都裝到系統預設目錄則容易搞混。虛擬環境也可以建立多個,若同時跑多個專案或想切換不同 library 版本也很方便。另一方面也可以釐清開發這個專案"最小需求 library"是哪些,將來要 deploy 時也會比較順利。

2012/06/19

ActiveMQ - 訊息傳遞的仲介

ActiveMQ 是一種 Message-oriented middleware,主要是當作不同 process 或不同主機間的溝通媒介。

會用到 queue 的時機點有很多,就我個人工作上的範疇,通常是
1. Web server 短時間內遇到大量的 request,需要先接起來再由後端子系統處理,達到分散式架構
2. 這件事情本來就沒有很急,丟在 queue 裡面慢慢處理就好


類似 ActiveMQ 的 product 還有 RabbitMQZeroMq這邊列出了更多種的選擇。我的需求很基本,通常會考量幾個點:
1. 夠不夠 robust
2. Client端有沒有支援熟悉的語言 (C/C++, Java, .Net, Python, Php, Ruby, ...)
3. 文件寫得夠不夠清楚
4. 有在持續維護中

本來想用 C 來連接 ActiveMQ,但發現所有的 C library都沒在維護了,而且也沒 API 文件可以看,是有 C++ 的 ActiveMQ-CPP 可以用,但我不熟lol,最後選擇了 Python 的,pyactivemq也是一段時間沒更新了,重點是編譯不過,stomppy 或許是個不錯的選擇:)

訊息交換方式可以參考 Messaging pattern 裡面定義的,而我常用的也就下面兩種:
1. Push - Pull
2. Publish - Subscribe
這也是大多 product 都支援的

上述講得是 application 層的 ,至於 ActiveMQ 底層的傳輸 protocol 有二
1. STOMP (Streaming Text Oriented Messaging Protocol)
2. OpenWire protocol
差別在哪不是很清楚...只知道 stomp 是個大家熟知的標準,follow就對了!

意外發現一篇好文章,是在 promote ZeroMQ 的:
新世紀通訊函式庫 – ZeroMQ | 程式設計 遇上 小提琴

--

發現 ActiveMQ-CPP 裡面也有提供 C 的 interface,終於不用被 C++ 所擾,在 tar 檔裡面找不到,在 github 上才有,連結在此

2012/06/11

MySQL 如何避免 SQL injection

遇到這個問題,大部分的人應該會說:用 ORM (Object-relational mapping) 呀!但我就沒有打算多學一套 >< 一方面也是不清楚它欄位的屬性、資料形態是否完全支援 mysql,加上我執行的 query 也蠻複雜的。

先前有找了幾套:
  • SQLAlchemy - 大家比較推薦的
  • Quick ORM - fully compatible with the newest SQLAlchemy

ORM 的好處是你只要設定一次 mapping,之後 select、update、insert 都很方便,但我實在是沒有必要為了一個 injection protect 就用了這麼強大的東西。


除了 ORM 以外...
Search 了一下發現 MySQLdb 本身就有支援以參數的方式來執行 query:
cursor.execute('SELECT * FROM `user` WHERE user=%s AND password=%s;', usr, pwd)
所有的參數都會根據其資料型態來取代 format string,特殊字元自然就會被跳脫了。

要注意的是 statement 內的引號 " 要去掉,format string 通通使用 %s,像下面兩個語法就會有問題:
cursor.execute('SELECT * FROM `user` WHERE username="%s";', username)
cursor.execute('SELECT * FROM `user` WHERE id=%d;', my_id)

另外,也不要把 format string 串起來才傳過去,這樣就會真的發生 SQL injection了:
cursor.execute('SELECT * FROM `user` WHERE user=%s AND password=%s;' % (usr, pwd))

至於其他的 database 可以找看看有沒有 prepared statement 可以使用。


參考資料

Python: 頑固的 variable length argument

這邊卡了很久,做個筆記...

可變長度的參數傳遞
def p(fmt, *arg):
    print fmt % arg

# 傳多個參數
s1 = "hello"
s2 = "world"
p("%s %s", s1, s2)

# 把 list 的 element 當作參數
l = ["Allen", "Billy", "Elmer"]
p("%s %s %s", *tuple(l))

# 混合模式
l = ["Allen", "Billy", "Elmer"]
s = "hello"
p("%s, %s %s %s", *((s, ) + tuple(l)))
Output:
hello world
Allen Billy Elmer
hello, Allen Billy Elmer

2012/06/07

社交網路設計:在地圖中找尋附近的人

很多社交網路App都會用到地圖,以自己為中心,找尋附近的店家、朋友等等。
用程式的邏輯來表達就是:
以 (x, y) 座標為中心,找尋方圓 n 公里以內的點

資料庫不外乎就是儲存經緯度 (longitude, latitude)
-----------------------------
 id | lat       | lng
----+-----------+------------
 1  | 25.151000 | 121.549000
 2  | 25.010000 | 121.574000
 3  | 25.070000 | 121.589000


今天我想找 User1 附近 10km 以內的人
方法一
來看"距離"跟"經緯度"的關係是什麼,從 Decimal degrees - Wikipedia 知道,其實他們並非線性的關係,而是一堆三角函數算出來的,因為隨著緯度越高,單位經緯度所對應的距離就越短。從 別搗蛋 歸納出的對應表:
台灣地區:
兩地經緯度相差 0.5度:距離相差約 50公里
兩地經緯度相差 0.1度:距離相差約 10公里
兩地經緯度相差 0.05度:距離相差約 5公里
兩地經緯度相差 0.01度:距離相差約 1公里

2012/06/01

Mac OS 內建抓圖功能 (Print Screen)

抓圖指令
把抓圖儲存到桌面 (Command 就是蘋果鍵"⌘")
  • Command-Shift-3: 抓全螢幕
  • Command-Shift-4: 可用滑鼠自己選取範圍
  • Command-Shift-4, 再按Space: 抓取視窗元件

和上面一樣,只是儲存到剪貼簿
  • Command-Control-Shift-3
  • Command-Control-Shift-4
  • Command-Control-Shift-4, 再按Space

參考資料
Taking Screenshots in Mac OS X - Mac Guides


改變抓圖儲存的位置
打開終端機,下達指令:
$ defaults write com.apple.screencapture location ~/Desktop/Snapshot
$ killall SystemUIServer

若要改回來原本位置:
$ defaults write com.apple.screencapture location ~/Desktop/
$ killall SystemUIServer

參考資料
Change the Screen Shot Save File Location in Mac OS X


改變抓圖的預設檔名
預設的名字是"螢幕抓圖 (datetime)"
$ defaults write com.apple.screencapture name "Snapshot"
$ killall SystemUIServer

參考資料
Change The File Name Of Screenshot In Mac OS X - YouTube