본문 바로가기

Projects/CoVNC

자바와 C/C++ 간의 데이터 통신

author : Yoon Kyung Koo(yoonforh@moon.daewoo.co.kr)
Copyright (c) 1999 Yoon Kyung Koo, All rights reserved.

자바 프로그램과 C/C++ 프로그램 사이에 데이터 통신(주로 소켓을 사용하겠지요...)을 할 때에는
자바의 객체 스트림(java.io.Serializable or java.io.Externalizable을 인터페이스를 구현하고
ObjectInput/OutputStream 사용)을 사용할 수가 없습니다.
자바의 객체 스트림은 자바에 고유한 형식을 사용하기 때문에 C/C++ 프로그램이 이해할 수 없기
(어렵기?) 때문입니다.
따라서 자바와 C/C++ 프로그램 사이에서 데이터를 주고 받으려면 기본 유형으로 변환하여
주고 받을 수밖에 없습니다.
예를 들어 다음과 같은 필드들로 구성된 자바 클래스를 C/C++ 프로그램과 주고 받는다고
생각해봅시다.

class TestData {
  byte[] bytes = new byte[100] ;
  char c; // assume this char value will not excced one byte (i.e. cannot use Hangul char)
  boolean val;
  String string;
}

대응되는 C/C++ 데이터 구조체를 다음과 같이 생각할 수 있겠습니다.

typedef struct {
  char bytes[100];
  char c; // 물론 이렇게 구현하면 안됩니다. C의 구조체 alignment에 따라
          // 사용되지 않는 몇 바이트가 채워지게 됩니다.
  bool val; // 보통 C++의 bool 유형은 int처럼 4바이트지만 컴파일러와 O/S에 따라 다릅니다.
             // 예를 들어 64비트 머신인 디지털 유닉스 4.0의 g++ 2.95에서 bool은 8바이트를
             // 차지합니다. (같은 기계에서 cxx의 경우 4바이트를 차지합니다.)
  int str_length; // 문자열의 길이
  char * string;
} Object ;

이럴 경우에 발생하는 문제는 여러 가지가 있을 수 있습니다.
1. C/C++에서 structure가 차지하는 각 필드들의 바이트 수...
위의 경우처럼 char가 중간에 혼자 들어가게 되면 3바이트가 덧붙는 경우가 생기게 됩니다.

2. 각 기본 유형들의 데이터 길이...
자바는 고정되어 있지만 C/C++의 경우에는 컴파일러와 O/S 등에 따라 차이가 납니다.
예를 들어 long의 경우 자바는 8바이트이지만 C/C++의 경우 운영 체제에 따라 4바이트와 8바이트가
각각 사용될 수 있습니다.
GNU의 C/C++ 컴파일러를 사용하면 8바이트 유형인 long long를 사용하여 자바의
long에 대응시킬 수 있습니다.
위 예제에서는 bool 유형(보통 4바이트지만 어떤 컴파일러/운영체제에서는 8바이트로 처리),
int 유형(16비트 운영체제에서는 2바이트로 처리), char *와 같은 주소 유형(CPU 종류에 따라
2바이트, 4바이트, 8바이트 등으로 처리) 등이 C/C++ 쪽에서 주의해야 할 유형입니다.

3. short, int, long 등의 바이트 순서
자바는 네트웍 순서(빅 엔디안)로 고정되어 있지만 C/C++의 경우에는 CPU 유형에 따라
달라지므로 반드시 보정해줘야 합니다.
만약 자바쪽에서 리틀 엔디안으로 변환하여 보내려면 htonl/htons 기능을 하는 메소드를
구현하여야 합니다.
참고 : 자바 기본 유형의 바이트 순서 변환

4. char의 인코딩...
자바는 String을 내부적으로 유니코드를 사용하니까 바이트로 변환하여 C/C++와 주고받아야 합니다.
특히 한글과 같은 2바이트 문자를 사용할 때에는 더욱 중요합니다.
DataOutput 혹은 ObjectOutput의 void writeBytes(String)과 같은 메소드는
무조건 문자를 1바이트로 처리하여 쓰므로 2바이트 문자가 사용될 경우에는 사용하지 말아야 할
메소드입니다.
(String의 length()와 문자열을 바이트열로 변환한 byte[]의 length 값이 다를 수 있다는 건 아시지요?)
다음은 간단한 예제입니다. 소켓 프로그램으로 할 수도 있지만 마찬가지이므로 간단하게 파일로 했습니다.
자바 프로그램이 데이터를 파일로 쓰고, C++ 프로그램이 그 파일을 읽는 것이지요.
C++ 프로그램은 위에서 말씀드린 여러 가지 이유로 운영 체제에 따라 수정할 필요가 있습니다.
제가 테스트한 운영체제는 솔라리스 2.6입니다.

=================================

