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

Projects/CoVNC 2007.01.23 13:40 Posted by soulfree >동네청년<

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();
}
   }
}
 
신고

strdup()함수

Projects/CoVNC 2007.01.18 15:11 Posted by soulfree >동네청년<

사용법

#include <string.h>

char *strdup(const char *s);

설명

strdup() 함수는 문자 s를 복사하고 복사된 문자열을 가리키는 포인터를 반환한다. 문자를 복사할 공간을 확보하기 위해서 내부적으로 malloc()이 호출된다. 그러므로 strdup() 함수를 호출해서 문자열 복사를 했다면 free() 등을 통해서 공간이 필요없게 되었을때 커널에 되돌려주어야 한다.

반환값

복사된 문자열의 주소를 가리키는 포인터를 반환한다. 에러발생시에는 NULL 을 되돌려준다.

에러

ENOMEM

복사할 문자열을 할당하기 위해 이용할수 있는 메모리가 충분하지 않다.

예제

#include <string.h>

int main()
{
       char *line = "hello world!";
       char *dupstr = NULL;
     
       dupstr = strdup (line);
       printf("%s\n", dupstr);
      
free(dupstr);
}

신고

에디트 박스 컨트롤

Projects/CoVNC 2006.11.28 11:37 Posted by soulfree >동네청년<

void SendMsgToEdit( HWND hEdit, LPSTR Msg )
{
char pszTmp[128];

// 커서를 맨 끝으로 옮기는 루틴
SendMessage( hEdit, EM_SETSEL, 0, -1 );
SendMessage( hEdit, EM_SETSEL, -1, -1 );

// 문자열을 추가하는 루틴
wsprintf( pszTmp, "%s\r\n", Msg );
SendMessage( hEdit, EM_REPLACESEL, 0, (LPARAM)pszTmp );
}


신고

C, C++ DOM API

Projects/CoVNC 2006.11.09 18:13 Posted by soulfree >동네청년<
출처 블로그 > 사람은 언제나 웃을 수 있는 태세를 갖추어야 한다
원본 http://blog.naver.com/iku88/130003398020

Source: saveDOM.cpp


역시 MSDN에 있는 예제.


.NET 2003에서 실행했다.


그대로 복사해서 실행하면 myData.xml이란 파일을 생성한다.


나의 경우, 역시 오류가 뜨던데.. vsprintf_s 란 이름을 vsprintf 로 바꿔주면 된다.


아마 2005버전에선 에러가 안 날것 같다. (안해봐서 확신은 못하지만)


보면..


bstr = SysAllocString(L"<r>\n<t>top</t>\n<b>bottom</b>\n</r>");


일단 bstr이란 스트링 변수에 위의 내용을 넣었다.

이걸

HRCALL(pXMLDom->loadXML(bstr, &status),
              "dom->loadXML(): ");

명령으로 xml 클래스에 등록한다.


그 다음, bstr을 free시킨 후에


HRCALL(pXMLDom->get_xml(&bstr), "dom->get_xml: ");

이렇게 해서 아까 저장한 내용을 다시 읽어온 후에


dprintf("XML DOM loaded from stocks.xml:\n%S\n",bstr);

콘솔에 출력하고 있다.


이때, 콘솔을 보면, 아까의 bstr은 줄바꿈문자('\n')는 있었지만, 탭문자('\t')는 없었는데, 결과를 보면 알아서 탭이 들어가 있다. 저렇게 만들어주나 보다.


그 다음에는 var에 저장할 파일 이름을 저장해서

HRCALL(pXMLDom->save(var), "dom->save: ");

명령으로 파일에 저장을 하고 있다.

신고
TAG C/C++, DOM

API에서 HINSTANCE와 HWND에 대한 질문입니다.

Projects/CoVNC 2006.11.08 22:48 Posted by soulfree >동네청년<

안녕하세요. API에 대해 공부하고 있는 학생입니다.


공부를 하던 중 궁금한 점이 있어서 이렇게 질문을 드립니다.


다름이 아니고..


왜 HINSTANCE(프로그램을 구별하기 위한 핸들)과 HWND(윈도우를 구별하기 위한 핸들)


을 왜 나누는것이죠? 차라리 하나를 합치는게 출력할때 더 편하지 않을까요? 어떤 윈도우


에대가 출력해라가 아니라.. 어떤 프로그램에 출력해라가 더 좋지 않나요? 굳이 HWND를


사용할 필요가 있나요? 아니면 하나의 프로그램에 하나의 윈도우가 아니라 하나의 프로그


램에 다수의 윈도우에다가 출력할려고 그러는건가요? 만약 그렇다면... 예좀 들어주십시요


.. 제가 이해가 잘못되어서인지 잘 모르겠습니다. 복잡하게 질문을 드렸는데.. 제가 초보라


서 그렇습니다.. 황당한 질문이시겠지만 답변을 주시면 감사하겠습니다

