ATL 웹서비스에서 바이너리 데이터 전송에서의 메모리 해제

읽어보시고, 답변 아시는분.. 꼭 댓글 부탁드립니다~ ^^

위의 제목을 영어로 한다면….. 음.. The Release of Memory allocated for the Translating Binary Data in the ATL WebService.. 맞나?? ㅎㅎ 틀렸겠지요~ 무척 짧은 영어다보니…

이번주 월요일부터 오늘까지로 해서 ATL에서 웹서비스를 제작하는데 몇가지 문제점을 파악중입니다. 몇가지 문제점 중에 한가지 풀리지 않은 의문이 있는데요. 바로 웹서비스에서 바이너리 데이터를 클라이언트로 전송할때 서버측의 웹서비스에서 바이너리 데이터를 메모리에 할당하는데….. 이렇게 할당을 했으면 메모리에서 해제를 해야함은 당연합니다. 그런데, MSDN에서 제공하는 예제에서는 할당만 있을뿐 해제는 없더군요. 아래는 서버측(서비스부)에서 바이너리 데이터를 전송해주는 Test 함수의 모습입니다.

HRESULT CServiceXGE::HelloDump(ATLSOAP_BLOB *bstrOutput)
{
    bstrOutput->size = 1024*1024;
    bstrOutput->data = 
        (unsigned char *)malloc(bstrOutput->size);
    
    static bool bFlag = true;
    if(bFlag)
        strcpy((char *)bstrOutput->data, "This is HelloDump");
    else
        strcpy((char *)bstrOutput->data, "tHIS IS hELLOdUMP");
    
    bFlag = !bFlag;
    
    return HTTP_SUCCESS;
}

그리고 아래는 서비스를 호출하는 클라이언트에 대한 코드입니다.

    cs_.PerformanceTest();

    ServiceXGE::CServiceXGE *pSvcXGE = new ServiceXGE::CServiceXGE();

    ATLSOAP_BLOB blobDta;
    HRESULT hRet = pSvcXGE->HelloDump(&blobDta);

    char szBuf[20];
    sprintf(szBuf, "size: %ld =>;", blobDta.size);

    cs_.Write(szBuf);
    cs_.Writeln((const char *)(blobDta.data));

    free(blobDta.data);

    delete pSvcXGE;

    cs_.PerformanceTest(FALSE);

클라이언트에서는 free가 있습니다!! 하지만… 서비스의 프로세스와 클라이언트의 프로세스가 별개일뿐만 아니라 서비스와 클라이언트가 동일한 PC일리도 없습니다. 즉, 서비스의 메모리 할당에 대한 해제가 아니라는 것이지요.

다시 돌아가서, malloc를 통해 클라이언트에게 전송할 데이터를 메모리에 할당을 했습니다. 그럼 적절한 시점에서 클라이언트로 메모리에 할당된 데이터가 쓩~~ 날라가겠지요. 이제 다 날라갔다면.. malloc의 짝꿍인 free 함수를 사용해서 해제를 해야는게 상식일겁니다. 하지만 MSDN의 예제나 구글에서 검색을 해봐도 할당만 했을뿐 해제를 하는 경우가 없습니다. 그럼 해제는 웹서비스가 알아서 해주나보다… 하면 될것을, 저는 아무래도 찜찜해서 이것저것 더 찾아보았습니다. 그러나… 결론은 역시 웹서비스가 알아서 해주나보다…로 결론이 날듯합니다. 역시 여전이 찝찝합니다. 사실 곰곰이 생각해보면 클라이언트로 전송해주기 위해 메모리를 할당하고 클라이언트로 전송이 끝나면 이제 할당받은 메모리를 해제해주는 흐름에 대한 개념은 절대 틀리지 않다고 봅니다. 그런데… 문제는 해제를 해줄 시점에 대한 코드부를 찾을 수 없다는 점입니다. 위의 코드에서 return HTTP_SUCCESS; 바로 직전에 free 함수를 호출하면 아직 클라이언트로 전송하지 않은 시점이기 때문에 에러가 발생하구요.. 그래서, 역시 데이터를 클라이언트로 전송할 시점이 내부적으로 깊이 숨겨져 있어, 그 시점에 접근할 수 없으므로 개발자가 메모리 할당은 했지만 해제는 자동으로 하는 것으로 생각이 굳혀졌습니다.

