随笔-17  评论-6  文章-1  trackbacks-0
  2006年2月20日
find   basedir   -name   CVS   |xargs   rm   -rf  
posted @ 2007-04-17 14:32 小铁匠 阅读(321) | 评论 (0)编辑 收藏


    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;

        int length = req.getContentLength();
        if (length > 0) {
            BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(req,length);

            InputStream is = bufferedRequest.getInputStream();
            byte[] content = new byte[length];
           
            int pad = 0;
            while(pad < length){
                pad += is.read(content, pad, length);
            }

            request = bufferedRequest;
        }
        chain.doFilter(request, response);      
    }


BufferedRequestWrapper .java

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class BufferedRequestWrapper extends HttpServletRequestWrapper {

    ByteArrayInputStream bais;

    BufferedServletInputStream bsis;

    byte[] buffer;

    public BufferedRequestWrapper(HttpServletRequest req,int length) throws IOException {
        super(req);
        // Read InputStream and store its content in a buffer.
        InputStream is = req.getInputStream();
        buffer = new byte[length];

        int pad = 0;
        while(pad < length){
            pad += is.read(buffer, pad, length);
        }
    }

    public ServletInputStream getInputStream() {
        try {
            // Generate a new InputStream by stored buffer
            bais = new ByteArrayInputStream(buffer);
            // Istantiate a subclass of ServletInputStream
            // (Only ServletInputStream or subclasses of it are accepted by the
            // servlet engine!)
            bsis = new BufferedServletInputStream(bais);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
        }
        return bsis;
    }

}




BufferedServletInputStream .java

import java.io.*;
import javax.servlet.ServletInputStream;

/*
 Subclass of ServletInputStream needed by the servlet engine.
 All inputStream methods are wrapped and are delegated to
 the ByteArrayInputStream (obtained as constructor parameter)!
 */
public class BufferedServletInputStream extends ServletInputStream {

    ByteArrayInputStream bais;

    public BufferedServletInputStream(ByteArrayInputStream bais) {
        this.bais = bais;
    }

    public int available() {
        return bais.available();
    }

    public int read() {
        return bais.read();
    }

    public int read(byte[] buf, int off, int len) {
        return bais.read(buf, off, len);
    }

}

posted @ 2006-10-25 17:01 小铁匠 阅读(2729) | 评论 (1)编辑 收藏
$ gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
will compile and link a program called x11fred using the version of the library libX11 found in the
/usr/openwin/lib directory.
posted @ 2006-07-10 14:54 小铁匠 阅读(415) | 评论 (0)编辑 收藏

有发送人名称中文支持,支持bytes格式附件,附件中文支持

  public static boolean send(String fromName, String fromAddr, String to, String subject, String
                             body, String fileName, byte[] file) throws
      Exception {
        //发送人名称,用base64编码,再加上特殊标志
        fromName = "=?GB2312?B?" + new String(base64.encode((fromName).getBytes()))  + "?=";
    Properties props = new Properties();
    Session session = Session.getInstance(props, null);
    props.put("mail.smtp.host", Constants.mailhost);
    props.put("mail.smtp.auth", "false"); 
    Message msg = new MimeMessage(session);
      msg.setFrom(new InternetAddress(fromAddr,fromName));
//后面的BodyPart将加入到此处创建的Multipart中
    Multipart mp = new MimeMultipart();
// Create the message part
    BodyPart messageBodyPart = new MimeBodyPart();

    // Fill the message
    messageBodyPart.setText(body);

    mp.addBodyPart(messageBodyPart);

      /*发送附件*/
     if (file != null && file.length > 0) {
       //利用枚举器方便的遍历集合
         MimeBodyPart mbp = new MimeBodyPart(); 
//         File fileTmp = null;
         //得到数据源
//         FileDataSource fds = new FileDataSource(fileTmp);
         //得到附件本身并至入BodyPart
         mbp.setDataHandler(new DataHandler(new ByteArrayDataSource(file,"application/octet-stream")));
         //得到文件名同样至入BodyPart
         mbp.setFileName(MimeUtility.encodeWord(fileName,"GB2312",null));
         mp.addBodyPart(mbp);
     }
   
    //Multipart加入到信件
    msg.setContent(mp);

    msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
    msg.setSubject(subject);

    msg.setHeader("X-Mailer", "personal Email Sender");
    msg.setSentDate(new Date());

    Transport transport = session.getTransport("smtp");

    //添加认证信息
    transport.connect(Constants.mailhost, Constants.user, Constants.pwd);
    transport.sendMessage(msg, msg.getRecipients(Message.RecipientType.TO));
    transport.close();
    return true;
  }




import java.io.*;
import javax.activation.*;

public class ByteArrayDataSource implements DataSource {
    /** * Data to write. */
    private byte[] _data;

    /** * Content-Type. */
    private String _type;

    /* Create a datasource from an input stream */
    public ByteArrayDataSource(InputStream is, String type) {
        _type = type;
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            int ch;

            // XXX : must be made more efficient by
            // doing buffered reads, rather than one byte reads
            while ((ch = is.read()) != -1)
                os.write(ch);
            _data = os.toByteArray();
        } catch (IOException ioe) {
        }
    }

    /* Create a datasource from a byte array */
    public ByteArrayDataSource(byte[] data, String type) {
        _data = data;
        _type = type;
    }

    /* Create a datasource from a String */
    public ByteArrayDataSource(String data, String type) {
        try {
            // Assumption that the string contains only ascii
            // characters ! Else just pass in a charset into this
            // constructor and use it in getBytes()
            _data = data.getBytes("iso-8859-1");
        } catch (UnsupportedEncodingException uee) {
        }
        _type = type;
    }

    public InputStream getInputStream() throws IOException {
        if (_data == null)
            throw new IOException("no data");
        return new ByteArrayInputStream(_data);
    }

    public OutputStream getOutputStream() throws IOException {
        throw new IOException("cannot do this");
    }

    public String getContentType() {
        return _type;
    }

    public String getName() {
        return "dummy";
    }
}

posted @ 2006-05-10 18:02 小铁匠 阅读(630) | 评论 (1)编辑 收藏
C/C++头文件一览

C、传统 C++

#include <assert.h>    //设定插入点
#include <ctype.h>     //字符处理
#include <errno.h>     //定义错误码
#include <float.h>     //浮点数处理
#include <fstream.h>    //文件输入/输出
#include <iomanip.h>    //参数化输入/输出
#include <iostream.h>   //数据流输入/输出
#include <limits.h>    //定义各种数据类型最值常量
#include <locale.h>    //定义本地化函数
#include <math.h>     //定义数学函数
#include <stdio.h>     //定义输入/输出函数
#include <stdlib.h>    //定义杂项函数及内存分配函数
#include <string.h>    //字符串处理
#include <strstrea.h>   //基于数组的输入/输出
#include <time.h>     //定义关于时间的函数
#include <wchar.h>     //宽字符处理及输入/输出
#include <wctype.h>    //宽字符分类

//////////////////////////////////////////////////////////////////////////

标准 C++ (同上的不再注释)

#include <algorithm>    //STL 通用算法
#include <bitset>     //STL 位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>     //复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>      //STL 双端队列容器
#include <exception>    //异常处理类
#include <fstream>
#include <functional>   //STL 定义运算函数(代替运算符)
#include <limits>
#include <list>      //STL 线性列表容器
#include <map>       //STL 映射容器
#include <iomanip>
#include <ios>       //基本输入/输出支持
#include <iosfwd>     //输入/输出系统使用的前置声明
#include <iostream>
#include <istream>     //基本输入流
#include <ostream>     //基本输出流
#include <queue>      //STL 队列容器
#include <set>       //STL 集合容器
#include <sstream>     //基于字符串的流
#include <stack>      //STL 堆栈容器    
#include <stdexcept>    //标准异常类
#include <streambuf>    //底层输入/输出支持
#include <string>     //字符串类
#include <utility>     //STL 通用模板类
#include <vector>     //STL 动态数组容器
#include <cwchar>
#include <cwctype>

using namespace std;

//////////////////////////////////////////////////////////////////////////

C99 增加

#include <complex.h>   //复数处理
#include <fenv.h>    //浮点环境
#include <inttypes.h>  //整数格式转换
#include <stdbool.h>   //布尔环境
#include <stdint.h>   //整型环境
#include <tgmath.h>   //通用类型数学宏

posted @ 2006-03-09 16:55 小铁匠 阅读(526) | 评论 (0)编辑 收藏
string.h中的函数

@函数名称:   strdup
函数原型:   char *strdup(const char *s)
函数功能:   字符串拷贝,目的空间由该函数分配
函数返回:   指向拷贝后的字符串指针
参数说明:   src-待拷贝的源字符串
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
#include <alloc.h>
int main()
{
  char *dup_str, *string="abcde";
  dup_str=strdup(string);
  printf("%s", dup_str);
  free(dup_str);
  return 0;
}


@函数名称:   strcpy
函数原型:   char* strcpy(char* str1,char* str2);
函数功能:   把str2指向的字符串拷贝到str1中去
函数返回:   返回str1,即指向str1的指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char string[10];
  char *str1="abcdefghi";
  strcpy(string,str1);
  printf("the string is:%s\n",string);
  return 0;
}


@函数名称:   strncpy
函数原型:   char *strncpy(char *dest, const char *src,int count)
函数功能:   将字符串src中的count个字符拷贝到字符串dest中去
函数返回:   指向dest的指针
参数说明:   dest-目的字符串,src-源字符串,count-拷贝的字符个数
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char string[10];
  char *str1="abcdefghi";
  strncpy(string,str1,3);
  string[3]='\0';
  printf("%s",string);
  return 0;
}


@函数名称:   strcat
函数原型:   char* strcat(char * str1,char * str2);
函数功能:   把字符串str2接到str1后面,str1最后的'\0'被取消
函数返回:   str1
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>

int main()
{
  char buffer[80];

  strcpy(buffer,"Hello ");
  strcat(buffer,"world");
  printf("%s\n",buffer);
  return 0;
}


@函数名称:   strncat
函数原型:   char *strncat(char *dest, const char *src, size_t maxlen)
函数功能:   将字符串src中前maxlen个字符连接到dest中
函数返回:
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>

char buffer[80];

int main()
{
  strcpy(buffer,"Hello ");
  strncat(buffer,"world",8);
  printf("%s\n",buffer);
  strncat(buffer,"*************",4);
  printf("%s\n",buffer);
  return 0;
}


@函数名称:   strcmp
函数原型:   int strcmp(char * str1,char * str2);
函数功能:   比较两个字符串str1,str2.
函数返回:   str1<str2,返回负数; str1=str2,返回 0; str1>str2,返回正数.
参数说明:
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char *buf1="aaa", *buf2="bbb", *buf3="ccc";
  int ptr;
  ptr=strcmp(buf2, buf1);
  if(ptr>0)
    printf("buffer 2 is greater than buffer 1\n");
  else
    printf("buffer 2 is less than buffer 1\n");
  ptr=strcmp(buf2, buf3);
  if(ptr>0)
    printf("buffer 2 is greater than buffer 3\n");
  else
    printf("buffer 2 is less than buffer 3\n");
  return 0;
}


@函数名称:   strncmp
函数原型:   int strncmp(char *str1,char *str2,int count)
函数功能:   对str1和str2中的前count个字符按字典顺序比较
函数返回:   小于0:str1<str2,等于0:str1=str2,大于0:str1>str2
参数说明:   str1,str2-待比较的字符串,count-比较的长度
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  int ptr;
  char *buf1="aaabbb",*buf2="bbbccc",*buf3="ccc";
  ptr=strncmp(buf2,buf1,3);
  if (ptr>0)
    printf("buffer 2 is greater than buffer 1");
  else
    printf("buffer 2 is less than buffer 1");
    ptr=strncmp(buf2,buf3,3);
  if (ptr>0)
    printf("buffer 2 is greater than buffer 3");
  else
    printf("buffer 2 is less than buffer 3");
  return(0);
}


@函数名称:   strpbrk
函数原型:   char *strpbrk(const char *s1, const char *s2)
函数功能:   得到s1中第一个“同时也出现在s2中”字符的位置指针
函数返回:   位置指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
char *p="Find all vowels";

