반응형

제가 하는 업무가 Platform 이나 Framework을 개발하는 것이기 때문인지 , 어떤 기능이 있었으면 좋겠다는 아이디어가 떠오르거나 막히는 문제가 발생하면, 음, "Windows OS에서는 어떻게 처리했지?" 라는 질문을 던지고 답을 찾아보게 됩니다.

최근에 Open Platform으로 갖춰야 할것 중 하나가 개발 툴인데,
IDE같은 개발 툴들은 대부분 Import, Plug-in, User Defined Control(ActiveX control) 등과 같이, 툴이 만들어지고 나서 새로운 class 나 객체들을 자연스럽게 설치, 추가하여 사용할 수 있도록 해주는 부분들이필요 하게 됩니다.

이런  framework  구현부는 실제 class의 실체를 모른 상태에서 runtime시에 binding 되는 class의 객체를 생성하는 기술이 필요하게 됩니다.

 

class의 인스턴스 생성방법은 다들 알다시피 new를 통해 생성합니다.

BaseClass * pInstance = new SubClass;  

 

바꾸어 말하면 우리는 반드시 SubClass를 알고 있어야 한다는 것입니다. 그래야만 instance를 생성 할 수 있습니다.

아래와 같이 CameraPlugIn class를 framework에서 모르는 상태라고 한다면 절대 instance를 만들 수 없다는 예기죠.

IPlugIn * pPlugIn = new CameraPlugIn;  

만약 우리가  CameraPlugIn를 모르는 상태 즉,  PlugIn Framework 개발당시에는 CameraPlugIn이라는 class를 모르는 상태에서  camera.dll또는 lib을 download받아서, 이를 link 또는 load 해서 사용할 수 있는 기능을 제공하려 할때,
class를 모르는 상태에서 class 를 runtime시에 동적으로 이름이 무엇인지, 알아내서 instance를 생성할 수 있도록 하는 기술이 필요하게됩니다.

이러한 기술을 리플렉션( Reflection) 이라한다.

 

C# 에서도 이와같은 리플렉션은 지원하고 있고, Runtime중에 type binding을 지원하는 Dynaimc 이라는 새로운 타입이 추가될 예정이라고 합니다.

 

그외  루비, 스몰토크, PHP, 파이썬, 펄 등의 언어에서는 리플렉션 객체를 제공하며, 대부분 유사한 형태를 띄고 있습니다.

 


 

아쉽게도 C++ 에서는 공식적으로 지원하지 않는 기능이지만, 전세계의 수많은 똑똑한 개발자 분들이 이런 기능을 다양한 방법으로 제공하고 있습니다.

 

MFC의 CRuntimeClass는 리플랙션과 비슷한 기능을 위해 만들어진 가벼운 Macro 정도이고 실제 훨씬 파워풀하고 플랙서블한 그런 library들도 많이 있으니 관심이 있다면 구글링으로 찾아보셔도 좋을 것입니다.

 


 

그중 하나의 기술 (?, 그렇죠 말그대로 Programming 테크닉이라 불리울 만한 기술이죠. )MFC에서 Runtime class 라는 것이 있습니다.

 


저도 나름 이와 비슷한 아이디어를 떠올리면서 구현을 했었는데 , 거의 구현이 완료되고 나서 생각해보니 MFC에 이런게 있었다는 것이 생각이나, Source code를 뒤져봤답니다.

역시나 이미 상용화되어서 아주 ~~ 아주 잘 쓰고 있는 메크로들이라 그런지, 제가 생각했던 개념과 유사한 뼈대위에 많은 살들이 붙어있더군요.

회사에서 사용하기 위해 작성한 코드가 좋은 예가 될수 있을텐데, 회사의 자산이라 공개를 할 수 없어서  MFC의 Runtime class의 내용을 살펴보는 것으로 대신 하겠습니다.

저는 사실 한 일주일 전쯤 해서  이런 고민에 빠져 있었습니다.

만약 내가 개발 툴을 만들어주고, application 개발자는 자기가 짠 class를 이 개발툴에 plug-in 한 후에 ,
개발 툴에서 재공하는 Generator 를 이용해  Generator("TestClass") 라고 하면 testclass의 instance를 만들어준다고 합시다.
이때 개발툴을 개발할 당시에는 TestClass를 모르고 있기 때문에 일반적인 개발 방법으론, 이를 구현할 수가 없습니다.
즉, Runtime 중에 class를 register하고 이를 parsing하여 instance를 생성 시킬 수 있는 기술이 필요하다.!!

class라는 게 어찌 보면 type인데, 내가 type을 모른 상태에서 object를 생성할 수 있을까?
예를 들면
Object* pA = CreateInstance("TestClass");
라고하면 pA는 class TestClass 로 생성이되는 이런걸 만들고 싶었습니다.
하지만 흔히 이렇게 사용할 수 있으려면, 기본적으로 TestClass를 CreateInstance 내에서 알고 있어야 합니다. 
그래야 "TestClass" 라는 스트링을 parsing한 다음에 TestClass로 생성을 해줄 수가 있기 때문입니다.
그래서 대부분 아래 처럼 머리속에서 코딩을 하겠죠?

 

Object* CreateInstance(char* pClassName) {        
	:
    if(strcmp(pClassName,"TestClass")==0)
    	return (Object*)new TestClass; 
}

CreateInstance를 제너럴하게 만들고 싶어서 pClassName 으로 들어오는 class이름이 무엇이든간에 이에 맞는 class를 찾아서 있으면 해당 class의 instance를 생성해주고, 없으면 null 을 return하기를 원한다면? 어떻게 만들까? 음... class들을 관리하는 table과 class name,그리고 class generator을 만들어서 구현 할 수 있을것 입니다.

Object* CreateInstance(char* pClassName) {
	:        
    for(;;){
    	if(strcmp(pClassName,gClassTable[i].szName)==0)
        	return (Object*) gClassTable[i].Construct(); 
    }
 }

그런데, CreateInstance라는 함수를 만들때는 "TestClass라는 것이 없는데 , 앞으로 누군가가 만들것이다." 라고 한다면? 흠 흠.. 이경우는 난감한가요? 분명 이렇게 되어야 합니다. gClassTable 에 RegisterClass라는 API를 만들어서 이름과 생성자를 등록하여 사용할 수 있도록 해야합니다.

void RegisterClass(char* classname, Object *(*fpConstruct)()) {
	:     
    gClassTable[i].szName = classname;
    gClassTable[i].Construct= fpConstruct; 
 }

헌데 이렇게 되면 상당히 귀찮아지는 것이 fpConstruct를 만드는 일이 됩니다. 개발자의 손이 덜가고 오류를 줄이려면 형식을 제한해야 하는데요.

