星期日, 十月 07, 2007

把文件保存到图片中


把文件内容保存到无损图片中,就可以将这些代码放到网上了。上面的小黑块就是上面代码的内容。

这里,先用bmp尝试一下。或许可以将文件内容编排成比较好玩的东西,隐藏在其他图片中就更加有意思了。


星期五, 七月 20, 2007

Web Page to Mail

如果服务器是自己的,而且可以在后台编写CGI或者其他方法实现的服务器程序来处理用户提交的内容,那可以离开本页。

在公司内部除了邮件之外,通过内部服务器的Web页面来交互是非常方便的。但是,如果你向公司IT部门的工程师要求在页面上添加一些交互页面,则是一件非常复杂而且昂贵的事情。

问题
比如现在有这样一个需求:公司的一个部门需要在页面上显示一个样品申请表格,这样公司的其他工作人员就可以在网页上直接查看产品的信息,然后根据需要填写申请书。但是,由于使用服务器端脚本的开发费用无法处理,所以只好在每个样品的信息下面链接了申请受理人的邮箱。这样,当申请者选中某个样品的时候,就会通过OutLook来向他发送邮件。

虽然,申请人的OutLook基本是随时随地都开启的,所以不需要耗费很长时间。但是,这个处理方式的麻烦之处是申请人必须自己填写申请信件的全部内容,包括主题,产品信息和数量。而受理者同样还是需要面对申请信件内容无法统一,处理不方便的困扰。

既然无法选用HTTP-POST的方式来处理这个需求,所能想到的还是如何改进用现有的邮件处理方式来解决这个问题。

一、POST to MAIL
直接将网页表格的内容提交到邮箱中,是最直接的想法。在大多数浏览器中都支持这个方法。需要修改Form的Action属性,将提交处理交给邮件地址。这样和一般CGI处理方式类似,提交的内容通过编码后就可以发送到指定的邮箱中。
form method="post" action="mailto:stest@yourmail.com"
需要说明的是:在IE和Firefox两种浏览器中,这个方法的处理效果是不同。IE需要更多的安全确认,并将提交内容打包作为附件发送。
为了实现用正文不编码发送,需要在上面所显示的Form中指明编码方式:
enctype="text/plain"
用户设定的内容现在就可以作为邮件的正文发送过来了。

二、链接方式
想必大家对href="mailto:stest@yourmail.com"这样的链接方式都很熟悉。所以,就可以联想:既然POST方式都支持,那GET方式是不是也支持呢?是不是象一般的链接一样支持携带参数呢?

答案是:的确可以。而且,参数就是我们熟悉的邮件组成部分:subject, cc, bcc, body。而编码方式就是大家熟悉的URL编码方式。例如:
href="mailto:stest@yourmail.com?subject=Test&Body=Hello%0DThis%20is%20test."
大家可以对这里展开想象了。这样邮件的标题和内容就可以让页面设定为统一的样子了。

解决问题
从使用方便的角度来看,个人觉得链接的方式还是比较方便的。可是链接的内容实在是太难编写了,而且维护起来也不方便。而且和网页交互不方便,也是静态链接输给POST的一个原因。

但是想一想,我们还有一个很重要的工具没有使用——JavaScript。只要我们把需要交互的内容,通过脚本处理,并生成链接,那么网页上用户的输入就可以直接生成邮件的内容。

现在,我的页面中有如下的一些内容组成了一个样品选择表格:
  • id=aQtyProduct1 …… 4的四个文本输入框
  • id=aProduct1……4的四个单选框(主 要是用来演示更加负载的交互而用的)
  • 以及其他说明文字等
  • 一个链接:id="aMailBook" href="#" onclick="updateOrderList()",用来提交。
