C#과 C++ 사이의 Interop 완성하기 – Step 2.

지난 번 시간에는 C++ 클래스의 생성자와 소멸자에 대한 프록시 함수를 디자인 하는 것에 관해서 다루어보았습니다. 이번 시간에는 일반 멤버 함수, 정적 멤버 함수, 멤버 변수에 관한 사항을 다루어보도록 하겠습니다.


그리고 강좌를 시작하기 전에 따로 팁&강좌란에 옮겨주신 운영자 내길님께 감사의 뜻을 전합니다. 미약한 강좌이지만 여러 분께 도움이 되길 바라는 마음에서 힘이 닿는대로 계속 강좌를 써 나가도록 하겠습니다.


* 지난번 글을 다시 여시지 않도록 하기 위하여 클래스를 다시 정의합니다.


// TestClass.h에 정의하는 내용입니다.


class TestClass


{


public:


    void TestFunction(int value);


    int MyFunction(void);


private:


    int m_myValue;


public:


    int GetMyValue(void);


    void SetMyValue(int value);


public:


    TestClass(int value);


}


// TestClass.cpp에 정의하는 내용입니다.


#include <iostream>


void TestClass::TestFunction(int value)


{


    stdout << value;


}


int TestClass::MyFunction(void)


{


    return 123;


}


int TestClass::GetMyValue(void)


{


    return this->m_myValue;


}


void TestClass::SetMyValue(int value)


{


    this->m_myValue = value;


}


TestClass::TestClass(int value)


{


    SetMyValue(value);


}


3. 일반적인 멤버 함수의 프록시 함수 만들기