이를 구현하기 위해서 MFC에서는 runtime class라는 개념이 만들어지게 됩니다. MFC의 CRuntimeClass를 살펴보면, 클레스 전체를 살펴보면 좋겠지만.. 대충 필요한 것들.. 핵심이 될만한 것들만 언급하자면,

struct CRuntimeClass {
// Attributes 
    1) LPCSTR m_lpszClassName; 
    2) CRuntimeClass* m_pBaseClass; 
    3) CObject* CreateObject(); 
    4) static CObject* PASCAL CreateObject(LPCWSTR lpszClassName); 
    5) CRuntimeClass* m_pNextClass;       // linked list of registered classes 
}

아마 가장 핵심이 되는 것이 위의 1)~4) 까지의 내용일 겁니다. 왜냐? 다음 RuntimeClass를 만드는 메크로들을 보면 알 수 있습니다.

#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name)) #define DECLARE_DYNAMIC(class_name) \
public: \
    static const CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \
    #define _DECLARE_DYNAMIC(class_name) \
public: \
    static CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \
    #define IMPLEMENT_RUNTIMECLASS(class_name) \
    const CRuntimeClass class_name::class##class_name = { \ 
    	#class_name, sizeof(class class_name), \
	}; \
CRuntimeClass* class_name::GetRuntimeClass() const \
	{ return RUNTIME_CLASS(class_name); } \

이런 메크로들이 있습니다. 약간 길죠?. IMPLEMENT_RUNTIMECLASS 는 설명하기 귀찮은 것들은 다 뺐습니다. 이렇게 메크로만 보면 "뭐하자는 거지?" 라고 의문이 드는 것들이 실제 사용하는 예를 보면 "아하!!" 라고 하게 됩니다.

//in MyTestControl.hpp
//---------------  
class MyTestControl {
  _DECLARE_DYNAMIC(MyTestControl)   //<-- static const Runtimeclass  classMyTestControl 가
                                    // MyTestControl class안에 정의된다.     
        :    
        :
};

 

//in MyTestControl.cpp
//---------------  
//파일 맨 위에 
IMPLEMENT_RUNTIMECLASS(class_name) 
// static 객체인 MyTestControl::classMyTestControl 를 초기화 한다.

//in MyTestApp.cpp  
void main(void) {    
  AfxInitClass(RUNTIME_CLASS(MyTestControl));
  CRuntimeClass::CreateObject("MyTestControl");
}

대충의 설명을 하자면 ,
1. Runtime class로 선언하고자 하는 class 내부에

DECLARE_DYNAMIC(classname)

을 해주고나면, static runtime class 하나가 생기는데

"class"+classname

의 형태로 된다.
2. 이 "classMyTestControl"라고 새로 만들어진 static runtimeclass 는 멤버함수인

3).의 CreateObject()

를 MyTestControl를 생성할 수 있는 함수로 만든다.
3. 그 형식은 아래와 같다.

CreateObject()  {   return new MyTestControl; } 

따라서 우리는 메크로에 의해 생성된 classMyTestControl 이라는 runtime class를 이용해서 MyTestControl이라는 class의 instance를 생성할 수 있게 됩니다.

 

 마지막으로 한가지 설명 안하고 넘어간 것이 있는데.

5) CRuntimeClass* m_pNextClass;       // linked list of registered classes 

이 녀석에 대한 내용이다. 이게 왜 있는가? 제가 개발할때, 위와 같은 구현을 완료하고 나서 Register 단계( MFC에서는 AfxInitClass 단계일듯하다.)에서 결국 table을 만들고, table과 Runtime class를 묶는 작업을 하고, search하는 함수를 만들고... 등등을 작업했었다.

CSystemClassFactroy {
    list runtimeClass;  
    RegisterClass(CRuntimeClass);
};  
application에서 사용할때는 아래와 같이 register를 해야 되는 상황이 있었습니다. 

main() {    
  // application 초기화    
  Factroy.RegisterClass(MyTestControl);  
  :
  :
}

이때 문득 MFC에서는 어떻게 하지? 라는 생각이 들어 이 RuntimeClass를 살펴본바... m_pNextClass 를 이용해서 각 register시에 RuntimeClass들을 줄줄이 사탕으로 엮어놓은 것이라 추측 할수 있었다.!!! 아하.!! 요렇게 하면 runtime class를 managing 하는 manager class가 필요 없겠구나.!! 하는 필(feel)을 받고, 제 코드도 요렇게 바꿨답니다. ㅎㅎ 

 
------------------------------------------------------------------------------------------------------------------------
 

 

잘 동작 않는다는 의견이 있어서 한번 짤막한 코드를 작성해봤습니다.
[셈플 코드]
rt_class.h
#ifndef __RT_CLASS_H__
#define __RT_CLASS_H__

#include <string>
#include <functional>
class Object
{
public:
 Object() = default;
 virtual ~Object() = default;
 virtual void test(){};
};


class RtClass {
public:
 explicit RtClass(std::string name, std::function<Object*(void)> func);
 virtual ~RtClass();
 std::string className;
 std::function<Object*(void)> createObject;
 RtClass* nextClass{nullptr};

 static Object* CreateObject(std::string className);
};


#define RT_CLASS(class_name) ((RtClass*)(&class_name::class##class_name)) 

#define DECL_RT_CLASS(class_name) \
 public: \
 static const RtClass class##class_name; \
 virtual RtClass* GetRuntimeClass() const;

#define IMPL_RT_CLASS(class_name) \
 const RtClass class_name::class##class_name(#class_name, [](void)->Object*{return (Object*)new(std::nothrow) class_name; }); \
 RtClass* class_name::GetRuntimeClass() const \
 { return RT_CLASS(class_name); }


#endif //__RT_CLASS_H__
 
 
 
 
rt_class.cpp

  

 
#include "rt_class.h"
 
static RtClass* pHead = nullptr;
 
Object* 
RtClass::CreateObject(std::string className)
{
  for (auto it = pHead; it != nullptr; it = it->nextClass){
    if (it->className.compare(className) == 0){
     return it->createObject();
    }
  }
 return nullptr;
}
 
RtClass::RtClass(std::string name, std::function<Object*(void)> func)
: className(name)
, createObject(func) {

  if (pHead == nullptr){
    pHead = this;
  }
  else{
    this->nextClass = pHead;
    pHead = this;
  }
}
 
RtClass::~RtClass()
{
 if (this == pHead){
   pHead = pHead->nextClass;
 }
 else{
   for (auto prev = pHead, it = pHead->nextClass; it != nullptr; it = it->nextClass){
     if (it == this){
       prev->nextClass = this->nextClass;
       return;
     }
     prev = it;
   }
 }
}

 

 
 