HTML的编写中不需要特别注意什么,需要处理的事件也只有链接本身一个而已。
剩下的就是编写提交处理函数:
function updateOrderList(){
var linkstr = "mailto:stest@yourmail.com?subject="
+ escape("Book Product");
var bodystr = "";

for(var i = 1;i <= 4;i ++){
var product = document.getElementById('aProduct' + i);
var quantity =document.getElementById('aQtyProduct' + i);
if(product == null || quantity == null){
continue;
}
if(product.checked && parseInt(quantity.value) > 0){
bodystr += "Product" + i + ": " + quantity.value + "\n";
}
}
if(bodystr == ""){
linkstr = "#";
}else{
linkstr += "&body=" + escape(bodystr);
}

var maillink = document.getElementById("aMailBook");
if(maillink != null){
maillink.href = linkstr;
}
}
代码很简单:点击链接的时候计算一下到底申请了那些东西,数量是多少,然后将这些作为邮件的正文编码后添加到邮件链接中。
如果函数不返回false,那么点击之后浏览器就会自动起动默认的邮件处理客户端,这些内容就会出现在编辑器中。用户只要点击发送即可了。
是不是方便了不少。

剩下的就是可以研究一下如何生成更加漂亮的邮件正文;如何自动生成用户选择报表等等。

星期六, 七月 14, 2007

Using wxWidgets on Windows

在Windows上使用除了VC之外的UI开发工具进行开发工作其是来还真是挺麻烦的。不过,自己掏钱安装一套那个东西实在是有点力不从心。如果贪图方便的话,建议使用Dev-cpp或者Eclipse套装来工作。如果不嫌麻烦,可以自己从网上将需要的工具一个一个的下载下来,自己建立一个UI开发工作环境。

所需要的软件有:
  1. Windows操作系统(如果没有正版的系统,可以考虑多看看Linux下的开发)
  2. MinGW32环境,可以从MingGW-Download网页看到下载连接。
  3. wxWidgets的源代码,本文讨论的就是使用这个开发包。
  4. 合适的编辑环境,这里推荐使用Code::Blocks来配合wxWidgets的开发。
  5. 窗口设计工具wxFormBuilder
第一步,需要搭建编译环境,也就是下载正确的MinGW32包,然后将他们解压缩到一个目录下即可。如果本机之前没有安装合适的GCC环境,就不要下载源代码了。直接下载下面的这些包:
  1. binutils
  2. GCC
  3. MinGW Runtime
  4. windows API
  5. make
然后将他们解压缩到一个目录下,比如d:\mingw32下面。

写一个hello.cpp然后用
d:\mingw32\bin\g++ hello.cpp -o hello
编译一下看看是否可以生成正确的可执行程序。

默认安装的make程序的名字叫做mingw32-make,这可能是为了和系统中原有的make程序区别而设计的。后面说到make的时候,就是指这个程序了。
如果方便,可以将这个bin目录放到PATH环境变量中去。

wxWidgets的特点就是用本地操作系统的图形系统实现了一套移植性非常好的开发包。如果全部采用wx的类库开发,整个程序在很多PC,甚至phone都可以方便的进行编译、运行。但是为了使用中文,请注意在编译系统的时候,一定要要记得支持Unicode。

在以上我们建立的编译系统中工作,需要使用“\build\msw\”目录下面的makefile.gcc脚本来编译。关于编译的设置信息在同一目录中的config.gcc文件中。需要设置的内容有:
  1. UNICODE := 1 支持Unicode,以后所有的代码文件最好使用UTF-8来编写。
  2. BUILD := debug 或者 release,最好设置两次编译两个不同的版本,一个用来调试程序,另一个用来发布程序。
  3. SHARED := 1 采用共享库的方式来编译,也就是wx库最后编译成dll的方式吗?个人觉得调试的时候还是变为dll的共享库比较好,可以有效的减少连接的时间,缩小程序执行程序的大小。发布的时候则可以考虑编译为静态链接库的方式,以一个单文件的方式发布。
  4. MONOLITHIC := 1 将wx库编译成一个大的库文件,而不是多个单独的文件。个人觉得这样简单一下。