while(p)
{
  printf("%s\n",p);
  p=strpbrk(p+1,"aeiouAEIOU");
}
return 0;
}


@函数名称:   strcspn
函数原型:   int strcspn(const char *s1, const char *s2)
函数功能:   统计s1中从头开始直到第一个“来自s2中的字符”出现的长度
函数返回:   长度
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>

int main()
{
  printf("%d\n",strcspn("abcbcadef","cba"));
  printf("%d\n",strcspn("xxxbcadef","cba"));
  printf("%d\n",strcspn("123456789","cba"));
  return 0;
}


@函数名称:   strspn
函数原型:   int strspn(const char *s1, const char *s2)
函数功能:   统计s1中从头开始直到第一个“不来自s2中的字符”出现的长度
函数返回:   位置指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
#include <alloc.h>
int main()
{
  printf("%d\n",strspn("out to lunch","aeiou"));
  printf("%d\n",strspn("out to lunch","xyz"));
  return 0;
}


@函数名称:   strchr
函数原型:   char* strchr(char* str,char ch);
函数功能:   找出str指向的字符串中第一次出现字符ch的位置
函数返回:   返回指向该位置的指针,如找不到,则返回空指针
参数说明:   str-待搜索的字符串,ch-查找的字符
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char string[15];
  char *ptr, c='r';
  strcpy(string, "This is a string");
  ptr=strchr(string, c);
  if (ptr)
    printf("The character %c is at position: %d\n",c,ptr-string);
  else
    printf("The character was not found\n");
  return 0;
}


@函数名称:   strrchr
函数原型:   char *strrchr(const char *s, int c)
函数功能:   得到字符串s中最后一个含有c字符的位置指针
函数返回:   位置指针
参数说明:
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char string[15];
  char *ptr,c='r';
  strcpy(string,"This is a string");
  ptr=strrchr(string,c);
  if (ptr)
    printf("The character %c is at position:%d",c,ptr-string);
  else
    printf("The character was not found");
  return 0;
}


@函数名称:   strstr
函数原型:   char* strstr(char* str1,char* str2);
函数功能:   找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符)
函数返回:   返回该位置的指针,如找不到,返回空指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char *str1="Open Watcom C/C++",*str2="Watcom",*ptr;
  ptr=strstr(str1,str2);
  printf("The substring is:%s\n",ptr);
  return 0;
}


@函数名称:   strrev
函数原型:   char *strrev(char *s)
函数功能:   将字符串中的所有字符颠倒次序排列
函数返回:   指向s的指针
参数说明:
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char *forward="string";
  printf("Before strrev():%s",forward);
  strrev(forward);
  printf("After strrev(): %s",forward);
  return 0;
}


@函数名称:   strnset
函数原型:   char *strnset(char *s, int ch, size_t n)
函数功能:   将字符串s中前n个字符设置为ch的值
函数返回:   指向s的指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char *string="abcdefghijklmnopqrstuvwxyz";
  char letter='x';
  printf("string before strnset: %s",string);
  strnset(string,letter,13);
  printf("string after strnset: %s",string);
  return 0;
}


@函数名称:   strset
函数原型:   char *strset(char *s, int ch)
函数功能:   将字符串s中所有字符设置为ch的值
函数返回:   指向s的指针
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char string[10]="123456789";
  char symbol='c';
  printf("Before strset(): %s", string);
  strset(string, symbol);
  printf("After strset(): %s", string);
  return 0;
}


@函数名称:   strtok
函数原型:   char *strtok(char *s1, const char *s2)
函数功能:   分解s1字符串为用特定分隔符分隔的多个字符串(一般用于将英文句分解为单词)
函数返回:   字符串s1中首次出现s2中的字符前的子字符串指针
参数说明:   s2一般设置为s1中的分隔字符
        规定进行子调用时(即分割s1的第二、三及后续子串)第一参数必须是NULL
        在每一次匹配成功后,将s1中分割出的子串位置替换为NULL(摘下链中第一个环),因此s1被破坏了
        函数会记忆指针位置以供下一次调用
       
所属文件:   <string.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char *p;
  char *buffer;
  char *delims={ " .," };

  buffer=strdup("Find words, all of them.");
  printf("%s\n",buffer);
  p=strtok(buffer,delims);
  while(p!=NULL){
    printf("word: %s\n",p);
    p=strtok(NULL,delims);
  }
  printf("%s\n",buffer);
  return 0;
}


@函数名称:   strupr
函数原型:   char *strupr(char *s)
函数功能:   将字符串s中的字符变为大写
函数返回:
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char *string="abcdefghijklmnopqrstuvwxyz",*ptr;
  ptr=strupr(string);
  printf("%s",ptr);
  return 0;
}


@函数名称:   strlwr
函数原型:   char *strlwr(char *s)
函数功能:   将字符串中的字符变为小写字符
函数返回:   指向s的指针
参数说明:
所属文件:   <string.h>

#include<string.h>
int main()
{
  char str[]="HOW TO SAY?";
  printf("%s",strlwr(str));
  return 0;
}


@函数名称:   strlen
函数原型:   unsigned int strlen(char * str);
函数功能:   统计字符串str中字符的个数(不包括终止符'\0')
函数返回:   返回字符串的长度.
参数说明:
所属文件:   <string.h>

#include <stdio.h>
#include<string.h>
int main()
{
  char str[]="how are you!";
  printf("the lence is:%d\n",strlen(str));
  return 0;
}


@函数名称:   strerror
函数原型:   char *strerror(int errnum)
函数功能:   得到错误信息的内容信息
函数返回:   错误提示信息字符串指针
参数说明:   errnum-错误编号
所属文件:   <string.h>

#include <stdio.h>
#include <errno.h>
int main()
{
  char *buffer;
  buffer=strerror(errno);
  printf("Error: %s",buffer);
  return 0;
}


@函数名称:   memcpy
函数原型:   void *memcpy(void *dest, const void *src, size_t n)
函数功能:   字符串拷贝
函数返回:   指向dest的指针
参数说明:   src-源字符串,n-拷贝的最大长度
所属文件:   <string.h>,<mem.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char src[]="******************************";
  char dest[]="abcdefghijlkmnopqrstuvwxyz0123456709";
  char *ptr;
  printf("destination before memcpy:%s\n",dest);
  ptr=memcpy(dest,src,strlen(src));
  if (ptr)
    printf("destination after memcpy:%s\n",dest);
  else
    printf("memcpy failed");
  return 0;
}


@函数名称:   memccpy
函数原型:   void *memccpy(void *dest, const void *src, int c, size_t n)
函数功能:   字符串拷贝,到指定长度或遇到指定字符时停止拷贝
函数返回:
参数说明:   src-源字符串指针,c-中止拷贝检查字符,n-长度,dest-拷贝底目的字符串指针
所属文件:   <string.h>,<mem.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char *src="This is the source string";
  char dest[50];
  char *ptr;
  ptr=memccpy(dest,src,'c',strlen(src));
  if (ptr)
  {
    *ptr='\0';
    printf("The character was found:%s",dest);
  }
  else
    printf("The character wasn't found");
  return 0;
}


@函数名称:   memchr
函数原型:   void *memchr(const void *s, int c, size_t n)
函数功能:   在字符串中第开始n个字符中寻找某个字符c的位置
函数返回:   返回c的位置指针,返回NULL时表示未找到
参数说明:   s-要搜索的字符串,c-要寻找的字符,n-指定长度
所属文件:   <string.h>,<mem.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char str[17];
  char *ptr;
  strcpy(str,"This is a string");
  ptr=memchr(str,'r',strlen(str));
  if (ptr)
  printf("The character 'r' is at position: %d",ptr-str);
  else
  printf("The character was not found");
  return 0;
}


@函数名称:   memcmp
函数原型:   int memcmp(const void *s1, const void *s2,size_t n)
函数功能:   按字典顺序比较两个串s1和s2的前n个字节
函数返回:   <0,=0,>0分别表示s1<,=,>s2
参数说明:   s1,s2-要比较的字符串,n-比较的长度
所属文件:   <string.h>,<mem.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char *buf1="ABCDE123";
  char *buf2="abcde456";
  int stat;
  stat=memcmp(buf1,buf2,5);
  printf("The strings to position 5 are ");
  if(stat) printf("not ");
  printf("the same\n");
  return 0;
}



@函数名称:   memicmp
函数原型:   int memicmp(const void *s1, const void *s2, size_t n)
函数功能:   按字典顺序、不考虑字母大小写对字符串s1,s2前n个字符比较
函数返回:   <0,=0,>0分别表示s1<,=,>s2
参数说明:   s1,s2-要比较的字符串,n-比较的长度
所属文件:   <string.h>,<mem.h>

#include <stdio.h>
#include <string.h>
int main()
{
  char *buf1="ABCDE123";
  char *buf2="abcde456";
  int stat;
  stat=memicmp(buf1,buf2,5);
  printf("The strings to position 5 are ");
  if(stat) printf("not");
  printf("the same");
  return 0;
}


@函数名称:   memmove
函数原型:   void *memmove(void *dest, const void *src, size_t n)
函数功能:   字符串拷贝
函数返回:   指向dest的指针
参数说明:   src-源字符串,n-拷贝的最大长度
所属文件:   <string.h>,<mem.h>

#include <string.h>
#include <stdio.h>
int main()
{
  char dest[40]="abcdefghijklmnopqrstuvwxyz0123456789";
  printf("destination prior to memmove:%s\n",dest);
  memmove(dest+1,dest,35);
  printf("destination after memmove:%s",dest);
  return 0;
}


@函数名称:   memset
函数原型:   void *memset(void *s, int c, size_t n)
函数功能:   字符串中的n个字节内容设置为c
函数返回:
参数说明:   s-要设置的字符串,c-设置的内容,n-长度
所属文件:   <string.h>,<mem.h>

#include <string.h>
#include <stdio.h>
#include <mem.h>
int main()
{
  char buffer[]="Hello world";
  printf("Buffer before memset:%s\n",buffer);
  memset(buffer,'*',strlen(buffer)-1);
  printf("Buffer after memset:%s",buffer);
  return 0;
}

posted @ 2006-03-09 16:54 小铁匠 阅读(1997) | 评论 (0)编辑 收藏

C++字符串完全指南 - Win32字符编码(一)

前言

字符串的表现形式各异,象TCHAR,std::string,BSTR等等,有时还会见到怪怪的用_tcs起头的宏。这个指南的目的就是说明各种字符串类型及其用途,并说明如何在必要时进行类型的相互转换。

在指南的第一部分,介绍三种字符编码格式。理解编码的工作原理是致为重要的。即使你已经知道字符串是一个字符的数组这样的概念,也请阅读本文,它会让你明白各种字符串类之间的关系。

指南的第二部分,将阐述各个字符串类,什么时候使用哪种字符串类,及其相互转换。

字符串基础 - ASCII, DBCS, Unicode

所有的字符串类都起源于C语言的字符串,而C语言字符串则是字符的数组。首先了解一下字符类型。有三种编码方式和三种字符类型。

第一种编码方式是单字节字符集,称之为SBCS,它的所有字符都只有一个字节的长度。ASCII码就是SBCS。SBCS字符串由一个零字节结尾。

第二种编码方式是多字节字符集,称之为MBCS,它包含的字符中有单字节长的字符,也有多字节长的字符。Windows用到的MBCS只有二种字符类型,单字节字符和双字节字符。因此Windows中用得最多的字符是双字节字符集,即DBCS,通常用它来代替MBCS。

在DBCS编码中,用一些保留值来指明该字符属于双字节字符。例如,Shift-JIS(通用日语)编码中,值0x81-0x9F 和 0xE0-0xFC 的意思是:“这是一个双字节字符,下一个字节是这个字符的一部分”。这样的值通常称为前导字节(lead byte),总是大于0x7F。前导字节后面是跟随字节(trail byte)。DBCS的跟随字节可以是任何非零值。与SBCS一样,DBCS字符串也由一个零字节结尾。

第三种编码方式是Unicode。Unicode编码标准中的所有字符都是双字节长。有时也将Unicode称为宽字符集(wide characters),因为它的字符比单字节字符更宽(使用更多内存)。注意,Unicode不是MBCS - 区别在于MBCS编码中的字符长度是不同的。Unicode字符串用二个零字节字符结尾(一个宽字符的零值编码)。