rt_sample.cpp
 
#include <stdio.h>
#include "rt_class.h"
 
 
class A: public Object{
 DECL_RT_CLASS(A);
 
 virtual void test(){
 printf("I'm A\n");
 };
};
 
 
 
class B: public Object{
 DECL_RT_CLASS(B);
 
 virtual void test(){
 printf("I'm B\n");
 };
};
 
 
IMPL_RT_CLASS(A)
IMPL_RT_CLASS(B)
 
 
 
 
test_main.cpp

 

#include "rt_class.h"


int main(){
 Object* pA = RtClass::CreateObject("A");

 pA->test();


 Object* pB = RtClass::CreateObject("B");
 pB->test();
 return 0;
}

 

 

구조를 잠깐 설명하자면,
1. rt_class 는 platform에 해당 합니다.
2. rt_sample.cpp 는 개발자 본인이나 다른 개발자가 개발하여 platform에 등록(추가) 되는 모듈입니다. ( 이부분이 update가 되는 부분)
  ( lib이나 dll 등으로 가져와서 사용한다고 생각하면 됩니다.)
3. test_main.cpp 는 어플리케이션입니다. 
 
이런 의도로 작성되었고, 실제로 rt_class.so, rt_sample.so, test_main 으로 나눠서 작업해봐도 동작할것입니다.
 
해당 application은 A와 B가 어딘가에서 제공된다는것을 알고 있지만 class는 모릅니다.
"A" 와 "B" 라는 이름(string)으로 클래스를 생성 하여 테스트 하는 코드입니다.
 
test_main은 실제 A와 B를 모르기 때문에, Object라는 상위 class가 제공하는 기능밖에 사용 할 수 없습니다.
test()라는 함수죠.!
그래서 이 방식으로 리플렉션(refection)을 완전히 대처하기는 힘듭니다.
 
Object에 공통된 interface들을 만들고 사용하는데에는 용이합니다. ( 큰 오버해드 없이 간략하게 사용할 수 있는 구조입니다.)
 
 
결과는 : 
I'm A
I'm B
 
 
찾아보기 키워드!!! 

[SEAL Reflex] 
[LibReflection] 
[XCppRefl 라이브러리]

 
<참고 링크>
[MSDN : C++ 리플렉션 in Visual C++ ]http://msdn.microsoft.com/ko-kr/library/y0114hz2(VS.90).aspx



 

 

 

 

반응형

소프트웨어 개발 업을 하다보니,  주변에서 좀 경험이 있든 없든 간에 많은 개발자분들로부터 "S/W는 재사용하기 힘들다" 이 말을 정말 많이 듣는 것 같습니다.

왜 S/W는 재사용이 어려울까? 이런 저런 생각들을 하다보니 S/W 공학과 재사용이라는 용어와 그 내용에 대해서 다시한번 고민해보게 되었습니다.
흔히들 공학이라고 불리는 하드웨어적인 부분들은, 아날로그적인 부분들을 수학적 접근에 의해 원리와 개념을 설명하는 것입니다.

또 내구도등 기타 여러가지 요인에 의해  매번 측정마다 약간씩의 오차가 발생하게 됩니다.그래서 이런 오차를 줄이고, 정확한 측정을 위한 알고리즘을 찾고 적용하고 보정하는 것에 대한 것입니다. 

그런데, 이런 관점에서 생각해보면 S/W 는 사실  공학적 접근이 필요하다고 말에 어폐가 있어보입니다.

S/W는 1+1=2 가 오차 없이 항상 나오고 CD를 백만번 카피해도 CD의 물리적인 오류가 없는한은 똑같은 DATA를 백만번 카피 할수 있습니다.
전자공학이나 생명공학 기계공학에서는 있을 수 없는 일입니다.

S/W는 기대한 만큼 완벽하게 결과를 내 놓기 때문에 측정이나 측정기의 검증등이 필요 없을 수 밖에 없습니다.

즉, S/W는 Code로 이뤄져있고 Digital Data로 만들어지며 , 오차가 존재하지 않기 때문입니다.


이러한 특성을 보면, 공학 보다는 수학에 가깝게 느껴집니다.

불변의 진리, 논리적 완벽성, 무한, 항상성(같은 결과) 등등 의 특성들을 보면 말이죠.

그리고 실제로 수학적 증명에서 부터 시작한것이구요.

 

이렇게 따져 보면 어떤 다른 분야보다도 S/W는 제 사용성이 높은 것이 맞습니다.

카피 해서 사용하면 되는 것이니까요.

 

그러면 왜 ? 유독 코드의 재사용성에 대한 얘기가 많이 나올까요?

좀 큰 S/W를 개발하다보면 항상 나오는 이야기인데 "S/W 재사용" 얘기를 빼 놓을 수 없습니다.

종종 H/W 부품들과 빗대어 표현할때도 있는데요.

"H/W는 블럭을 잘 디자인하고 표준화 해서 잘 쓰고 있는데 왜? S/W는 그렇게 못하냐"

"부품 처럼 갈아 끼울수 있으면 좋을텐데" 이런식의 표현이죠.

참 이상하죠.. Code는 수학과 비슷해서 한번 완성이 되고 나면 항상 같은 결과를 내기 때문에 불량도 없고, 고칠 필요도 없는데 말이죠.

과연 무엇과 비교해서 재 사용성이 낮은 것일까요?


으흐흐.. 대부분 사람들이 이렇게 얘기하죠..
"하면 좋죠... 근데 S/W는 그렇게 하기 어렵습니다." 요렇게 대답하죠... 

 


왜 S/W는 어려울까요?

H/W와 S/W의 가장 큰 차이부터 얘기를 해야 합니다.
H/W는 물질적인 것이고 S/W는 논리적인 것입니다.

물질 적인것은 분명 크기나 무게 등 물리적인 한계가 있습니다.  또 하나를 만들어 내기 위해서는 자원이 필요합니다.
또 자원을 조립하는 기계가 필요합니다. 또 대량 생산을 위해서는 공장이 필요합니다.
이렇기 때문에 이 비용을 최소화 하기 위해 표준화하는 것입니다. 만약 표준화가 안되면 대량 생산이 안되겠죠...
이렇게 표준화된 부품들 때문에 제조회사들에서 덕을 좀 보긴 합니다.
핸드폰에서 사용하던 LCD칩을 가지고 D-TV를 만들때도 사용 하기도 하죠. 

(그렇지만 조금만 생각해보면, 표준화된 칩을 사용하더라도 검증과 테스트에 많은 비용이 들어가죠, 오차, 오류 등등)
근데 이걸 보고 S/W도 이렇게 하라는 것인데요.

