어떻게 하다보니 오늘 Com Dll을 만드는 일을 하게 되었습니다. 원인 인즉, .Net 1.1로 이미 배포되어있던 프로그램에 추가했던 Ftp 모듈이 약간 신뢰성이 안좋은 부분이 있어 그 부분을 수정하기 위해서 였습니다. 당시 %진행도가 필요한 Ftp 다운로드 모듈이 필요하여 웹상에 공개되어 있던 소스를 하나 받아 구성하긴 했는데, 그 마저도 바로 적용하지는 못하고 수정을 거쳐 적용을 했었습니다. 그런데 이번에 부족한 부분이 없도록 수정을 해야 했는데, .Net 1.1에서 지원하지도 않는 것 소켓을 이용해서 통째로 짜기에는 너무 시간이 부족하더군요. 그래서 일단 WinAPI를 땡겨쓴 dll을 만들기로 했습니다. 제가 짧은 시간에 만든 것 보다야 MS에서 오래전에 공개된 API가 신뢰성이 높다고 판단하기 때문이었습니다. 조금씩 만들어가다 보니 디자인이 영 별로더군요. 그래서 CFtpProgress 라는 클래스에서 Download, Upload 함수를 파일명과 함께 호출을 하면 CProgressObject라는 하나의 Com 개체를 리턴하는 쪽으로 수정하였습니다. 그런데 이게 또 아주 잘 알지 못하는 부분이다 보니 삽질을 하게 되더군요. 일단 삽질을 거쳐 수정을 하긴 했습니다. 방법이 대충 아래와 같더군요.( 테스트 설명 과정이다 보니 개체를 대충 작성 했습니다. )
일단 해당 Main 클래스를 정의한 C++ 소스에서 위와 같이 IDispatch를 ISub로 바꾸어 줍니다. 그리고 하나 더바꿔주어야 하는 부분이 있습니다. 바로 IDL 파일 부분입니다.
IMain, ISub의 순서를 제대로 바꿔주어야 합니다. 아래에 있는 정의를 위에서 판단하지는 못하겠죠? 일단 ISub를 위로 순서를 수정한 다음, IMain을 아래로 바꾸어 주고, IDispatch역시 ISub로 바꾸어 줍니다. 그리고 마지막 남은 소스 부분에서 정의 해야 합니다.
바로 위와같이 해당 기능을 구현합니다. CComQIPtr SmartPointer 클래스를 통하여 ISub 인터페이스에 대한 관리를 쉽게 처리하기로 했습니다. 그냥 그저 ISub 자체만으로도 처리는 가능합니다. 하지만 여기서 다루지는 않겠습니다. 그리고 CoCreateInstance( CLSID_Sub );를 통한 SmartPointer에 실질적인 Com 개체를 생성합니다. 그런 다음 바로 중요한 것이 QueryInterface인데 Com개체가 Addref, Release와 같은 함수를 통하여 Reference Counting을 수행한다는 것을 알고 계신분들이 계실겁니다. 그래서 그냥 그러 *RetVal = ComPtr;과 같은 대입식으로 Com개체를 리턴한다면 해당 ComPtr이 Scope를 벗어났을때 해당 Com개체는 메모리상에서 삭제되게 됩니다. 그래서 QueryInterface기본 구현에서 Addref를 호출해주므로 이를 통한 Interface를 얻으면서 대입합니다. 그런 다음 Sub에서는 ShowMessageBox같은 함수를 만들어 테스트를 해보도록 하겠습니다.
이렇게 테스트를 해보시길 바랍니다. 이렇게 하면 ShowMessageBox에서 처리한대로 MessageBox 함수를 통한 메시지박스가 표시됩니다. 어떻게 생각하면 약간 복잡할 수도 있지만 의외로 간단할 수 있습니다. 그럼 이를 이용한 좋은 라이브러리를 만들어 보세요^^.
COM 이라는 것은 Component Object Model의 준말입니다. Windows의 기반 기술이기도 하죠. 이 COM 이라는 것은 흔히 말하는 Language Independent 속성을 가지고 있습니다. 즉, 어떠한 언어라도 COM규약만 지킨다면 작성할 수 있고, 호출또한 가능하다는 얘기가 되죠. 그럼 C++ 에서는 COM을 어떻게 작성하면 될까요? MFC ActiveX 로 개발할 수 도 있고, IUnknown 인터페이스를 지켜 개발할 수도 있지만 가장 좋은 방법은 ATL 이라는 COM 작성을 위한 아주 심플하고 속도가 빠른 라이브러리를 사용하는 것입니다. 이 Active Template Library를 사용하여 COM 라이브러리를 개발할 경우 Class 들의 집합으로 프로그램이 개발되는데, 이 Class들 또한 OOP의 개념답게 상속의 개념을 구현할 수 있습니다. 어떻게 하냐구요?? 한 번 따라해보시겠습니까??
일단 ATL 프로젝트를 생성합니다. 이름은 어떻게 적어도 상관없습니다.
프로젝트가 생성되었다면 클래스 뷰를 엽니다.
클래스 뷰를 보게 되면 아래의 그림과 같은 Class 들이 존재하는 것을 확인할 수 있습니다. 개중 중요한 것은 PS가 붙은 클래스가 아닌 프로젝트 명과 동일한 클래스죠.
이 클래스에서 마우스 오른쪽을 눌러봅시다. 그럼 Add 메뉴가 보이실겁니다. 우리가 구현할 것은 OOP의 개념 중의 하나인 상속입니다. 그럼 Class를 2개 만들어야 겠죠?
보시다시피 CBase 클래스와 CInherit 클래스를 생성하였습니다. 클래스 생성시 C문자가 자동으로 붙었습니다. 그리고 보시면 ICBase, ICInherit 라는 것도 보이는군요. COM 모델에서 I가 붙은 것은 매우 중요합니다. 눈치 빠른 분들은 아시겠지만 바로 인터페이스 입니다. 밖에서 현재의 라이브러리로 접근하기 위한 인터페이스죠.
ICBase 와 ICInherit 클래스에 ShowMessage 함수를 각각 생성 해보도록 하겠습니다. 설명보다는 그림이 훨씬 간단할거 같군요.
위 그림에서 보이는 메뉴를 선택하여 ShowMessage 라는 메소드를 각 인터페이스에 생성하였습니다. in, out, retval 이라는 체크 부분도 보이지만 지금은 그냥 넘어가도록 하겠습니다. 중요한 것은 상속이지 저런게 아니니까요^^ 일단 생성 코드를 보시면 다음과 같이 추가한 메소드에 대한 코드가 존재합니다.
그럼 여기서 ShowMessage 함수를 간단하게 구현해야 하겠죠? 저는 CBase 클래스에는 CBase 라는문자를 CInherit 클래스에는 CInherit 라는 문자를 메시지박스로 띄우는 코드를 넣겠습니다.
이제 끝이냐? 하면 그렇지는 않습니다. COM 이라는 것과 CORBA 라는 것은 IDL 이라는 언어로 인터페이스가 정의된다고 합니다. 그래서 이 부분 또한 고쳐주어야 하죠. 일단 제 경우는 테스트 프로젝트가 InheritTest 이므로 InheritTest.idl 파일을 열어보겠습니다.
클래스 정의가 간단하게 보이는 군요. 뭐 워낙 기존 형식언어들과 비슷하기 때문에 큰 어려움은 없을 것으로 생각이 듭니다. 해당 idl 파일에서 ICInherit 부분을 ICBase 가 상속을 받도록 합니다. 그리고 상속을 받기 때문에 함수 충돌이 일어나지 않게 메소드를 지워줍니다. 그리고 CInherit coclass 가 ICBase 를 인터페이스로 가질 수 있도록 설정합니다.
그럼 이제 CInherit.h 파일을 보면 COM_INTERFACE_ENTRY 라는 부분이 있는데 이 또한 ICBase 인터페이스가 접근할 수 있도록 수정하여 줍니다.
그런 다음 컴파일을 합니다. 에러가 발생하지 않네요..
그런 다음 C#에서 다음과 같이 코드를 작성하고 실행하여 봅니다.
CBase 문자열이 뜨지 않고 CInherit라는 문자열이 뜨는 것을 확인 하실 수 있으실 겁니다. 미심쩍거나 잘못된 부분이 있으면 ATL을 배워가는 도중이라 댓글 부탁드려요.
예전에 팡야 계산기를 만든 적이 있습니다. Microsoft Visual C++ IDE을 사용하여 만든 계산기였죠. 그 프로그램은 비록 수식과 변수를 입력받아 처리하게 하였지만 제가 만든 프로그램으로만 작동한다는 점이 있었죠. 그래서 생각한 것이 Microsoft 의 COM 기술을 사용한 라이브러리를 만들어보자는 것이었습니다. 그래서 만든 것이 IPangYa 라이브러리입니다. 사용할 수 있는 언어는 COM 기술을 지원하는 언어라면 모두 사용할 수 있습니다. 이를테면 C#, VB, C++, Java, Delphi 같은 언어들 말이죠. 사용법은 특정 언어에 종속된 것으로 설명하지는 않겠습니다.
먼저 아래 사항을 실행하시기 전 regsvr32 "dll경로\IPangYa.dll"로 등록을 해주셔야 합니다.
IPangYa 라이브러리에는 CCalculator 클래스가 존재합니다. 이 클래스가 팡야 계산기 라이브러리의 대들보죠. 이 클래스에는 void SetVarialble( string VariableName, double VariableValue ); 함수와 double EvalExpress( string Expression, out int ErrorIndex ); 함수가 존재합니다. 라이브러리를 사용할 때 수식에 필요한 변수는 SetVariable로 세팅 후, EvalExpress로 수식을 계산하게 됩니다. 그런데 수식을 어떻게 작성하고 변수와 함수를 호출하느냐가 중요한 포인트겠죠^^?
먼저 수식을 그냥 입력할 때입니다.
SetVariable 함수를 호출하지 않습니다.
EvalExpress함수의 첫 번째 인자에 "1 + 2 + 3 * ( 4 - 3 )" 과 같은 수식을 입력합니다.
리턴 값을 이용합니다.
변수를 사용하여 계산할 때입니다.
ABC 변수에 100이라는 값을 넣으려고 한 다면 SetVariable( "ABC", 100 );을 호출합니다.
EvalExpress함수의 첫 번째 인자에 "1 + 2 + [ABC]" 를 입력하고 호출합니다.
EvalExpress함수의 첫 번째 인자에 "1 + 2 + [sin{30}]" 라고 입력하고 호출합니다.
리턴 값을 이용합니다.
만약 변수를 함수의 인자로 이용하고 싶다면 "1 + 2 + [sin{ABC}]" 라고 입력하면 됩니다. 물론 그전에 SetVariable로 ABC에 값을 할당해야 합니다.
위와 같이 사용할 수 있습니다. 다만, 각 언어에 대해서 COM 객체의 클래스를 할당받는 방법은 다릅니다. 간단하게 C#을 예로 들겠습니다. C++도 있지만 갑자기 에러가 나네요.;
위와 같이 쓰시면 해당 팡야 계산기 라이브러리를 이용하여 계산하실 수 있으실 겁니다. 그리고 EvalExpress 의 두 번째 인자인 ErrorIndex 는 에러가 난 지점을 건네받는데, 만약 그 값이 -3이라면 에러가 발생하지 않았고, -2 라면 알 수 없는 에러가 발생, 그 이후는 에러 지점의 한 칸 앞을 리턴 해줍니다. 다만 에러 그 자체가 아닌 에러가 발생한 단위의 인덱스입니다. 미진한 부분이 많은 라이브러리이지만 유용하게 쓰셨으면 합니다.
댓글을 달아 주세요
와우 멋진데 >ㅁ<
-b
COM 소스는 언제봐도 적응이 안된다야 ㅜ_ㅜ
드라이버 소스가 더 쉬운거 같오... ㅜ_ㅜ)-b
헐 ㅋㅋ 형 드라이버가 더 어려운거잖아요 ㅠㅠ
이건 드라이버에 비하면 ㅠㅠ