单字节字符集是拉丁字母,重音文字,用ASCII标准定义,用于DOS操作系统。双字节字符集用于东亚和中东语言。Unicode用于COM和Windows NT内部。

读者都很熟悉单字节字符集,它的数据类型是char。双字节字符集也使用char数据类型(双字节字符集中的许多古怪处之一)。Unicode字符集用wchar_t数据类型。Unicode字符串用L前缀起头,如:

  wchar_t  wch = L'1';      // 2 个字节, 0x0031

  wchar_t* wsz = L"Hello";  // 12 个字节, 6 个宽字符

字符串的存储

单字节字符串顺序存放各个字符,并用零字节表示字符串结尾。例如,字符串"Bob"的存储格式为:

Unicode编码中,L"Bob"的存储格式为:

用0x0000 (Unicode的零编码)结束字符串。

DBCS 看上去有点象SBCS。以后我们会看到在串处理和指针使用上是有微妙差别的。字符串"日本语" (nihongo) 的存储格式如下(用LB和TB分别表示前导字节和跟随字节):

注意,"ni"的值不是WORD值0xFA93。值93和FA顺序组合编码为字符"ni"。(在高位优先CPU中,存放顺序正如上所述)。

字符串处理函数

C语言字符串处理函数,如strcpy(), sprintf(), atol()等只能用于单字节字符串。在标准库中有只用于Unicode字符串的函数,如wcscpy(), swprintf(), _wtol()。

微软在C运行库(CRT)中加入了对DBCS字符串的支持。对应于strxxx()函数,DBCS使用_mbsxxx()函数。在处理DBCS字符串(如日语,中文,或其它DBCS)时,就要用_mbsxxx()函数。这些函数也能用于处理SBCS字符串(因为DBCS字符串可能就只含有单字节字符)。

现在用一个示例来说明字符串处理函数的不同。如有Unicode字符串L"Bob":

x86 CPU的排列顺序是低位优先(little-endian)的,值0x0042的存储顺序为42 00。这时如用strlen()函数求字符串的长度就发生问题。函数找到第一个字节42,然后是00,意味着字符串结尾,于是返回1。反之,用wcslen()函数求"Bob"的长度更糟糕。wcslen()首先找到0x6F42,然后是0x0062,以后就在内存缓冲内不断地寻找00 00直至发生一般性保护错(GPF)。

strxxx()及其对应的_mbsxxx()究竟是如何运作的?二者之间的不同是非常重要的,直接影响到正确遍历DBCS字符串的方法。下面先介绍字符串遍历,然后再回来讨论strxxx()和 _mbsxxx()。

字符串遍历

我们中的大多数人都是从SBCS成长过来的,都习惯于用指针的 ++ 和 -- 操作符来遍历字符串,有时也使用数组来处理字符串中的字符。这二种方法对于SBCS 和 Unicode 字符串的操作都是正确无误的,因为二者的字符都是等长的,编译器能够的正确返回我们寻求的字符位置。

但对于DBCS字符串就不能这样了。用指针访问DBCS字符串有二个原则,打破这二个原则就会造成错误。

1. 不可使用 ++ 算子,除非每次都检查是否为前导字节。

2. 绝不可使用 -- 算子来向后遍历。

先说明原则2,因为很容易找到一个非人为的示例。假设,有一个配制文件,程序启动时要从安装路径读取该文件,如:C:\Program Files\MyCoolApp\config.bin。文件本身是正常的。

假设用以下代码来配制文件名:

bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
    // 这里从注册表读取文件的安装路径,假设一切正常。
    // 如果路径末尾没有反斜线,就加上反斜线。
    // 首先,用指针指向结尾零:
char* pLastChar = strchr ( szConfigFilename, '\0' );
    // 然后向后退一个字符:
    pLastChar--;  
    if ( *pLastChar != '\\' )
        strcat ( szConfigFilename, "\\" );
    // 加上文件名:
    strcat ( szConfigFilename, "config.bin" );
    // 如果字符串长度足够,返回文件名:
    if ( strlen ( szConfigFilename ) >= nBuffSize )
        return false;
    else
        {
        strcpy ( pszName, szConfigFilename );
        return true;
        }
}

这段代码的保护性是很强的,但用到DBCS字符串还是会出错。假如文件的安装路径用日语表达:C:\ヨウユソ,该字符串的内存表达为:

这时用上面的GetConfigFileName()函数来检查文件路径末尾是否含有反斜线就会出错,得到错误的文件名。

错在哪里?注意上面的二个十六进制值0x5C(蓝色)。前面的0x5C是字符"\",后面则是字符值83 5C,代表字符"ソ"。可是函数把它误认为反斜线了。

正确的方法是用DBCS函数将指针指向恰当的字符位置,如下所示:

bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
    // 这里从注册表读取文件的安装路径,假设一切正常。
    // 如果路径末尾没有反斜线,就加上反斜线。
    // 首先,用指针指向结尾零:
char* pLastChar = _mbschr ( szConfigFilename, '\0' );
    // 然后向后退一个双字节字符:
    pLastChar = CharPrev ( szConfigFilename, pLastChar );
    if ( *pLastChar != '\\' )
        _mbscat ( szConfigFilename, "\\" );
    // 加上文件名:
    _mbscat ( szConfigFilename, "config.bin" );
    // 如果字符串长度足够,返回文件名:
    if ( _mbslen ( szInstallDir ) >= nBuffSize )
        return false;
    else
        {
        _mbscpy ( pszName, szConfigFilename );
        return true;
        }
} 

这个改进的函数用CharPrev() API 函数将指针pLastChar向后移动一个字符。如果字符串末尾的字符是双字节字符,就向后移动2个字节。这时返回的结果是正确的,因为不会将字符误判为反斜线。

现在可以想像到第一原则了。例如,要遍历字符串寻找字符":",如果不使用CharNext()函数而使用++算子,当跟随字节值恰好也是":"时就会出错。

与原则2相关的是数组下标的使用:

 2a. 绝不可在字符串数组中使用递减下标。

出错原因与原则2相同。例如,设置指针pLastChar为:

char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

结果与原则2的出错一样。下标减1就是指针向后移动一个字节,不符原则2。

再谈strxxx() _mbsxxx()

现在可以清楚为什么要用 _mbsxxx() 函数了。strxxx() 函数不认识DBCS字符而 _mbsxxx()认识。如果调用strrchr("C:\\", '\\')函数可能会出错,但 _mbsrchr()认识双字节字符,所以能返回指向最后出现反斜线字符的指针位置。

最后提一下strxxx() 和 _mbsxxx() 函数族中的字符串长度测量函数,它们都返回字符串的字节数。如果字符串含有3个双字节字符,_mbslen()将返回6。而Unicode的函数返回的是wchar_ts的数量,如wcslen(L"Bob") 返回3

C++字符串完全指南 - Win32字符编码(二)
翻译:连波
15/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098306,00.htm

Win32 API中的MBCS Unicode

API的二个字符集

也许你没有注意到,Win32的API和消息中的字符串处理函数有二种,一种为MCBS字符串,另一种为Unicode字符串。例如,Win32中没有SetWindowText()这样的接口,而是用SetWindowTextA()和 SetWindowTextW()函数。后缀A (表示ANSI)指明是MBCS函数,后缀W(表示宽字符)指明是Unicode函数。

编写Windows程序时,可以选择用MBCS或Unicode API接口函数。用VC AppWizards向导时,如果不修改预处理器设置,缺省使用的是MBCS函数。但是在API接口中没有SetWindowText()函数,该如何调用呢?实际上,在winuser.h头文件中做了以下定义:

BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
#ifdef UNICODE
 #define SetWindowText  SetWindowTextW
#else
 #define SetWindowText  SetWindowTextA
#endif

编写MBCS应用时,不必定义UNICODE,预处理为:

#define SetWindowText  SetWindowTextA

然后将SetWindowText()处理为真正的API接口函数SetWindowTextA() (如果愿意的话,可以直接调用SetWindowTextA() 或SetWindowTextW()函数,不过很少有此需要)。

如果要将缺省应用接口改为Unicode,就到预处理设置的预处理标记中去掉 _MBCS标记,加入UNICODE 和 _UNICODE (二个标记都要加入,不同的头文件使用不同的标记)。不过,这时要处理普通字符串反而会遇到问题。如有代码:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowText ( hwnd, szNewText );

编译器将"SetWindowText"置换为"SetWindowTextW"后,代码变为:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowTextW ( hwnd, szNewText );

看出问题了吧,这里用一个Unicode字符串处理函数来处理单字节字符串。

第一种解决办法是使用宏定义:
HWND hwnd = GetSomeWindowHandle();
#ifdef UNICODE
 wchar_t szNewText[] = L"we love Bob!";
#else
 char szNewText[] = "we love Bob!";
#endif
SetWindowText ( hwnd, szNewText );

要对每一个字符串都做这样的宏定义显然是令人头痛的。所以用TCHAR来解决这个问题:

TCHAR的救火角色

TCHAR 是一种字符类型,适用于MBCS 和 Unicode二种编码。程序中也不必到处使用宏定义。

TCHAR的宏定义如下:

#ifdef UNICODE
 typedef wchar_t TCHAR;
#else
 typedef char TCHAR;
#endif

所以,TCHAR中在MBCS程序中是char类型,在Unicode中是 wchar_t 类型。

对于Unicode字符串,还有个 _T() 宏,用于解决 L 前缀:

#ifdef UNICODE
 #define _T(x) L##x
#else
 #define _T(x) x
#endif

## 是预处理算子,将二个变量粘贴在一起。不管什么时候都对字符串用 _T 宏处理,这样就可以在Unicode编码中给字符串加上L前缀,如:

TCHAR szNewText[] = _T("we love Bob!");

SetWindowTextA/W 函数族中还有其它隐藏的宏可以用来代替strxxx() 和 _mbsxxx() 字符串函数。例如,可以用 _tcsrchr 宏取代strrchr(),_mbsrchr(),或 wcsrchr()函数。_tcsrchr 根据编码标记为_MBCS 或 UNICODE,将右式函数做相应的扩展处理。宏定义方法类似于SetWindowText。

不止strxxx()函数族中有TCHAR宏定义,其它一些函数中也有。例如,_stprintf (取代sprintf()和swprintf()),和 _tfopen (取代fopen() 和 _wfopen())。MSDN的全部宏定义在"Generic-Text Routine Mappings"栏目下。

String 和 TCHAR 类型定义

Win32 API 文件中列出的函数名都是通用名(如"SetWindowText"),所有的字符串都按照TCHAR类型处理。(只有XP除外,XP只使用Unicode类型)。下面是MSDN给出的常用类型定义:

 

类型

MBCS 编码中的意义

Unicode 编码中的意义

WCHAR

wchar_t

wchar_t

LPSTR

zero-terminated string of char (char*)

zero-terminated string of char (char*)

LPCSTR

constant zero-terminated string of char (constchar*)

constant zero-terminated string of char (constchar*)

LPWSTR

zero-terminated Unicode string (wchar_t*)

zero-terminated Unicode string (wchar_t*)

LPCWSTR

constant zero-terminated Unicode string (const wchar_t*)

constant zero-terminated Unicode string (const wchar_t*)

TCHAR

char

wchar_t

LPTSTR

zero-terminated string of TCHAR (TCHAR*)

zero-terminated string of TCHAR (TCHAR*)

LPCTSTR

constant zero-terminated string of TCHAR (const TCHAR*)

constant zero-terminated string of TCHAR (const TCHAR*)

何时使用TCHAR 和Unicode

可能会有疑问:“为什么要用Unicode?我一直用的都是普通字符串。”

在三种情况下要用到Unicode:

  1. 程序只运行于Windows NT。
  2. 处理的字符串长于MAX_PATH定义的字符数。
  3. 程序用于Windows XP中的新接口,那里没有A/W版本之分。