이런 관점에서는 소프트웨어는 표준화가 필요 없다는 것입니다.
소프트웨어는 이미 완성되는 순간 대량생산이 가능합니다. 어떻게? copy 명령어로 파일들 주루룩 카피하면 땡인것이죠..
근데 무슨 표준화가 필요하고 규격화할 필요가 있냔 것이죠.. 넌센스죠.

 

그런데 왜 업계에서 소프트웨어 재사용성이 어렵다고 하는지 이해하기 위해서는 먼저 소프트웨어를 어떻게 재사용하기를 기대하는지 먼저 정의할 필요가 있어보입니다.

- 구구단 어플리케이션의 계산 모듈을 스프레드쉬트 개발할때 사용하고 싶다.

- 구구단 어플리케이션을 다른 OS에서 사용하고 싶다.

- 구구단 어플리케이션의 GUI를 다른 어플리케이션에서 사용하고 싶다.

 

S/W의 재사용성에 대한 정의도 명확히 할  필요도 있습니다.

 

S/W의 재 사용성은 H/W와는 관점이 완전 다른것입니다.

한번 설계된 Code를 수정없이 다른 S/W(application이나 platform 등등) 에서 사용할수 있는가에 대한 것입니다.

소스 코드나 binary data는 다른 library를 사용하거나 제컴파일해서 사용하는 등 , 이식 작업때 환경의 변화가 발생하기 마련입니다.
Windows에서 사용하던 code를 Linux에서 사용한다거나, CPU가 변경된다거나, H/W가 변경되는 상황들이 발생하는 것이죠. 


또, 하나의 어플리케이션 내에서 여러 모듈들이 공동으로 사용하는 것을 공통된(Common) 한 모듈로 개발하는 것이 이 S/W의 표준화 목적이 될 수 있습니다.

 

하지만, 여기에 가장 큰 문제는 S/W는 너무 유연한 나머지 항상 변화하고 발전합니다. 

아무리 잘 만들어진 모듈이라 할지라도 몇년 지나면 기술에 뒤쳐지고 기능에서 뒤쳐지게 됩니다. 

가만히 있으면 아무도 사용하지 않는 모듈이 되는거죠. 그래서 늘 발전시키고 현재 필요한 기능들을 추가하고 보강하고 또는 삭제 하죠.

 

이렇게 변화하는 소프트웨어를 재사용성이라는 틀로 고정시키기에는 무리가 있을 것입니다.

 

어려운일이죠. 틀에 고정시키면 경쟁력을 잃고 자유롭게 하면 재사용이 어렵고 말이죠.

하지만 저는 Code의 제사용이라는 부분에 대해서는 아직도 많은 시도를 해볼 여지가 있다고 생각합니다.

 

제가 생각하는 미래의 소프트웨어 개발

(2024년 추가)

 

코드 재사용

코드의 재사용은 이제 더이상 문제가 안될것입니다. 인터페이스와 목적만 잘 기술 하면 AI가 코드를 작성해주니까요.

코딩

더이상 개발자가 텍스트 에디터를 열어서 코딩하지 않을것입니다.

소프트웨어 구성 모듈들을 그리고 나면 코드가 생성될 것입니다.

전체 아키텍쳐를 그릴 수 있는 사람이 중요해 지겠죠.

이 아키텍쳐를 그리는 것 조차 AI와 대화하면서 이뤄질 것입니다.

 

바로 앞까지 왔습니다.

 

#해피코딩

반응형
반응형

소프트웨어 개발을 비유하는 가장 흔한 메타포는 건물의 건축이다.
버트란트 마이어(Bertrand Meyer)는 'Software Construction'이라는 표현을 사용했다.

하지만 건축이라는 것을 생각을 이끄는 메타포로 사용한다면 다음 세가지를 전제하게 된다.

1. 설계자(Architect)가 설계도를 그린다.
2. 건축업자는 땅의 기반을 다지고, 상부구조를 세우고, 전선과 배관을 잇고,최종 마루리를 한다.
3. 입주자가 건물 안에 이사와서 행복하게 산다. 문제가 생기면 건물 관리소에 연락해서 고친다.

하지만 Software는 이런식으로 돌아가지 않는다. 소프트웨어는 건축보다 오히려 정원일 에 가깝다.
정형화 되어있기 보다는 유기적인 존재이다.

1. 초기 계획과 조건에 따라서 여러가지 식물을 심는다.
2. 몇몇은 자라고 몇몇은 퇴비가 될 운명이다.
3. 빛과 그림자, 바람과 비의 상호작용을 더 잘 이용하기 위해 식물들의 상대적인 위치를 옮기기도 한다.
4. 너무 많이 자란 식물은 포기를 나누거나 가지치기를 한다.
5. 식물의 색이 서로 충돌하면 더 아름답게 보이도록 위치를 바꾸기도 한다.
6. 잡초를 뽑고, 특별히 더 돌볼 필요가 있는 식물에게는 비료를 주기도 한다.
7. 정원의 건강 상태를 지속적으로 관찰하며, 필요하면 토양, 식물, 정원 배치를 조정하기도 한다.

정원일의 경우에는 초기계획 이후에 전체적인 흐름은 있지만, 보다시피 상황상황에 맞춰 대처해 나가는 프로세스가 많다.

사업가들은 건물 건축이라는 메타포를 더 편안하게 여긴다.
정원일보다는 더 과학적이며, 반복가능하고, 관리를 위한 엄격한 보고 위계질서도 있고...

하지만 실제 소프트웨어 개발의 현실은 건축과는 사뭇 다르다.
어떤 루틴이 너무 커지면 이를 둘로 나눠야 한다. 계획한 대로 잘 되지 않는 것들은 잡초 제거를 하거나 가지치기를 해 주어야 한다.

코드를 다시 작성하기, 다시 작업하기, 다시 설계하기를 반복 수행하는 것이 S/W의 리펙토링 이다.

- 실용주의 프로그래머 ( The Programatic Programmer)

'개발 Note > it 이야기' 카테고리의 다른 글

소프트웨어, 공학일까? - 재사용성  (1) 2009.02.24
Android - developer  (0) 2009.02.23
Design pattern -강의 메모  (0) 2009.01.20
Modal의 구현 형태  (0) 2009.01.12
UI Modal 컨셉  (0) 2009.01.06
반응형

 

디자인 패턴(Design Pattern)

디자인 패턴 S/W 개발 & 설계 OOP 등에서 빠지지 않는 주제죠...
최근 강좌에서 들었던 내용을 메모했던 내용들을 적었더니, 뭔가  두서가 없어져서 습득하기는 힘들듯 해보입니다.

그래도 정리 내용은 남겨놨습니다.

설계 패턴은 자주 발생하는 설계상의 문제를 해결하기 위한 반복적인 해법[Smalltalk Companion]