其他的一些可以自己看着注释设置。建议编译共享库的release和debug两个版本,以及release的一个release静态库的版本。然后就可以在这个目录下使用GCC的编译器来编译了。
make -f makefile.gcc
喝一杯茶,看看闲书,等待它编译完成吧。

编译结束之后,首先可以考虑编译一下wx代码中带的那些例子,先看看wx的库是否都已经成功的建立了。

然后建议将其中的art、contrib、demos、include、lib这几个目录拷贝出来,其他的内容在以后的开发中已经没有什么用途了。
为了防止以后程序执行中出现找不到动链接库的问题,可以将这些lib目录下的dll文件拷贝到PATH环境变包含的目录下面去。

好马配好鞍,快马加一鞭。编译器、开发包都一应俱全了,再加上Code::Blocks这个开发环境就更加完美了。建议直接使用Night Build版本,不用安装,完全绿色的。

第一次运行后,需要设置一下编译器和调试工具的Global Compiler Setting相关内容:设定MinGW32的目录,然后写入各个应用程序的名字(应该默认好像就是正确的)。

然后可以尝试在向导的指引下建立第一个wxWidgets程序。然后在项目树顶端上下文菜单中选择Build Options项,然后在链接项目中加入我们编译成功的那个库文件,比如debug版本需要链接libwxmsw28ud.a文件,release版本需要链接libwxmsw28u.a文件。在相关查找目录中添加对应这些文件所在的目录位置。

最后,还有一个wxFormBuilder需要安装,不过就很简单了。
它生成的窗口程序代码需要作为我们开发程序的父类来使用,然后在开发中我们直接重写相关的事件处理方法就可以了,非常方便。

其他开发相关的内容,请参考wxWidgets类库的手册。

星期一, 十一月 06, 2006

和内核一起做些事情:/proc

首先,我尝试搬抄一下使用/proc文件访问内核的方式。这个方式在以前的书本上写要注册一个proc_dir_entry结构体的内容,然后注册等。但是现在这些情况有所变化。如果只是向用户输出(也就是用户只读),那么只需要写一个读取的方法就好了,注册的方法很简单。

下面用代码说明:
static int procfile_read(char *buffer,
char **buffer_location,// Buffer
off_t offset, // current offset to read
int buffer_length,
int *eof,
int *data)
{
static char mybuffer[128];
static int readcount = 0;
int len;

if(offset > 0){
return 0;
}

readcount ++;
len = sprintf(mybuffer, "This is %d time to read me.\n", readcount);
*eof = 1;
return len;
}
代码主要来自这个实例,虽然是2.4内核的,可是效果在999之内还是不错的。
需要注意的是读取函数的参数发生了变化。不过,我的编译器还是一再提醒我注册时指针参数不匹配的问题,不知道指的是哪一个参数?

注册:
create_proc_read_entry("hello", 0, NULL, procfile_read, NULL);
注销:
remove_proc_entry("hello", NULL);
这里为了简单都采用了默认的参数,直接放到了/proc/hello这个位置上。

采用文件名来操作的确比使用inode方便的多了。

星期四, 十一月 02, 2006

Linux 下驱动程序的开发简单入门步骤

2.6内核的初学者笔记

第一步,下载一个内核源代码并编译安装。
这里需要注意的问题是需要将自己根目录所在的硬盘的驱动和文件系统的驱动全部直接编译到内核中。
否则在启动的时候会出现问题,在装入根目录的阶段停止不前。
建议是第一次宁可多选择一些,如SICI和ATA-2(并口硬盘),以及SATA等直接编译进系统。
而Ext2和Ext3也同样处理。
注意不要将grub菜单中原来系统的启动内核项目取消,也不要急着删除原来的旧内核和模块文件。
如果menu.lst中对原内核和驱动的引用为没有版本号的连接文件,如/boot/vmlinux等,
请改为绝对的文件名。因为一会儿安装新内核的时候这些连接会改变。
这样可以防止万一新的内核不能启动的情况下还可以恢复使用。
此外,优化内核的编译设定,这也算是一种乐趣。
千万不要忙着删除内核代码,这里是很大的文档宝库。

