회사에서 Handlebars.java 와 관련된 이슈가 공유가 되었다.

Handlebars 가 Javascript 버전과 Java 버전의 #with helper 결과가 동일하지 않습니다.

우선 이 이슈 버그를 해결한 코드는 필자의 github 저장소 https://github.com/powerumc/handlebars.java.bug-fix 에 커밋 되어 있고, 원본 저장소의 이슈 번호 #314, Pull Request #315 에 등록 되었다.

Handlebars vs Handlebars.java

이 테스트에서 사용되는 handlebars 데이터는 다음과 같습니다.

{ "company": { "ko": "쿠팡",
               "en": "Coupang" }}

그리고 handlebars 템플릿은 다음과 같다.

{{#with company}}
  우리 회사는 {{ko}}
  My company is {{company.en}}
{{/with}}
- Javascript handlebars 결과
  우리 회사는 쿠팡
  My company is 
- Handlebars.java 결과
  우리 회사는 쿠팡
  My company is Coupang

문제 원인

Handlebars.java 를 반나절 정도 분석하고 나니 대충 (정말 대충) 어떻게 흘러가는 지 조금 이해가 되었다.

문제는 Handlebars.java 에서는 #with helper 에서 value 값을 resolving 하지 못하면 parent object (=context) 에서 찾는다. #with helper 에게 전달된 데이터를 model 이라고 하면 전달된 데이터 전체를 context 로 불린다. 그래서 model 의 parent 는 context 가 된다.

위의 상황을 보면 My company is {{company.en}} 와 같은 코드의 model 에서 {#with company}}company.en을 찾지 못해서 context 에서 company.en을 찾게 된다.

문제 해결 방법

문제의 원인을 파악했으니 코드를 디버깅해 알겠지만 튜닝 포인트가 매우 다양하다. #with helper 전체를 뜯어 고칠 수 도 있고, 내부적으로 CompositeValueResolver 를 튜닝하거나, 그 외에 다양한 방법으로 고칠 수 있다.

가장 간단하게 이 버그를 픽스하기 위해 또 반나절 정도를 적용해 보고, 가장 적은 코드로 튜닝할 수 있는 코드를 만들었다.

Options.java 코드에서 wrap(final Object) 메서드를 다음과 같이 픽스하였다. 딱 세 줄만 추가해 주면 된다.

public Context wrap(final Object model) {
  if (model == context) {
    return context;
  }
  if (model == context.model()) {
    return context;
  }
  if (model instanceof Context) {
    return (Context) model;
  }
  if (model.getClass() == LinkedHashMap.class) {  // 이 코드부터...
    return Context.newContext(model);
  }                                               // 여기까지 추가...
    return Context.newContext(context, model);
  }

그럼 아래의 이슈가 되는 테스트 코드가 무사히 통과하고,Handlebars.java 의 모든 테스트도 통과한다.

public class Issue314 extends AbstractTest {

  @Test
  public void withHelperSpec() throws IOException {
    String context = "{ obj: { context: { one: 1, two: 2 } } }";

    shouldCompileTo("{{#with obj}}{{context.one}}{{/with}}",     context, "1");
    shouldCompileTo("{{#with obj}}{{obj.context.one}}{{/with}}", context, "");
 }
}


Posted by 땡초 POWERUMC

댓글을 달아 주세요

얼마 전 자바8 람다 나머지 이야기를 보면서 평소 필자가 알던 Java와는 완전히 달라 보였다. 필자가 알고 있던 Java는 보수적이지만 정통적이라고 생각 해왔는데 과감히 이 생각을 깨졌다.



Java 8 Lambda 에 대해 궁금한 부분은 필자가 예전에 작성한 아티클을 참고하기 바란다.

Java 8 Interface 변경 사항 default 키워드

오라클의 Defining an Interface 문서에 의하면 Java Interface의 정의는 변경되었다. Java Interface는 abstract methods, default methods, static methods 를 정의할 수 있다고 한다.

The interface body can contain abstract methods, default methods, and static methods.

명세에 따르면 Java Interface에 default 키워드를 통해 메서드를 구현할 수 있다. 또, 이를 구현하는 클래스는 Interface의 메서드를 @Override 할 수 있다.

Oracle 문서에서 아래와 같은 예제를 볼 수 있다.

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
   default boolean didItWork(int i, double x, String s) {
       // Method body 
   }  
}

Java 8 Interface, 그 인터페이스는 그 인터페이스가 아니다.

원래 객체지향 언어에서 Interface는 그 시그너처와 선언이 변하지 않는다는 것을 전제로 하여 다형성(polymophism)을 정의하는 객체간의 규약이다. 그러나 Java8 Interface는 Interface를 업그레이드하는 개념을 도입하였다.

Now users of your code can choose to continue to use the old interface or to upgrade to the new interface.
Alternatively, you can define your new methods as default methods

객체지향 언어의 객체 규약을 정의함에 있어 항상 논의 되는 것이 Interface와 Abstract 클래스 둘 중 어떤 것을 쓸 것이가에 대한 것이다. 어떤 것을 사용해도 무방하겠지만 객체 간의 규약(서로간의 약속)은 Interface 로 정의해야 한다. 구현체가 없는 온전한 인터페이스 역할을 해야 하기 때문이다.

특히 분산 객체(distributed object)는 분산된 두 객체의 규약, 즉 Interface 의 프록시(proxy)를 통해 분산 객체를 사용한다. Java8 Interface는 구현체가 포함될 수 있으므로 더 이상 분산 객체를 정의하는 규약으로 부적합하다.

이는 반드시 분산 객체 뿐만 아니라, 일반적인 객체로서도 문제의 소지가 충분하다. 아래는 이런 현상을 약간 억지스럽게 구현한 코드이다.

이를 통해 알 수 있는 것은 Java 8 Interface 로 규약된 분산 객체에 구현 코드가 포함이 되므로 더 이상 ‘규약’이라는 표현 자체가 규약이 될 수 없을 것 같다.

interface Duck {
    void say();
    void walk();
}

interface MyDuck extends Duck { }

interface YourDuck extends Duck {
    default void walk() { say(); }
}

class MyDuckImpl implements MyDuck {
    @Override
    public void say() { System.out.println("MyDuck: 꽥~"); }

    @Override
    public void walk() { System.out.println("MyDuck: 뒤뚱~"); }
}

class YourDuckImpl implements YourDuck {
    @Override
    public void say() { System.out.println("YourDuck: 꽥꽥꽥~"); }
}

public class Main {
    public static void main(String[] args) {
        new MyDuckImpl().say();
        new YourDuckImpl().say();

        new MyDuckImpl().walk();
        new YourDuckImpl().walk();
    }
}

// 결과
MyDuck: 꽥~
YourDuck: 꽥꽥꽥~
MyDuck: 뒤뚱~
YourDuck: 꽥꽥꽥~

상대적인 취약해진 Java 8 Interface

일반적으로 실행 파일(executable file)이나 라이브러리(library) 파일은 코드와 데이터 등을 구조적으로 저장하고 링크(link) 과정을 거친 바이너리 파일이다. 반면 Java는 각 클래스 파일을 컴파일하면 .class 확장자를 가진 바이너리 파일을 출력한다.

일반적으로 코드와 데이터가 모두 포함된 바이너리 파일(일반적으로 portable executable)은 가리키는 offset 에 따라 RVA(relative virtual address)/VA(virtual address)와 매핑된다. 따라서 바이너리 파일을 비정상적으로 수정(삽입/삭제)하면 offset 정보도 함께 변경해 주어야 한다. 그렇지 않으면 메모리에 로드될 때 코드가 정상적으로 동작하지 않을 수 있다.

반면 Java 의 컴파일 된 바이너리는 클래스 별로 컴파일되어 .class 파일로 출력되기 때문에 이런 offset 정보가 필요가 없다. 컴파일된 코드가 논리적인 구조에 따라 물리적인 파일을 생성하지 않는다. 할 수 있는 건 Stack Size 정도 늘리거나 줄일 수 있다.

따라서 Java 8 Interface 의 .class 파일에 악의적으로 default 메서드 구현을 임의로 수정하면 간편하게 응용 프로그램 전체에 영향을 끼치도록 소정의 목적을 달성할 수 있다. (상대적으로 Java의 바이너리가 더 취약하다는 의미이다.)

다시 불거지는 다중 상속 문제

Java 8 Interface는 다시 다중 상속 문제에서 자유롭지 못하게 되었다.

Java는 단일 상속만 가능하지만 Interface는 다중으로 구현할 수 있다. 이는 C++의 다중 상속으로 인해 발생하는 문제점과 복잡성으로 Java는 단일 상속 구조를 택하게 되었다.

다중 상속으로 발생하는 모호성에 대해서도 Java 8 Interface 가 내놓은 해결책은 C++ 의 해결책과 다를 바가 없다.

아래는 Java 8 Interface 다중 상속(구현)으로 인한 모호성 문제 해결 방법이다.

public interface OperateCar {
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public interface FlyCar {
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}

public class FlyingCar implements OperateCar, FlyCar {
    public int startEngine(EncryptedKey key) {
        FlyCar.super.startEngine(key);
        OperateCar.super.startEngine(key);
    }
}

아래는 C++ 의 다중 상속으로 인해 발생하는 모호성을 해결 하는 방법이다.

#include <iostream>  
using namespace std;  

class OperateCar {  
public:  
    int startEngine(EncryptedKey *key) {  
        // Implementation
    }  
};  

class FlyCar {  
public:  
    int startEngine(EncryptedKey *key) {  
        // Implementation
    }  
};  

class CCC : public OperateCar, public FlyCar {  
public:  
    int startEngine(EncryptedKey *key){  
        OperateCar::startEngine(key);  
        FlyCar::startEngine(key);
    }  
};  

위의 Java와 C++ 소스 코드를 비교해 보면 결국 Java 8 Interface는 다중 상속의 개념이 다시 도입된 것을 알 수 있다.

참 아이러니 하다.

결론

어느 것이 답이 될 수는 없다. 이전까지 지향하고 고수하던 객체지향이 반드시 정답은 아닐 것이다. 다만, Java가 추구하던 Interface의 개념이 그 동안 알고 있던 개념과 이치에 맞지 않고, 어쩔 도리 없이 구현한 스팩일 수 있다.

필자처럼 C# 5.0의 가장 최신 스팩까지 쭉 경험한 개발자라면 도저히 용납할 수 없을 지도 모르겠다.

까마득한 2007년도에 릴리즈한 C# 3.0 스팩의 Lambda, LINQ, Extension Methods 의 언어 스팩과 비교해보면 Java 8 는 너무나도 기대에 미치지 못한 방법으로 구현해 놓았다는 게 실망스럽다.

아직 Java 8 모든 것을 훓어 본 것이 아니므로 필자가 오해하고 있는 부분은 정정해 주길 바란다. (이념, 철학, 사상적인 부분은 사양한다)

참고로 이해를 돕기 위해 2007년도 C# 3.0 스팩과 2014년 Java 8 스팩을 비교하면 다음과 같다.

  • C# Lambda = Java Lambda
  • C# Extension Methods = Java Stream API
  • C# LINQ = Java 엔 없다!


Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 경준씨 2015.06.30 10:16 Address Modify/Delete Reply

    메소드가 void startEngine면 아래과 같이 적어도 됩니다.
    public class FlyingCar implements OperateCar, FlyCar {
    public void startEngine(EncryptedKey key) {
    FlyCar.super.startEngine(key);
    OperateCar.super.startEngine(key);
    }
    }
    그러나 메소드가 int startEngine라면
    public class FlyingCar implements OperateCar, FlyCar {
    public int startEngine(int key) {
    FlyCar.super.startEngine(key);
    return OperateCar.super.startEngine(key);
    }
    }
    이런식으로 자바의 인터페이스는 무조건 Override를 해야합니다.
    이 Override를 상속할 때 return이 void값은 둘다 받을 수 있지만 부모의 return 값은 둘 중의 하나만 명시적으로 선택해야 return값으로 쓸 수 있습니다.

  2. brian 2016.09.09 17:18 Address Modify/Delete Reply

    감사합니다. 잘보고 갑니다.

  3. 이창현 2016.11.10 16:12 Address Modify/Delete Reply

    C#의 LINQ 를 Java 8 의 Stream 에 대응시킬 수 있지 않나요?

    • 땡초 POWERUMC 2016.11.11 09:32 신고 Address Modify/Delete

      LINQ 는 C# 언어와 통합된 쿼리식을 말합니다.
      가령 이런거죠.
      var query = from i in list
      select i;

      Java StreamAPI 와 대응되는 것은 C# 의 Where, Select 와 같은 확장 메서드라고 보시면 될 것 같습니다.

  4. 2017.04.05 19:56 Address Modify/Delete Reply

    비밀댓글입니다

프로그래밍은 언제나 숫자와의 경쟁인 것 같다. 반올림이 되느냐, 부동소수점이냐, 정수 오버플로우(integer overflow) 등은 백발이 되어 코드를 만질 때 까지 항상 따라다니는 문제가 될 것이다.

MySQL 날짜 관련 이슈

얼마 전 필자가 다니는 회사에서 발생한 데이터베이스 관련 이슈로 다음과 같은 문제가 발생하였다. 아래는 MySQL 관련 문제에 대하여 공유된 내용이다.

MySQL 5.6.4 부터 시간값 저장시 밀리세컨드를 지원한다. 하지만 DATETIME의 경우 길이가 6일 경우에만 가능하다. 그런데, DATETIME 타입(이는 DATETIME(4)와 같다)일 경우 밀리세컨드 부분을 반올림(round)하는 버그가 있다. #68760
이 버그로 인해 1999년 12월 31일 23시 59분 59초 500ms 같은 값을 DATETIME 타입에 입력하면 반올림되어 2000년 1월 1일 0시 0분 0초 0ms 가 돼 버리는 현상이 발생한다.

MySQL Community Server 의 C 언어 소스코드를 보면 날짜와 관련된 데이터를 다루는 세 가지가 데이터 타입이 있다. MYSQL_TYPE_DATE, MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP.

// MySQL 데이터 타입  
/*****************/  
#define CLIENT_MULTI_QUERIES    CLIENT_MULTI_STATEMENTS      
#define FIELD_TYPE_DECIMAL     MYSQL_TYPE_DECIMAL  
#define FIELD_TYPE_NEWDECIMAL  MYSQL_TYPE_NEWDECIMAL  
#define FIELD_TYPE_TINY        MYSQL_TYPE_TINY  
#define FIELD_TYPE_SHORT       MYSQL_TYPE_SHORT  
#define FIELD_TYPE_LONG        MYSQL_TYPE_LONG  
#define FIELD_TYPE_FLOAT       MYSQL_TYPE_FLOAT  
#define FIELD_TYPE_DOUBLE      MYSQL_TYPE_DOUBLE  
#define FIELD_TYPE_NULL        MYSQL_TYPE_NULL  
#define FIELD_TYPE_TIMESTAMP   MYSQL_TYPE_TIMESTAMP  
#define FIELD_TYPE_LONGLONG    MYSQL_TYPE_LONGLONG  
#define FIELD_TYPE_INT24       MYSQL_TYPE_INT24  
#define FIELD_TYPE_DATE        MYSQL_TYPE_DATE  
#define FIELD_TYPE_TIME        MYSQL_TYPE_TIME  
#define FIELD_TYPE_DATETIME    MYSQL_TYPE_DATETIME  
#define FIELD_TYPE_YEAR        MYSQL_TYPE_YEAR  
#define FIELD_TYPE_NEWDATE     MYSQL_TYPE_NEWDATE  
#define FIELD_TYPE_ENUM        MYSQL_TYPE_ENUM  
#define FIELD_TYPE_SET         MYSQL_TYPE_SET  
#define FIELD_TYPE_TINY_BLOB   MYSQL_TYPE_TINY_BLOB  
#define FIELD_TYPE_MEDIUM_BLOB MYSQL_TYPE_MEDIUM_BLOB  
#define FIELD_TYPE_LONG_BLOB   MYSQL_TYPE_LONG_BLOB  
#define FIELD_TYPE_BLOB        MYSQL_TYPE_BLOB  
#define FIELD_TYPE_VAR_STRING  MYSQL_TYPE_VAR_STRING  
#define FIELD_TYPE_STRING      MYSQL_TYPE_STRING  
#define FIELD_TYPE_CHAR        MYSQL_TYPE_TINY  
#define FIELD_TYPE_INTERVAL    MYSQL_TYPE_ENUM  
#define FIELD_TYPE_GEOMETRY    MYSQL_TYPE_GEOMETRY  
#define FIELD_TYPE_BIT         MYSQL_TYPE_BIT  

날짜가 포함되는 데이터 타입에 대해 코드를 분석해 본 결과 날짜 데이터를 조작하여 저장하는 코드를 찾지 못했다. 꼼꼼하게 분석한 것은 아니기 때문에 혹시 놓친 부분도 있을 거라고 생각한다.

MySQL Server에서 문제를 발견하지 못했다면,다음 봐야 할 것이 MySQL Client와 MySQL Connector/J 소스 코드인데, MySQL Connector/J 에서 이슈와 직/간접적으로 관련된 코드를 발견하였다.

MySQL Connector/J

MySQL Connector/J의 소스 코드에서 /src/com/mysql/jdbc/TimeUtil.java 파일에서 한 가지 이슈를 재연할 수 있는 코드를 발견했다. 메서드 이름  public static Time changeTimezone(…) 메서드를 살펴보면 클라이언트와 서버 간에 TimeZone이 다른 경우 이를 보정하는 연산이 포함된다.

다음은 TimeZone.java의 코드의 일부분이다.

Calendar fromCal = Calendar.getInstance(fromTz);  
fromCal.setTime(tstamp);  

int fromOffset = fromCal.get(Calendar.ZONE_OFFSET)
               + fromCal.get(Calendar.DST_OFFSET);  
Calendar toCal = Calendar.getInstance(toTz);  
toCal.setTime(tstamp);  

int toOffset = toCal.get(Calebndar.ZONE_OFFSET)
              + toCal.get(Calendar.DST_OFFSET);  
int offsetDiff = fromOffset - toOffset;  
long toTime = toCal.getTime().getTime();  

if (rollForward || (conn.isServerTzUTC() && !conn.isClientTzUTC())) {  
    toTime += offsetDiff;  
} else {  
    toTime -= offsetDiff;  
}  

Timestamp changedTimestamp = new Timestamp(toTime);  

만약, 클라이언트와 서버 사이에 0.001ms 시간 차이가 생긴다면, TimeZone은 13이라는 값의 차이가 발생한다. 클라이언트와 서버의 시간 차이 기준은 MySQL Server에서 열린 세션(session)을 기준으로 판단한다. 혹시나 클라이언트와 서버의 TimeZone이 상이하게 설정된 경우 어마 어마한 시간 차이가 생기게 된다.

다음과 같이 필자가 의도적으로 0.001ms 차이가 발생하는 경우 MySQL Connector/J 가 DateTime을 교정하는 것을 확인하였다. (단, getTimeFast 메서드는 MySQL Connector/J 에 포함된 메서드임)

Calendar calendar = Calendar.getInstance();  
calendar.set(1999, 12, 31, 23, 59, 59);  
calendar.setTimeInMillis(nanos);  

System.out.println(getTimeFast(0, bits, 12, 14, calendar, tz, true));  

// output  
2000-01-01 00:00:00.0  

결론

MySQL 날짜 관련 이슈의 직접적인 발생 원인은 찾지 못했다. 하지만 MySQL Connector는 MySQL Client와 쌍으로 움직이니 간접적으로 영향을 미칠 수 있을 것이다.

현재까지 살펴본 바로는 가장 가능성이 있는 이 이슈/버그는 MySQL Connector/J와 MySQL Client일 가능성이 가장 크고, MySQL Server 내부 버그일 가능성은 다소 낮다고 본다. 이 이슈/버그 밑에 코멘트에 MySQL .NET Connector를 사용하는 경우도 문제가 있다고 하니.. 어쨌거나 저쨋거나..

Posted by 땡초 POWERUMC

댓글을 달아 주세요

Gradle 로컬 캐시로 인한 빌드 실패

얼마 전 회사에서 Java 버전을 Java 7 버전으로 업그레이드 했다. 이에 따라 JDK, Tomcat 7을 구성하고 언어 스팩을 @1.7 버전으로 설정한 후 다음과 같은 오류가 발생하였다.

MCPOWERUMC:coupang powerumc$ ./gradlew

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'coupang'.
> Could not resolve all dependencies for configuration ':classpath'.
   > Timeout waiting to lock artifact cache (/Users/powerumc/.gradle/caches/artifacts-24). It is currently in use by another Gradle instance.
     Owner PID: 7306
     Our PID: 8436
     Owner Operation: resolve configuration ':classpath'
     Our operation: resolve configuration ':classpath'
     Lock file: /Users/powerumc/.gradle/caches/artifacts-24/artifacts-24.lock

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 1 mins 2.294 secs 

아마도 필자에게 발생하는 오류로 봐선 머신의 환경적인 요소가 문제가 되었나보다. 오류의 주 원인이라고 콘솔에 출력된 Timeout waiting to lock artifact cache 메시지로 보아 다른 프로세스에서 해당 파일을 점유하는 것이 아닐까 생각할 수 있다. 하지만, 프로세스를 kill 또는 머신을 재부팅 한 후에도 같은 오류가 발생한다.

따라서 간단하게 Gradle 로컬 캐시를 지우기로 했다.

Gradle 로컬 캐시 제거 후 빌드

구성된 Gradle의 로컬 캐시는 사용자의 홈 디렉토리의 .gradle 디렉토리에 캐싱된다. 다음의 명령으로 로컬 캐시를 지워보자.

MCPOWERUMC:coupang powerumc$ rm -R /Users/powerumc/.gradle

그리고, 다시 Gradle 빌드를 수행하면 구성요소를 다운로드 후 다음과 같이 정상적으로 빌드가 완료된다.

MCPOWERUMC:coupang powerumc$ ./gradlew
Downloading http://services.gradle.org/distributions/gradle-1.6-bin.zip
...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Unzipping /Users/powerumc/.gradle/wrapper/dists/gradle-1.6-bin/72srdo3a5eb3bic159kar72vok/gradle-1.6-bin.zip to /Users/powerumc/.gradle/wrapper/dists/gradle-1.6-bin/72srdo3a5eb3bic159kar72vok
Set executable permissions for: /Users/powerumc/.gradle/wrapper/dists/gradle-1.6-bin/72srdo3a5eb3bic159kar72vok/gradle-1.6/bin/gradle
Download http://devel.coupang.com/nexus/content/groups/public/com/eriwen/gradle-js-plugin/1.5.0/gradle-js-plugin-1.5.0.pom

................. 이하 생략 .................

Download http://devel.coupang.com/nexus/content/groups/public/com/asual/lesscss/lesscss-engine/1.3.0/lesscss-engine-1.3.0.jar
:help

Welcome to Gradle 1.6.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see a list of command-line options, run gradlew --help

BUILD SUCCESSFUL

Total time: 35.054 secs 


Posted by 땡초 POWERUMC

댓글을 달아 주세요

IntelliJ 배포 오류

Gradle로 구성된 프로젝트가 여러개 있는데, 그 중 하나만 IntelliJ에서 제대로 인식하는데 실패한 적이 있다. Tomcat으로 올바로 구동이 되어야 하는데, 배포가 되었다고만 나오고 Tomcat Context에 로드되지 않는 현상이 발생한다.


관련된 오류를 검색해 보았다. 하지만 전혀 다른 문제의 답변이었다.


 Artifact XXX:war exploded: Server is not connected. Deploy is not available. 


그러다가 Modules 구성을 살펴보니 여기에 문제가 있었다. 일전에 우리팀 루피님께서 Spring 구성이 잘 안되었다고 봐준적이 있는데, 어찌어찌 시간이 없어 이제서야 다시 살펴보게 되었다.

아래는 올바른 모듈의 Spring 구성이다. 


아래는 올바르지 않은 모듈 구성이다.  


문제가 되는 모듈을 제거하자. 


Gradle을 Import 로 다시 불러오자. 





이제 Tomcat에서 Spring MVC 응용 프로그램이 올바르게 구성이 된다. 


'Java' 카테고리의 다른 글

[IntelliJ] Tomcat 구동 오류 해결  (0) 2013.10.21
Posted by 땡초 POWERUMC

댓글을 달아 주세요

개요

SpringSource Tool Suite for Eclipse Juno(이하 STS) 를 Eclipse Marketplace 를 통해 설치를 하고자 할 때, Dependencies 체크 후에 아래와 같은 메시지와 함께 설치가 되지 않는다.

Cannot complete the install because one or more required items could not be found. Software being installed: Spring IDE Security Extension (optional) 3.1.0.201210040510-RELEASE
...
...
이하 생략    

이 문제는 인터넷을 통해 필자와 같은 문제의 Thread를 찾을 수 있었다.

  • Thread: cannot install Spring Tool Suite (STS) for Eclipse Juno (3.8 + 4.2) 3.1.0.RELEASE     필자와 같은 문제가 발생하는 경우 위 링크의 답변처럼 GEF(Graphical Editing Framework) 를 설치/업데이트 하면 된다고 한다. 아래의 원문의 링크를 이클립스에서 Help -> Install New Software를 이용하여 설치/업데이트를 할 수 있다. 뭐가 뭔지 잘 모르겠다면 Releases 의 링크를 통해 업데이트하면 된다.

GEF Update-Sites
Using the Eclipse Update Manager (see Eclipse Help for detailed instructions) GEF can be installed from the following update sites:

GEF(Graphical Editing Framework)의 정보는 IBM 사이트에서 찾을 수 있다. 모델링이나 이미지/그래픽 기반으로 Editing 할 수 있는 오픈 소스 프레임워크이다. STS에서 GEF를 이용한 기능적인 향상이나 종속적인 바이너리 등이 변경이 된 듯 싶다.


[이미지 1] GEF Extension 예시 / 이미지 출처 - http://www.eclipse.org/gef/


[이미지 2] Visual Studio DGML / 이미지 출처 - http://msdn.microsoft.com/en-us/library/vstudio/jj739835.aspx  

마치 Visual Studio 의 DGML(Directed Graph Markup Language)과 비슷하게 보인다. 하지만, Eclipse에서의 GEF와 Visual Studio에서의 DGML의 가장 큰 차이점이라고 하면 DGML은 정의된 스키마(Defined Schema)이고, GEF는 확장 가능한 프레임워크라는 점이다. 그 점에서 Eclipse의 GEF에 더 좋은 점수를 주고 싶다..    

GEF와 DGML의 자세한 내용은 아래의 링크를 참고하기 바란다.

Eclipse에서 GEF를 설치/업데이트가 성공하였다면 다시 Eclipse Marketplace에서 STS을 설치한다. 그럼 아래와 같이 못 보던 추가 구성 요소들이 더 많아진다. 그리고 계속 진행하면 설치가 성공할 것이다.    

Posted by 땡초 POWERUMC

댓글을 달아 주세요

개요

Java 8 버전에서 Lambda 표현을 지원한다. 아직 Java 8은 Beta 버전이다. 여러 언어 중에서 Lambda 표현을 지원하지 않는 언어로 손꼽힌다. Wikipedia에서 Anonymous Function을 참고해보면 Java 언어가 언어의 표현력에 있어서 추세를 따라가지 못하는 것이 아닐까 생각한다.

반면,

  • C#은 2007년도에 C# 3.0 버전에 LINQ 라는 대주제를 중심으로 Lambda, Anonymous Class, Extension Methods를 내놓았고,
  • C# 4.0은 2010년도에 Dynamic이라는 대주제를 중심으로 동적 프로그래밍이 가능해졌다.
  • C# 5.0은 2012년도에 비동기 라는 대주제를 중심으로 비동기 프로그래밍을 언어적으로 지원한다.

Wikipedia에서 C# 역사에 대해 더 자세히 알고 싶은 분은 'C# (programming language)' 를 참고하면 좋겠다.

Java를 이용하여 프로그래밍을 하려고 하면 정말 C#이 많이 생각난다. C#에서 한 줄짜리 문장을 Java에서는 십여 줄 넘는 경우가 많기 때문이다. 굳이 예를 들자면, 우리나라에서 유행하는 줄임말 '엄친아'를 풀어서 '엄마 친구의 아들' 로만 말해야 하는 것과 같은 느낌이랄까… 어쨌든 Java는 Java만의 매력이 있는 법. 그 매력을 찾아보는 것도 재미있겠다.

각설하고, 먼저 Java 8을 사용하여 개발할 수 있는 환경부터 간단히 살펴보자.

현재 Java 8 버전은 베타 버전이다. 현재 Java 8은 Sun사의 JDK를 칭한다. 그러므로 Oracle 사이트에서 Java 8 버전을 다운로드 받을 수 없다.

그리고 Project Lambda를 지원하는 개발 툴을 사용해야 한다. 다음의 링크의 NetBeans와 IntelliJ IDEA 12 버전에서 Project Lambda를 사용해볼 수 있다. 아래의 링크에서 다운로드 받을 수 있다.

설치와 JDK 1.8 버전의 환경 구성이 완료되었으면 Lambda 표현을 Java 에서 사용할 수 있다.

Java 8 의 Lambda 샘플 예제 간단한 예제만 소개하겠다.

(Java에서 권장하는 네이밍이나 코드 구현 방식에 맞지 않는 부분이 있더라도 양해 바란다.) 간단한 더하기 계산을 Lambda 표현으로 작성하면 다음과 같다. 



위의 코드로 말미암아 Lambda 표현은 (arguments) -> { … } 로 표현할 수 있겠다.

간단하게 Thread를 돌리는 코드를 Lambda 표현식으로 작성해보자. 




다음은 ExecutorService를 Lambda 표현으로 작성하였다. 





Java의 Lambda 이야기가 나온 김에 어떻게 Lambda 표현으로 발전하였는지도 짤막하게 보자.

원래 이런 코드가 있었다. Runnable Interface를 구현하는 코드이다. 




또는 Java의 Local Class를 이용할 수 있다. Local Class는 메서드 구현부에서 Class를 선언하여 이를 인스턴스화 할 수 있다.

위의 Runnable Interface를 구현한 코드를 Anonymous Class(익명 클래스)로 표현할 수 있게 되었다. 그래서 아래의 예제와 같이 Interface를 구현하는 Class를 만들지 않아도 된다. 



위 Anonymous Class를 Lambda 표현으로 작성하면 더 간결하게 표현할 수 있다. 


단, Java 8의 Lambda 표현에 제약이 있다.

그리고 Project Lambda를 소개하는 페이지의 Functional Interfaces 에서 제약에 대한 설명이 있다. 하지만 이는 근본적으로 Java에서는 C/C++의 Pointer를 표현할 방법이 없는 이유이다. 그러므로 함수를 가리키는 Pointer도 있을 수 없다. 반면, C#에서는 함수포인터를 표현하기 위해 Delegate(대리자)를 지원한다. C#에서는 함수포인터를 안전하게 다룰 수 있다.

그래서 Java에서는 함수포인터를 표현하기 위해서 Listener 형태의 패턴을 주로 사용한다. 다른 말로 Observer 패턴이라고 부른다. Java의 Thread가 대표적이다. Java의 Thread는 Runnable을 인자로 받는 생성자가 있다. 위의 코드에서도 볼 수 있듯이 Runnable은 void run() 메서드만 달랑 가지고 있는 Interface이다. Java의 Thread는 이 Runnable Interface만 알고 있으면 되고, Runnable Interface를 구현하는 인스턴스를 Thread에게 넘겨주면 된다.

반면, C#의 Thread는 Delegate(대리자-안전한 함수 포인터)를 이용하여 Thread를 실행한다. C# 컴파일러는 Delegate를 결국 Class 로 취급한다. 이로 말미암아 Java와 C#에서 포인터라는 것은 언어적으로는 전혀 메커니즘으로 작동하지만 런타임 입장에서는 유사한 메커니즘으로 동작한다는 것을 알 수 있다. 하지만 Java에서 함수포인터를 흉내를 낼 수 있는 방법은 있다. 키/쌍의 컬렉션을 이용하여 참조를 전달하는 방법이다. 아래는 간단한 예제 코드이다. 



어찌되었든 결국, Java의 Lambda는 Interface를 이용하여 Lambda 함수를 만듦으로써 Interface의 함수가 단 1개만 있어야 Lambda 표현을 할 수 있는 제약이 생겼다. Interface를 이용하여 Lambda를 표현한다고 함은 내부적으로 Proxy 객체를 생성하여 그 안에 Lambda 표현을 메서드로 만든다.

아래의 익명 클래스를 보자. 아래의 runnable 로컬 변수를 리플랙션을 이용하여 getMethods() 의 목록이다. 



아래의 Lambda 표현을 보자. 마찬가지로 runnable 로컬 변수를 리플랙션을 이용하여 getMethods() 의 목록이다. 


이를 통해 Java의 Lambda 표현식은 내부적으로 Proxy 클래스가 생성됨이 확인되었다. 그런데 이 Proxy 클래스가 언제 생성이 될까? 컴파일 타임에 생성이 될까, 아니면 런타임에 생성이 될까?

이를 JD-GUI 도구를 이용하여 Decompile 결과를 확인하려고 하였으나, Java 1.8.0 버전에 대해서 JD-GUI 가 올바르게 인식을 하지 못해 전혀 class 파일을 전혀 읽을 수 없다. 대신 .class 파일을 Text Editor 로 열어서 대략적인 내용을 확인할 수 있는데, Text Editor 에서는 Lambda 표현으로 구현된 Proxy Class 를 찾을 수 없었다. 따라서 Lambda 표현은 런타임에 구현 객체 Proxy 가 생성된다는 것을 알 수 있다. (다만, 확신은 못하겠다.)

한가지, Java 8의 Lambda 표현의 다른 점이라면 Lambda 표현의 Proxy 객체는 java.lang.invoke.MagicLambdaImpl 클래스를 상속한다는 점이다. 앞서 얘기했듯이 JD-GUI 도구가 Java JRE 1.8.0 의 rt.jar 파일을 상위 호환성이 아직 지원되지 않아 구현 내용을 알 수는 없었다. 이는 좀 더 Java 8의 Release 시기가 다가오기를 기다려야 할 것 같다. 



결론은 Java 8의 Lambda 표현을 하기 위해서는 Interface 의 구현 함수는 반드시 1개여야 한다는 점이다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 짜두 2013.01.17 08:59 Address Modify/Delete Reply

    이제 베타버전이니 좀더 발전해야 겠네요. 굉장히 빠른 속도로 발전해나가지 않을까하는 생각이 드네요.ㅎ

개요

간단하게 작성한 C++ 코드가 컴파일이 되지 않는다. auto 키워드와 lambda 식을 제대로 해석을 하지 못하는 모양이다.

인터넷을 통해 쉽게 문제를 해결할 수 있었다. 아래의 원문의 링크를 참고하면 된다. 필자는 아래의 링크를 참고하여 스샷좀 뜨고, 예제 샘플 정도만 만들었으니 설정에 어려움이 없다면 아래의 참고 링크만으로 충분할 것이다.

필자가 받은 GCC 4.7.2 버전의 Release 변경 사항을 보면 도움이 될 것이다.

그리고 몇 가지 std 함수 중 to_string 함수에 버그가 있는데, 아직도 Pending 상태라 되도록 사용하지 말고(사용자체가 안된다 ^^;), stringstream 등을 사용하도록 권장한다. SourceForge에서 GCC 버그 항목을 찾아보면 2011년도에 버그가 등록되었지만, 우선순위가 낮아 당분간 고칠 생각이 없는것 같다. (SourceForge GCC to_string 버그 링크)

MinGW-GCC 에서 C++11 컴파일 환경 구성

Project Explorer -> Project Properties -> C/C++ Build 탭 -> Settings 탭 -> Tool Settings 탭 -> Miscellaneous 항목 -> Other Flags 에 -std=c++0x 를 추가한다.

그리고 C/C++ General 탭으로 이동한 후 Paths and Symbols 탭 -> Symbols 탭 -> __GXX_EXPERIMENTAL_CXX0X__ 항목의 Value 값을 0 으로 설정한다.

모두 완료되었다면 Clean Project를 해서 다시 컴파일하자. 아래와 같이 auto 키워드와 lambda 구문에 더 이상 경고와 오류 문구가 뜨지 않고 컴파일도 성공한다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

Qt 개발 환경을 만들려는 참에 Eclipse에서 Visual C++로 만든 프로젝트를 MinGW GCC로 변환해야 할 필요가 생겼다. '인터넷 검색 링크를 잊어버려서… 다시 참고 원문 링크는 찾으려니 찾아지지 않아서... 패스....'

우선 프로젝트를 변환하는 방법은 크게 두 가지가 있는데, 예를 들어, 첫 번째는 전혀 다른 프로젝트를 Dynamic Web Application으로 바꾼다거나… 이런 경우에는 Project Explorer에서 -> Propject Properties -> Project Facet에서 변경하면 된다고 한다.

   

두 번째, 필자가 필요한 것은 이 방법이다. Eclipse에서 Visual C++로 만든 프로젝트를 MinGW로 변경하고자 한다.

Project Explorer -> Project Properties -> C/C++ 탭 -> Tool Chain Editor에서 변경할 수 있다.

   

이제 MinGW GCC 컴파일러를 이용하여 컴파일이 되도록 환경을 수정해야 한다. 이 방법은 아래의 링크를 참고하면 된다.


참고로 필자의 MinGW GCC 환경 변수의 경로이다.

  • INCLUDE - D:\Program Files\MinGW\include
  • LIB/LIBPATH - D:\Program Files\MinGW\lib
  • PATH - D:\Program Files\MinGW\bin

   

여기에서 주의해야 할 것은 Environment 옵션에서 'Append variables to native environment' 항목을 선택한다.

   

Posted by 땡초 POWERUMC

댓글을 달아 주세요

Eclipse 개발 도구의 장점이라면 많은 벤더가 Eclipse 개발 환경을 지원하고, 오픈 커뮤니티 포럼도 굉장히 활성화가 되어있다는 장점이 있다. 그리고 오픈 소스이며 순수 Java로만 구현이 되어있어 Eclipse를 확장하거나 개발 환경을 구성하기 매우 쉽다. Eclipse에서 여러 언어를 지원하고 다양한 무료 플러그 인을 제공한다. (일부 언어는 컴파일러를 별도로 설치해야 한다.)

그 중 인터넷 자료를 찾아보면 Eclipse에서 C++ 개발 환경을 구성할 수 있는데, GCC(GNU C Compiler)에 포함된 컴파일러를 대상으로 소개하고 있어, 이를 Visual C++ 환경을 구성하는데 몇 가지 시행착오를 겪은 부분이 있어 이를 공유하고자 한다.

물론, Microsoft에서는 Visual Studio Express 2012 for Windows Desktop Application라는 무료 개발 버전을 제공한다. 하지만, 무료 개발 버전인 Express는 여러 가지 무료 플러그 인을 설치하는데 제약이 있어, 거의 순정 그대로 사용 해야 하는 불편한 점이 있다. 무료 개발 툴이 Eclipse를 이용하여 Visual C++ 개발 환경을 구성하고, Eclipse의 다양한 플러그 인을 그대로 사용할 수 있도록 하기 위해, 개발을 위해 추가 비용 없이 무료 도구인 Eclipse 에서 Visual C++ 개발 환경을 구성하는 것이 도움이 될 것이다.

     

1. 먼저 Visual C++ 컴파일러가 포함된 SDK 를 다운로그 하면 된다.

가장 최신 버전인 Windows SDK for Windows 8 버전은 아쉽게도 모든 컴파일러가 포함되지 않는다. Microsoft가 무슨 수작을 부리려는 것인지는 몰라도, 지금껏 꾸준히 제공된 SDK 를 충실히 제공하지 않는다.

 

더 자세한 내용은 필자가 작성한 다음의 링크를 참고하기 바란다.

     

비교적 가장 최신 버전인 Windows 7 SDK 또는 Windows 8 SDK를 설치하면 된다.

     

     

2. 그 다음, Eclipse C++ 개발 버전을 다운로드 받아서 설치한다.

이미 Eclipse IDE for Java EE Developers 버전 등을 이미 설치했다면, 다운로드 받은 Eclipse IDE for C/C++ Developers을 압축을 풀어서 features 폴더와 plugins 폴더를 기존의 Java EE 버전에 복사를 하고, 중복된 파일은 Skip 하면 된다.

올바르게 설치가 되었으면 필자와 같이 C++ 프로젝트를 생성할 수 있다.

     

아래와 같이 Microsoft Visual C++ 프로젝트를 생성할 수 있는 항목이 있다. 그렇다고 프로젝트를 생성한 후 컴파일은 되지 않을 것이다. 그런 이유로 필자는 지금 이 아티클을 쓰고 있지 않은가? ^^

     

Visual C++ 프로젝트가 생성이 되면, 아래와 같이 붉은 색의 x 표시가 너덜 너덜 널려있다.

     

Ctrl+F11 을 눌러 실행시키면 다음과 같은 오류 메시지가 나온다. 정상이니 놀라지 말자.

     

     

3. 컴파일 환경을 구성하기 위해 환경 변수 정보를 수집하자.

Eclipse에서 Visual C++을 컴파일 하기 위해서 다음의 환경 변수 정보가 필요하다. 이 경로는 Visual C++ 컴파일러와 Visual Studio에서 C++ 빌드를 하기 위해 필요한 경로이다.

  • PATH
  • INCLUDE
  • LIB
  • LIBPATH

     

Visual C++ 컴파일러인 cl.exe 파일은 %ProgramFiles(x86)%\C:\Microsoft Visual Studio 11.0\VC\bin\cl.exe 에 위치한다. 그러나, 이 cl.exe는 현재 폴더의 하위에 존재하는 CPU 아키텍처 버전별 폴더에 있는 .dll 파일을 필요로 한다. 이 폴더에도 cl.exe 파일이 존재하므로 현재 자신의 컴퓨터의 CPU 아키텍처 버전의 폴더만 알면 된다.

     

자신의 컴퓨터의 CPU 프로세스 아키텍처를 모르면 Command Line에서  SET PROCESS 를 입력해 본다. 그럼 아래와 같이 확인할 수 있다.

     

이로써, 컴파일에 필요한 cl.exe 가 포함된 폴더는 %ProgramFiles(x86)\Microsoft Visual Studio 11.0\VC\bin\amd64 임을 알 수 있다. 이 폴더를 메모해 놓자.

그리고 더 필요한 폴더가 있다.

%ProgramFiles(x86)%\C:\Microsoft Visual Studio 11.0\VC\bin\vcvars32.bat 파일은 'Visual Studio 개발자용 명령 프롬프트'이다. 이 파일의 내용을 보면 컴파일이나 명령 도구를 수행하기 위해 필요한 경로를 설정하는 부분이 있는데, 이 폴더들도 필요하다.

     

위와 같이 PATH, INCLUDE, LIB, LIBPATH 가 Visual C++을 컴파일 하기 위해 필요하다. 이 Batch 파일을 보면 위의 환경 변수에 조건에 따라 계속 경로를 추가한다. 그러므로 우리는 편의상 설정된 전체 경로를 그대로 복사해서 사용할 것이다.

먼저 Visual Studio 개발자 명령 프롬프트(Command Line)을 실행 시킨 후, 아래와 같이  SET INCLUDE 와  SET LIB 명령을 실행하면 완전한 환경 변수의 경로를 구할 수 있다.   

     

이 경로를 클립보드에 복사하기 편하도록 하려면, 다음과 같이 파일에 결과를 쓰도록 하면 된다. 그럼 log.txt 파일이 없으면 파일을 생성하고, 파일이 존재하면 콘솔의 출력 내용이 log.txt 파일 끝에 추가 된다.

C:…\>SET INCLUDE >>log.txt
C:…\>SET LIB >>log.txt
C:…\>SET PATH >>log.txt

     

     

4. 이제 Visual C++ 컴파일러인 cl.exe 파일을 Eclipse에서 컴파일이 되도록 설정해보자.

일부 환경 변수의 경로는 %PATH% 환경 변수의 경로에 추가해 주면 되는데, 그렇게 되면 너무 번거로워질 것 같아서, Eclipse에서 제공하는 기능을 통해 환경 변수 정보를 추가하겠다.   

Eclipse 메뉴에서 Windows -> Preferences -> C/C++ 탭 -> Build 탭 -> Environment 로 이동하자.

     

이 곳에서 아래의 그림과 같이 PATH, INCUDE, LIB, LIBPATH 환경 변수를 입력하면 된다.

     

이제 Ctrl+F11 을 눌러 컴파일하여 실행하면 정상적으로 실행이 될 것이다.

하지만 C++ 편집기에는 붉은 색의 경고 문구들이 사라지지 않았다.

     

그렇다. 지금까지 Eclipse에서 C++ 소스 코드를 빌드하는 환경을 구성한 것이다.

     

     

5. Eclipse 에서 C++ 소스 코드 편집 환경 구성을 해야 한다.

여기까지 잘 구성을 하신 분이라면 이번 구성도 어렵지 않게 할 수 있을 것이다. 환경 변수의 경로만 몇 개 추가해 주면 된다.

Eclipse의 좌측 Project Explorer에서 C++ 프로젝트에 마우스를 갖다 놓고 오른쪽 버튼을 클릭해보자. 프로젝트 속성을 변경할 수 있는 Properties 메뉴 항목이 보일 것이다. 클릭하면 속성 창이 뜬다.

     

C/C++ General 탭 -> Paths and Symbols 탭으로 이동하여 GNU C++ 항목에서 Includes 탭을 보자. 이곳에서 Add 버튼을 클릭하여 INCLUDE 환경 변수의 값을 하나 하나씩 넣자. 반드시 경로를 ';' 로 붙여져 있는 것은 분리해서 하나 하나씩 입력해야 한다.

마찬가지로 Libraries 탭으로 이동한 후 LIB 환경 변수의 경로 값을 하나씩 입력하자.

     

6. 이제 Eclipse에서 Visual C++ 개발 환경을 구성하는 것이 모두 완료되었다.

#include 파일 목록이 보이지 않았고 Intellisense가 동작하지 않았었는데, 이제 모두 동작한다.

     

     

부록으로 아래와 같은 오류 메시지를 만날 경우 대처 방법이다.

오류 1 : 컴파일 시에 발생한 오류인데, iostream 헤더 파일을 찾을 수 없어서 발생하는 오류이다. 위의 3번과 4번의 방법으로 빌드 환경을 구성하자.

     

오류 2 : 이는 컴파일 중 발생한 오류처럼 보이지만, cl.exe 파일을 찾을 수 없거나 cl.exe 에 종속된 DLL을 찾지 못하는 경우 발생하는 오류이다. 1번의 SDK 가 올바르게 설치되었는지 확인하자. 설치가 올바르게 되었다면 3번과 4번을 다시 따라해보자.

    

Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 2014.02.17 11:33 Address Modify/Delete Reply

    비밀댓글입니다

    • 땡초 POWERUMC 2014.02.20 23:01 신고 Address Modify/Delete

      이클립트는 workspace마다 설정을 복제할 수 있는 것으로 알고 있거든요.
      숨김 폴더의 .eclipse 인가(가물가물), 이 폴더를 복사하시면 됩니다.

  2. 경준씨 2014.03.06 21:50 Address Modify/Delete Reply

    http://msdn.microsoft.com/ko-KR/windows/hardware/gg454513
    Windows 8.1 SDK가 업데이트 되었습니다.
    수정 바랍니다.

    • 땡초 POWERUMC 2014.03.06 23:03 신고 Address Modify/Delete

      아래의 링크를 보시면 이렇게 써있네요. 잘못 알고 계신 것 같은데요?

      명령줄 빌드 환경
      Windows SDK는 더 이상 전체 명령줄 빌드 환경을 제공하지 않습니다. 대신 Windows SDK를 사용하려면 컴파일러와 빌드 환경을 별도로 설치해야 합니다.

      http://msdn.microsoft.com/ko-kr/windows/desktop/bg162891.aspx

  3. 경준씨 2014.03.06 23:39 Address Modify/Delete Reply

    맨 아래쪽에 SDK가 있습니다.

    • 땡초 POWERUMC 2014.03.07 18:22 신고 Address Modify/Delete

      SDK가 있는 건 알고 있어요.
      명령줄 빌드를 제공하지 않는다는 얘기가 써 있어요.

      제가 찾는 건 SDK가 아니랍니다.

  4. dkkim 2014.09.30 17:51 Address Modify/Delete Reply

    안녕하세요. 올리신 글 많은 도움이 됐습니다.
    한가지 질문이 있는데... 기존에 visual studio에서 개발한 c++ 소스를 말씀하신 Eclipse 환경에서 빌드하고 실행까지 되는게 확인 되는데요. 디버깅이 잘 안되는데 따로 설정할 것이 있을까요?