그래서 이번엔 서비스 측의 코드를 수정해 다른 테스트를 해보았습니다.

char buf[1024*1024];

HRESULT CServiceXGE::HelloDump(ATLSOAP_BLOB *bstrOutput)
{
   bstrOutput->size = 1024*1024;
   bstrOutput->data = buf;

   static bool bFlag = true;
   if(bFlag)
      strcpy((char *)bstrOutput->data, "This is HelloDump");
   else
      strcpy((char *)bstrOutput->data, "tHIS IS hELLOdUMP");

   bFlag = !bFlag;

   return HTTP_SUCCESS;
}

즉, 동적 할당이 아닌 정적 할당입니다. 실행해보면 잘… 됩니다. 물론 클라이언트 코드는 그대로입니다.사실 에러가 나주길 바랬습니다. 내부적으로 메모리 할당을 자동으로 해준다면 정적 메모리 주소에 대한 메모리 해제는 에러가 나야 하기 때문입니다. 그러나… 잘 됩니다. 그렇다면 내부적으로 메모리 해제를 자동으로 해주지 않는다는 말인데?? 라는 생각이 듭니다. 하지만 혹시 내부적으로 메모리가 스택에 있는지, 힙에 있는지를 파악해서 스택이면 메모리 해제를 하지 않고 힙일 경우에만 하는것이 아닌가라는 생각이 듭니다.

않되겠다 싶어서, 물 막고 품는 방식으로 확인을 해보았습니다. 방법은 클라이언트에서 수백번 서비스를 요청을 작업관리자의 메모리 사용량으로 확인을 해보았습니다. 물론 서비스는 동적 메모리 방식으로 말이지요. 수백번 요청이면 한 요청당 1MB 씩 할당하므로 수백MB의 메모리 릭이 발생할 것으로 판단했습니다. 결론은 메모리릭이 없다… 입니다. 그럼 역시 내부적으로 자동으로 메모리를 해제해주는구나.. 라는 판단이 옳은것같습니다. 하지만 역시 왠지 찝찝합니다. 그래서 좀더 검색을 해보던중 CodeProject에 좋은 예를 하나 발견했는데, 그 코드를 살펴보니 ATL에 메모리 관리자가 있다는 것을 알았습니다. 그 메모리 관리자를 사용해 코드를 다시 작성하면 아래와 같습니다.

HRESULT CServiceXGE::HelloDump(ATLSOAP_BLOB *bstrOutput)
{
    IAtlMemMgr *pMem = GetMemMgr();

    bstrOutput->size = 1024*1024;
    bstrOutput->data = 
             (unsigned char *)pMem->Allocate(bstrOutput->size);

    static bool bFlag = true;
    if(bFlag)
        strcpy((char *)bstrOutput->data, "This is HelloDump");
    else
        strcpy((char *)bstrOutput->data, "tHIS IS hELLOdUMP");

    bFlag = !bFlag;

    return HTTP_SUCCESS;
}

즉, 메모리 관리자를 얻어와서 이 메모리 관리자를 통해 필요한 만큼의 메모리 덩어리를 할당받습니다. 그리고 할당받은 메모리를 해제하는 역시 없습니다. 아마도 이 메모리 할당자가 알아서 메모리를 해제해주는 특별한 행동을 한다고 생각해도 좋을듯 싶습니다. 이제 위의 서비스에 대한 클라이언트 부분에 대한 코드입니다.

  cs_.PerformanceTest();

  ServiceXGE::CServiceXGE *pSvcXGE = new ServiceXGE::CServiceXGE();

  ATLSOAP_BLOB blobDta;
  HRESULT hRet = pSvcXGE->HelloDump(&blobDta);

  char szBuf[20];
  sprintf(szBuf, "size: %ld =>", blobDta.size);

  cs_.Write(szBuf);
  cs_.Writeln((const char *)(blobDta.data));

  IAtlMemMgr *pMem = pSvcXGE->GetMemMgr();
  pMem->Free(blobDta.data);

  delete pSvcXGE;

  cs_.PerformanceTest(FALSE);