大部分Unicode API不可用于Windows 9x。所以如果程序要在Windows 9x上运行的话,要强制使用MBCS API (微软推出一个可运行于Windows 9x的新库,叫做Microsoft Layer for Unicode。但我没有试用过,无法说明它的好坏)。相反,NT内部全部使用Unicode编码,使用Unicode API可以加速程序运行。每当将字符串处理为MBCS API时,操作系统都会将字符串转换为Unicode并调用相应的Unicode API 函数。对于返回的字符串,操作系统要做同样的转换。尽管这些转换经过了高度优化,模块尽可能地压缩到最小,但毕竟会影响到程序的运行速度。

NT允许使用超长文件名(长于MAX_PATH 定义的260),但只限于Unicode API使用。Unicode API的另外一个优点是程序能够自动处理输入的文字语言。用户可以混合输入英文,中文和日文作为文件名。不必使用其它代码来处理,都按照Unicode编码方式处理。

最后,作为Windows 9x的结局,微软似乎抛弃了MBCS API。例如,SetWindowTheme() 接口函数的二个参数只支持Unicode编码。使用Unicode编码省却了MBCS与Unicode之间的转换过程。

如果程序中还没有使用到Unicode编码,要坚持使用TCHAR和相应的宏。这样不但可以长期保持程序中DBCS编码的安全性,也利于将来扩展使用到Unicode编码。那时只要改变预处理中的设置即可!

C++字符串完全指南(2) - 各种字符串类(一)
翻译:连波
19/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098621,00.htm

前言

C语言的字符串容易出错,难以管理,并且往往是黑客到处寻找的目标。于是,出现了许多字符串包装类。可惜,人们并不很清楚什么情况下该用哪个类,也不清楚如何将C语言字符串转换到包装类。

本文涉及到Win32 API,MFC,STL,WTL和Visual C++运行库中使用到的所有的字符串类型。说明各个类的用法,如何构造对象,如何进行类转换等等。Nish为本文提供了Visual C++ 7的managed string 类的用法。

阅读本文之前,应完全理解本指南第一部分中阐述的字符类型和编码。

字符串类的首要原则:

不要随便使用类型强制转换,除非转换的类型是明确由文档规定的。

之所以撰写字符串指南这二篇文章,是因为常有人问到如何将X类型的字符串转换到Z类型。提问者使用了强制类型转换(cast),但不知道为什么不能转换成功。各种各样的字符串类型,特别是BSTR,在任何场合都不是三言二语可以讲清的。因此,我以为这些提问者是想让强制类型转换来处理一切。

除非明确规定了转换算子,不要将任何其它类型数据强制转换为string。一个字符串不能用强制类型转换到string类。例如:

void SomeFunc ( LPCWSTR widestr );
main()
{
  SomeFunc ( (LPCWSTR) "C:\\foo.txt" );  // 错!
}

这段代码100%错误。它可以通过编译,因为类型强制转换超越了编译器的类型检验。但是,能够通过编译,并不证明代码是正确的。

下面,我将指出什么时候用类型强制转换是合理的。
C语言字符串与类型定义

如指南的第一部分所述,Windows API定义了TCHAR术语。它可用于MBCS或Unicode编码字符,取决于预处理设置为_MBCS 或 _UNICODE标记。关于TCHAR的详细说明请阅指南的第一部分。为便于叙述,下面给出字符类型定义:

Type

Meaning

WCHAR

Unicode character (wchar_t)

TCHAR

MBCS or Unicode character, depending on preprocessor settings

LPSTR

string of char (char*)

LPCSTR

constant string of char (constchar*)

LPWSTR

string of WCHAR (WCHAR*)

LPCWSTR

constant string of WCHAR (const WCHAR*)

LPTSTR

string of TCHAR (TCHAR*)

LPCTSTR

constant string of TCHAR (const TCHAR*)

另外还有一个字符类型OLECHAR。这是一种对象链接与嵌入的数据类型(比如嵌入Word文档)。这个类型通常定义为wchar_t。如果将预处理设置定义为OLE2ANSI,OLECHAR将被定义为char类型。现在已经不再定义OLE2ANSI(它只在MFC 3以前版本中使用),所以我将OLECHAR作为Unicode字符处理。

下面是与OLECHAR相关的类型定义:

Type

Meaning

OLECHAR

Unicode character (wchar_t)

LPOLESTR

string of OLECHAR (OLECHAR*)

LPCOLESTR

constant string of OLECHAR (const OLECHAR*)

还有以下二个宏让相同的代码能够适用于MBCS和Unicode编码:

Type

Meaning

_T(x)

Prepends L to the literal in Unicode builds.

OLESTR(x)

Prepends L to the literal to make it an LPCOLESTR.

宏_T有几种形式,功能都相同。如: -- TEXT, _TEXT, __TEXT, 和 __T这四种宏的功能相同。

COM中的字符串 - BSTR VARIANT

许多COM接口使用BSTR声明字符串。BSTR有一些缺陷,所以我在这里让它独立成章。

BSTR是Pascal类型字符串(字符串长度值显式地与数据存放在一起)和C类型字符串(字符串长度必须通过寻找到结尾零字符来计算)的混合型字符串。BSTR属于Unicode字符串,字符串中预置了字符串长度值,并且用一个零字符来结尾。下面是一个"Bob"的BSTR字符串:

注意,字符串长度值是一个DWORD类型值,给出字符串的字节长度,但不包括结尾零。在上例,"Bob"含有3个Unicode字符(不计结尾零),6个字节长。因为明确给出了字符串长度,所以当BSTR数据在不同的处理器和计算机之间传送时,COM库能够知道应该传送的数据量。

附带说一下,BSTR可以包含任何数据块,不单是字符。它甚至可以包容内嵌零字符数据。这些不在本文讨论范围。

C++中的BSTR变量其实就是指向字符串首字符的指针。BSTR是这样定义的:

typedef OLECHAR* BSTR;

这个定义很糟糕,因为事实上BSTR与Unicode字符串不一样。有了这个类型定义,就越过了类型检查,可以混合使用LPOLESTR和BSTR。向一个需要LPCOLESTR (或 LPCWSTR)类型数据的函数传递BSTR数据是安全的,反之则不然。所以要清楚了解函数所需的字符串类型,并向函数传递正确类型的字符串。

要知道为什么向一个需要BSTR类型数据的函数传递LPCWSTR类型数据是不安全的,就别忘了BSTR必须在字符串开头的四个字节保留字符串长度值。但LPCWSTR字符串中没有这个值。当其它的处理过程(如Word)要寻找BSTR的长度值时就会找到一堆垃圾或堆栈中的其它数据或其它随机数据。这就导致方法失效,当长度值太大时将导致崩溃。

许多应用接口都使用BSTR,但都用到二个最重要的函数来构造和析构BSTR。就是SysAllocString()和SysFreeString()函数。SysAllocString()将Unicode字符串拷贝到BSTR,SysFreeString()释放BSTR。示例如下:

BSTR bstr = NULL;
bstr = SysAllocString ( L"Hi Bob!" );
if ( NULL == bstr )
    // 内存溢出
   // 这里使用bstr
SysFreeString ( bstr );

当然,各种BSTR包装类都会小心地管理内存。

自动接口中的另一个数据类型是VARIANT。它用于在无类型语言,诸如JScript,VBScript,以及Visual Basic,之间传递数据。VARIANT可以包容许多不用类型的数据,如long和IDispatch*。如果VARIANT包含一个字符串,这个字符串是BSTR类型。在下文的VARIANT包装类中我还会谈及更多的VARIANT。
C++字符串完全指南(2) - 各种字符串类- CRT类
翻译:连波
20/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098682,00.htm

_bstr_t

字符串包装类

我已经说明了字符串的各种类型,现在讨论包装类。对于每个包装类,我都会说明它的对象构造过程和如何转换成C类型字符串指针。应用接口的调用,或构造另一个不同类型的字符串类,大多都要用到C类型指针。本文不涉及类的其它操作,如排序和比较等。

再强调一下,在完全了解转换结果之前不要随意使用强制类型转换。

CRT类

_bstr_t

_bstr_t 是BSTR的完全包装类。实际上,它隐含了BSTR。它提供多种构造函数,能够处理隐含的C类型字符串。但它本身却不提供BSTR的处理机制,所以不能作为COM方法的输出参数[out]。如果要用到BSTR* 类型数据,用ATL的CComBSTR类更为方便。

_bstr_t 数据可以传递给需要BSTR数据的函数,但必须满足以下三个条件:

首先,_bstr_t 具有能够转换为wchar_t*类型数据的函数。

其次,根据BSTR定义,使得wchar_t* 和BSTR对于编译器来说是相同的。

第三,_bstr_t内部保留的指向内存数据块的指针 wchar_t* 要遵循BSTR格式。

满足这些条件,即使没有相应的BSTR转换文档,_bstr_t 也能正常工作。示例如下:

 // 构造
_bstr_t bs1 = "char string";        // 从LPCSTR构造 
_bstr_t bs2 = L"wide char string"; // 从LPCWSTR构造
_bstr_t bs3 = bs1;              // 拷贝另一个 _bstr_t
_variant_t v = "Bob";
_bstr_t bs4 = v;              // 从一个含有字符串的 _variant_t 构造
// 数据萃取
LPCSTR psz1 = bs1;              // 自动转换到MBCS字符串
LPCSTR psz2 = (LPCSTR) bs1;     // cast OK, 同上
LPCWSTR pwsz1 = bs1;            // 返回内部的Unicode字符串
LPCWSTR pwsz2 = (LPCWSTR) bs1;  // cast OK, 同上
BSTR    bstr = bs1.copy();      // 拷贝bs1, 返回BSTR
// ...
  SysFreeString ( bstr );

注意,_bstr_t 也可以转换为char* 和 wchar_t*。这是个设计问题。虽然char* 和 wchar_t*不是常量指针,但不能用于修改字符串,因为可能会打破内部BSTR结构。

_variant_t
_variant_t

_variant_t 是VARIANT的完全包装类。它提供多种构造函数和数据转换函数。本文仅讨论与字符串有关的操作。

// 构造
_variant_t v1 = "char string"; // 从LPCSTR 构造
_variant_t v2 = L"wide char string"; // 从LPCWSTR 构造
_bstr_t bs1 = "Bob";
_variant_t v3 = bs1; // 拷贝一个 _bstr_t 对象
// 数据萃取
_bstr_t bs2 = v1; // 从VARIANT中提取BSTR
_bstr_t bs3 = (_bstr_t) v1; // cast OK, 同上

注意,_variant_t 方法在转换失败时会抛出异常,所以要准备用catch 捕捉_com_error异常。

另外要注意 _variant_t 不能直接转换成MBCS字符串。要建立一个过渡的_bstr_t 变量,用其它提供转换Unicode到MBCS的类函数,或ATL转换宏来转换。

与_bstr_t 不同,_variant_t 数据可以作为参数直接传送给COM方法。_variant_t 继承了VARIANT类型,所以在需要使用VARIANT的地方使用_variant_t 是C++语言规则允许的。
C++字符串完全指南(2) - STL和ATL类
翻译:连波
21/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098845,00.htm

STL类

STL类

STL只有一个字符串类,即basic_string。basic_string管理一个零结尾的字符数组。字符类型由模板参数决定。通常,basic_string被处理为不透明对象。可以获得一个只读指针来访问缓冲区,但写操作都是由basic_string的成员函数进行的。

basic_string预定义了二个特例:string,含有char类型字符;which,含有wchar_t类型字符。没有内建的TCHAR特例,可用下面的代码实现:

// 特例化
typedef basic_string tstring; // TCHAR字符串
// 构造
string str = "char string"; // 从LPCSTR构造
wstring wstr = L"wide char string"; // 从LPCWSTR构造
tstring tstr = _T("TCHAR string"); // 从LPCTSTR构造
// 数据萃取
LPCSTR psz = str.c_str(); // 指向str缓冲区的只读指针
LPCWSTR pwsz = wstr.c_str(); // 指向wstr缓冲区的只读指针
LPCTSTR ptsz = tstr.c_str(); // 指向tstr缓冲区的只读指针

与_bstr_t 不同,basic_string不能在字符集之间进行转换。但是如果一个构造函数接受相应的字符类型,可以将由c_str()返回的指针传递给这个构造函数。例如:

// 从basic_string构造_bstr_t 
_bstr_t bs1 = str.c_str();  // 从LPCSTR构造 _bstr_t
_bstr_t bs2 = wstr.c_str(); // 从LPCWSTR构造 _bstr_t
ATL类
CComBSTR

