o5m_demo

作者: 微雨旧时歌丶 | 来源:发表于2018-01-11 11:56 被阅读0次
// o5m_demo 2012-10-14 17:10
// (c) 2012 Markus Weber, Nuernberg
//
// 这个程序从标准输入读取.o5m文件,并使用简单的文本格式将其内容写入标准输出。
// 这是如何读取.o5m文件的一个实验性例子。
//
// 编译这个文件:
// gcc o5m_demo.c -o o5m_demo
//

#define _FILE_OFFSET_BITS 64
#include <inttypes.h>  //提供各种进制的转换宏
#include <stdlib.h> //申请、释放内存空间
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h> // 是POSIX标准定义的unix类系统定义符号常量的头文件
#include <fcntl.h> // 用来避免一些系统安全问题

typedef enum {false= 0,true= 1} bool;
typedef uint8_t byte; // 一字节
#if !__WIN32__
  #define O_BINARY 0
#endif
#define ONAME(i) \
  (i==0? "node": i==1? "way": i==2? "relation": "unknown object")

static inline char *stpcpy0(char *dest, const char *src) {
  // 重新定义C99的stpcpy(),因为它在MinGW中是缺少的,
  // 而在Linux头文件中的声明似乎是错误的;
  while(*src!=0)
    *dest++= *src++;
  *dest= 0;
  return dest;
  }  // stpcpy0()

static inline int strzcmp(const char* s1,const char* s2) {
  // 类似于strcmp(),这个过程比较两个字符串;
  // 在这里,要比较的字符数限于第二个字符串的长度;
  // 即,该过程可以用来识别长字符串s1内的短字符串s2;
  // s1[]: 第一个字符串;
  // s2[]: 与第一个字符串作比较的字符串;
  // 返回:
  // 0: 两个字符串是相同的;第一串可以比第二串长;
  // -1: 第一个字符串比第二个字母小;
  // 1: 第一个字符串比第二个字符串更大;
  while(*s1==*s2 && *s1!=0) { s1++; s2++; }
  if(*s2==0)
    return 0;
  return *(unsigned char*)s1 < *(unsigned char*)s2? -1: 1;
  }  // strzcmp()



//------------------------------------------------------------
// Module pbf_   protobuf转换模块
//------------------------------------------------------------

// 这个模块提供了从protobuf格式到常规数字转换的程序;
// 像往常一样,一个模块的所有标识符都有相同的前缀;
// 此时是 'pbf'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----
// 许多程序都有一个参数'pp';这里预期会有一个缓冲区指针
// 这个指针会增加被转换的protobuf元素消耗的字节数

//------------------------------------------------------------

static inline uint32_t pbf_uint32(byte** pp) {
  // 获取无符号整数的值;
  // pp: see module header;
  byte* p;
  uint32_t i;
  uint32_t fac;

  p= *pp;
  i= *p;
  if((*p & 0x80)==0) {  // 只有一比特 (高位为0,代表只有一比特)
    (*pp)++; // 指针后移
return i; // 返回获取的整数值
    }
  i&= 0x7f;
  fac= 0x80;
  while(*++p & 0x80) {  // 更多的比特
    i+= (*p & 0x7f)*fac; // 计算整数值
    fac<<= 7;
    }
  i+= *p++ *fac;
  *pp= p;
  return i;
  }  // pbf_uint32()

static inline int32_t pbf_sint32(byte** pp) {
  // 获取有符号整数的值;
  // pp: see module header;
  byte* p;
  int32_t i;
  int32_t fac;
  int sig;

  p= *pp;
  i= *p;
  if((*p & 0x80)==0) {  // 只有一比特
    (*pp)++;
    if(i & 1)  // 负数
return -1-(i>>1);
    else
return i>>1;
    }
  sig= i & 1; // 符号位
  i= (i & 0x7e)>>1;
  fac= 0x40;
  while(*++p & 0x80) {  // more byte(s) will follow
    i+= (*p & 0x7f)*fac;
    fac<<= 7;
    }
  i+= *p++ *fac;
  *pp= p;
    if(sig)  // 负数
return -1-i;
    else
return i;
  }  // pbf_sint32()