"설계패턴 반복되는 구조를 설계할 때 설계를 재 활용하는데 초점을 두는데 비하여 프레임워크는 세부 설계와 구현에 초점을 두고 있다."[Coplien & Schmidt]

 


MVC 패턴

가장 대표적인 패턴으로 소프트웨어의 유저 인터페이스를 Model-View-Control 구조로 설계하는 것을 말합니다.

(1979 년 )
시작은 Xerox PARC (그 유명한 제록스의 팔로알토 연구소)에서 Smalltalk-80 기반으로 동작하는 모델-뷰-컨트롤러 를 소개하면서 부터입니다.

 

Model(data): 화면에 출력될 자료 관리
View: 화면 출력 담당
Controller: 사용자와 view간의 상호작용을 담당


Smalltalk : 가장 객체지향적인 언어.

- 다뤄보지 않아서 어떤 언어인지는 감이 없습니다.

 



GoF Design Pattern

Software계의 거장들인 Gang Of Four (GoF)가 여러가지 다양한 소프트웨어 패턴들중에서 23개를 3가지 유형으로 분류하여 목록화 하였습니다.

-  Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,

 이외에 Grady Booch 도 엄청 유명한 사람.


Creational Patterns

- Abstract factory , Builder, Dependency Injection, Factory method, Lazy initialization, Multiton, Object pool, Prototype, RAII(Resource acquisition is initialization), Singleton
Structural Patterns

- Adapter, Wrapper, Translator, Birdge, Composite, Decorator, Delegation, Extension object, Facade, Flyweight, Front Controller, Marker, Module, Proxy, Twin
Behavioral Patterns
- Blackboard, Chain of responsibility, Command, Fluent interface, Interpreter, Iterator, Mediator, Memento, Null object, Observer Pub/Sub, Servant, Specification, State, Strategy, Template method, Visitor

 

 

Concurrency patterns[POSA2 - Pattern-Oriented Softeware Architecture]

- Active Object, Balking, Binding properties, Compute kernel, Double-checked locking, Event-based asynchronous, Guarded suspension, Join, Lock, Messaging design patterm(MDP), Monitor object, Reactor, Read-write lock, Scheduler, Service handler pattern, Thread pool, Thread-specific storage, Safe Concurrentcy with Exclusive Ownership, CPU atomic operation

[객체지향 설계 철학]

역할별 분리 - 객체화, 모듈화
표준화 - 인터페이스 표준화
계층화 - 계층화 아키텍쳐

 

 

대표적인 패턴 몇가지에 대해서...

 


[Singleton]

클래스에서 객체가 1개만 만들어지도록 강제하는 하는 패턴이다.
싱글톤을 사용하는 방법과 클래스 전체를 클래스 메소드로 만들어 사용하는 방법과 의 차이
 - 객체를 생성 소멸의 lifecycle 을 통제 할수 있다.
 -1 개로 제한 할때 뿐만 아니라 개수를 제한 할 수도 있다.
      ftp server의 connection 개수 제한 등...
   때문에 클래스 메소드 보다 융통성이 있다.


[Factory Method]

 - 객체를 생성해야할 시기, 방법, 절차가 공통
 - 생성되는 객체의 class가 그때 그때 다를때...  그때그때 달라요...

 abstract class를 만든다.

추상화 클래스의 구현 클래스들을 만든다.

Factory class 를 만들고 인스턴스 생성 함수(factory method)를 만든다.

Factory()->create(클레스 구분자) 형태로 사용한다.

 

 

[Prototype 패턴]

- 객체 복사를 위한 패턴.


[Iterater]

- 반복 순환하는 구조를 컨테이너와 반복 알고리즘을 분리


[Mediator]
여러 객체들이 단위 작업을 수행하기 위해 서로 얽혀 있을때, 중계자를 두어 복잡한 작업을 중계자에게 주고 나머지 부분은 중계자에게 notify 준다.
Mediator
역할을 하는 객체를 만들어서 복잡한 작업은 Mediator에서 수행하고, 객체들은 단위 작업만 수행하도록 ,\.


[
정리 - 주장하는바]
-
설계 패터을 적용하여 설계 구현할때 초기 투자 비용이 든다.
-
하지만 이후 설계 변경 비용은 감소한다.
-
설계변경이 많다면 결과적으로 이익이다.

 

 

#해피코딩

반응형
BOOL DoModal()
{
       :
       :
     while(!SomethingFinished()) // message loop
    {
        if(GetMessage(&msg,NULL,0,0)) // wait message
        {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
         }

         else
        {
              WM_QUIT 처리 루틴
               CancelSomething();
               PostQuitMessage(msg,wParam);  // WM_QUIT 을 다시 message loop 에 넣는 구문.
               fReturn = FALSE;
               break;
        }
     }
   return fReturn;
}
반응형

1. Modal / Transient , 의 개념

어플리케이션의  코드 실행 중간에, 사용자의 피드백(interaction)을 유도하는 대화상자를 띄워 사용자가 선택을 하고 대화상자가 닫힐 때까지, 코드 흐름이 멈춰 있도록 할 수 있는 기능을 말합니다.

이런 기능은 특정 상황에서 매우 유용하고, 편리한 코드 작성을 할 수 있도록 도와줍니다.

예를 들면아래와 같은 플로우(flow) 작성하고 싶다고 가정 합니다.

1. 프로그램 실행
2. copy button 선택
3. copy 할 것인지 묻는 message box 를 띄움  (Do you wanna start copy?)
4. 사용자가 OK를 누르면, copy를 실행함.

Platform을 개발하면서 Windows 에서 제공하는 Modal 과 같은 기능을 어떤 방식으로 지원할까에 대해서 여러차례 고민 해봤습니다.

실제로 기능 자체의 구현은 어려운 것이 아니나, 예외 상황에 대한 처리가 항상 문제가 되더군요.


가장 기본적인 예외 상황은 UI 상 modal 과 Code상으로 Modal 상태에서 parent window가 종료된다거나, modal을 실행한 주체가 종료된다면, messagebox를 띄워서 기다리고 있는 코드의 다음 라인들은 어떻게 처리되어야 하는 가 입니다.

이 외에도 여러가지 예외상황을 만들때가 많습니다.이는 마치, multi-thread programming 과 비슷한 양상을 나타냅니다.

그래서 아래 코드의 pWind->DoModal()의 앞 과 뒤에서 내부 변수나 외부 변수들 그리고 객체의 상태가 달라질 수 있다는 의미 입니다.

즉 MyTestWindow 가 삭제되었거나 그 내부변수들의 정보가 바뀌어있을 수도 있다는 의미죠.

 