CComBSTR 是ATL的BSTR包装类。某些情况下比_bstr_t 更有用。最主要的是,CComBSTR允许操作隐含BSTR。就是说,传递一个CComBSTR对象给COM方法时,CComBSTR对象会自动管理BSTR内存。例如,要调用下面的接口函数:

// 简单接口
struct IStuff : public IUnknown
{
  // 略去COM程序...
  STDMETHOD(SetText)(BSTR bsText);
  STDMETHOD(GetText)(BSTR* pbsText);
};

CComBSTR 有一个BSTR操作方法,能将BSTR直接传递给SetText()。还有一个引用操作(operator &)方法,返回BSTR*,将BSTR*传递给需要它的有关函数。

CComBSTR bs1;
CComBSTR bs2 = "new text";
pStuff->GetText ( &bs1 );       // ok, 取得内部BSTR地址
  pStuff->SetText ( bs2 );        // ok, 调用BSTR转换
  pStuff->SetText ( (BSTR) bs2 ); // cast ok, 同上

CComVariant
CComBSTR有类似于 _bstr_t 的构造函数。但没有内建MBCS字符串的转换函数。可以调用ATL宏进行转换。

// 构造
CComBSTR bs1 = "char string"; // 从LPCSTR构造
CComBSTR bs2 = L"wide char string"; // 从LPCWSTR构造
CComBSTR bs3 = bs1; // 拷贝CComBSTR
CComBSTR bs4;
bs4.LoadString ( IDS_SOME_STR ); // 从字符串表加载
// 数据萃取
BSTR bstr1 = bs1; // 返回内部BSTR,但不可修改!
BSTR bstr2 = (BSTR) bs1; // cast ok, 同上
BSTR bstr3 = bs1.Copy(); // 拷贝bs1, 返回BSTR
BSTR bstr4;
bstr4 = bs1.Detach(); // bs1不再管理它的BSTR
// ...
SysFreeString ( bstr3 );
SysFreeString ( bstr4 );

上面的最后一个示例用到了Detach()方法。该方法调用后,CComBSTR对象就不再管理它的BSTR或其相应内存。所以bstr4就必须调用SysFreeString()。

最后讨论一下引用操作符(operator &)。它的超越使得有些STL集合(如list)不能直接使用CComBSTR。在集合上使用引用操作返回指向包容类的指针。但是在CComBSTR上使用引用操作,返回的是BSTR*,不是CComBSTR*。不过可以用ATL的CAdapt类来解决这个问题。例如,要建立一个CComBSTR的队列,可以声明为:

  std::list< CAdapt> bstr_list;

CAdapt 提供集合所需的操作,是隐含于代码的。这时使用bstr_list 就象在操作一个CComBSTR队列。

CComVariant

CComVariant 是VARIANT的包装类。但与 _variant_t 不同,它的VARIANT不是隐含的,可以直接操作类里的VARIANT成员。CComVariant 提供多种构造函数和多类型操作。这里只介绍与字符串有关的操作。

// 构造
CComVariant v1 = "char string";       // 从LPCSTR构造
CComVariant v2 = L"wide char string"; // 从LPCWSTR构造
CComBSTR bs1 = "BSTR bob";
CComVariant v3 = (BSTR) bs1;          // 从BSTR拷贝
// 数据萃取
CComBSTR bs2 = v1.bstrVal;            // 从VARIANT提取BSTR

跟_variant_t 不同,CComVariant没有不同VARIANT类型之间的转换操作。必须直接操作VARIANT成员,并确定该VARIANT的类型无误。调用ChangeType()方法可将CComVariant数据转换为BSTR。

CComVariant v4 = ... // 从某种类型初始化 v4
CComBSTR bs3;
if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) ))
    bs3 = v4.bstrVal;

跟 _variant_t 一样,CComVariant不能直接转换为MBCS字符串。要建立一个过渡的_bstr_t 变量,用其它提供转换Unicode到MBCS的类函数,或ATL转换宏来转换。

ATL转换宏

ATL转换宏

ATL的字符串转换宏可以方便地转换不同编码的字符,用在函数中很有效。宏按照[source type]2[new type] 或 [source type]2C[new type]格式命名。后者转换为一个常量指针 (名字内含"C")。类型缩写如下:


 A:MBCS字符串,char* (A for ANSI)
 W:Unicode字符串,wchar_t* (W for wide)
 T:TCHAR字符串,TCHAR*
 OLE:OLECHAR字符串,OLECHAR* (实际等于W)
 BSTR:BSTR (只用于目的类型)

例如,W2A() 将Unicode字符串转换为MBCS字符串,T2CW()将TCHAR字符串转换为Unicode字符串常量。

要使用宏转换,程序中要包含atlconv.h头文件。可以在非ATL程序中使用宏转换,因为头文件不依赖其它的ATL,也不需要 _Module全局变量。如在函数中使用转换宏,在函数起始处先写上USES_CONVERSION宏。它表明某些局部变量由宏控制使用。

转换得到的结果字符串,只要不是BSTR,都存储在堆栈中。如果要在函数外使用这些字符串,就要将这些字符串拷贝到其它的字符串类。如果结果是BSTR,内存不会自动释放,因此必须将返回值分配给一个BSTR变量或BSTR的包装类,以避免内存泄露。

下面是若干宏转换示例:

// 带有字符串的函数:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// 返回字符串的函数:
void Baz ( BSTR* pbstr );
#include 
main()
{
using std::string;
USES_CONVERSION;    // 声明局部变量由宏控制使用
// 示例1:送一个MBCS字符串到Foo()
LPCSTR psz1 = "Bob";
string str1 = "Bob";
Foo ( A2CW(psz1) );
  Foo ( A2CW(str1.c_str()) );
// 示例2:将MBCS字符串和Unicode字符串送到Bar()
LPCSTR psz2 = "Bob";
LPCWSTR wsz = L"Bob";
BSTR bs1;
CComBSTR bs2;
bs1 = A2BSTR(psz2);         // 创建 BSTR
  bs2.Attach ( W2BSTR(wsz) ); // 同上,分配到CComBSTR
Bar ( bs1 );
  Bar ( bs2 );
SysFreeString ( bs1 );      // 释放bs1
  // 不必释放bs2,由CComBSTR释放。
// 示例3:转换由Baz()返回的BSTR
BSTR bs3 = NULL;
string str2;
Baz ( &bs3 );          // Baz() 填充bs3内容
str2 = W2CA(bs3);      // 转换为MBCS字符串
  SysFreeString ( bs3 ); // 释放bs3
}

可以看到,向一个需要某种类型参数的函数传递另一种类型的参数,用宏转换是非常方便的。
C++字符串完全指南(2) - MFC类
翻译:连波
22/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39098983,00.htm

MFC类

MFC类

CString

MFC的CString含有TCHAR,它的实际字符类型取决于预处理标记的设置。通常,CString象STL字符串一样是不透明对象,只能用CString的方法来修改。CString比STL字符串更优越的是它的构造函数接受MBCS和Unicode字符串。并且可以转换为LPCTSTR,因此可以向接受LPCTSTR的函数直接传递CString对象,不必调用c_str()方法。

// 构造
CString s1 = "char string"; // 从LPCSTR构造
CString s2 = L"wide char string"; // 从LPCWSTR构造
CString s3 ( ' ', 100 ); // 预分配100字节,填充空格
CString s4 = "New window text";
// 可以在LPCTSTR处使用CString:
SetWindowText ( hwndSomeWindow, s4 );
// 或者,显式地做强制类型转换:
SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 );

也可以从字符串表加载字符串。CString通过LoadString()来构造对象。用Format()方法可有选择地从字符串表读取一定格式的字符串。

// 从字符串表构造/加载
CString s5 ( (LPCTSTR) IDS_SOME_STR );  // 从字符串表加载
CString s6, s7;
// 从字符串表加载
  s6.LoadString ( IDS_SOME_STR );
// 从字符串表加载打印格式的字符串
  s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... );

第一个构造函数看上去有点怪,但它的确是文档标定的字符串加载方式。

注意,CString只允许一种强制类型转换,即强制转换为LPCTSTR。强制转换为LPTSTR (非常量指针)是错误的。按照老习惯,将CString强制转换为LPTSTR只能伤害自己。有时在程序中没有发现出错,那只是碰巧。转换到非常量指针的正确方法是调用GetBuffer()方法。

下面以往队列加入元素为例说明如何正确地使用CString:

CString str = _T("new text");
LVITEM item = {0};
item.mask = LVIF_TEXT;
  item.iItem = 1;
  item.pszText = (LPTSTR)(LPCTSTR) str; // 错!
  item.pszText = str.GetBuffer(0);      // 正确
ListView_SetItem ( &item );
  str.ReleaseBuffer();  // 将队列返回给str

pszText成员是LPTSTR,一个非常量指针,因此要用str的GetBuffer()。GetBuffer()的参数是CString分配的最小缓冲区。如果要分配一个1K的TCHAR,调用GetBuffer(1024)。参数为0,只返回指向字符串的指针。

上面示例的出错语句可以通过编译,甚至可以正常工作,如果恰好就是这个类型。但这不证明语法正确。进行非常量的强制类型转换,打破了面向对象的封装原则,并逾越了CString的内部操作。如果你习惯进行这样的强制类型转换,终会遇到出错,可你未必知道错在何处,因为你到处都在做这样的转换,而代码也都能运行。

知道为什么人们总在抱怨有缺陷的软件吗?不正确的代码就臭虫的滋生地。然道你愿意编写明知有错的代码让臭虫有机可乘?还是花些时间学习CString的正确用法让你的代码能够100%的正确吧。

CString还有二个函数能够从CString中得到BSTR,并在必要时转换成Unicode。那就是AllocSysString()和SetSysString()。除了SetSysString()使用BSTR*参数外,二者一样。

// 转换成BSTR
CString s5 = "Bob!";
BSTR bs1 = NULL, bs2 = NULL;
bs1 = s5.AllocSysString();
  s5.SetSysString ( &bs2 );
// ...
  SysFreeString ( bs1 );
  SysFreeString ( bs2 );

COleVariant 与CComVariant 非常相似。COleVariant 继承于VARIANT,可以传递给需要VARIANT的函数。但又与CComVariant 不同,COleVariant 只有一个LPCTSTR的构造函数,不提供单独的LPCSTR和LPCWSTR的构造函数。在大多情况下,没有问题,因为总是愿意把字符串处理为LPCTSTR。但你必须知道这点。COleVariant 也有接受CString的构造函数。

// 构造
CString s1 = _T("tchar string");
COleVariant v1 = _T("Bob"); // 从LPCTSTR构造
COleVariant v2 = s1; // 从CString拷贝

对于CComVariant,必须直接处理VARIANT成员,用ChangeType()方法在必要时将其转换为字符串。但是,COleVariant::ChangeType() 在转换失败时会抛出异常,而不是返回HRESULT的出错码。

// 数据萃取
COleVariant v3 = ...; // 从某种类型构造v3
BSTR bs = NULL;
try
    {
    v3.ChangeType ( VT_BSTR );
    bs = v3.bstrVal;
    }
  catch ( COleException* e )
    {
    // 出错,无法转换
    }
SysFreeString ( bs );

WTL类

WTL类

CString

WTL的CString与MFC的CString的行为完全相同,参阅上面关于MFC CString的说明即可。

CLR 及 VC 7 类

System::String 是.NET的字符串类。在其内部,String对象是一个不变的字符序列。任何操作String对象的String方法都返回一个新的String对象,因为原有的String对象要保持不变。String类有一个特性,当多个String都指向同一组字符集时,它们其实是指向同一个对象。Managed Extensions C++ 的字符串有一个新的前缀S,用来表明是一个managed string字符串。

// 构造
String* ms = S"This is a nice managed string";

可以用unmanaged string字符串来构造String对象,但不如用managed string构造String对象有效。原因是所有相同的具有S前缀的字符串都指向同一个对象,而unmanaged string没有这个特点。下面的例子可以说明得更清楚些:

String* ms1 = S"this is nice";
String* ms2 = S"this is nice";
String* ms3 = L"this is nice";
Console::WriteLine ( ms1 == ms2 ); // 输出true
Console::WriteLine ( ms1 == ms3);  // 输出false