static inline uint64_t pbf_uint64(byte** pp) {
  // 获取无符号整数的值;
  // pp: see module header;
  byte* p;
  uint64_t i;
  uint64_t fac;

  p= *pp;
  i= *p;
  if((*p & 0x80)==0) {  // just one byte
    (*pp)++;
return i;
    }
  i&= 0x7f;
  fac= 0x80;
  while(*++p & 0x80) {  // more byte(s) will follow
    i+= (*p & 0x7f)*fac;
    fac<<= 7;
    }
  i+= *p++ *fac;
  *pp= p;
  return i;
  }  // pbf_uint64()

static inline int64_t pbf_sint64(byte** pp) {
  // 获取有符号整数的值;
  // pp: see module header;
  byte* p;
  int64_t i;
  int64_t fac;
  int sig;

  p= *pp;
  i= *p;
  if((*p & 0x80)==0) {  // just one byte
    (*pp)++;
    if(i & 1)  // negative
return -1-(i>>1);
    else
return i>>1;
    }
  sig= i & 1;
  i= (i & 0x7e)>>1;
  fac= 0x40;
  while(*++p & 0x80) {  // more byte(s) will follow
    i+= (*p & 0x7f)*fac;
    fac<<= 7;
    }
  i+= *p++ *fac;
  *pp= p;
    if(sig)  // negative
return -1-i;
    else
return i;
  }  // pbf_sint64()

//------------------------------------------------------------
// end   Module pbf_   protobuf转换模块
//------------------------------------------------------------



//------------------------------------------------------------
// Module read_   OSM文件读取模块
//------------------------------------------------------------

// 本模块提供缓冲读取标准输入的程序;
// 像往常一样,一个模块的所有标识符都有相同的前缀;
// 此时是 'read'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----

#define read_PREFETCH ((32+3)*1024*1024) 
  // 每次调用read_input()后缓冲区中可用的字节数;
#define read__bufM (read_PREFETCH*5)  // 缓冲区的长度;
static bool read__eof;  // 输入文件末尾的标识符。
static byte* read__buf = NULL;  // 文件输入缓冲区的起始地址

//------------------------------------------------------------

static byte* read_bufp= NULL;  // (全局指针)
  // 可能会被外部增加
  // 在read_input()再次调用之前增加到read_PREFETCH字节数
static byte* read_bufe= NULL;  // (全局指针)  
  // 可能不会从外部改变

static void read__close() {
  // 关闭先前打开的输入流的预取缓冲区;
  // 将在程序结束时自动调用;
  if(read__buf!=NULL) {  // buffer is valid
    free(read__buf);
    read__buf= NULL;
    }  // buffer is valid
  read_bufp= read_bufe= NULL;
  }  // read__close()

static int read_open() {
  // 使用预取缓冲区打开标准输入;
  // 返回: 0: 正常; !=0: 错误;
  // read__close() 将在程序结束时自动调用;

  // 获取文件信息和输入缓冲区的内存空间。
  if(read__buf==NULL) {  // 缓冲未分配
    read__buf= (byte*)malloc(read__bufM);  // 分配缓冲区空间
    if(read__buf==NULL) {
      fprintf(stderr,"Could not get %i bytes of memory.\n",read__bufM); // 分配空间失败
  return 1;
      }
    atexit(read__close); // 结束程序
    }  // 缓冲未分配

  // 初始化读取缓冲区的指针
  read__eof= false;  // 我们不在输入文件的末尾。
  read_bufp= read_bufe= read__buf;
    // 指向buf []中有效输入结尾的指针

return 0;
  }  // read_open()