void MyTestWindow::KeyPressed() {
  HFile hDBCopyLog = CreateFiile("Log.txt");
  pWind = CreateModalMessageBox("Do you wanna start copy?");
  returnValue = pWind->DoModal(); //<-- -3. Modal window가 뜨고 코드상으로 아래로 진행되지 않고 멈춰 있는 부분 
           if (returnValue == OK) {
    CopyFile(pathDest, pathSrc);
    sprintft(testBuf, "handle=%d", this->m_hWin); //< --B.this와 this->m_hWin 이 정상적이다라는 보장이 없음 
    WriteFile( hDBCopyLog, testBuf);
    CloseFile(hDBCopyLog);
    return TRUE;
  }
 

만약 위와 같은 코드가 있다고 한다면, DoModal() 내부에서 분명 event 처리를 할수 있는 event loop 이 구현 되어있을 것입니다.
이 이벤트 loop에서 pWind를 가지고 있는 application 이나 window를 종료 했다면 User 입력을 기대하고 작성했던 OK일때 처리 부분이 수행이 안되게 될 수도 있습니다.
또 더 심각하게 본다면 this 나 m_hWin 도 정상적으로 살아있을것이라는 보장도 못하게 됩니다.


2. Modal 기능 구현

DoModal 기능은 의외로 간단한데요.

아래와 같이 platform에서 제공하는 event를 처리loop 코드를 참조하여 event queue 처리 로직을 loop로 제공하면 됩니다.

 

result MessageBox::DoModal() {
  while (!bEixt) {
    ReceiveMessage(&msg);
    ProcessMsg(&msg);
  }
}

WIN32 기준으로 봤을때...
Window들을 모두 종료 시키고 어플리케이션을 빠져 나가야 하는 경우라고 가정한다면,

 

WM_QUIT 메세지를 받게 되면, WM_QUIT에 의해 modal window가 삭제 될것 입니다. 그러나 parent window는 삭제가 되지 않기 때문에, WM_QUIT을 다시 한번 event queue에 넣어야 합니다.

이렇게 하면 Modal loop을 빠져 나가고,WM_QUIT 메세지를 재 생성해서(PostQuitMessage) 바깥에 있는 main loop에서 정리할 수 있도록 해줘야 합니다.

result MessageBox::DoModal() {
  while (!bEixt) {
    ReceiveMessage(&msg);
    if (msg.type == WM_QUIT) {
      bExit = TRUE;
      PostMessageFront(&msg);
      bException = TRUE;
    }
    if (!bException)
      ProcessMsg(&msg);
  }
}


platform 을 개발하는 입장에서   Modal을 구현하여 제공 해야 한다면 위와 비슷한 방법으로 application 종료 event를 받으면 우선 이 이벤트를 부모 window부터 처리하는 것이 아니라 modal window에서 먼저 처리를 하고 , modal window를 종료 시키고, 같은 event를 다시 event queue에 넣어서 modal event loop을 빠져 나간 다음에 main loop(또는 상위 event loop) 에서 처리할 수 있도록 합니다.

modal이 한개가 아니라 중첩, 3중첩 되어 있을 가능 성도 있기 때문입니다.

 

 

1. 프로그램 실행
2. delete button 선택
3. delete 할 것인지 묻는 message box 를 띄움  (Do you wanna delete this file?)
4. 진짜로 지울 것인지 묻는 message box 를 띄움  (리얼리?)
5 파일을 지움

 

 

반응형

 

이 글에서 다뤄보고자 하는 내용은 2가지 입니다.

하나는 name mangling 이고 다른 하나는 extern "C" 입니다.

이 두가지는 c와 c++의 호환성과 관련된 부분이기도 하고 라이브러리를 사용하다보면 가끔 문제를 일으키는 부분이기도 한 부분입니다.

1. name mangling 이란?


간단히 말하면 compiler 가 임의로 함수나 변수의 이름을 변경하는 것을 의미합니다.
그렇다면 왜 함수나 변수의 이름을 변경하는 것인가?
이를 설명하기 위해서는  C++언어의 성격상 function overloading을 먼저 설명해야 하는데요.

함수 오버로딩(function overloading)이란 개발자가 소스코드를 작성할때 같은 이름으로 다른 기능들을 수행하는 함수를 만들수 있도록 하는 기능을 예기합니다.

int Add( int a,int b){return a+b;}
float Add( float a,float b){return a+b;}
float Add( float a,float b, float c){return a+b+c;}

 


위와 같이 Add라는 함수를 int형을 더하는 함수, float 형을 더하는 함수, 세개의 변수를 더하는 함수등등 여러개의 함수를 작성하고, 개발자가 이를 용도에 맞게 쓸수 있습니다.

이런것이 가능하도록 해주는 기능이 바로 함수 오버로딩이라는 개념입니다.

그럼 컴파일러(compiler)는 소스 코드에서 Add를 호출 했을때 어떻게 각각의 Add를 알아서 찾는가 하는 의문이 생길수 있을것인데,  이를 가능하게 해주는것이 name mangling 입니다.

compiler는 symbol을 생성할때 함수 이름과 함수 parameter를 고려 하여 symbol을 만듭니다.
즉,
int Add( int a,int b)  -> Add + int+ int --> Addii 와 같은 형식으로 만든다는 것입니다.
물론, compiler 마다 다른 mangling 규칙을 가지고 있습니다.

인터넷에 떠도는 문서들을 찾다보니 아주 한눈에 잘 들어오도록 정리된 표가 있어서 샤샥!! 퍼옴...


 

Compiler void h(int) void h(int, char) void h(void)
Intel C++ 8.0 for Linux _Z1hi _Z1hic _Z1hv
HP aC++ A.05.55 IA-64 _Z1hi _Z1hic _Z1hv
GNU GCC 3.x and 4.x _Z1hi _Z1hic _Z1hv
HP aC++ A.03.45 PA-RISC h__Fi h__Fic h__Fv
GNU GCC 2.9x h__Fi h__Fic h__Fv
Microsoft VC++ v6/v7 ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Digital Mars C++ ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Borland C++ v3.1 @h$qi @h$qizc @h$qv
OpenVMS C++ V6.5 (ARM mode) H__XI H__XIC H__XV
OpenVMS C++ V6.5 (ANSI mode) CXX$__7H__FI0ARG51T CXX$__7H__FIC26CDH77 CXX$__7H__FV2CB06E8
OpenVMS C++ X7.1 IA-64 CXX$_Z1HI2DSQ26A CXX$_Z1HIC2NP3LI4 CXX$_Z1HV0BCA19V
SunPro CC __1cBh6Fi_v_ __1cBh6Fic_v_ __1cBh6F_v_
Tru64 C++ V6.5 (ARM mode) h__Xi h__Xic h__Xv
Tru64 C++ V6.5 (ANSI mode) __7h__Fi __7h__Fic __7h__Fv
Watcom C++ 10.6 W?h$n(i)v W?h$n(ia)v W?h$n()v

 

위와 같이 컴파일러 마다 다른 mangling 규칙을 가지고 있답니다..

 

 

 

자! 그럼 C에 대한 이야기도 해야 하는데요, C의 경우는 mangled 된 함수 이름이 필요 없습니다.
이유는 C에서는 함수 오버로딩(function overloading)을 지원하지 않기 때문에 한 binary안에서는 하나의 함수만 존재 합니다.( 더 정확히는 하나의 symbol 만 허용됩니다.)
하지만 !!!  실제로 컴파일을 해보면 각 함수 이름을 살짝 바꿔 놓기는 합니다.
void Add(int a,int b) ---> _Add 라는 형식 으로 앞에 '_'를 붙입니다.  C++로 작성된 함수의 심벌(symbol) 과는 다릅니다.

C언어의 경우에는 함수 앞에 _를 붙이는 것이 표준인듯 합니다. 이부분에 대해서 찾아본 것이 아니라서 장담은 할 수 없지만 모든 컴파일러들이 공통일 것입니다.)