要与没有S前缀的字符串做比较,用String::CompareTo()方法来实现,如:

  Console::WriteLine ( ms1->CompareTo(ms2) );
  Console::WriteLine ( ms1->CompareTo(ms3) );

二者都输出0,说明字符串相等。

在String和MFC 7的CString之间转换很容易。CString可以转换为LPCTSTR,String有接受char* 和 wchar_t* 的二种构造函数。因此可以直接把CString传递给String的构造函数:

  CString s1 ( "hello world" );
  String* s2 ( s1 );  // 从CString拷贝

反向转换的方法也类似:

  String* s1 = S"Three cats";
  CString s2 ( s1 );

可能有点迷惑。从VS.NET开始,CString有一个接受String对象的构造函数,所以是正确的。

  CStringT ( System::String* pString );

为了加速操作,有时可以用基础字符串(underlying string):

String* s1 = S"Three cats";
Console::WriteLine ( s1 );
const __wchar_t __pin* pstr = PtrToStringChars(s1);
for ( int i = 0; i < wcslen(pstr); i++ )
    (*const_cast<__wchar_t*>(pstr+i))++;
Console::WriteLine ( s1 );

PtrToStringChars() 返回指向基础字符串的 const __wchar_t* 指针,可以防止在操作字符串时,垃圾收集器去除该字符串。
C++字符串完全指南(2) - 总结
翻译:连波
23/11/2002
URL: http://www.zdnet.com.cn/developer/tech/story/0,2000081602,39099061,00.htm

字符串类的打印格式函数

对字符串包装类使用printf()或其它类似功能的函数时要特别小心。包括sprintf()函数及其变种,以及TRACE 和ATLTRACE 宏。它们的参数都不做类型检验,一定要给它们传递C语言字符串,而不是整个string对象。

例如,要向ATLTRACE()传递一个_bstr_t 里的字符串,必须显式用(LPCSTR)或 (LPCWSTR)进行强制类型转换:


  _bstr_t bs = L"Bob!";
  ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine);

如果忘了用强制类型转换,直接把整个 _bstr_t 对象传递给ATLTRACE,跟踪消息将输出无意义的东西,因为_bstr_t 变量内的所有数据都进栈了。

所有类的总结

常用的字符串类之间的转换方法是:将源字符串转换为C类型字符串指针,然后将该指针传递给目标类的构造函数。下面列出将字符串转换为C类型指针的方法,以及哪些类的构造函数接受C类型指针。

Class

string
type

convert to char*?

convert to constchar*?

convert to wchar_t*?

convert to const wchar_t*?

convert to BSTR?

construct from char*?

construct from wchar_t*?

_bstr_t

BSTR

yes, cast1

yes, cast

yes, cast1

yes, cast

yes2

yes

yes

_variant_t

BSTR

no

no

no

cast to
_bstr_t3

cast to
_bstr_t3

yes

yes

string

MBCS

no

yes, c_str()
method

no

no

no

yes

no

wstring

Unicode

no

no

no

yes, c_str()
method

no

no

yes

CComBSTR

BSTR

no

no

no

yes, cast
to BSTR

yes, cast

yes

yes

CComVariant

BSTR

no

no

no

yes4

yes4

yes

yes

CString

TCHAR

no6

in MBCS
builds, cast

no6

in Unicode
builds, cast

no5

yes

yes

COleVariant

BSTR

no

no

no

yes4

yes4

in MBCS builds

in Unicode builds

附注:

  1. 虽然 _bstr_t 可以转换为非常量指针,但对内部缓冲区的修改可能导致内存溢出,或在释放BSTR时导致内存泄露。
  2. bstr_t 的BSTR内含 wchar_t* 变量,所以可将const wchar_t* 转换到BSTR。但这个用法将来可能会改变,使用时要小心。
  3. 如果转换到BSTR失败,将抛出异常。
  4. 用ChangeType()处理VARIANT的bstrVal。在MFC,转换失败将抛出异常。
  5. 虽然没有BSTR的转换函数,但AllocSysString()可返回一个新的BSTR。
  6. 用GetBuffer()方法可临时得到一个非常量TCHAR指针。
posted @ 2006-03-09 16:52 小铁匠 阅读(27893) | 评论 (3)编辑 收藏
http://dev.csdn.net/article/82070.shtm
http://blog.csdn.net/lxblg/archive/2004/09/14/104207.aspx
posted @ 2006-03-09 15:08 小铁匠 阅读(402) | 评论 (0)编辑 收藏
     摘要: Iptables 指南 1.1.19 Oskar Andreasson      oan@frozentux.net     Copyright © 2001-2003 by Oskar Andreasson 本文在符合 GNU Free Documentation 许可版本1.1的...  阅读全文
posted @ 2006-03-09 15:07 小铁匠 阅读(807) | 评论 (0)编辑 收藏

启动server时如果遇到找不到stub问题,原因是rmiregistry找不到stub,而不是java com.Server找不到stub,解决方法,在stub的类同一个目录下启动rmiregistry

posted @ 2006-03-08 11:20 小铁匠 阅读(685) | 评论 (0)编辑 收藏
问:我源文件为main.c, x.c, y.c, z.c,头文件为x.h,y.h,z.h
如何编译成.so动态库?
编译器用gcc
最好能给出详细参数解释,谢谢

答:
# 声称动代连接库,假设名称为libtest.so
gcc x.c y.c z.c -fPIC -shared -o libtest.so

# 将main.c和动态连接库进行连接生成可执行文件
gcc main.c -L. -ltest -o main

# 输出LD_LIBRARY_PATH环境变量,一边动态库装载器能够找到需要的动态库
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

# 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
ldd main

# 执行就不用说了吧

-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

-L.:表示要连接的库在当前目录中

-ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用
/sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
posted @ 2006-03-03 16:20 小铁匠 阅读(5117) | 评论 (0)编辑 收藏

keytool -genkey -dname "CN=demo, OU=softDept, O=company, L=puddong,S=shanghai, C=cn" -alias demo -keyalg RSA -keysize 1024 -keystore demoKeystore -validity 3650 -storepass storePwd -keypass demoPwd
生成保存公钥和私钥的密钥仓库,保存在demoKeystore文件中。这里storepass 和 keypass 不要有java 正则表达式中的特殊字符,否则程序里要转义麻烦。

keytool -export -alias demo -keystore demoKeystore -rfc -file demo.cer //从密钥仓库中导出保存公钥的证书
输入keypass 即demoPwd 


  try{     
   //密钥仓库
   KeyStore ks = KeyStore.getInstance("JKS");
//读取密钥仓库
   FileInputStream ksfis = new FileInputStream("demoKeystore");
   BufferedInputStream ksbufin = new BufferedInputStream(ksfis);
   char[] storePwd = "storePwd".toCharArray();
   ks.load(ksbufin, storePwd);
   ksbufin.close();
   char[] keyPwd = "demoPwd".toCharArray();
//从密钥仓库得到私钥
   PrivateKey priK = (PrivateKey) ks.getKey("demo", keyPwd);  
//生成cipher
   Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding",new org.bouncycastle.jce.provider.BouncyCastleProvider());
//用私钥初始化cipher
   cipher.init(Cipher.ENCRYPT_MODE, priK);
   byte[] plain = "This is plain text".getBytes("UTF-8");
   
   //因为用的1024位rsa算法,一次只能加密1024/8-11字节数据,分开加密
   byte[] code = new byte[(((plain.length-1)/117+1))*128];  
            int ixplain = 0;
            int ixcode = 0;
            while((plain.length - ixplain) > 117) {//每117字节做一次加密
                ixcode += cipher.doFinal(plain, ixplain, 117, code, ixcode);
                ixplain += 117;
            }
            cipher.doFinal(plain, ixplain, plain.length - ixplain, code, ixcode);
            //加密后的code
            System.out.println(Arrays.toString(code));
            //通常会用base64编码
           String base64 = encoder.encode(code);

   CertificateFactory certificatefactory = CertificateFactory
     .getInstance("X.509");
   //读取证书
   FileInputStream fin = new FileInputStream("demo.cer");
   X509Certificate certificate = (X509Certificate) certificatefactory
     .generateCertificate(fin);
   fin.close();
   //得到公钥
   PublicKey pubK = certificate.getPublicKey();
         //初始化cipher
            cipher.init(Cipher.DECRYPT_MODE, pubK);
      //base64解码
            code = decoder.decodeBuffer(base64);
            System.out.println(Arrays.toString(code));
            byte[] plain2 = new byte[code.length];
            int ixplain2 = 0;
            int ixcode2 = 0;
            while((code.length - ixcode2) > 128) {//每128字节做一次解密
                ixplain2 += cipher.doFinal(code, ixcode2, 128, plain2, ixplain2);
                ixcode2 += 128;
            }
            ixplain2 += cipher.doFinal(code, ixcode2, code.length - ixcode2, plain2, ixplain2);
            String s2 = new String(plain2, 0, ixplain2, "UTF-8");
            System.out.println(s2);
   
  }catch(Exception ex){
   ex.printStackTrace();
  }

keytool使用方法可以参考jdk文档
Java keytool工具的作用及使用方法

posted @ 2006-03-02 14:32 小铁匠 阅读(3429) | 评论 (0)编辑 收藏
在c++中new的对象,如果不返回java,必须用release掉,否则内存泄露。包括NewStringUTF,NewObject
。如果返回java不必release,java会自己回收。

 jstring jstr = env->NewStringUTF((*p).sess_id);
   ...
 env->DeleteLocalRef( jstr);

jobject jobj = env->NewObject(clazz,midInit);
return jobj;

内存泄露可以先从windows资源管理器中,看到随程序运行,内存不断增长的趋势,具体可以用hp jmeter检测在运行程序时,加jvm参数 -Xrunhprof:heap=all,cutoff=0 ,生成java.hprof.txt,用jmeter打开,Metric -> Residual Objects (Count),可以看到未回收的对象,选中要查看的对象,点Mark记录下要查看的对象,Window -> New Window 打开新窗口,用Metric -> Reference Graph Tree,然后点Find Immediately可以看到对象被哪里引用。


找出内存泄漏另一方法

程序有内存泄漏的第一个迹象通常是它抛出一个 OutOfMemoryError,或者因为频繁的垃圾收集而表现出糟糕的性能。幸运的是,垃圾收集可以提供能够用来诊断内存泄漏的大量信息。如果以 -verbose:gc 或者 -Xloggc 选项调用 JVM,那么每次 GC 运行时在控制台上或者日志文件中会打印出一个诊断信息,包括它所花费的时间、当前堆使用情况以及恢复了多少内存。记录 GC 使用情况并不具有干扰性,因此如果需要分析内存问题或者调优垃圾收集器,在生产环境中默认启用 GC 日志是值得的。

有工具可以利用 GC 日志输出并以图形方式将它显示出来,JTune 就是这样的一种工具(请参阅 参考资料)。观察 GC 之后堆大小的图,可以看到程序内存使用的趋势。对于大多数程序来说,可以将内存使用分为两部分:baseline 使用和 current load 使用。对于服务器应用程序,baseline 使用就是应用程序在没有任何负荷、但是已经准备好接受请求时的内存使用,current load 使用是在处理请求过程中使用的、但是在请求处理完成后会释放的内存。只要负荷大体上是恒定的,应用程序通常会很快达到一个稳定的内存使用水平。如果在应用程序已经完成了其初始化并且负荷没有增加的情况下,内存使用持续增加,那么程序就可能在处理前面的请求时保留了生成的对象。


图 1 显示  GC 之后应用程序堆大小随着时间的变化图。上升趋势是存在内存泄漏的警示信号。(在真实的应用程序中,坡度不会这么大,但是在收集了足够长时间的 GC 数据后,上升趋势通常会表现得很明显。)


图 1. 持续上升的内存使用趋势

确信有了内存泄漏后,下一步就是找出哪种对象造成了这个问题。所有内存分析器都可以生成按照对象类进行分解的堆快照。有一些很好的商业堆分析工具,但是找出内存泄漏不一定要花钱买这些工具 —— 内置的 hprof 工具也可完成这项工作。要使用 hprof 并让它跟踪内存使用,需要以 -Xrunhprof:heap=sites 选项调用 JVM。