/*
* @(#)FileIOTest.C 0.1 1999/09/10
*
* Copyright 1999 by Yoon Kyung Koo.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Yoon Kyung Koo. ("Confidential Information").  You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Yoon Kyung Koo.
*
*/
/**
* This program provides a test over
* communication with java apps
*
* @version 0.1 1999/09/10
* @author <A HREF="Yoon'>mailto:yoonforh@moon.daewoo.co.kr">Yoon Kyung Koo</A>
*/
#include <iostream>
#include <fstream>
extern "C" {
#include <arpa/inet.h>
}
typedef struct {
   char bytes[100];
   char c;
   bool val; // usually bool type is defined as int, but it depends on O/S
   int str_length; // length of string
   char * string; // string bytes
} Object ;
void usage(const char * name) {
   cout << "\nUsage : " << name << " file1" << endl;
}
int main(int argc, char ** argv)
{
   if (argc < 2) {
usage(argv[0]);
exit(1);
   }
    Object object;
   cout << "address of object : " << hex << &object << endl;
   cout << "offset to bytes   : " << dec << (int)&object.bytes - (int)&object << endl;
   cout << "offset to c       : " << dec << (int)&object.c - (int)&object << endl;
   cout << "offset to val     : " << dec << (int)&object.val - (int)&object << endl;
   cout << "offset to str_l   : " << dec << (int)&object.str_length - (int)&object << endl;
   cout << "offset to string  : " << dec << (int)&object.string - (int)&object << endl;
   cout << "whole size of the Object class    : " << dec << sizeof(Object) << endl;
    ifstream fs(argv[1]);
   if (fs.bad()) {
cerr << "cannot open " << argv[1] << endl;
exit(1);
   }
    // size of whole object - size of Object::string
   int len = sizeof(Object) - sizeof (char *);
    fs.read((char *)&object, len);
   if (fs.bad()) {
cerr << "stream errored." << endl;
exit(1);
   }
    // always fix byte order
   object.val = ntohl(object.val);
   object.str_length = ntohl(object.str_length);
    len = object.str_length;
   object.string = new char[object.str_length + 1];
    fs.read(object.string, len);
   if (fs.bad()) {
cerr << "stream errored." << endl;
exit(1);
   }
    object.string[object.str_length] = 0; // append null
    cout << "bytes  :" << endl;
   for (int i=0; i<100; i++) {
if (i%10 == 0)
    cout << endl;
cout << dec << (int) object.bytes[i] << " ";
   }
   cout << endl;
   cout << "c      : " << object.c << endl;
   cout << "val    : " << (object.val?"true":"false") << endl;
   //    cout << "len    : " << object.str_length << endl;
   cout << "string : " << object.string << endl;
}

=================================

/*
* @(#)FileIOTest.java 0.1 1999/09/10
*
* Copyright 1999 by Yoon Kyung Koo.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Yoon Kyung Koo. ("Confidential Information").  You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Yoon Kyung Koo.
*
*/
/**
* This class provides a test over
* communication with C/C++ apps
*
* @version 0.1 1999/09/10
* @author <A HREF="Yoon'>mailto:yoonforh@moon.daewoo.co.kr">Yoon Kyung Koo</A>
* @see java.io.Test
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutputStream
*/
import java.io.*;
/**
* This class is a sample data class
*
* NOTE that object stream cannot be used for communicating with C/C++ apps.
*   java uses its proprietary format to represent objects.
*
* Corresponding C data structure will be somewhat like this
* struct {
*    char bytes[100];
*    char c; // this will be problem... this will accompany non-used three bytes
*    bool val; // bool type is defined as int
*    int str_length; // length of string
*    char * string; // string bytes
* } ;
*    
*/
class TestData {
   byte[] bytes = new byte[100] ;
   char c; // assume this char value will not excced one byte (i.e. cannot use Hangul char)
   boolean val;
   String string;
    /*
    * fill with some data
    */
   public TestData() {
for (int i=0; i<bytes.length; i++)
    bytes[i] = (byte)i;
c = 'c';
val = true;
string = "어떤 string";
   }

   public void writeDataExternal(java.io.DataOutputStream stream)
throws IOException
   {
stream.write(bytes);
stream.write(c);
for (int i=0; i<3; i++)
    stream.write(0); // fill three byte
stream.writeInt(val?1:0);
byte[] strBytes = string.getBytes("EUC_KR");
// first write the byte length of string
stream.writeInt(strBytes.length);
stream.write(strBytes);
// NOTE: never use writeBytes() method. writeBytes() methods write strings
// converting each chars to byte, so only string.length() byte will be written
// stream.writeBytes(string);
   }
    public void readDataExternal(java.io.DataInputStream stream)
throws IOException
   {
bytes = new byte[100];
stream.read(bytes, 0, bytes.length);
c = (char) stream.read();
for (int i=0; i<3; i++)
    stream.read(); // discard three byte
val=((stream.readInt()==0)?false:true);
 int len = stream.readInt();
byte[] strBytes=new byte[len];
stream.read(strBytes, 0, len);
string = new String(strBytes, "EUC_KR");
   }
    /*
    * display member fields
    */
   public void print() {
System.out.println("bytes  :");
for (int i=0; i<bytes.length; i++) {
    if (i%10 == 0)
 System.out.println();
    System.out.print(bytes[i] + " ");
}
System.out.println();
System.out.println("c      : " + (char) c);
System.out.println("val    : " + val);
System.out.println("string : "+string);
   }
}
class FileIOTest
{
   public static void usage() {
System.out.println("\nUsage : java FileIOTest filename");
   }
    public static void main(String[] args) throws IOException {
if (args.length < 1) {
    usage();
    System.exit(1);
}
 // write file
FileOutputStream f_out=new FileOutputStream(args[0]);
DataOutputStream d_out=new DataOutputStream(f_out);
 TestData td = new TestData();
 try {
    td.writeDataExternal(d_out);
    d_out.flush();
} finally {
    d_out.close();
    f_out.close();
}
 // read file
FileInputStream f_in=new FileInputStream(args[0]);
DataInputStream d_in=new DataInputStream(f_in);
 TestData td2 = new TestData();
 try {
    td2.readDataExternal(d_in);
    td2.print();
} finally {
    d_in.close();
    f_in.close();
}
   }
}