static inline bool read_input() {
  // 从标准输入读取数据,使用内部缓冲;
  // 使数据在read_bufp可用;
  // 在调用此过程之前,必须调用read_open();
  // 返回: 没有(更多)字节来读取;
  // read_bufp: 可用的下一个字节开始;
  //            可以由调用者递增,直到read_bufe;
  // read_bufe: 缓冲区中字节的结束;
  //            不能被调用者更改;
  // 在调用此过程之后, 在地址read_bufp中至少有read_PREFETCH字节可用
  //  - 有一个例外: 如果从标准输入中没有足够的可读取的字节, 
  // 缓冲区中文件结束后的剩余部分直到read_bufp + read_PREFETCH所有字节都将被设置为0x00

  int l,r;

  if(read_bufp+read_PREFETCH>=read_bufe) {  // 读缓冲区过低
    if(!read__eof) {  // 仍然是文件中的字节(文件结尾标志位false)
      if(read_bufe>read_bufp) {  // 缓冲区中有剩余的字节(当前字节未到达缓冲区结束字节)
        memmove(read__buf,read_bufp,read_bufe-read_bufp);
          // 移动剩余的字节以开始缓冲区
          // (由read_bufp所指内存区域复制read_bufe-read_bufp个字节到read_buf所指内存区域)
        read_bufe= read__buf+(read_bufe-read_bufp);
          // 在缓冲区开始时保护剩余的字节
        }
      else  // 缓冲区中没有剩余字节
        read_bufe= read__buf;  // 没有剩余的字节来保护
        // 添加读取字节到调试计数器
      read_bufp= read__buf;
      do {  // 缓冲区没有被填满
        l= (read__buf+read__bufM)-read_bufe-4;
          // 要读取的字节数
        r= read(0,read_bufe,l); // 从打开的文件中读取l字节的数据,返回实际读取的字节数
        if(r<=0) {  // 文件中不再有字节
          read__eof= true;
            // 记住我们在文件的末尾
          l= (read__buf+read__bufM)-read_bufe;
            // 缓冲区中有剩余空间
          if(l>read_PREFETCH) l= read_PREFETCH;
          memset(read_bufe,0,l);  // 2011-12-24
            // 将缓冲区中直到prefetch的剩余空间设置为0
      break;
          }
        read_bufe+= r;  // 为数据的结束设置新标记
        read_bufe[0]= 0; read_bufe[1]= 0;  // 设置4个空终止符
        read_bufe[2]= 0; read_bufe[3]= 0;
        } while(r<l);  // end   在缓冲区未被填充时结束
      }  // 仍然读取字节
    }  // 读缓冲区过低
  return read__eof && read_bufp>=read_bufe;
  }  // read_input()

//------------------------------------------------------------
// end   Module read_   OSM文件读取模块
//------------------------------------------------------------



//------------------------------------------------------------
// Module str_   字符串读取模块
//------------------------------------------------------------

// 此模块提供了从数据流对象中存储的字符串转换为c格式字符串的过程;
// 像往常一样,一个模块的所有标识符都有相同的前缀,
// 此时为 'str'; 在全局可访问的对象中将有一个下划线
// 在本模块之外不可访问的对象将有两个下划线
// 私有和公共定义的部分用水平线分隔:----

#define str__tabM (15000+4000)
  // +4000是因为可能发生一个对象有很多key/val对或者refroles的情况
  // 它们没有被存储
#define str__tabstrM 250  // 必须是< str__tab[]的行大小

static char str__tab[str__tabM][256];
  // 字符串表; see o5m documentation;
  // 行长度必须至少是str__tabstrM+2;
  // 每一行包含一个双字符串; 
  // 两个字符串中的每一个都以零字节结尾,
  // 逻辑长度总共不超过str__tabstrM字节;
  // 这个数组的第一个str__tabM行被用作字符串的输入缓冲区;
static int str__tabi= 0;
  // 在字符串表中最后输入元素的索引;
static int str__tabn= 0;  // 字符串表中有效字符串的数量;

//------------------------------------------------------------

static inline void str_reset() {
  // 清空字符串表;
  // 必须在本模块的任何其他进程之前调用
  // 可能在任何字符串进程需要重启时调用;
  str__tabi= str__tabn= 0;
  }  // str_reset()