清单 3 显示分解了应用程序内存使用的 hprof 输出的相关部分。(hprof 工具在应用程序退出时,或者用 kill -3 或在 Windows 中按 Ctrl+Break 时生成使用分解。)注意两次快照相比,Map.EntryTaskint[] 对象有了显著增加。

请参阅 清单 3

清单 4 展示了 hprof 输出的另一部分,给出了 Map.Entry 对象的分配点的调用堆栈信息。这个输出告诉我们哪些调用链生成了 Map.Entry 对象,并带有一些程序分析,找出内存泄漏来源一般来说是相当容易的。


清单 4. HPROF 输出,显示 Map.Entry 对象的分配点


TRACE 300446:
	java.util.HashMap$Entry.<init>(<Unknown Source>:Unknown line)
	java.util.HashMap.addEntry(<Unknown Source>:Unknown line)
	java.util.HashMap.put(<Unknown Source>:Unknown line)
	java.util.Collections$SynchronizedMap.put(<Unknown Source>:Unknown line)
	com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
	com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)




另外
jstring jstr = (jstring)env->CallObjectMethod(authenRequest, mid_authenReq_getSdId_S);
 env->GetStringUTFRegion(jstr,0,env->GetStringLength(jstr),authenReq.sd_id);
当jstr是null时,env->GetStringLength(jstr)会出错,导致jvm崩溃
posted @ 2006-03-02 11:23 小铁匠 阅读(4230) | 评论 (0)编辑 收藏
HP-UX下使用JNI访问标准C++程序

问题的关键在于用aCC编译时的参数
根据HP网站上的两篇文章可以很容易的使用JNI访问传统C++(Classical C++)程序
http://www.hp.com/products1/unix/java/infolibrary/prog_guide/JNI_java2.html 
http://forums1.itrc.hp.com/service/forums/questionanswer.do?admit=716493758+1092296929165+28353475&threadId=245738 
但是,如果代码中使用到了标准C++,也就是用到了STL,就会出现莫名其妙的JVM crash. 而且一般的现象是使用string的时候出错

最后发现是JVM的多线程机制和aCC编译的缺省的多线程机制不一样.所以编译时需要加参数指定
总的说来,编译参数为
OPTS=-AA +z +u4 -D_RWSTD_MULTI_THREAD -D_REENTRANT -D_HPUX -D_HPUX_SOURCE -D_POSIX_C_SOURCE=199506L -D_XOPEN_SOURCE_EXTENDED 

其中,-D_RWSTD_MULTI_THREAD -D_REENTRANT 是指定多线程机制;同时必须添加-D_HPUX_SOURCE 参数,否则,编译时会出现奇怪的错误
连接参数为
-AA -b -lCsup_v2 -lstd_v2 
值得注意的是根据上面所说的第二篇文章可知使用-AA编译连接时,要连的库是libCsup_v2.sllibstd_v2.sl(这两个库是支持标准C++的库),而不是第一篇文章中提到的libCsup.sllibstd.sl(这两个库是支持传统C++的库). 

另外,有几个碰到的问题
1. 
如果编译参数没有指定多线程机制,禁用JIT(启动JVM加参数:-Djava.compiler=none -Xint )可以使简单的例子通过,但是有些情况下还是会出错

2. 
null作为String传入JNI native接口代码中是,使用env->GetStringUTFChars(jstring)会出现如下错误导致虚拟机崩溃
Function=verify_instance_jfieldID__18jfieldIDWorkaroundSFP12klassOopDescP9_jfieldID 

3. 
在使用String作为JNI的传入传出参数,使用GetStringUTFChars解决不了中文问题,还是会有乱码正确的解决方法是使用以下两个函数
void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) 

    jclass cls = env->FindClass(name); 
    /* if cls is NULL, an exception has already been thrown */ 
    if (cls != NULL) { 
        env->ThrowNew(cls, msg); 
    } 
    /* free the local ref */ 
    env->DeleteLocalRef(cls); 


jstring JNU_NewStringNative(JNIEnv *env, const char *str) 

  if (str==NULL) 
  { 
   return NULL; 
  } 
  jclass jcls_str = env->FindClass("java/lang/String"); 
  jmethodID jmethod_str = env->GetMethodID(jcls_str, "", "([B)V"); 

  jstring result; 
  jbyteArray bytes = 0; 
  int len; 

  if (env->EnsureLocalCapacity(2) < 0) { 
    return NULL; /* out of memory error */ 
  } 
  len = strlen(str); 
  bytes = env->NewByteArray(len); 
  if (bytes != NULL) { 
    env->SetByteArrayRegion(bytes, 0, len,(jbyte *)str); 
    result = (jstring)env->NewObject(jcls_str, jmethod_str, bytes); 
    env->DeleteLocalRef(bytes); 
    return result; 
  } /* else fall through */ 
  return NULL; 



char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr) 

    jbyteArray bytes = 0; 
    jthrowable exc; 
    char *result = 0; 
    if (env->EnsureLocalCapacity(2) < 0) { 
        return 0; /* out of memory error */ 
    } 
jclass jcls_str = env->FindClass("java/lang/String"); 
jmethodID MID_String_getBytes = env->GetMethodID(jcls_str, "getBytes", "()[B"]; 

    bytes = (jbyteArray)env->CallObjectMethod(jstr, MID_String_getBytes); 
    exc = env->ExceptionOccurred(); 
    if (!exc) { 
        jint len = env->GetArrayLength( bytes); 
        result = (char *)malloc(len + 1); 
        if (result == 0) { 
            JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 
                            0); 
            env->DeleteLocalRef(bytes); 
            return 0; 
        } 
        env->GetByteArrayRegion(bytes, 0, len, (jbyte *)result); 
        result[len] = 0; /* NULL-terminate */ 
    } else { 
        env->DeleteLocalRef(exc); 
    } 
    env->DeleteLocalRef(bytes); 
    return (char*)result; 


★注意:使用char *JNU_GetStringNativeChars()获得的指针用完后要显式的free().

posted @ 2006-02-27 15:54 小铁匠 阅读(974) | 评论 (0)编辑 收藏

使用Axis传送附件有两种方式:

1. 将你要传送的文件封装在DataHandler中,然后将DataHandler对象或DataHandler数组(多个文件传送的时候)作为客户端调用函数的参数(从客户端上传文件到服务器)Axis服务的返回类型(从服务器端下载文件到客户端)进行传输。

2. 还有一种方式是直接修改soap信封的内容,将附件信息加到soap信封中发送。

这里我们只讨论第一种方法,因为它实现起来非常简单。关于第二种方法在Axis包的webapps/attachments/TestRf.java中有详细的原码可以参考。

下面的例子是把文件从服务器端下载到客户端:

1.服务端程序:

假设传输多个文件:在服务器端将文件取出来,并将文件封装在DataHandler数组中。
代码如下:

 DataHandler[] ret = new DataHandler[totalFileNum];
 ... ...
 java.io.File myFile = new java.io.File(filePath);
 if(myFile.isFile() && myFile.canRead())
 {
  String fname = myFile.getAbsoluteFile().getCanonicalPath();
  DataHandler[0] = new DataHandler(new FileDataSource(fname));
 }
 ... ...

 return ret;

上面的代码将所有的文件封装在了DataHandler数组中,并返回。

2. 客户端的访问:

代码如下:
 Service service = new Service();
 Call call = (Call) service.createCall();

 URL myURL = new URL("http://192.168.0.26:8080/axis/servlet/AxisServlet");
 call.setTargetEndpointAddress(myURL); //设定服务的主机和位置
 call.setOperationName(new QName("urn:MyAttachServer","echoDir")); //设置要调用的服务的方法
 QName qnameAttachment = new QName("urn:MyAttachServer","DataHandler");

 call.registerTypeMapping(DataHandler.class, qnameAttachment, JAFDataHandlerSerializerFactory.class,JAFDataHandlerDeserializerFactory.class); //为附件(即DataHandler类)创建序列化生成器

 call.addParameter("source", XMLType.XSD_STRING ,ParameterMode.IN); //设置服务调用方法的传入参数类型
 call.setReturnType(XMLType.SOAP_ARRAY); //设置调用服务方法的返回类型,由于返回的是DataHandler数组,所以设置为SOAP_ARRAY类型
 javax.activation.DataHandler[] ret = (javax.activation.DataHandler[])call.invoke(new Object[]{null}); //调用方法

 for (i = 0; i < ret.length; ++i)
        {
            DataHandler recDH = ret[i];
            java.io.File receivedFile = new java.io.File(recDH.getName()); //文件生成
        }

3. 服务的部署:

注意:你要在部署的时候,定义DataHandler的序列化生成器。

  编写deploy.wsdd文件:

 <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:ns1="urn:att_STC_Server" >
  <service name="urn:att_STC_Server" provider="java:RPC" >
    <parameter name="className" value="samples.att_STC.att_STC_Server"/>
    <parameter name="allowedMethods" value="echoDir"/>

 <typeMapping deserializer="org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory"
   languageSpecificType="java:javax.activation.DataHandler" qname="ns1:DataHandler"
    serializer="org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory"
    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
  </service>

</deployment>

运行java org.apache.axis.client.AdminClient %* deploy.wsdd,部署服务。

posted @ 2006-02-20 16:28 小铁匠 阅读(1011) | 评论 (1)编辑 收藏

2004-05-29 17:39:53

主题: 使用Java实现CA(四)

    前面几篇文章已经把如何用Java实现一个CA基本上讲完了.但是他们都有一个特点,就是用户的信息都是在现场获取的,不能做申请和签发相分离.今天我们要讲述的是PKCS#10证书请求文件.它的作用就是可以使申请和签发相分离.

    PKCS#10证书请求结构中的主要信息包含了被签发者(证书申请者)的主体名称(DN)和他的公钥.因此一个CA在获取到一个PKCS#10证书请求后,就可以从中获取到任何和签发证书有关的信息,然后用它自己的私钥签发证书.

    使用BC Provider在Java中构造一个证书请求格式的对象调用其构造函数即可,这个函数如下:

    PKCS10CertificationRequest(java.lang.String signatureAlgorithm, X509Name subject, java.security.PublicKey key, ASN1Set attributes, java.security.PrivateKey signingKey)

    它的参数是自签名算法,证书申请者的DN,证书申请者的公钥,额外的属性集(就是要申请的证书的扩展信息),申请证书者的私钥.申请证书者的私钥仅仅是用来进行一下自签名,并不出现在证书请求中,需要自签名的目的是保证该公钥确实为申请者所有.

    调用该对象的getEncoded()方法可以将其进行DER编码,然后储存起来,该对象还有另一个构造函数:
    PKCS10CertificationRequest(byte[] bytes)
    这个构造函数的作用就是直接从储存的DER编码中把这个对象还原出来.

    利用证书请求结构进行证书签发的代码如下,这里假设CSR是一个已经获取出来的PKCS10CertificationRequest结构:

    PublicKey SubjectPublicKey = CSR.getPublicKey();
    CertificationRequestInfo CSRInfo = CSR.getCertificationRequestInfo();
    X509Name SubjectDN = CSRInfo.getSubject();
    ASN1Set Attributes = CSRInfo.getAttributes();

    这样,申请者的主体DN,申请者的公钥,申请者希望在证书扩展信息中填写的属性都得到了,剩下的事情就和用户在现场输入时一样了,其它的信息一般是申请者不能决定的.另外证书请求格式中有一样信息没有明确给出来,那就是证书的有效期,这个应该单独询问用户,或者用其它的方法保存起来.



[返回顶部]


2004-05-28 16:46:12