第二,参照书本些一个最为简单的模块。
就是能在进入和退出内核的时候显示两条日志即可。比如:


#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int hello_init(void)
{
printk(KERN_ALERT "Hello, linux kernel module\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, I've created a linux kernel module sucessfully\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jason Xing <xsinuz<at>someone.at.prv<");

建议是最好能按照Linux内核的传统代码风格编写。这个可以在内核中找到很多demo。
注意代码和2.4的不同,比如必须自己注册入口和出口方法。

第三,编译这个模块。
请仔细了解本版本下内核编译的方法。有用的资料依然是内核中那些已经存在的Makefile。
可以照着写一个:

obj-m := hello-1.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

之所以能写的如此简单是由于Kbuild的帮助。

然后执行make编译:

make
make -C /lib/modules/2.6.18/build SUBDIRS=/home/jason/projects/test/kernel-dev/hello1 modules
make[1]: Entering directory `/usr/src/linux-2.6.18'
CC [M] /home/jason/projects/test/kernel-dev/hello1/hello-1.o
Building modules, stage 2.
MODPOST
CC /home/jason/projects/test/kernel-dev/hello1/hello-1.mod.o
LD [M] /home/jason/projects/test/kernel-dev/hello1/hello-1.ko
make[1]: Leaving directory `/usr/src/linux-2.6.18'


得到了内核模块文件 hello-1.ko!

第四,测试。
这里需要知道三个命令:lsmod,insmod, rmmod。从名字上也大体应该知道他们的作用了。
不过这个简单的模块并不能用显示出来。
在插入内核后我们可以通过dmesg看到我们写入的第一条信息。卸除后可以看到第二条信息。
注意:这里需要管理员权限才可一对内核操作。


第五,下一步。
买一本《Linux内核驱动开发》也许是必要的。不过有点贵,而且是英文的,真的要考虑一下。

----
参考文献

星期三, 十月 25, 2006



这里列出了PIC单片机重的一些常用程序和方法

1. 关于org的使用例子:

在这个例子中用了两次org伪指令,ORG 0 和 ORG 1FFH, 前者是程序开始的地方。
而后者的位置是芯片复位的位置。

ORG 0 ; 程序从零地址开始
MAIN:
;..............
GOTO MAIN ; 主循环
;-----------------------------
ORG 1FFH
GOTO MAIN
;-----------------------------
END ;程序结束


2. BCD码转换

;--方法 1--
;----------------------------------------------------------
; Function: 转换为BCD码显示和存储
;----------------------------------------------------------
BCD2:
MOVLW 8;
MOVWF COUNT_BIT ;循环左移计数器置8
CLRF BCD_BUF1 ;清除缓冲器
MOVF DISPLAY_NUM,W
MOVWF BCD_BUF2
LOOP1:
RLF BCD_BUF2,1 ;二进制数转换成BCD码(以便显示)
RLF BCD_BUF1,1
DECFSZ COUNT_BIT,1 ;左移8次
GOTO ADJUST
MOVF BCD_BUF1,W
MOVWF DISPBUF
MOVF DISPBUF,W
RETURN
ADJUST:
MOVLW 3 ;二进制转BCD的调整
ADDWF BCD_BUF1,W ;每次移位后都检查低四位LSD+3是否大于7
MOVWF BCD_SUM
BTFSC BCD_SUM,3
MOVWF BCD_BUF1
MOVLW 30H
ADDWF BCD_BUF1,W ;如果是则再加3,否则不加。
MOVWF BCD_SUM
BTFSC BCD_SUM,7
MOVWF BCD_BUF1 ;接着再将高四位MSD作相同处理
GOTO LOOP1

;--方法 2--
;----------------------------------------------------------
; Function: 8位二进制数转换成2位BCD码
; 转换为两个BCD码:BCD_MSD,BCD_LSD
;----------------------------------------------------------
BIN2BCD:
CLRF BCD_MSD
MOVWF BCD_LSD
GETENTH:
MOVLW 10
SUBWF BCD_LSD,0
BTFSS STATUS,C
GOTO OVER
MOVWF BCD_LSD
INCF BCD_MSD,1
GOTO GETENTH
OVER:
RETURN

3. 延时函数


;--- 所用的临时变量----
DELAYH EQU 023H
DELAYL EQU 024H
;----------------------------------------------------------
; Function: 延时
;----------------------------------------------------------
DELAY:
MOVLW 0FFH
MOVWF DELAYH
DELAY_LOOP:
MOVLW 0FFH
MOVWF DELAYL
DELAY_LOOP1:
NOP
DECFSZ DELAYL
GOTO DELAY_LOOP1
DECFSZ DELAYH
GOTO DELAY_LOOP
RETURN


4. 中断处理程序的设计


【一】 ;中断的入口地址
ORG 0004H
GOTO INTSERVE

然后在INTSERVE地址写入判断中断入口的代码:
INTSERVE: MOVWF W_TEMP ;保存寄存器
SWAPF STATUS,W ;注意使用SWAPF指令,才不会影响Z标志位。
BCF STATUS,RP0
MOVWF STATUS_TEMP
这样W,STATUS分别保存到W_TEMP,STATUS_TEMP两个地方了。

【二】 ;中断的出口处理:恢复W,STATUS两个寄存器
;所有的中断处理结束后都必须这样处理。
SWAPF STATUS_TEMP,W
MOVWF STATUS
SWAPF W_TEMP,1
SWAPF W_TEMP,W
RETFIE ;中断返回

【三】 ;中断的判断中断源,分别进入特定的处理程序
BTFSC PIR1,ADIF ;A/D转换中断
GOTO ADINTSERV
BTFSC PIR1,RCIF ;串口接收中断
GOTO RCINTSERV
BTFSC PIR1,TMR2IF ;定时器2中断
GOTO TIMER2SERV
GOTO RETFIEO ;直接到中断的出口处理

【四】 ;特定中断的处理
TIMER2SERV ;定时器2的中断处理
BCF PIR1,TMR2IF ;清除中断标志位
……
GOTO RETFIEO ;直接到中断的出口处理



5. A/D转换的程序设计


【一】各个寄存器的使用

ADRESH : A/D转换结果高字节
ADRESL : A/D转换结果低字节
ADCON0 : 控制A/D操作
ADCON1 : 控制A/D引脚

(1)ADCON0 地址:1FH
Bit7 Bit0
+-------+-------+------+------+------+------------+----+------+
| ADCS1 | ADCS0 | CHS2 | CHS1 | CHS0 | GO/DONE- | -- | ADON |
+-------+-------+------+------+------+------------+----+------+
__________/ | |
| | +-在工作或者被关闭
| +--1:正在进行A/D转换
+---A/D时钟选择,00,01,10分别表示2,8,32分频,或者11用内部RC振荡器

CHS2, CHS1,CHS0用来选择模拟通道

(2)ADCON1 地址:9FH
Bit7 Bit0
+-------+-----+-----+-----+-------+-------+-------+-------+
| ADFM | --- | --- | --- | PCFG3 | PCFG2 | PCFG1 | PCFG0 |
+-------+-----+-----+-----+-------+-------+-------+-------+
| ______________________/
| |
| +-端口配置
++- 1:右移,ADRESH高六位读作零
+- 0:左移,ADRESL低六位读作零

(3)ADRESH(地址:1EH),ADRESL(地址:9EH)

(4)TRISA : PORTA的方向寄存器设置
选择好A/D端口号后,必须在A/D转换开始之前设置号TRISA,
使得A/D输入通道为输入态。

【二】当A/D转换结束
A/D转换完成了以后,10位的A/D结果存放在ADRESH,ADRESL,同时GO/DONE-将被清零。
而且,A/D转换中断标志位ADIF位将被置一。

【三】实现A/D转换的步骤
1)设置A/D转换模块
a、对模拟引脚、基准电压、数字I/O(ADCON1)进行设置
b、选择A/D输入通道 ADCON0
c、选择A/D转换时钟 ADCON0
d、打开A/D转换模块 ADCON0
2)如果需要A/D转换中断功能,设置A/D中断
a、对A/D转换完成标志位ADIF清零。
b、对A/D转换中断使能位ADIE置一。
c、对PEIE位置一。
d、对全局中断使能位GIE置一。
3)所需等待采样时间
4)对GO/DONE-置一,启动A/D转换。
5)等待A/D转换完成,有两种方法:
a、软件查询GO/DONE-位的状态是否为1
b、等待A/D转换完成的中断。
6)读入A/D结果 ADRESH,ADRESL。如果为中断处理就清除ADIF位。