수정된 것은 일단 서비스의 프록시를 통해 서비스의 메모리 관리자를 얻어와서 이 메모리 관리자를 통해 메모리를 해제합니다. 뭔가 딱딱 들어 맞는듯합니다. 위의 메모리 관리자의 메모리 해제하는 함수(Free)는 내부적으로 클라이언트의 메모리를 해제하는 것은 분명하고… 아마도, 또 다른 임무인, 자신의 서비스 프록시를 통해 서비스에게 데이터 전송이 끝났으니 아까 메모리 덩어리를 해제해도 좋다는 메세지를 날리는 것이 아닐까? 라는 짐작을 해봅니다. 하지만 이것은 어디까지나 짐작이고… 사실 솔찍히 말한다면, 이건 아닐거라고 봅니다. 하지만 제가 살펴본 ATL로 만든 웹서비스를 통해 바이너리 데이터를 전송하는 코드 중에서는 가장 세련되어 보입니다. 일단 문제 생기면 그때 다시 파악해 보렵니다.

명확하게 이것은 이렇게 저렇게 된다! 라고 명시된 책의 문구가 있으면 좋겠는데… ATL을 이용한 웹서비스의 개발은 대세가 아닌지라.. 그런 문구를 찾지 못해 이런 추측으로 마무리를 하네요. ^^; 참고로 Visual Studio 2008에서는 ATL을 이용해 웹서비스를 개발하는 프로젝트가 제거된듯합니다. 암만 찾아봐도 2008의 새로운 프로젝트에서는 나오질 않네요. 웹서비스를 만들기 위해서는 .NET을 이용하라.. 인가 봅니다.

반환타입이 다르게 가상함수 재정의할 수 있는가?

코딩중 궁금하여 Test 해본 코드이다. 순수가상함수가 있다고 할때 이를 속상받는 클래스에서 순수가상함수를 재정의할때 반환타입을 다르게 할 수 있는가라는 스스로의 질문에 대한 정리이다.

기본적으로 Base가 되는 클래스 둘이 있는데 아래와 같다.

class ReturnType {
protected:
	int x;
public:
	ReturnType(int x) {
		this->x = x;
	}

	virtual int GetValue() {
		return x;
	}
};

class Base {
public:
	virtual ReturnType* Get(int a, int b) = 0;
};

클래스 Base는 Get이라는 순수가상함수를 가지고 있고 반환값으로 ReturnType을 갖는다. 여기서 따져보고 싶은 것은 Base의 Get이라는 순수가상함수의 반환 Type을 다르게 하여 선언할 수 있냐는 것이다. 대답은 “않된다”이지만 반환 Type도 OO의 다형성을 활용해서 다르게 선언할 수 있다는 것이다. 아래를 보자.

class ReturnType2 : public ReturnType {
public:
	ReturnType2(int x) : ReturnType(x) {
	}

	virtual int GetValue() {
		return x*x;
	}
};

class Derive1 : public Base {
public:
	virtual ReturnType2* Get(int a, int b) {
		ReturnType2* pRT = new ReturnType2(a+b);
		return pRT;
	}
};

class Derive2 : public Base {
public:
	virtual ReturnType* Get(int a, int b) {
		ReturnType* pRT = new ReturnType(a*2+b*2);
		return pRT;
	}
};

ReturnType으로부터 상속받은 ReturnType2 클래스가 있으며 Base로부터 상속받은 Derive1과 Derive2가 있다. 분명한것은 GetValue라는 순수가상함수의 반환타입이 상속받은 Base와 다르다는 점이다. 하지만 완전이 다른것은 아니고 반환타입이 상속관계를 갖는다. 이런 상속관계가 반환타입을 다르게 할 수 있는 이유가 되는 것이다.

내 코드는 걸레다, 개선을 위한 강한 자극이 필요하다.

문득.. 내 코드가 걸레로 보인다. 기분 탓일 수도 있겠지만.. 예전에 오픈소스로 공개한 코드를 살펴봤을때의 그런 일목요연하고 간결하며 체계적인 느낌이… 내가 작성한 코드에서는 느껴지지 않는다.

개발에 대한 체계적인 교육을 받지 못한 이유일까… 어쩌면 요즘 넘쳐대는 버그로 인해, 아직은 그 원인을 알지 못하는 이유로.. 코드가 전반적으로 꼬여있다라는 선입견 때문일까…