이런 변경이름(mangled name) 을 확인 해보는 방법은 컴파일된 object file(xxx.o) 나 실행 화일 또는 .lib 파일을 hex editor로 열어서 확인 해보면 눈으로 볼 수 있답니다.


2. extern "C" ?

C/C++ 호환 및 binary 호환 과 같은 주제들을 다루고 고민하다보면, abi니 eabi 니 name mangling 이니 compiler 호환이니 하는 단어들이 나오게 됩니다.

그중에 이 extern "C" 도 가끔 보이구요.  그래서 extern "C" 에 대해서 좀 더 자세히 다뤄보고 저의 경험을 토대로 예기 해보고자 이 글을 쓰게 되었습니다.

저도 언어를 처음 배울때에는 막연하게 "extern "C"를 붙이면 함수의 이름이 C 처럼 나오기 때문에 C++로 짜여진 함수도 C에서 사용할 수 있다." 라고만 알고 있었습니다.

이런 저런 개발을 하다보니 정확한 용법이나 메카니즘을 이해하지 않고 막연하게 사용하는 코드들이나 개념들은 결국 부메랑이 되어 저에게 돌아오더군요.!!
무슨 말이냐 하면,C++에서 짠 함수가 C에서도 사용할수 있게 하려면  extern "C" 를 붙이라는데 어디다? 어떻게 ? 라는 의문이 생겨날 것입니다.
근데 extern "C"에 대해 잘 모르는 사람이 위와 같은 조언을 들었으면,, 분명 extern "C"를 남발하게 됩니다.!!  이는 결국 나중에 compile error를 양산하기도 하고, 코드를 지저분하게 만들기도 하고, 아무튼 문제를 많이 일으키게 되죠.!!! 그런적 없다구요? 전 당했습니다.!! 


자 그럼 하나 하나 차근 차근 짚어 보겠습니다.


extern "C"  --> 이 형식은 extern "C" 이후에 오는 내용을 name mangling 하지 않고 C처럼 symbol을 만들 어라 라는 명령어입니다.

즉, abc.cpp 라는 소스코드에서 아래 abc라는 함수와 def라는 함수를 예로 들어보면,

[file : abc.cpp]

int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
{
return 0;
}

extern "C" int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
{
return 0;
}

def는 ii 같은 것이 추가되지 않고 그냥 def라는 이름을 갖게 됩니다.

그러면, 모든 함수 구현 루틴에 extern "C" 를 붙이는것이 사실 귀찮고 힘든 일입니다.
그래서 , 연륜이 있는 개발자들은(저처럼 ㅋㅋㅋ)  extern "C" {}를 활용하게 되는 것이죠.

[file : abc.cpp]

extern "C"
{//<-- extern 시작
int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
{
return 0;
}

int def(int a,int b)  // def 라는 symbol을 가지게 되는 것입니다.
{
return 0;
}
}//extern 끝

이렇게 extern "C" {}를 사용하면 {} 안에 있는 모든 내용은 name을 mangling하지 않는 다는 선언이 됩니다.

이렇게 사용하면 모든 함수들을 따라다니면서 extern "C" 를 붙일 필요가 없어지는 것입니다.


또, 다른 형식으로 사용하는 case가 Header file extern "C"를 사용하는 경우가 있습니다.
(대단히 많이 사용하는 형식입니다. 이유는 관리하기가 편해서? )

[file : abc.h]

#ifndef __ABC_H__
#define __ABC_H__

extern "C"
{//<-- extern 시작

#include "sysconfig.h"
#include "hello.h"
#include "test.h"

//function prototype
int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.


}//extern 끝

#endif //__ABC_H__

자!! 이제 위와 같은 포멧으로 주로 많이들 사용하는데, 위의 내용을 보면 어떤가요? 문제가 있어보이나요 없어보이나요?

얼핏 보기엔 아무런 문제가 없어 보입니다.
하지만!!!!!

한가지 찾아낸 분이 있군요.... 그렇숩니다. ^^a 위의 해더파일은 C 파일에서는 include 해서 사용할 수 없습니다.

#include "abc.h"



int main()
{
  int a = abc(1,2);
}


이유는 extern "C" 는 C++ compiler에서만 지원하기 때문입니다. 
a.c 에서 #include "abc.h"를  사용하면 error가 발생합니다. 
그래서 

#ifdef __cplusplus 를 이용해서 extern "C" 를 묶어서 사용합니다. 

 

[file : abc.h]

#ifndef __ABC_H__
#define __ABC_H__

#ifdef __cplusplus 
extern "C" {//<-- extern 시작
#endif
#include "sysconfig.h"
#include "hello.h"
#include "test.h"

//function prototype
int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.

#ifdef __cplusplus 
}//extern 끝
#endif
#endif //__ABC_H__

이렇게 사용함으로 해서 C compiler에서는 extern "C"를 사용하지 않고 compile됩니다.
C compiler에서는 c니까 당연히 name mangling이 발생하지 않겠죠.!!!! <-- 요거 중요한 예기...


자 그럼 두번째 문제점은 어디에 있을까요??
못찾겠죠... 흔히들 실수 할 수 있는 부분입니다.

( 찾았다구요?? 이 글 왜 읽고 있습니까?? 자리로 돌아가서 일하세요.. ㅎㅎ 다 아시는 분이.. ~~)

두번째 문제는 사실 편하게 쓰고자 extern "C" {} 를 사용하는데 이게 아래와 같이 사용하면 문제를 만듭니다. 무슨 말이냐 하면.. extern "C" {} 중간에 header file을 포함하고 있죠??