멤버 함수는 크게 두 종류로 구분이 됩니다. 하나는 일반 멤버 함수이며 또 하나는 정적 멤버 함수입니다. 일반 멤버 함수는 클래스의 인스턴스가 생성된 이후에 호출할 수 있는 함수를 뜻합니다. 반면 정적 멤버 함수는 클래스의 이름을 네임스페이스로 취급하여 호출될 수 있는 함수를 뜻합니다. 쉽게 생각하시면 static 키워드를 붙인것과 붙이지 않은 것의 차이입니다. (C++의 static 키워드가 C#에서도 쓰이고 있으며, VB .NET에서는 Shared 키워드를 사용합니다.)


우선은 일반적인 멤버 함수에 관하여 생각해 보기로 하겠습니다.



    void TestClass_TestFunction(TestClass* pInstance, int value)


    {


      if(pInstance)


           pInstance->TestFunction(value);


    }


    int TestClass_MyFunction(TestClass* pInstance)


    {


      if(pInstance)


           return pInstance->MyFunction();


      return 0;


    }


값을 반환하지 않는 멤버 함수의 프록시 함수를 만드는 것은 반환할 값에 관해 연연할 필요가 없으며 단지 받은 포인터가 NULL 참조인지 아닌지만을 확인해서 선택적으로 호출하기만 하면 되는 것입니다.


값을 받는 멤버 함수의 프록시 함수를 만드는 것은 정상적으로 호출이 이루어졌을 경우와 그렇지 않을 경우를 구분하여 값을 반환해야 합니다. 즉, 성공하면 메서드가 반환하는 값 그 자체를 반환해야 하며 호출에 실패할 경우 실패하였음을 공지하여야 합니다. 반환 값을 반환할 때에는 실행이 어디서 실패한 것인지를 분명히 구분해주어야 하므로 복잡한 로직을 설계하는 경우 신경써야 합니다.


4. 정적 멤버 함수의 프록시 함수 만들기


정적 멤버 함수는 클래스의 이름을 하나의 네임스페이스로 취급하는 함수이기 때문에 클래스의 인스턴스를 필요로 하지 않습니다. 단지 static으로 선언된 C++ 멤버 함수 하나만을 호출해주면 됩니다. 여기서는 앞에서 선언한 TestFunction과 MyFunction이 정적 멤버 함수라고 가정하고 예제를 보여드리도록 하겠습니다.



    void TestClass_static_TestFunction(int value)


    {


      TestClass::TestFunction(value);


    }


    int TestClass_static_MyFunction()


    {


      return TestClass::MyFunction();


    }


5. 멤버 변수에 관한 프록시 함수 만들기


멤버 변수에 관한 프록시 함수를 만들기 위해서는 몇 가지 사항을 더 고려해 봐야 합니다. 첫 번째 조건은 멤버 변수의 액세스 제한이 public 인지 아니면 private, protected와 같은 제약이 걸려있는지 여부입니다. 두 번째는 일반 멤버 변수인지 정적 변수인지 확인해야 합니다.


첫 번째 조건에서 후자의 경우에 해당된다면 클래스의 멤버 함수로 getter와 setter 멤버 함수를 도입해야 합니다. 보호된 멤버를 직접 액세스해서 값을 변경할 수는 없기 때문입니다. 그러나 전자의 경우에 해당된다면 getter와 setter 멤버 함수를 도입하지 말고 프록시 함수로 getter 함수와 setter 함수를 정의하시면 됩니다.


두 번째 조건에서 후자의 경우에 해당된다면 멤버 함수와 마찬가지로 getter와 setter 함수 모두에서 클래스의 인스턴스는 필요치 않습니다. 그러나 전자의 경우에 해당된다면 getter와 setter 함수 모두에서 클래스의 인스턴스가 필요합니다. getter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 실패 코드를 적절히 통지해야 하며 setter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 통지 코드나 출력용 매개 변수를 받아서 통지하는 것이 좋습니다. 저수준 코드에서는 C#의 try-catch 핸들러를 알지 못하기 때문입니다.


모두 네 가지 경우의 수가 존재합니다. 각각 살펴보도록 하겠습니다.


5.1. 만약 m_myValue가 public: 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현



    int TestClass_public_GetMyValue(TestClass* pInstance)


    {


      if(pInstance != NULL)


           return pInstance->m_myValue;


      else return 0;


    }


    int TestClass_public_SetMyValue(TestClass* pInstance, int value)


    {


      if(pInstance != NULL)


       {


           pInstance->m_myValue = value;


           return 1; // 성공했음을 알리는 표현


      }


      return 0; // 액세스에 실패했음을 알리는 표현


    }


5.2. 만약 m_myValue가 public: 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현



    int TestClass_public_static_GetMyValue()


    {


      return TestClass::m_myValue;


    }


    // 정적 멤버 변수는 특별한 상황이 아닌 이상 실패하는 경우는 없습니다.


    // 굳이 정확성을 추구하자면 C++의 Structured Exception Handling을 사용해도 좋지만


    // 복잡한 로직을 사용하지 않을 경우 성능상의 손해가 있을 수 있습니다.


    void TestClass_public_static_SetMyValue(int value)


    {


      TestClass::m_myValue = value;


    }


5.3. 만약 m_myValue가 public:이 아닌 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이어야 합니다.)



    int TestClass_private_GetMyValue()


    {


      if(pInstance != NULL)


           return pInstance->GetMyValue();


      else return 0;


    }


    int TestClass_private_SetMyValue(int value)


    {


      if(pInstance != NULL)


       {


           pInstance->SetMyValue(value);


           return 1; // 성공했음을 알리는 표현


      }


      return 0; // 액세스에 실패했음을 알리는 표현


    }


5.4. 만약 m_myValue가 public:이 아닌 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이면서 정적 멤버 함수여야 합니다.)



    int TestClass_private_static_GetMyValue()


    {


      return TestClass::GetMyValue();


    }


    void TestClass_private_static_SetMyValue(int value)


    {


      TestClass::SetMyValue(value);


    }


오늘 시간에는 세 가지 사항을 살펴봤습니다. 다음 시간에는 마지막으로 C#과의 연동 방법에 관하여 살펴보도록 하겠습니다.


오늘 시간에 강의한 내용이 프록시 함수를 설계하는 패턴의 전부는 아닙니다. 얼마든지 새로운 방식을 사용하여 입맛에 맞게 고치실 수 있음을 인지하시고 활용해 주십시오. 그리고 이 프록시 함수는 포인터를 활용해서 구현된다는 점을 잊으시면 안됩니다.


참고 자료: Win32 API를 플랫폼 호출할 수 있도록 미리 코딩된 플랫폼 호출 코드들의 라이브러리를 제공하는 사이트가 있습니다. http://www.pinvoke.net/


긴 강좌를 봐주셔서 감사합니다. 행복한 하루 되세요. ^^

댓글 남기기