主题: 使用Java实现CA(三)

    前几次我已经基本上把如何做CA所需要的基础知识讲得差不多了,今天直接讲如何用Java程序来实现一个CA应该就不是什么太困难的事情了.

    要做CA,第一步要准备好自己的证书和私钥.私钥如何从文件里面读取出来前面已经讲过了.从文件系统中读出证书的代码如下:

    CertificateFactory certCF = CertificateFactory.getInstance("X.509");
    X509Certificate caCert = certCF.generateCertificate(certBIS);

    这里cerBIS是一个InputStream类型的对象.例如一个标准的X509v3格式的证书文件所形成的输入流.

    第二步就是从用户那里获取输入,然后构造主体名称结构DN,如何构造DN上次已经说过了,如何从用户那里获取输入,这个不在本文讨论范围之内.

    下一步就是获取用户的公钥,好和他所需要的证书对应起来.也有不少CA的做法就是在这里替用户现场生成一对密钥对,然后把公钥放到证书中签发给用户.这个应该看实际需要选择合适的方式.

    现在一切信息都已经准备好了,可以签发证书了,下面的代码说明了这个过程:

    //构造一个证书生成器对象

    X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

    // 从CA的证书中获取签发者的主体名称(DN)
    // 这里有一点小技巧,我们要把JCE中定义的
    // 用来表示DN的对象X500Principal转化成在
    // BC Provider中的相应的对象X509Name
    // 先从CA的证书中读取出CA的DN进行DER编码
    DERInputStream dnStream =
                 new DERInputStream(
      new ByteArrayInputStream(
       caCert.getSubjectX500Principal().
        getEncoded()));
    // 马上又从编码后的字节流中读取DER编码对象
    DERConstructedSequence  dnSequence =
     (DERConstructedSequence)dnStream.readObject();
    // 利用读取出来的DER编码对象创建X509Name
    // 对象,并设置为证书生成器中的"签发者DN"
    certGen.setIssuerDN(new X509Name(dnSequence));
    // 设置好证书生成器中的"接收方DN"
    certGen.setSubjectDN(subjectDN);
    // 设置好一些扩展字段,包括签发者和
    // 接收者的公钥标识
    certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
    createSubjectKeyId(keyToCertify));
    certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
    createAuthorityKeyId(caCert.getPublicKey()));
    // 设置证书的有效期和序列号
    certGen.setNotBefore(startDate);
    certGen.setNotAfter(endDate);
    certGen.setSerialNumber(serialNumber);
    // 设置签名算法,本例中使用MD5hash后RSA
    // 签名,并且设置好主体的公钥
    certGen.setSignatureAlgorithm("MD5withRSA");
    certGen.setPublicKey(keyToCertify);

    // 如果以上一切都正常地话,就可以生成证书了
    X509Certificate cert = null;
    cert = certGen.generateX509Certificate(caPrivateKey);

    这里是上面用到的生成签发者公钥标识的函数: 

    protected AuthorityKeyIdentifier createAuthorityKeyId(PublicKey pubKey)
    {
    AuthorityKeyIdentifier authKeyId = null;

    try
    {
    ByteArrayInputStream bIn = new ByteArrayInputStream(pubKey.getEncoded());
    SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
        (DERConstructedSequence)new DERInputStream(bIn).readObject());
    authKeyId = new AuthorityKeyIdentifier(info);
    }
    catch (IOException e)
    {
    System.err.println("Error generating SubjectKeyIdentifier:  " +
        e.toString());
    System.exit(1);
    }

    return authKeyId;
    }

    生成主体公钥标识的函数和上面的类似,把AuthorityKeyIdentifier替换成SubjectKeyIdentifier就可以了.

    这里要注意的是,CA的公钥也是在一份证书里,这种证书的特点是签发者DN和接收者DN一样,也就是说,这种证书是CA自己给自己颁发的证书,也就是"自签名证书",它上面的公钥是CA自身的公钥,用来签名的私钥就是该公钥对应的私钥.一般每个CA都要有这么一份证书,除非该CA不是根CA,即它的权威性不是由它自己证明,而是由它的上级CA证明.但是,最后总归要有一个根CA,它为各个安全应用程序的用户所信赖.

    到这里我们已经把CA最基本的功能如何用Java实现讲完了,下一次讲如何从PKCS#10格式证书请求文件中读取出用户信息,然后直接签发公钥.



[返回顶部]


2004-05-27 15:34:59

主题: 使用Java实现CA(二)

    昨天本来快写完了,结果不小心按了"tab"键,然后向按退格键,结果退到前一个页面了,然后全部都白写了,不爽.只好今天重新写了.

    上次我们讲到如何生成密钥对,以及如何将诸如公钥,私钥,证书等这一类安全对象在文件系统和内存之间来回转换.这些是准备开CA的基本功,今天我们讲一下CA的基本原理以及如何使用主体名称结构DN(Distinguish Name)来表示每一个证书中的主体.

    一份证书就是一个权威机构对一个主体的身份的确认的证明.即一份证书表示一个权威机构确认了一个主体就是它自己,而不是其它的冒名顶替者.主体可以是一个个人,也可以不是,例如,在需要安全服务的时候,需要为一台网站的服务器颁发证书,这种证书的主体就是一台服务器.签署证书的权威机构就叫做CA,该权威机构本身也是一个主体.权威机构通过对包含有待认证的主体的一系列信息的待签名证书"(TBS,to be signed)进行数字签名来表示该机构对它的认可.一份包含了待认证的主体的相关信息的TBS再加上CA使用自己的私钥进行签名产生的字节流放在一起,就构成了一份标准的X509证书.

    一个TBS中包含了以下这些主要信息:

    证书的版本,通常是3(X509v3)

    证书的序列号,RFC3280中规定,每个CA必须确保它颁发的每一份证书的序列号都是唯一的,并且序列号只能使用非负整数.

    签发者(CA)的主体名称,一个DN对象.

    证书的有效期,表示方法是两个时间值,表示了该证书从何时开始生效,何时开始作废.

    待认证的主体的主体名称,也是一个DN对象.

    待认证的主体的公钥,任何安全应用在确认完证书的有效性后,就可以开始使用该主体的公钥与之进行安全通信.

    如果是X509v3证书,即版本号是3的话,后面还有一个证书扩展信息字段,可以在证书里面添加一些其它的信息.

    下面我们来看一下表示主体的主体名称结构:DN.这个结就构是一个属性的集合.每个属性有属性名称和属性值.它的作用就是用来表示"我是谁",也就是说,这个证书到底是谁颁发给谁的,这个证书对应的公钥是谁拥有的.

    通常使用一个字符串来表示DN结构,这种字符串说明了这种结构中的各个属性的类型和值:

    C=CN;S=BeiJing;L=BeiJing;O=PKU;OU=ICST;CN=wolfenstein

    这里C是国家和地区代码,S和L都是地区代码,S相当于省或者州这样的级别,L相当于城市级别,O是组织机构名称,OU是次级组织机构名称,CN是主体的通用名(common name).在这里,C,S,L等等属性的类型都是相对固定的,例如C一般就是用来表示国家和地区代码,在DN结构中还可以添加一些其它类型的信息,一般也都是以"xxx=xxx"这样来表示的.

    下面我们来说明如何在Java语言中构造出一个主体名称对象.

    BC Provider中使用X509Name对象来表示DN,构造一个X509Name的步骤大体如下:

    先构造两个vector对象,用来表示属性名称和属性值:

    Vector oids = new Vector();
    Vector attributes = new Vector();

    然后在oids这个用来表示属性名称的vector对象中将属性名称一个一个添加进去:

    oids.addElement(X509Name.C);

    ......

    oids.addElement(X509Name.CN);

    X509Name对象里面有若干常量,例如上面的X509Name.C.还有X509Name.ST等等,都可以和上面举的例子对应起来.

    然后将属性值添加到attributes对象中:

    attributes.addElement("CN");

    ......

    attributes.addElement("Wolfenstein");

    最后就可以构造出一个X509Name对象:

    X509Name SubjectDN = new X509Name(oids, attributes);

    这样,我们的主体名称结构就确立起来了.

    下次我们就可以讲关键的部分了,那就是如何用Java程序完成CA最重要的功能,签署证书.



[返回顶部]


2004-05-25 21:23:52

主题: 使用Java实现CA(一)

    通过我昨天的文章大家应该已经清楚了,用Java写信息安全方面的程序需要做的准备工作.好了,现在假设你已经是一个对Java语言本身比较熟悉,能够用Java写一些程序的人,并且这些该下载的jar包已经下载好了,可以正式开工了.

    在所有的此类程序的开头(无论是在类的构造函数中也好,在初始化函数中也好),都要先来上这么一句:Security.addProvider(new BouncyCastleProvider());将BouncyCaslte的Provider添加到系统中,这样以后系统在运行相关程序的时候调用的就是这个Provider中的加密算法.

    然后我们就可以开始开CA了.首先,作为一个CA要有自己的一对公钥和私钥,我们先要生成这么一对.使用KeyPairGenerator对象就可以了,调用KeyPairGenerator.getInstance方法可以根据要生成的密钥类型来产生一个合适的实例,例如常用的RSA,DSA等.然后调用该对象的initialize方法和generateKeyPair方法就可以产生一个KeyPair对象了.然后调用KeyPair对象中的相应方法就可以获取生成的密钥对中的公钥和私钥了.

    有了公钥和私钥对以后,下面的一个很现实问题就是如何把它们储存起来.通常我们要对这些安全对象,如公钥,私钥,证书等先进行编码.编码的目的是为了把结构复杂的安全对象变成字节流以便存储和读取,如DER编码.另外,通常还把DER编码后的字节流再次进行base64编码,以便使字节流中所有的字节都变成可打印的字节.

    在Java语言中,这些安全对象基本上都有getEncoded()方法.例如:

    byte[] keyBytes = privateKey.getEncoded();

    这样就把一个私钥进行了DER编码后的结果保存到一个byte数组中了.然后就可以把这个byte数组保存到任何介质中.如果有必要的话,可以使用BC Provider中的Base64编码解码器类进行编码,就像这样:

    byte data[] = Base64.encode(keyBytes);

    要从文件中读取私钥则应该这样:

    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyData);
    KeyFactory kfac = KeyFactory.getInstance("RSA");
    privateKey = kfac.generatePrivate(spec);

    这里说明一下,对RSA私钥进行编码就会自动地按照PKCS8进行.因此读取的时候将包含编码的字节数组作为PKCS8EncodedKeySpec对象的构造函数的参数就可以生成一个该类型的对象.然后创建一个密钥工厂对象就可以按照要求生成一个RSA私钥了.很显然这里的keyData应该是和上面的keyBytes内容相同.

    为了提高系统的安全性,通常私钥在经过DER编码后,还会使用一个口令进行加密,然后再储存在文件系统中.在使用私钥的时候,如果没有正确的口令,是无法把私钥还原出来的.

    保存证书和保存私钥类似.Certificate对象中也有一个getEncoded的方法.

    这次就讲这些.大家应该可以把这些安全对象很熟练地从文件系统和内存之间来回地折腾了吧.这对以后实现CA是很重要的.下次我会讲一下证书中表示主体的方法:DN.



[返回顶部]


2004-05-24 17:23:06

主题: 使用Java开发和信息安全相关的程序

    这里说的信息安全是相对于系统安全而言的,它更侧重于加密,解密,数字签名,验证,证书等等.而系统安全主要侧重于系统本身是否有安全漏洞,如常见的由于软件设计的不完善而导致的满天飞的缓冲区溢出等等.

    Java语言中负责加密功能的部件是JCE(Java Crypto Extenstion),它使用开放式的思想,可以允许用户自己编写加密算法的具体实现的模块等.这些东西被称为JCE Provider,JCE的提供者.SUN公司本身提供了一些Provider.不过我推荐使用Bouncy Castle的Provider.原因是它实现的加密算法多,连比较新的椭圆曲线(ECC)算法都有了.去http://www.bouncycastle.org/可以找到你所希望的.Bouncy Castle除了提供Provider本身以外,还包括了一个S/MIME和一个open pgp 的jar包只有Provider本身是必要的,后两个包是方便你编程而提供的.例如有了S/MIME包后,你就不再需要为诸如"加密一个字符串或者一片文章然后签名"之类的很现实的应用程序写上一大堆代码了,只要引用S/MIME包中的某几个类,很快就可以搞定.而open pgp的存在,使你在用Java编写和PGP/GPG交互的程序时方便多了.

    这次先写这么多了,下次开始具体讲把这些东西搞下来后怎么开始干活吧.我以我现在手头上正在做的事情告诉大家,如何做一个CA.

    有了BC Provider,开CA,真的就是这么简单!

posted @ 2006-02-20 11:07 小铁匠 阅读(6100) | 评论 (0)编辑 收藏