질문자가 선택한 답변 
re: API에서 HINSTANCE와 HWND에 대한 질문입니다. 

theuhm (2005-10-18 23:43 작성) 
이의제기 | 신고

질문자 평
감사합니다^^ 

우선 한가지 오해가 있습니다.

HINSTANCE는 프로그램의 핸들이 아닙니다. HINSTANCE는 프로그램 코드를 담고 있는 모듈에 대한 핸들이죠. 즉, 프로그램이 수행되려면, 프로그램 코드를 담고 있는 파일을 메모리의 특정 영역에 올려서 명령을 하나씩 읽어가면서 수행할 수 있도록 준비해 놓아야 합니다. 이렇게 메모리에 올려진 프로그램 코드 덩어리를 윈도우에서 관리하기 위해서 일종의 고유 식별 번호를 부여하는데, 이것이 인스턴스핸들, HINSTANCE입니다. 기본적으로 프로세스를 실행하는 실행파일의 코드를 메모리에 올려놓은 모듈이 하나 있어야 하므로, 실행파일의 모듈에 대한 인스턴스 핸들을 OS가 어플리케이션에 WinMain의 인자로 넘겨주는 것입니다.

한 프로세스가 여러개의 모듈을 로딩하여 프로그램을 실행하고 있다면 하나의 프로그램이 여러개의 인스턴스 핸들을 할당받아 쓰고 있을 수가 있습니다. (물론 한 개의 모듈을 여러 프로세스가 공유하고 있을 수도 있습니다) 대표적인 예가 바로 IE입니다. 간단하게, DLL파일 한개를 쓸 때마다 이 DLL모듈에 대한 인스턴스 핸들이 한개씩 생긴다고 보시면 됩니다. 물론 DLL이 한번 로딩되면 다른 프로그램 사이에서 공유된다는 점은 알고 계시리라 믿습니다.


한개의 프로그램에서 HINSTANCE가 한 개만 있는 것이 아니며, 또한 하나의 인스턴스핸들이 한개의 프로그램에만 종속되는 것이 아니므로, 어떤 프로그램의 출력 대상을 지정하는 데에는 부적절하다는 점을 알 수 있을 겁니다. 물론, 인스턴스 핸들은 애초부터 화면 출력을 고려하여 만들어진 식별자는 아닙니다. 인스턴스 핸들은 단지 프로그램 코드 덩어리를 관리하기 위해 만들어진 리소스입니다.



윈도우라는 OS에서 화면 출력을 위해 관리하는 리소스가 바로 윈도우핸들입니다. MSN메신저등을 보시면 알겠지만, 하나의 프로그램이 하나의 창을 사용한다는 보장이 없습니다. 오히려 99.99%의 프로그램은 한 개 이상의 윈도우로 구성되어 있습니다. 지금 쓰고 계실 것으로 추정되는 IE역시  메뉴바, 툴바, 하단 상태바, 주소창 등등이 모두 별개의 윈도우입니다. 물론 가장 바깥쪽의 프레임 윈도우와, 내부에 웹문서를 보여주는 클라이언트 윈도우도 따로 존재합니다. 즉, 한 프로그램의 윈도우가 겉보기에는 단일한 대상 영역으로 보일지라도, 실제로는 구성요소별로 분리하여 별개의 윈도우로 만들어 각 윈도우는 자기 자신이 맡은 부분에 대한 화면 출력과 사용자 입력만을 담당합니다. 자연히 하나의 프로그램에서 사용하는 윈도우핸들, HWND타입의 개체 역시 1개 이상이 될 수밖에 없으며, 이러한 상황에서 특정 위치에 특정한 동작을 수행하기 위해서는 HWND로 대상 영역을 구분할 수밖에 없는 것이죠.

신고

win32 API를 이용한 file dialog 띄우기 예제

Projects/CoVNC 2006.11.05 02:49 Posted by soulfree >동네청년<

#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass="FODial";

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
  ,LPSTR lpszCmdParam,int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;

WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);

hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);

while(GetMessage(&Message,0,0,0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
OPENFILENAME OFN;
char str[300];
char lpstrFile[MAX_PATH]="";
switch(iMessage) {
case WM_LBUTTONDOWN:
memset(&OFN, 0, sizeof(OPENFILENAME));
OFN.lStructSize = sizeof(OPENFILENAME);
OFN.hwndOwner=hWnd;
OFN.lpstrFilter="Every File(*.*)\0*.*\0Text File\0*.txt;*.doc\0";
OFN.lpstrFile=lpstrFile;
OFN.nMaxFile=256;
OFN.lpstrInitialDir="c:\\";
if (GetOpenFileName(&OFN)!=0) {
  wsprintf(str,"%s 파일을 선택했습니다.",OFN.lpstrFile);
  MessageBox(hWnd,str,"파일 열기 성공",MB_OK);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}


신고