static void str_read(byte** pp,char** s1p,char** s2p) {
  // 从标准输入缓冲中读取一个o5m格式的字符串(对),如. key/val;
  // 如果读取到一个字符串引用,则使用内部字符串表解释它;
  // 如果字符串总长度超过250个字符,则不使用引用(包括终止符为252);
  // pp: 缓冲指针的地址;
  //     这个指针将会增加被转换的protobuf元素消耗的字节数;
  // s2p: ==NULL: 读取的不是字符串对,而是单个字符串;
  // 返回:
  // *s1p,*s2p: 已读取的字符串的指针;
  char* p;
  int len1,len2;
  int ref;
  bool donotstore;  // 字符串“不存储标志”  2012-10-01

  p= (char*)*pp;
  if(*p==0) {  // 直接给出的字符串(对)
    p++;
    donotstore= false;
    #if 0  // 不使用,因为字符串不再是透明的
    if(*++p==(char)0xff) {  // 字符串有“不存储标志”  0xff是重置字节
      donotstore= true;
      p++;
      }  // 字符串有“不存储标志”
      #endif
    *s1p= p;
    len1= strlen(p); // 获取当前字符串长度
    p+= len1+1; // 指针后移到下一个字符串的起始地址
    if(s2p==NULL) {  // single string
      if(!donotstore && len1<=str__tabstrM) {
          // 单个字符串对于字符串表来说足够短
        stpcpy0(str__tab[str__tabi],*s1p)[1]= 0;
          // 添加第二个终结符,以防万一有人会尝试
          // 将这个单一字符串作为字符串对来读取;
        if(++str__tabi>=str__tabM) str__tabi= 0;
        if(str__tabn<str__tabM) str__tabn++;
        }  // 单个字符串对于字符串表来说足够短
      }  // single string
    else {  // 字符串对
      *s2p= p;
      len2= strlen(p);
      p+= len2+1;
      if(!donotstore && len1+len2<=str__tabstrM) {
          // 字符串对 对于字符串表来说足够短
        memcpy(str__tab[str__tabi],*s1p,len1+len2+2);
        if(++str__tabi>=str__tabM) str__tabi= 0;
        if(str__tabn<str__tabM) str__tabn++;
        }  // 字符串对 对于字符串表来说足够短
      }  // 字符串对
    *pp= (byte*)p;
    }  // 直接给出的字符串(对)
  else {  // 通过引用给出的字符串(对)
    ref= pbf_uint32(pp);
    if(ref>str__tabn) {  // 无效的字符串引用(因为ref > 字符串表中有效字符串的数量)
      fprintf(stderr,"Invalid .o5m string reference: %i->%i\n",
        str__tabn,ref);
      *s1p= "(invalid)";
      if(s2p!=NULL)  // 调用者需要一个字符串对
        *s2p= "(invalid)";
      }  // 无效的字符串引用
    else {  // 有效的字符串引用
      ref= str__tabi-ref; // 向前找引用的索引
      if(ref<0) ref+= str__tabM;
      *s1p= str__tab[ref];
      if(s2p!=NULL)  // 调用者需要一个字符串对
        *s2p= strchr(str__tab[ref],0)+1; 
        // (strchr: 从字符串 str__tab[ref] 中寻找字符0第一次出现的位置),然后+1,即定位到了下一个字符串
      }  // 有效的字符串引用
    }  // 通过引用给出的字符串(对)
  }  // str_read()

//------------------------------------------------------------
// end   Module str_   字符串读取模块
//------------------------------------------------------------



static inline char* sint32tosfix7(int32_t v,char* sp) {
  // 将sint32作为7位小数定点值并将其转换为字符串;
  // v: 整数值(定点);
  // sp[13]: 目标字符串;
  // 返回: sp;
  char* s1,*s2,c;
  int i;

  s1= sp;
  if(v<0) // 负数
    { *s1++= '-'; v= -v; }
  s2= s1;
  i= 7;
  while((v%10)==0 && i>0)  // trailing zeroes
    { v/= 10;  i--; }
  while(--i>=0)
    { *s2++= (v%10)+'0'; v/= 10; }
  *s2++= '.';
  do
    { *s2++= (v%10)+'0'; v/= 10; }
    while(v>0);
  *s2--= 0;
  while(s2>s1)
    { c= *s1; *s1= *s2; *s2= c; s1++; s2--; }
  return sp;
  }  // sint32tosfix7()