星期一, 十月 02, 2006

OS说:要更多并发,于是有了多线程

这里我将说明一下自己在使用pthread遇到的一些问题,顺便作为这里长期空白的补丁。

先做一个广告好了。我的系统是ubuntu6.06 Dapper Drake,相信现在使用这个发行版本的用户数量是最为庞大的。而且由于Debian的强大支持,这个系统在安装和使用中都是非常适合入门,以及拥有相当Linux知识的人士使用。不过默认情况下是没有gcc和make这些东西的,只有一个链接器ld被安装。所以,之前请安装它们两个软件。(用sudo执行apt-get install gcc make 即可。)不过如果你需要边听着mp3边工作的话,请自己搜索一下让rhythmbox支持它的方法吧。

网络知识资源:pthread入门 这是我强力推荐的站点。
IBM的developerWorks的thread专栏 下面也有很多关于Linux的线程问题的讨论,用心挖掘会了解很多有益的东西的。
当然,如果手边有一本像样的pthread书籍的话那就更加完美了。

现在可以创建你的C文件,并include像是unistd.h, stdio.h, 当然还有pthread.h这些文件。

如何创建线程?
只用这一句即可:
iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
不过里面的参数却要说明一下。
首先是thread1这个参数,它是pthread_t类型的,作为返回值告诉我们创建的这个线程编号。而print_message_function和message1则是线程函数和它的入参了。返回值表示成功与否。