요즘에는 C++에 대한 감이 많이 상실된 느낌이다. 매일같이 사용하는 언어인데 말이다. 매일같이 바쁘게 사용하다보니, 늘 쓰는 것만 쓰게된다. 분명 어디선가 더 좋은 문법과 구문이 있다는 것을 알고 있음에도.. 비효율적이기는 하지만, 그냥 전에 썼던 방식대로 해 나간다. 그러다보니 전에 알고 있던 중요한 문법과 구문도 쓰지 않으니 기억속에서 희미하게 잊혀져간다.

정도(옳바른 길)을 걷지 못하고 있다는 느낌이다. 먼가 자극이 필요하다. 배움이 필요하다. 정도(옳바른 길)로 가는 깨닭음이 절실히 필요하다….

Software Design에 대한 짧은 생각과 팁

  • 파생 클래스 생성에 부담이 느껴진다면, 여전이 클래스 계층에 대한 추상화가 덜 되어 있다는 증거다.
2008년 1월 2일 오후 4시 29일

 

  • 속성 맴버 변수를 외부로 노출시키지 말고 get/set 함수를 두어 접근하도록 하는 것이, 클래스간의 독립성을 유지하는데 도움이 된다. get/set 함수를 만드는 수고로움에 대한 보상으로 말이다.
  • Class Diagram의 작성은 “인터페이스의 정의”로 시작된다.

12월 6일 오전 9시 56분

 

  • 데이터베이스에서 한번 정의된 스키마의 변경에 많은 소요비행이 발생한다면, 소프트웨어 개발에서는 클래스 관계도와 같은 UML 설계문서가 아닌 “전제조건”의 변경에 많은 소요비용이 발생한다. 여기서 말하는 전제조건이란, 어떤 기능을 실행하기 위해 준비되어야할 조건이다.

    11월 27일 오후 5시

     

  • 두렵다. 구현 단계에서 결국은, 비록 길지는 않은 시간이지만, 짧지만도 않은 시간 동안 설계한 내용을 폐기해야할지도 모른다는 생각에…. 그래서 분석 설계는 최대한 간결하게 하라고 했나보다. “어차피 내일은 변하다”니말이다. 하지만 최소한 설계 단계가 의미있는 이유는 실제 구현단계에서 마주치게 될 위험요소를 미리 파악할 수 있다는 큰 장점이 있다는 점이다. 그래서 인지… 설계를 하면 할 수록 두렵다….

10월 4일 오전 11시 36분

  • 영업인력이 고객에게 기술적으로  감동을 줄 수 있도록 개발인력은 소프트웨어를 설계하고 개발해야 한다.

9월 19일 14시 55분

 

  • 예를들어, Base라는 클래스로부터 상속받은 Derived라는 클래스가 있다고 할때, Base의 생성자의 인자가 (int a, int b, int c, int d)라고 하면, Derived 클래스의 생성자도 Base의 생성자를 실행하기 위해 인자로써 (int a, int b, int c, int d)를 최소한 가져야 하는데.. 만약 Base의 생성자의 인자가 하나 추가되면 Derived의 생성자의 인자도 동일하게 추가되어야한다. 하나의 변경으로 두개 이상의 변경이 발생하는 이 문제를 해결하기 위해 Derived의 생성자의 인자로 바로 Base를 받아 해결할 수 있다.
9월 19일 10시 12분

 

  • 서술식 글쓰기도 설계 방법 중의 하나.. UML과 글쓰기의 적절한 결합이 필요하다..
    9월 18일 12시 49분

     

  • 어디 쓸만한 꽁짜 UML 툴없나… 지금 쓰고 있는 StarUML은 결과는 그런대로 산뜻하긴 한데, UML 스펙을 지원하지 않는게 넘 많다. 지원하는 척… 하곤 결국 완벽이 지원하지 않는 그런 모습. 언제 쓸만한 공짜 UML 툴 찾아봐야겠다.

    9월 14일 12시 57분

     

 

  • 소프트웨어 설계라는 것이 유지보수와 소프트웨어의 생명주기를 길게 하는 효과는 있으나, 소프트웨어의 성능에 오히려 방해가 되는 것 같다는 생각이 든다.

    9월 13일 9시 54분