static inline void uint64totimestamp(uint64_t v,char* sp) {
  // 将sint64转换为OSM格式的时间戳,
  // 举例说: "2010-09-30T19:23:30Z";
  // v: 时间戳值;
  // sp[21]: 目标字符串;
  time_t vtime;
  struct tm tm;
  int i;

  vtime= v;
  #if __WIN32__
  memcpy(&tm,gmtime(&vtime),sizeof(tm));
  #else
  gmtime_r(&vtime,&tm);
  #endif
  i= tm.tm_year+1900;
  sp+= 3; *sp--= i%10+'0';
  i/=10; *sp--= i%10+'0';
  i/=10; *sp--= i%10+'0';
  i/=10; *sp= i%10+'0';
  sp+= 4; *sp++= '-';
  i= tm.tm_mon+1;
  *sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= '-';
  i= tm.tm_mday;
  *sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= 'T';
  i= tm.tm_hour;
  *sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= ':';
  i= tm.tm_min;
  *sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= ':';
  i= tm.tm_sec%60;
  *sp++= i/10+'0'; *sp++= i%10+'0'; *sp++= 'Z'; *sp= 0;
  }  // uint64totimestamp()

//------------------------------------------------------------

static int process_o5m() {
  // 开始处理o5m对象;
  // 返回: ==0: ok; !=0: 错误;
  bool write_testmode;  // 如果为真: 报告未知的 o5m ids
  int otype;  // 当前处理对象的类型;
    // 0: node; 1: way; 2: relation;
  int64_t o5id;  // for o5m delta coding
  int32_t o5lon,o5lat;  // for o5m delta coding
  int64_t o5histime;  // for o5m delta coding
  int64_t o5hiscset;  // for o5m delta coding
  int64_t o5ref[4];  // for o5m delta coding for nodes, ways, relations,
    // and dummy object (just to allow index division by 4)
  bool o5endoffile;  // 文件的逻辑结束(数据集id 0xfe)
  byte* bufp;  // 在读取缓冲区的指针
  byte* bufe;  // 在读取缓冲区的指针, 对象的结束
  byte b;  // 已读取的最新字节
  int l;

  // 过程初始化
  write_testmode= false;  // do not criticize unknown o5m ids
  o5endoffile= false;

  // 读 .o5m 文件头
  read_open();
  read_input();
  bufp= read_bufp;
  if(read_bufp>=read_bufe) {  // 文件为空
    fprintf(stderr,"Please supply .o5m file at stdin.\n");
return 2;
    }
  if(bufp[0]!=0xff || bufp[1]!=0xe0 || (
      strzcmp((char*)bufp+2,"\x04""o5m2")!=0 &&
      strzcmp((char*)bufp+2,"\x04""o5c2")!=0 )) {
      // not an .o5m format
      fprintf(stderr,"Unknown input file format.\n");
return 3;
    }
  bufp+= 7;  // 跳过.o5m文件头

  // 处理文件
  for(;;) {  //读取输入文件中的所有对象

    // 获取下一个对象
    read_input();
    if(read_bufp>=read_bufe)  // 物理文件结束
  break;
    if(o5endoffile) {  // 在文件的逻辑结束之后
      fprintf(stderr,"Warning: unexpected contents "
        "after logical end of file.\n");
  break;
      }
    bufp= read_bufp;
    b= *bufp;

    // 关心文件头对象和特殊对象
    if(b<0x10 || b>0x12) {  // 不是常规的OSM对象
      if(b>=0xf0) {  // 单字节数据集
        if(b==0xff) {  // 文件开始, resp. o5m 重置
          // 为写入o5m文件重置计数器;
          o5id= 0;
          o5lat= o5lon= 0;
          o5hiscset= 0;
          o5histime= 0;
          o5ref[0]= o5ref[1]= o5ref[2]= 0;
          str_reset();
          }
        else if(b==0xfe) // 文件结束
          o5endoffile= true;
        else if(write_testmode)
          fprintf(stderr,"Unknown .o5m short dataset id: 0x%02x\n",b);
        read_bufp++;
        }  // 单字节数据集
      else {  // 多字节数据集
        bufp++;
        l= pbf_uint32(&bufp);
        bufe= bufp+l;
        if(b==0xdc) {
            // 文件的时间戳
          if(bufp<bufe) {
            char ts[25];

            uint64totimestamp(pbf_sint64(&bufp),ts);
            printf("file timestamp: %s\n",ts);
            }
          }  // 文件的时间戳
        else if(b==0xdb) {  // border box
          char s[15];

          if(bufp<bufe) printf("bBox x1=%s\n",
            sint32tosfix7(pbf_sint32(&bufp),s));
          if(bufp<bufe) printf("bBox y1=%s\n",
            sint32tosfix7(pbf_sint32(&bufp),s));
          if(bufp<bufe) printf("bBox x2=%s\n",
            sint32tosfix7(pbf_sint32(&bufp),s));
          if(bufp<bufe) printf("bBox y2=%s\n",
            sint32tosfix7(pbf_sint32(&bufp),s));
          }  // border box
        else {  // 未知的多字节数据集
          if(write_testmode)
            fprintf(stderr,"Unknown .o5m dataset id: 0x%02x\n",b);
          }  // 未知的多字节数据集
        read_bufp= bufe;
        }  // 多字节数据集
  continue;
      }  // 不是常规的OSM对象
    // 这里: 常规OSM对象
    otype= b&3;
    bufp++;  // 跳过数据集id

    // 读一个osm对象

    // 读取对象id
    l= pbf_uint32(&bufp);
    read_bufp= bufe= bufp+l;
    printf("%s: %"PRIi64"\n",ONAME(otype),o5id+= pbf_sint64(&bufp));
    /* 读作者 */ {
      uint32_t hisver;
      int64_t histime;

      hisver= pbf_uint32(&bufp);
      if(hisver!=0) {  // author information available
        printf("    version: %"PRIi32"\n",hisver);
        histime= o5histime+= pbf_sint64(&bufp);
        if(histime!=0) {
          char ts[25];
          char* hisuid;
          char* hisuser;

          uint64totimestamp(histime,ts);
          printf("    timestamp: %s\n",ts);
          printf("    changeset: %"PRIi64"\n",
            o5hiscset+= pbf_sint64(&bufp));
          str_read(&bufp,&hisuid,&hisuser);
          printf("    uid/user: %"PRIu32" %s\n",
            (uint32_t)pbf_uint64((byte**)&hisuid),hisuser);
          }
        }  // author information available
      }  // 读作者
    if(bufp>=bufe)
        // 只有id和作者,即这是一个删除请求
      printf("  action: delete\n");
    else {  // not a delete request
      // 读取坐标(仅用于节点)
      if(otype==0) {  // node
        char s[15];

        printf("  lon: %s\n",
          sint32tosfix7(o5lon+= pbf_sint32(&bufp),s));
        printf("  lat: %s\n",
          sint32tosfix7(o5lat+= pbf_sint32(&bufp),s));
        }  // node
      // 阅读noderefs(仅用于路线)
      if(otype==1) {  // way
        byte* bp;

        l= pbf_uint32(&bufp);
        bp= bufp+l;
        if(bp>bufe) bp= bufe;  // (format error)
        while(bufp<bp)
          printf("  noderef: %"PRIi64"\n",
            o5ref[0]+= pbf_sint64(&bufp));
        }  // way
      // 阅读参考(仅用于关系)
      else if(otype==2) {  // relation
        byte* bp;
        int64_t ri;  // 暂时的,refid
        int rt;  // 暂时的, reftype
        char* rr;  // 暂时的, refrole

        l= pbf_uint32(&bufp);
        bp= bufp+l;
        if(bp>bufe) bp= bufe;  // (format error)
        while(bufp<bp) {
          ri= pbf_sint64(&bufp);
          str_read(&bufp,&rr,NULL);
          rt= *rr++ & 3;  // convert '0'..'2' to 0..2
          printf("  ref: %s %"PRIi64" %s\n",
            ONAME(rt),o5ref[rt]+= ri,rr);
          }
        }  // relation
      // 读取结点的key/val对
      while(bufp<bufe) {
        char* kp,*vp;

        str_read(&bufp,&kp,&vp);
        printf("  key/val: %s=%s\n",kp,vp);
        }
      }  // not a delete request

    }  // 读取输入文件中的所有对象
  if(!o5endoffile)  // 缺少文件的逻辑结束
    fprintf(stderr,"Unexpected end of input file.\n");
  return 0;
  }  // process_o5m()



int main(int argc,char** argv) {
  // main procedure;
  int r;

  #if __WIN32__
    setmode(fileno(stdout),O_BINARY);
    setmode(fileno(stdin),O_BINARY);
  #endif

  r= process_o5m();
  return r;
  }  // main()

相关文章

网友评论

      本文标题:o5m_demo

      本文链接:https://www.haomeiwen.com/subject/rtmznxtx.html