그래서 abc.h 가 sysconfig.h를 include 하고 있고, sysconfig.h는 def.h를 include하고 있다면,
extern "C"{ //<-- abc.h
    extern "C" { //<--sysconfig.h
           extern "C"{//<-- sysconfig.h 내에서 include 하고 있는 def.h
 
런 형식으로 가다가 결국 extern "C"가 너무 많이 중복 사용되었다는 에러메세지를 받고 당황하게 되실 것입니다. ㅎ
 
결국 VC에서는 아래와 같은 에러를 내면서 컴파일 에러를 냅니다.
fatal error C1045: compiler limit : linkage specifications nested too deeply
visual Studio 에서는 depth 제한이 10으로 되어있습니다.
 
그래서 당황하여 여기저기 extern "C"를 찾아서 지우고 옮기고 하다보면, 나중에 name mangling 되어 link error를 메세지를 받고 뒷목을 붙잡고 쓰러질 수도 있습니다.
이래서 어설프게 알고 사용하면 절대 안된다는 의미입니다.
저는 그래서 아래와 같이 header file 선언부 아래에 extern "C"를 사용하기를 권합니다.

 

[file : abc.h]

#ifndef __ABC_H__
#define __ABC_H__


#include "sysconfig.h"
#include "hello.h"
#include "test.h"

#ifdef __cplusplus 
extern "C" {//<-- extern 시작
#endif 
//function prototype
int abc(int a,int b); //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
int def(int a,int b);  // def 라는 symbol을 가지게 되는 것입니다.

#ifdef __cplusplus 
}//extern 끝
#endif
#endif //__ABC_H__

모든 header file들이 위와 같이 include file 아래서 부터 extern "C"를 사용하면, 아래와 같이 중복되지 않게 되어 depth가 늘어나지 않습니다.  <-- 요거 꽤 중요한 팁이니 자기가 맡고 있는 프로젝트가 있다면 한번 살펴보세요!! ㅎ

extern "C"
{//<-- sysconfig.h 내에서 include 하고 있는 def.h
}
extern "C" 
{ //<--sysconfig.h
}
extern "C"
{ //<-- abc.h
           :
           :
}


자!! 이제 가장 중요하고 핵심이 되는 문제가 남았습니다.

이 문제는 위에 있는 해더파일 내용으로만 봐서는 절대로 찾을 수 없는 내용인데요..

뭐냐하면..

위와 같이 header 파일을 잘 만들었다 하더라도 link error를 당할 수 있는 경우가 있다는 것입니다.!!

어떤 경우일까요? 흠흠.


어떤 경우냐 하면 ,  우리가 만든 abc 와 def 함수의 몸체는 abc.cpp 에 있죠?
function name 이 mangling 될때가 언제라고 했죠?
link 할때? 아니죠~~ compile 할때.. 즉 compiler가 abc.o를 만들때 name이 mangling됩니다.
즉, name mangling을 막을려면 abc.cpp가 compile될때 막아야 합니다.
즉 그말은 abc.cpp가 compile될때 이미 extern "C"가 함수들에 선언 되어 있어야 name mangling을 막을 수 있다는 예기 입니다.

이미 abc.h에 extern "C"로 잘 감싸 놨는데 무슨 예기냐 ~~ 모르겠다... 하시는 분들 계실 겁니다.

abc.cpp가 아래와 같이 extern "C"가 없다고 합시다. 그리고 main.c에서 아래와 같이 abc를 사용했다고 합시다.


[file : abc.cpp]

int abc(int a,int b) //abcii 라는 mangled 된 이름을 symbol로 가지게 됩니다.
{
return 0;
}

int def(int a,int b)  // defii 라는 symbol을 가지게 되는 것입니다.
{
return 0;
}

[file : main.c]

#include "abc.h"  //<-- error 발생

void main()
{
    abc(10,20);
}
아.. 하시는 분 있을거 같은데 ㅎㅎㅎ. 좀 감이 오시나요? 어쨌든 설명을 이어 가겠습니다.

main 에서 abc를 호출하면 main은 c 파일이기 때문에 mangliing 안된 abc 를 symbol로 link 합니다.

하지만 abc.cpp를 compiler가 컴파일 할때는 abcii, defii 로 mangling 된 이름으로 symbol을 만듭니다.
왜? abc.cpp를 보십시오. abc.cpp안에는 어디에도 extern "C"라고 선언된 부분이 없습니다.
그래서 C++ compiler는 함수 이름을 mangling해버립니다.
그래서 main에서 부른 abc는 symbol만있고 body가 없기 때문에 link 시에 에러가 발생합니다.

이제 아셨죠.?? 이런 정확한 메카니즘을 모르면 왜 링크에러나는지 모르고 이것 저것 삽질하다 수정되면 그냥 넘어가 버리게 됩니다.!!! 코드만 지저분해지게 만들구요..

그러면 abc, def가  mangling 안되게 만들려면 어떻게 할까요? 맨 위에 예제 처럼 cpp 파일 처음부터 끝까지 extern "C"로 감싸면 될까요?
제가 적극 추천하는 방법은  abc.h를 abc.c 에서 include 하는 방법을 추천합니다.

[file : abc.cpp]

#include "abc.h"


int abc(int a,int b) //_abc 라는 mangled 된 이름을 symbol로 가지게 됩니다.
{
return 0;
}

int def(int a,int b)  // _def 라는 symbol을 가지게 되는 것입니다.
{
return 0;
}

이렇게 하면 compiler가 abc.cpp 를 컴파일 할때 abc.h에 있는 extern "C"{} 로 되어있는 영역 내부의 내용에 해당하는 abc,def함수들을 모두 mangling안되게 해준답니다. 


3.마치며!!

항상 강조하지만 100% 이해하지 못하는 code나 함수를 내 프로그램에 추가하게 되면 반드시 그 대가를 치르게 됩니다.!!! 이는 불변의 진리입니다.!!!
그러니 여러분들도 "남들이 이렇게 쓰니 나도 이렇게 써야지 " 이렇게 하지 마시고, 자신이 사용하고자 하는 것에 대한 내용을 최대한 깊게 이해하려고 노력하십시오.

그러면 나중에 돌아올 부메랑을 막을 방법도 갖게 될 것입니다.!!

 

 

'개발 Note > it 이야기' 카테고리의 다른 글

Modal의 구현 형태  (0) 2009.01.12
UI Modal 컨셉  (0) 2009.01.06
Feature modeling - 아직 정리되지 않은 주제.  (0) 2009.01.04
Refactoring  (0) 2009.01.04
S60 platform architecture  (0) 2009.01.04

+ Recent posts