然后呢?
当然是线程开始工作。但是主线程,也就是创建它的那个线程该怎么办呢?它的使命结束了吗?
我们可以在创建一个线程后结束自己,然后看看会怎么样。
int main()
{
pthread_t threadid;
int ret;

ret = pthread_create( &threadid, NULL,
print_message_function, (void*)msgarg);
//pthread_join(threadid, NULL);
printf("I, the father process(%d) is exitting..\n", getpid());
exit(0);
}

void *print_message_function( void *ptr )
{
sleep(5);
puts((const char*) ptr);
return NULL;
}
结果就是完全看不到线程输出的信息,查看进程表的结果也同样说明了这个问题。主线程的结束将终结所有子线程。
这和在网络上看到一些言论不太一样,据报道有人在主线程先于子线程结束的情况下发现整个进程变成了“僵尸”
不过这里没有发现这种情况。

但是一般意义上,子线程的退出应该是可以预料的,比如使用一个全局的符号或者消息来通知它们。而不是如此粗暴的关闭程序,这样我们无法全面释放任务线程所占用的资源,这时候就需要在上面一段中被注释的那一行代码了--使用pthread_join来等待创建的线程结束。
(关于资源的占用和释放的主题请关注pthread_cleanup_push和pthread_cleanup_pop的相关内容。)

(待续……)