블로그 이미지
대갈장군

calendar

1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

Notice

2013. 9. 27. 03:45 프로그래밍/MSDN

http://msdn.microsoft.com/en-us/library/ms953320.aspx


Introduction

Visual Studio 2005에서 새로 추가된 ClickOnce라고 불리는 새로운 윈도우 폼 배포 방식이 있다. 이 방식은 어플리케이션의 설치와 웹 어플리케이션의 업그레이드를 스마트 클라이언트로 가능케 해주는 좋은 친구다. 이 글은 윈도우 폼의 장점과 ClickOnce라고 불리는 녀석에 대해서 자세하고 세세하게 살펴볼 예정이란다.


왜 윈도우 폼인가?

여기서 말하는 윈도우 폼이라 함은 .NET 을 기반으로 작성된 윈도우 폼 어플리케이션을 말한다. 뭐 대표적인 예로는 C#으로 작성된 윈폼이 있겠네. 웹 어플리케이션이 등장한 후로 사람들이 이 웹 어플리케이션에 지대한 관심을 보이는데 그 이유는 단지 '쿨'하다는 이유 말고도 다음과 같은 장점들이 있다.


  • 웹 어플리케이션은 세계 어디서나 언제든지 인터넷 커넥션만 있으면 접근 가능, 또한 실행 가능하다. 심지어는 클라이언트가 윈도우를 운영체제로 사용하지 않아도 상관이 없다. 유비쿼터스 환경을 추구하는 어플리케이션이라면 웹 어플리케이션이 짱!
  • 웹 어플리케이션은 배포와 업뎃이 쉽다. 걍 설치 파일을 서버에 업로드만 해놓으면 사용자들이 설치하면 끄읏! DLL 걱정도 없고 레지스트리 걱정도 없고 COM 클래스 등록 걱정조차도 없다. 걍 돌아간다... 이것이야 말로 프로그래머의 퐌타지!
이 글의 초점은 바로 '배포'에 있다. HTTP 프로토콜을 이용한 웹 어플리케이션의 설치 및 업뎃은 당연히 일반적인 윈도우즈 어플리케이션에 비해 장점이 있다.

하지만... 안타깝게도, 장점만 있는 것은 세상에 없으니 단점도 있다. 
  • 우선 유저 인터페이스가 구리다. 웹 어플리케이션은 일반적인 윈도우즈 어플리케이션이 가지는 드래그 드롭이나 우클릭등 이런게 아예 불가능하거나 지원이 매우 매우 힘들다. 
  • 비록 위의 단점을 해결하기 위해 쿨해보이는 유저 인터페이스나 각종 기능을 추가한다고 하더라도 해당 스크립트를 작성하는 게 절라게 어렵다. 이유는 설명안했지만, 아마도 다양한 클라이언트의 경우를 고려하려면 수 많은 경우를 고려해야 겠지?
  • 아시다시피 서버를 사용하는 중앙 집중형 방식이므로 사용자에게 기다릴 줄아는 참을성을 요구한다. 난 힘들겠는데...
  • 화면에 출력하는 프린팅이 오직 화면 출력만으로 제한된다. 
  • 플러그 인이나 ActiveX 컨트로 때문에 위에서 나열한 문제들이 종합되어 나타난다. 뭐, 그렇지만 이 문제점은 웹 어플리케이션이 아니더라도 나타는 문제점이래요. 이놈의 액티브엑스는 내논 자식이구만.
자, 이제 웹 어플리케이션과 윈도우 어플리케이션을 결혼시키면 어떨까? 물론 장점만 모은 자식이 나오기를 원해서다. 재미 있는 사실은 이미 .NET 프레임워크는 초기 버전에서부터 이 둘을 결혼시켜 놓았다는 점. 

이러한 종류의 어플리케션은 intranet이나 extranet 두가지 모두에 대해서 잘 적용될 수 있으며 클라이언트가 .NET 프레임워크와 인터넷 익스플로러를 가지고 있다는 전제하에 원활하고 수월하게 작동한다.

.NET Framework 1.x : HREFing .EXEs
.NET Framework 버전 1.0과 1.1에서 기본 탑제한 기능으로 윈도우 폼 어플리케이션을 HTTP를 통해 배포하기가 있었다. 기본적으로 이것은 HREF 태크를 사용해 Managed 실행파일을 링크로 걸어주는 것이다. 이렇게 하면 사용자는 인터넷 익스플로러를 이용하면 닷넷 프레임워크가 깔려있다는 전제하에서 실행파일을 다운받아서 설치하는 것 뿐만 아니라 필요한 DLL까지 같이 다운받아 준다. 이러한 형태의 배포를 이른바 'hrefing EXEs' 라고 부른다. HREF 헤더는 HTML 공부를 한 사람이라면 누구나 아는 이른바 '링크' 걸기 태그이다. 즉, 여기 누르면 여기로 연결됩니다 하는 것.

HREF 태그의 예를 보자면,
<a href="MainProject.exe">Call MainProject</a>

완전 HTML 문법과 동일하다. 이 방식은 상당히 쉽고 여러번 다른 글에서 논의된 적이 있는 방법이다. 아래 글 참조.
기본적으로 닷넷 어셈블리 (.exe 파일이나 .dll 파일들)는 배포를 위한 기본 구성요소이기 때문에 프로그래머는 어플리케이션을 여러개의 DLL과 한개의 메인 EXE파일로 구성하면 된다. 이렇게 하면 메인 EXE 파일이 중심을 잡고 각 함수를 DLL에서 불러다 사용하는 구조가 되므로 무언가 업뎃을 해야 한다면 메인은 거의 그대로 유지하고 단순히 DLL 파일만 바꿔주면 새로운 버젼이 된다.

지금까지 위에서 이야기 한걸로 보아하면 겁나 쉽고 만만하게 보이지만 사실 몇가지 해주어야 할 것들이 있다.
  • 닷넷 프레임워크가 반드시 클라이언트 (사용자) 컴퓨터에 설치되어 있어야 한다. 
  • 내가 만든 어플리케이션은 클라이언트 입장에서 봤을 때 '부분적으로 신뢰'된다. 이걸 좋게 보려면 참 장점인게 클라이언트의 컴퓨터에 최대한 접근을 적게하려 하므로 클라이언트 입장에서는 더욱 안전하다. 다만, 프로그래머 입장에서 좀더 심층적인 작업, 예를 들자면 파일을 열고 쓰고 닫고 COM 오브젝트를 불러다 쓰고 싶다면 반드시 클라이언트의 보안 규정에 따라야 하므로 좀 귀찮은 과정이 추가된다.
  • 기본적으로 어플리케이션은 몇몇 DLL을 필요로 하게 마련이고 이것을 로드해야 하는데 느린 인터넷 속도와 현재의 설계방식 때문에 좀 느릴수 있다는 점. 
  • 업데이트 자체가 파일 단위 업뎃이라서 어플리케이션이 반만 업데이트 될수도 있다. 헐퀴... 이거 무서운데?
  • 오프라인 모드도 있다는데 이것이 인터넷 익스플로러에만 있고 어플리케이션 자체에는 없다?
  • 기본적으로 어플리케이션의 속성을 조절하기 위한 .config 파일이 없다. 하지만 할 수 있는 방법이 있어. 근데 링크가 죽었네? ㅋㅋ 
  • 오, 프로그램 시작메뉴나 데스크 탑에 단축 아이콘 생성이 안돼! 헐 이거 대박 단점인데? 
Update Application Block
마이크로소프트에서는 위에서 언급한 단점들을 극복하기 위해서 이른바 Updater Application Block (UAB)라는 것을 만들었는데 이 업뎃 블락이라는 놈은 내가 작성한 어플리케이션에 HTTP를 통해 어플리케이션의 각각의 조각을 다운로드 하는 것을 관리하기 위한 라이브러리다. 

이것은 기본 닷넷 프레임워크가 제공하는 것보다 몇가지 장점을 가지는데 다음과 같다.
  • 이것은 로컬 어플리케이션으로 작동한다. 즉, 퍼포먼스 페널티가 없다. 왜냐면 하드 드라이브에서 실행하는 것과 같으므로.
  • 업뎃의 Atomic operation을 보장한다. 즉, 업뎃 하면 100% 업뎃 한다. 중간에 끊기 없기
  • 어플리케이션이 manifest 파일을 가진다. (명세서)
  • 100% Trusted 모드로 실행된다. 즉, 보안 규약이고 나발이고 걍 내 맘대로 할 수 있다.
  • 시작메뉴에 단축 아이콘 생성된다! 
반면, 아까 말한대로 무조건 장점만 있으면 세상이 아름답겠지만 단점도 있다.
  • 이걸 쓰려고 하면 어플리케이션의 대대적인 수정이 불가피하다.
  • 옛날 버전 윈도우에서는 안된다. 98, ME에서 작동 안되고 2000이상되어야 한다. 98/ME 아직도 쓰는 사람 있나? 손들어보자...
  • 100% 트러스트 모드로 작동하므로 난장판 만들 확률 또한 존재한다.
  • 마이크로 소프트에의해 지원 받지 못한다. 헐? 이건 뭔 소리야... 마이크로소프트에서 만들었다면서?
헐, 좀 보아하니까 이거 죄다 연결된 링크도 깨지고 안되네. 이 UAB라는 놈은 2004년 (이글이 쓰여진 시점)에는 한창 잘나갔으나 지금은 사라졌나?

ClickOnce
드이어 우리가 궁금해하는 그 이름. 클릭원스, 한번클릭 님이 등장하셨다. 기본적으로 이 ClickOnce는 UAB가 가지는 모든 장점을 흡수하고 적은 단점을 가지고 추가적인 기능을 더 가지는 놈이다. 즉, 킹.왕.짱.이다. 저자가 생각하는 최고의 장점은 코드 보안을 지킨다는 점이란다. 난 없는게 더 좋던데...ㅋㅋㅋ

HREF EXEs와 비교하자면 ClickOnce는 다음과 같은 장점을 가진다.
  • 업뎃의 Atomic Operation 100% 보장 (중간에 끊기 없음)
  • 오프라인모드 지원할 뿐만아니라 어느정도 수준으로 할 건지도 조절가능. 심지어 어플리케이션이 온라인인지 오프라인인지 알아내는 API도 제공됨
  • Visual Studio와 연동도 뛰어나 어플리케이션이 요구하는 보안 수준도 설정가능하다
  • Bootstraper와 함께 오기때문에 필요한 컴포넌트를 쉽게 다운 받을 수 있다. 심지어는 닷넷 프레이워크 자체를 다운받는 것도 가능하다. 즉, 백지상태의 클라이언트에서도 인터넷만 연결되어 있다면 필요한 모든 것을 다 받아 설치할 수 있다는 말.
  • 시작메뉴에 단축 아이콘 생성 가능!
ClickOnce는 Visual Studio 2005의 기능중 하나이며 이전에는 Whidbey라고 불렸단다. 왜 그런 이름을? 난 알길이 없네... 

A ClickOnce Application
자, 이제 어떻게 사용하는지 볼까나. 다음과 같은 단계를 거쳐야 한다.

  1. Visual Studio 시작 
  2. 새 프로젝트 생성
  3. 간단한 C# 윈폼 프로젝트 선택
  4. 이름 맘대로 정하고 생성
  5. 간단한 버튼 추가해 주고
  6. 버튼에 간단한 코드 MessageBox.Show() 같은거 넣어주기
이제 이 다음부터는 프로젝트 속성에서 설정하는 것만 남았다. 사실 이 글이 쓰여진 시점으로 부터 워낙 많이 시간이 흘러 지금 내가 사용하는 2012에서는 약간 다른 점도 있지만 기본적으로는 아주 유사하다.


프로젝트 속성의 Publish 탭에 들어가면 각종 퍼블리슁 옵션이 나오는데 여기서 주목해야 할 것은 Publishing Location과 Install Mode and Setting 부분이다.

Publishing Location에서 어디에다 이 어플리케이션을 퍼블리슁 할거냐 묻는 건데 보시다시피 네트워크 공간도 가능하다. 고로 웹 서버에 올려놓기도 가능하다.

그리고 아래 버튼에 보면 Prerequisites 버튼이 있는데 이게 참 착한 놈이다. 


위 그림처럼 어떤 어셈블리가 필요한지 선택해주면 내가 만든 어플리케이션을 설치하기 전에 미리 이 조건이 충족되는지 확인해 보고 없으면 알아서 다운 받아서 설치한다. 참 착하다.

여기서 기타 등등 모든 세부 메뉴를 설명하기에는 시간 낭비인것 같고, 아무튼 이 ClickOnce라는 기능은 참으로 착하다... 웹 서버에 내가 작성한 어플리케이션을 넣어두고 업뎃까지 자동으로 알아서 하며 윈도우 운영체제의 보안 체계도 지키면서 필요한 놈들은 알아서 척척 미리 설치까지 하는 이 기능이야 말로 정말 대다나다. 

마무리 하자면 배포에는 몇가지 방법이 있다. 일단 제일 간단한 방법이 필요한 파일, 즉, EXE 파일과 DLL을 걍 복사해서 USB나 CD로 다른 컴퓨터에 넣는 방법이다. 이 방법은 문제가 있을 확률이 높다. 왜냐면 내가 복사를 할 컴퓨터에 필요한 구성 요소들이 무조건 있다라고 가정하고 있기 때문이다. 이것은 매우 위험한 전제 가정이다. 

두 번째 방법은 위에서 언급한 방법보다 조금 진화된 방법인데, 필수 파일은 복사해서 주고 필요 구성 요소들은 마이크로소프트가 제공하는 Redistribution Package를 설치해서 충당하는 방법이다. 이것이 물론 좀더 진화되기는 했는만 여전히 어떤 Package를 설치해야 하는지 그리고 얼마만큼이나 설치를 해야 하는지에 대한 질문에 대답을 해야 한다. 고로 복잡하기는 마찬가지다.

세 번째 방법은 Setup 프로젝트를 솔루션에 추가하여 Setup.msi 파일을 생성해내는 것이다. 이것은 ClickOnce의 Offline 버전이라고 보면 된다. 일반적으로 CD로 배포되는 어플리케이션들이 죄다 이런 방식이다. 물론 웹에서 다운 받는 파일도 대부분이 이런 형태를 띄고 있다. 이 방법은 ClickOnce와 마찬가지로 각종 옵션을 설정할수 있으며 Prerequisites를 설정할 수 있기 때문에 좋다.

마지막이 아마도 이 ClickOnce라는 방법이 아닐까 싶다. 코드 보안도 유지하고 공간 낭비도 적고 매번 새로운 파일을 제공하지 않아도 알아서 업뎃도 하며 각종 편의 사항은 모두 지원하는... 다만 인터넷이 빨라야 하고 큰 크기의 어플리케이션 설치에 사용하기에는 조금 무리가 있지 않나 싶다. 하지만 요즘 나오는 대형 게임을 보면 알수 있다시피 거의 이런 ClickOnce 형태의 독자적인 웹 서버 베이스의 설치 과정을 가지는 것을 보면 분명 배포의 최종 단계는 중앙 서버를 이용한 인터넷 퍼블리슁이라고 봐야 겠다. 





'프로그래밍 > MSDN' 카테고리의 다른 글

Implicit Platform Invoke  (0) 2013.09.24
Explicit PInvoke C++ (DllImport Attribute)  (0) 2013.09.24
Mixed, Pure, Verifiable  (0) 2013.09.20
[MSDN] .NET Framework 구조  (4) 2013.05.03
Pure C++: Hello, C++/CLI  (0) 2013.04.25
posted by 대갈장군
2013. 9. 24. 05:57 프로그래밍/MSDN


// vcmcppv2_impl_dllimp.cpp
// compile with: /clr:pure user32.lib
using namespace System::Runtime::InteropServices;

// Implicit DLLImport specifying calling convention
extern "C" int __stdcall MessageBeep(int);

// explicit DLLImport needed here to use P/Invoke marshalling because
// System::String ^ is not the type of the first parameter to printf
[DllImport("msvcrt.dll", EntryPoint = "printf", CallingConvention = CallingConvention::Cdecl,  CharSet = CharSet::Ansi)]
// or just
// [DllImport("msvcrt.dll")]
int printf(System::String ^, ...); 

int main() {
   // (string literals are System::String by default)
   printf("Begin beep\n");
   MessageBeep(100000);
   printf("Done\n");
}
Begin beep
Done

위의 코드를 보면 PInvoke에 대한 경우들을 잘 설명하고 있는데 우선 첫번째가 Implicit DLLImport이다. Native로 작성된 DLL 어디엔가 __stdcall 호출 규약을 가지는 함수 MessageBeep(int) 가 정의되어 있다고 알려주는 한 줄의 코드로써 상황 종료다. 


다만 이런 암묵적인 PInvoke를 사용하기 위해서는 반드시 호출 함수의 입력 변수가 마샬링이 필요 없어야 한다. MessageBeep() 함수의 경우 int를 받기 때문에 Int32로 마샬링 없이 변환이 가능하므로 암묵적 PInvoke 사용이 가능하다.


두번째를 보게 되면 명시적인 DLLImport인데 이 명시적인 것도 두가지 방법이 있네. 첫번째는 보아하니 msvcrt.dll에서 printf 함수를 어떤 형태의 호출 규약으로 그리고 어떤 형태의 타입 (ANSI냐 Unicode냐)까지 명확하게 정의하는 것과 혹은 걍 간단하게 어떤 DLL을 가져와라라고 말하는 방법이다. 둘다 된다면 당연히 두번째 방법이 더 쉬지 않나? 


어쨌든 첫번째 방법은 단순한 함수 선언이고 두번째 방법은 DllImport 속성을 이용한 명시적 DLL 파일 이름을 지정하는 것. 그것이 가장 큰 차이점이고 두번째 방법 내부에서 또 추가적으로 더 명시할 수 있는 방법도 있다는 말. 


http://msdn.microsoft.com/en-us/library/2x8kf7zx.aspx

'프로그래밍 > MSDN' 카테고리의 다른 글

ClickOnce를 이용한 윈도우 폼의 배포  (3) 2013.09.27
Explicit PInvoke C++ (DllImport Attribute)  (0) 2013.09.24
Mixed, Pure, Verifiable  (0) 2013.09.20
[MSDN] .NET Framework 구조  (4) 2013.05.03
Pure C++: Hello, C++/CLI  (0) 2013.04.25
posted by 대갈장군
2013. 9. 24. 05:18 프로그래밍/MSDN

.NET Framework는 Platform Invoke (줄여서 PInvoke)라고 불리는 Managed Application에서 Unmanaged 함수를 호출할 수 있도록 해주는 기능을 Dllimport 속성을 이용해서 지원한다. 이 PInvoke는 두 가지 형태가 있는데 첫번째는 Explicit (명시적) 호출 방법이고 두 번째는 Implicit (암묵적) 호출 방법이다. 오늘 알아볼 녀석은 Explicit (명시적) 호출 방법이다.


DllImportAttribute를 사용해서 PInvoke를 수행하는데 이 녀석은 각각의 DLL 진입점에서 함수 선언 직전에 위치함으로 여기서 부터는 이 Unmanaged DLL을 사용하겠다고 명시하는 것이다. 


Unmanaged DLL 함수는 Managed 코드로 융합되기 위해서 몇가지 추가 코드와 간단한 데이터 변환을 추가로 가지게 되면 이를 통해서 Managed 코드는 Unmanaged DLL의 함수로 접근이 가능하다. PInvoke는 /clr, /clr:pure, /clr:safe 세 종류의 스위치에 대해서 모두 사용 가능하다. 소 파워풀!


Platform invoke


위의 그림을 보면 PInvoke에 대한 설명이 잘 나와 있는 것 같다. Managed코드가 컴파일러를 통과해서 CLR 서포트를 받는 어셈블리로 생성이 되면 메타 데이터와 IL (MSIL) 코드를 가지게 된다. 그리고 이런 Managed 코드 내부에서 만약 PInvoke가 호출되게 되면 Standard Marshalling Service를 통해 중간 과정에 필요한 변환을 거쳐서 Unmanaged DLL 함수를 호출하게 된다. 


이 PInvoke는 사실 CLR (Common Language Runtime)이 제공하는 서비스이다. 이 서비스를 통해서 Managed Code가 Unmanaged DLL 함수를 호출 할 수 있도록 해준다. 이러한 형태의 Marshalling, 마셜링 (컨테이너를 수송하기 위해 부두 내에서 이동, 정렬 하여 잠시 대기한다는 의미의 전문 용어) 서비스는 이미 Runtime과 COM의 호환 작용 및 "It Just Works" (IJW)를 위해서도 이미 사용된 적 있는 메커니즘이다. 


마셜링에 대해서 좀 더 자세히 살펴 보고 싶지만 일단 넘어가고, PInvoke를 실제적으로 어떻게 사용하는지 살펴보자.


다음의 코드는 PInvoke를 사용하는 예를 보여주는데 puts라고 불리는 native function은 msvcrt.dll에 정의되어 있다. 즉, puts가 바로 Unmanaged function이고 우리는 그것을 managed code에서 불러다가 사용하려고 하는 것이다. 


// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

위의 코드를 보아하니 메인 함수 진입점 직전에 DllImport 속성을 이용해서 msvcrt DLL 파일을 연결하고있다. 그리고 바로 다음 줄에 보니 puts 함수를 외부에 존재하는 함수라고 명시적으로 알려주고 있다. 그리고 내부 코드에서 puts를 사용해서 출력을 하고 있다.


위와 같은 역활을 하는 코드를 IJW로 작성해 보면 다음과 같다.


// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main() {
   String ^ pStr = "Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   puts(pChars);
   
   Marshal::FreeHGlobal((IntPtr)pChars);
}

IJW를 보아하니 명시적으로 Native DLL을 임포트 하라는 DllImport 속성을 사용하지 않았지만 간단하게 puts() 함수를 선언하고 있는 stdio.h 헤더를 선언하고 메인 함수 내부에서 마샬링 서비스를 직접적으로 호출해서 라이브로 변환한 다음 사용된 힙 메모리를 직접 해제 하는 것 같다. 불행히도 MSDN에서는 자세하게 위 코드에 대해서 설명하지 않는다. 아마도 IJW에 대한 페이지를 살펴봐야 정확한 의미를 알 수 있을 것 같다.


IJW를 사용하는 장점은 몇가지가 있는데 다음과 같다. 일단, DLL을 명시적으로 DllImport 속성을 이용해 연결할 필요 없고 그냥 헤더 파일이랑 라이브러리를 임포트 하면 장땡이다. 뭐, 그닥 장점처럼 안보이긴 하는데... 어쨌든.. 


IJW 방법을 사용하면 아주 약간 더 빠르다. 왜냐면 프로그래머가 명시적으로 어떤 타입의 어떤 포인터로 접근하라고 명시적으로 알려주기 때문이다. 


마찬가지로 같은 맥락에서 퍼포먼스에 대한 그림이 쉽게 그려진다는 것이 IJW의 또 다른 장점이다. 위의 코드를 예로 보자면 Unicode에서 ANSI로 변환이 필요하다는 것과 그 과정에서 추가적인 메모리 할당과 해제가 필요함이 확연히 드러나게 된다. 사실 위 경우에는 _putws와 PtrToStringChars를 사용하는 것이 퍼포먼스 면에서는 더 유리하다. 


마지막으로 같은 데이터에 대해서 매번 마샬링을 호출하기보다는 한번 호출해서 메모리를 복사해두고 복사된 메모리를 사용하면 성능향상에 매우 도움이 된다. DllImport를 사용하면 같은 데이터라도 매번 오버헤드가 발생하는 변환을 해야 한다.


자, 이제 안 좋은 점에 대해서 알아보자. 장점에서 언급한 것들의 정 반대가 단점이다. ㅋㅋ


우선 마샬링이 아주 아주 명시적이므로 코드를 아주 정확하게 적성해야만 작동한다. DllImport를 사용하면 이러한 복잡한 과정을 생략할 수 있다. 위 두 코드를 딱 봐도 DllImport가 훠얼씬 더 간단하다. 


IJW의 마샬링은 죄다 Inline 함수다. 고로 어플리케이션의 흐름에 지장을 줄수도 있다. 


마지막으로 명시적인 마샬링은 IntPtr 타입을 리턴하므로 반드시 ToPointer 함수를 사용해야 한다. 이게 왜 단점일까 라고 잠시 생각했다. 생각해보니 이것은 데이터 타입을 void로 바꾸는 것과 같다. 고로 프로그래머가 정확한 타입을 명시해 주어야 한다. 위 코드에서처럼 (char*)타입으로 명시적으로 캐스팅 하는 과정이 추가로 필요하다는 말. 


고로 위의 이야기를 요약하자면 IJW를 사용한 것이 더 효율적이 효과적인 프로그래밍이 가능하지만 만약 내가 작성하는 코드가 가끔씩 Unmanaged API를 호출하는 정도라면 둘 중 어떤 것을 사용해도 무방하다.


그 다음으로 MSDN에서 설명하고 있는 것이 PInvoke를 Windows API와 사용하는 것인데 일단 코드를 보자.



// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);

int main() {
   String ^ pText = "Hello World! ";
   String ^ pCaption = "PInvoke Test";
   MessageBox(0, pText, pCaption, 0);
}

사실 Managed 코드라면 얼마든지 MessageBox를 잘 불러다 사용할 수 있지만 이건 예를 들기 위해 이렇게 한거다. user32 DLL을 DllImport를 사용해 명시적으로 연결해 주고 메인 함수 내부에서 실제적으로 사용하고 있다. 아하, 두번째 인자에 대한 비밀이 풀렸다. 위의 예제들에서 보면 DllImport 속성자 다음에 오는 첫번째 인자는 DLL 파일의 이름이라는 것은 알았을테고 두번째 인자에 대해서 저건 왜 필요하지에 대해서 생각했었는데 바로 해답은 아래와 같다.


실제로 user32.dll 파일 안에는 MessageBox라는 함수는 없다. 왜냐면 user32.dll에는 MessageBoxA (ANSI 타입)과 MessageBoxW (Unicode 타입) 둘 만 존재한다. 그래서 저 CharSet = CharSet::Ansi 인자를 통해서 MessageBoxA를 사용하라고 알려주는 것이다. 재미 있는 점은 되도록이면 Unicode 버전을 쓰라는 건데 왜 위의 모든 예제 코드에서는 죄다 Ansi를 쓰는지 모르겠네. 된다는 걸 보여주기 위해서 인가? Unicode를 사용하면 오버헤드가 더 줄어든단다. 


중요한 게 한가지 나오는데 언제 PInvoke를 사용하면 안되느냐 하는 것이다.


예를 들어 다음과 같은 함수를 Unmanaged code로 작성해서 Dll로 만들었다고 치자. 

char * MakeSpecial(char * pszString);


그렇다면 이 함수를 불러다 쓰려면 다음과 같은 형태를 취해야 한다.

[DllImport("mylib")]

extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);

이 함수 호출의 문제점은 MakeSpecial로 호출되어 리턴되어지는 메모리를 해제 할 수가 없다는 점이다. PInvoke를 통해서 리턴된 포인터는 사용자가 해제할수 없는 내부 메모리로 저장이 되기 때문이란다. 고로 이런 경우에는 반드시 IJW를 써야 한다. 


PInvoke의 한계점은 입력으로 들어오는 변수를 출력으로 리턴할 수 없다는 데 있다. 입력으로 들어가는 변수가 PInvoke에 의해서 마샬링을 거치게 되면 이 변수는 리턴될 때 메모리 커럽션 에러가 발생한다. 방금 위에서 말한 것과 거의 같은 이유인것 같다.


__declspec(dllexport)
char* fstringA(char* param) 
{
   return param;
}

또 다른 간단한 예를 보자면 아래 코드와 같은 user32.dll에 있는 CharLower이라는 함수를 호출해서 사용하는 경우인데 이 함수도 입력으로 들어오는 입력 변수의 메모리 공간을 사용해서 다시 출력으로 돌려 보내는 함수이다. 고로 이렇게 하게 되면 입력으로는 제대로 된 변수와 메모리가 들어가지만 출력으로 돌아오는 변수와 메모리는 해제되어 버린 값이 된다. 간단하게 생각하면 PInvoke를 통해서 호출된 함수는 리턴하는 즉시 해제되어 버린다고 보면 되는건가? 변수와 메모리를 유지하는 일반적인 범위에서는 일어날 수 없는 일이지만 PInvoke는 마샬링 서비스를 통과하기 때문에 잠깐 동안 함수의 범위에서 벗어났다가 들어오기 때문인가..


// platform_invocation_services_5.cpp // compile with: /clr /c using namespace System; using namespace System::Runtime::InteropServices; #include <limits.h> ref struct MyPInvokeWrap { public: [ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ] static String^ CharLower([In, Out] String ^); }; int main() { String ^ strout = "AabCc"; Console::WriteLine(strout); strout = MyPInvokeWrap::CharLower(strout); Console::WriteLine(strout); }


PInvoke를 쓰더라도 같은 형태의 데이터 타입에 대해서는 마샬링이 필요가 없는데 예를 들자면 int 타입과 Int32 타입이다. 하지만 같은 형태의 데이터 타입이 아닌 경우에는 당연히 마샬링이 필요하다. 아래 테이블을 보면 다양항 형태의 타입에 대해서 어떤 마샬링이 맵핑 되는지 보여준다. 젠장 테이블이 안맞아서 걍 링크를 건다. http://msdn.microsoft.com/en-us/library/ms235282.aspx


마샬링을 수행하는 '마샬러' (젠장, 한국어로 바꾸려니 말 참 이상해지네..)는 Unmanaged 함수로 넘어가는 변수의 주소를 자동적으로 런타임 힙 메모리 공간에 Pin (꼳아 놓는다) 시켜 놓는다. 즉, 홀딩 시킨다. 이렇게 해야 가비지 컬렉터가 이 메모리 공간을 비워버리지 않는다. 


위에서 본 예제에서 CharSet이라는 두번째 변수를 이용해서 DllImport 속성자에 명시했던 것을 기억하는가? 이것이 바로 Managed String이 어떻게 마샬링 되어야 하는 가를 알려주는 단서다. 위의 예제에서는 Native 함수가 ANSI로 되어 있다고 알려주었다. 


이러한 마샬링에 필요한 정보를 각각의 Native 함수의 입력 변수마다 정의 할 수 있다. String * 입력 변수에 대해서 마샬링 할 수 있는 것들로는: BStr, ANSIBStr, TBStr, LPStr, LPTStr. 이 있고 기본 타입은 LPStr 이다. 


아래의 예제 코드를 보면 string이 2 바이트 유니코드 char string (LPWStr)으로 마샬링 하는 것을 보여주고 있다. 출력으로 나오는 놈은 문장 전체가 아니라 Hello World!의 첫 글자만 나온다. 왜냐면 마샬링된 문자열의 두번째 바이트가 null이므로 문장 종료로 인식되기 때문이다. 고로, 어떤 마샬링을 해야하는지 알려주는 과정이 굉장히 중요하고 또한 프로그래머의 책임이 뒤따른다.


// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

PInvoke는 x86 에서 실행시 각 호출마다 약 10 에서 30 의 오버헤드가 발생한다. 그리고 추가로 마샬링을 수행함으로써 또 다른 오버헤드가 발생한다. 다만 같은 형태의 타입에 대해서는 마샬링 오버헤드가 없다. 


고로 보다 나은 퍼포먼스를 위해서는 보다 적은 PInvoke를 사용하고 동시에 많은 데이터를 마샬링 하도록 하는 것이 많은 수의 PInvoke를 사용하며 동시에 적은 데이터를 마샬링 하는 것보다 더 빠르다.






'프로그래밍 > MSDN' 카테고리의 다른 글

ClickOnce를 이용한 윈도우 폼의 배포  (3) 2013.09.27
Implicit Platform Invoke  (0) 2013.09.24
Mixed, Pure, Verifiable  (0) 2013.09.20
[MSDN] .NET Framework 구조  (4) 2013.05.03
Pure C++: Hello, C++/CLI  (0) 2013.04.25
posted by 대갈장군
2013. 9. 20. 23:48 프로그래밍/MSDN

Visual C++은 .NET 프로그래밍 환경에서 세가지 다른 형태의 CLR (Common Language Runtime) 컴파일 옵션을 제공하는데 고놈들이 바로 Mixed, Pure 그리고 Verifiable이다. 


  • Mixed (/clr)

이 녀석은 일반적인 기본 옵션이다. 말 그대로 섞여 있다는 말인데 무엇과 무엇이 섞여 있는냐 하면은, Managed 코드와 Unmanaged 코드가 섞여 있다는 의미다. 이 옵션을 켬으로써 Managed 코드와 Unmanaged 코드의 결혼을 성사시킨다는 말이다... 이것을 이른바 C++ Interop이라 부른다. 아마도 Interoperable의 약자가 아닐까?


  • Pure (/clr:pure)

Pure 옵션의 경우는 Managed 와 Unmanaged 데이터 타입을 둘 다 가질 수 있지만 함수는 오직 Managed 형태만 소유 할 수 있다. Mixed와 마찬가지로 Pure Assembly는 P/Invoke를 통해서 Native DLL과의 Interop을 허락하지만 C++ Interop Feature는 불가하다. 잠깐만, 그러니까 이미 컴파일된 Managed 코드 DLL은 P/Invoke를 통해 함수를 호출해 사용할 수 있지만 코드 내에서 직접적으로 함수를 선언해서 호출하는 것 (다른 말로 C++ Interop)은 허락되지 않는다는 말인감? 그리고 한가지 더 중요한 점은 Pure Assembly는 Native 함수로 부터 호출가능한 함수를 Export할 수없는데 그 이유는 Pure Assembly는 __clrcall 호출 규약을 사용하기 때문이다. 



  • /clr:pure 사용의 장점
    • 더 나은 성능: Pure Assembly는 오직 MSIL만 포함하고 있으므로 Native Function을 내포하고 있지 않다. 고로 Managed/Unmanaged Transition이 없다. 왔다리 갔다리 안한다는 말. (다만 P/Invoke를 이용해서 호출되는 경우는 예외다. 여전히 왔다리 갔다리 한다는 말)

    • AppDomain Awareness: Managed 함수들과 CLR 데이터 타입은 전부다 Application Domain에 상주하고 있으므로 이런 함수와 데이터 타입에 대한 접근성이나 안정성이 Mixed assembly 경우보다 훨씬 더 낫다는 점. 그리고 .NET 프레임워크 내에서 다른 언어와의 호환성에서도 더 유리하다는 점.

    • Non-disk loading: Pure Assembly들은 메모리 상에 로딩 될 수도 있고 심지어는 스트리밍이 될 수도 있다. Mixed 경우에는 반드시 디스크 상에 물질적으로 존재해야만 한다는 조건이 따라 붙는다. 

    • Reflection: Mixed 된 실행 파일은 Reflection이 허용되지 않지만 Pure의 경우에는 완전 서포트 해준다.

    • Host Controllability: MSIL만 포함하고 있기 때문에 Mixed보다 예측가능하고 더 유연성 있게 코드를 작성할 수 있단다.

  • /clr:pure 사용의 한계
    • Pure Assembly는 Unmanaged Function으로부터 호출 되어 질 수 없다. 고로 Pure Assembly는 COM interface를 구현할 수 없고 또는 Native Callback을 드러낼 수도 없다. Pure Assembly는 __declspec(dllexport)나 .DEF 파일을 통해 함수를 Export할 수 없다. 

    • ATL과 MFC 라이브러리들은 지원되지 않는다. 

    • #import 지원 안됨

    • 익셉션 핸들링과 얼라인먼트를 위한 플로팅 포인트 옵션 조정 불가. 고로 fpieee.h 와 같은 파일은 pure 옵션으로 컴파일 안됨

    • GetLastError 함수는 pure 옵션에서 Undefined Behavior라는 에러로 처리됨. 헐퀴.

  • Verifiable (/clr:safe)
가장 강력크한 놈이 나타났다. 이 옵션을 키고 컴파일을 때리면 작성되는 코드는 CLR 환경에서 100% 호환되게 작동되며 어떤 보안 옵션도 어기지 않고 말 잘듣는 코드로 생성된다. 예를 들자면 실행전에 디스크에 파일을 생성하고 지울수 있는 권한이 있는지 미리 확인하고 체크하여 사용자에게 알려주는 그런 기능. 당연히 이 옵션을 키면 CRT 서포트는 없다. 고로 Managed 코드를 섞어 쓸수 없다는 말이다. 

이 옵션을 사용하면 일단 당연히 보안성이 높아지고 특정 개발 환경에서 요구되기도 하며 미래에 나올 윈도우즈 환경에서도 잘 작동하는 프로그램을 작성하게 되는 것이다. 단점이라면 C++ interop 을 사용할 수 없다는 점.

마지막으로 P/Invoke를 사용하면 어떤 옵션으로 컴파일 하던간에 Native DLL을 임포트 할 수 있지만 런타임 환경에서 잘 작동할거라는 보장은 없다. 당연한 이야기지...... ㅋㅋ 

다음으로 P/Invoke에 대해서 알아봐야 겠군.



posted by 대갈장군
2013. 5. 3. 00:05 프로그래밍/MSDN
MSDN에 나와 있는 .NET Framework에 대한 설명을 읽고 있자면 '지루하다'. -_-

일단 모르는 단어들이 너무 많이 튀어 나와서 처음 읽는 사람으로 하여금 급거부감을 주게된다.

모르는 단어를 클릭해서 링크를 따라 들어가다 보면 어느새 어딘지 알수 없는 곳에서 또 다시 모르는 단어를 클릭 하려는 내 모습을 발견하고는 깊은 한숨과 함께 '프로그래밍은 나랑 잘 맞지 않습니다...'를 연발하게 된다. 슬픈 현실이다... ㅡ.ㅜ

개인적으로 영어를 쓸 일이 많다보니 아무래도 한국어로 된 MSDN보다 영어로 된 MSDN을 좋아하는데 원래 프로그래밍 언어가 영어인데다가 영어는 불분명한 의미가 없는 정확한 표현을 쓰기 때문에 오히려 한국어로 된 설명보다 이해가 더 쉽게 될때가 많다.

그래서 MSDN은 영어로 읽는 것이 더 나을 것이라고 말하고 싶지만 영어를 격하게 싫어하시는 분들은 아마도 한국어가 나을 듯하다...

이제 .NET Framework에 대해서 이야기 해보자. 이 놈은 내가 Visual Studio 를 이용하면서 부터 꾸준히 내 머리 속에 맴돌던 놈인데 단 한번도 제대로 들어다 본적은 없었다.

왜냐면 일단 내가 차후 설명하게 될 Common Language Runtime(CLR)을 전혀 사용하지 않았기 때문이고 둘째로 이 .NET Framework는 알게 모르게 내 컴퓨터의 구석에서 내가 하는 일들을 돕고 있었다.

비록 그 실체를 몰랐어도 저놈이 있다는 것은 알고 있었다. 요즘들어서 내 프로그램을 다른 컴퓨터에 돌릴일이 많아지다 보니 이놈에 대해서 자연스럽게 접근하게 되었고 몇몇 MSDN 문서를 읽게 되었다.

아마존 닷 컴에서 Ivor Horton's Beginning Visual C++ 2008 이라는 책을 검색해 보면 저 책의 1장을 볼수 있는데 여기에 많은 설명이 잘 되어 있다. (공짜다.. ㅋㅋ)

사용자 삽입 이미지


내가 개인적으로 .NET Framework를 잘 설명하고 있다고 생각하는 그림이 바로 위 그림이다. 일반적으로 C나 C++을 이용해서 간단히 작성해 오던 방식이 그림 제일 왼편에 있는 그림이다. 이것을 보게되면 Native C++코드를 컴파일러(gcc나 각종 컴파일러들)로 Object 파일로 생성하고 Link를 통해 실행 파일을 만들어서 돌리게 된다.

이후 MFC가 나오면서 가운데 한단계가 더 추가되었다. (중간 그림) 사실 이 MFC는 한번도 사용해 본적은 없지만 아주 쉽고 간편하면서도 다양한 형태의 함수를 제공했다는 걸 알수 있었다.

.NET Framework가 나오고 나서도 물론 MFC가 지원되지만 .NET Framework가 MFC보다 한수 위인 이유는 정확히는 몰라도 .NET Framework가 가지는 강력한 Cross-PlatformCross-Language 특성 때문이 아닌가 싶다.

마지막으로 제일 오른편이 바로 .NET Framework를 제대로 사용하는 경우인데 뭔지 모를 2단계가 들어가 있는데 이것이 바로 .NET Framework를 구성하는 2개의 구성요소인 Common Language Runtime과 .NET Framework Library다.

간단하게 설명해서 .NET Framework Library는 걍 함수들의 집합이다. 이놈들은 전부 객체 지향적이므로 독립적으로 사용가능하며 독립적이므로 안전하고 아주 좋다... -_-

사실 CLR이 참 재밌는 놈인데 마지막 단어인 Runtime에 주목할 필요가 있다. 즉, 실행중이란 의미인데 내가 프로그램을 더블 클릭해서 실행에 들어가는 직후부터 프로그램이 죽는 순간까지를 Runtime이라 보면 된다.

이 CLR은 그 런타임에 작동을 하는 놈으로써 다양한 작업을 하는데 예를 들면 보안을 높이고 메모리를 관리하면 타입을 관리하고 각종 시스템의 핵심 서비스들을 제공하기까지 한단다...

나도 프로그램에 입문한지 얼마 되지는 않았기 때문에 정확한 것은 알 수 없지만 .NET Framework의 CLR이 '이것은 새롭다! 이것은 좋다!'라고 Microsoft가 외치는 이유가 바로 Runtime 이라는 저 단어 하나 때문이 아닌가 싶다.

기존의 프로그램 컴파일과 실행의 과정을 보게 되면 컴파일을 수행하는 단계에서 이미 현재 컴퓨터에 종속적 (dependent) 이게 되고 Linker를 통해 실행파일을 만드는 과정에서도 역시 종속적이게 되는 것 같았다. (내 생각일 뿐임... 물론 자바가 이 원리를 깨뜨렸다.)

헌데 이 새로나온 .NET Framework에서는 중간에 CLR와 JIT(Just-In-Time Compiler)를 실행 파일을 만드는 과정에 집어 넣음으로써 큰 변화를 시도했다는 점이 다른 것 같다.

우선 .NET Framework에서 생성되는 실행 파일은 실행 파일을 더블 클릭 한 뒤의 시점(일명 Runtime)에 여러가지 일을 수행하는데 이 일들이 바로 플랫폼에 종속적이지 않도록 만들어주는 일이다.

즉, 이전에는 활용하지 못했던 Runtime이라는 새로운 시간적 공간을 이용해서 각종 다양한 추가 기능 및 편리한 기능을 제공하고 있다는 점이 .NET Framework가 뛰어나다라고 말하는 이유인 것 같다. 자바가 먼저인지 .NET이 먼저인지는 몰라도 자바의 Virtual Machine의 개념도 이와 같다.

물론 이건 내 개인적인 생각이지 이게 이유라고 설명한 글은 본적이 없다... -_-

사실 .NET Framework를 제대로 설명하려면 Common Language Runtime과 더불어 MSIL (Microsoft Intermediate Language), CTS (Common Type System), JIT (Just-In-Time Compiler)를 다 설명해야 하지만 이걸 다 설명하려면 너무 길어질 뿐더러 읽는 사람의 가독력과 흥미가 너무 반감될것 같아 여기서는 말 안해야 겠다....

줄여서 결론을 말하자면 .NET Framework는,

프로그래머가 프로그램을 짤때 사용할 수 있는 새로운 '뼈대 (Frame)' 이다.


그리고 이 새로운 뼈대의 장점들은 무수히 많지만 대표적인 것들이

1. 플랫폼 Independent (플랫폼에 종속되지 않는다.)
2. 프로그램에 대한 다양한 형태의 접근제어컨트롤 방법을 제공한다. 즉, 내용물을 보여주되 복사하거나 그 소스를 못보게 하는 등의 접근제어를 말한다.
3. 강화된 보안을 제공한다. (접근제어도 일종의 보안 방법중 하나지요.)
4. Cross-Language를 제공한다. CLR에 근거하여 작동하는 모든 언어는 서로간에 정보 교환이 자유 자재로 된다는 의미. C#을 작성한 파일을 Visual Basic에서 불러와 사용가능하다는 말. (물론 코딩시 제약이 있다.)
5. Web 개발을 혁신적으로 개선했다. 이건 뭐 읽어보지 않았지만 ASP .NET이 바로 CLR에 기반을 두고 클라이언트가 서버의 컴퓨터에 접속하여 서버의 컴파일러에 의해 작동된다는 뭐 그런소리... 내가 사용할 일은 거의 없을 거라는...

또 무언가 장점이 있을텐데 기억이 안나는 관계로 여기까지만 적자.

Update
시간이 많이 흘러서 다시 이글을 보니 좀 부족한 설명이 있는 것 같아 추가로 몇자 적어 놓는다.

우선 가장 기본이 되는 CLI를 언급해야 할 것 같다. CLICommon Language Infrastructure의 줄임말로 하나의 '규약'을 명시해 놓은 '조립 설명서' 같은 거다.

이 CLI는 고레벨의 프로그래밍 언어 (C++/ C# 같은거)들이 다른 오퍼레이팅 시스템 환경에서 재 컴파일이나 소스 코드를 바꾸지 않고도 문제 없이 균일하게 같은 방식으로 동작할 수 있도록 만들어주는 Virtual Machine을 위한 '규약 설명서'이다. 

즉, 이 CLI 규약대로만 구현을 하면 어느 컴퓨터에서나 돌아갈 수 있는 Virtual Machine을 만들어 낼수 있다는 말인데 이 Virtual Machine의 대표적인 예가 바로 CLR이다. CLR은 Common Language Runtime으로써 CLI 규약 대로 만든 윈도우즈 용 CLI의 구현이다.

이 CLI 규약을 보면 Virtual Machine (CLR 같은 놈)을 위한 중간 단계의 언어를 명시하고 있는데 이것이 바로 MSIL (Microsoft Intermediate Language)이다. 이렇게 만들어진 중간 단계의 언어는 프로그램이 시작하는 시점에 작동을 하는 JIT (Just-in-time) 컴파일러에 의해서 이해되고 분석되어 진 후 내가 현재 사용하고 있는 컴퓨터의 환경에 맞는 적합한 machine code(실행 코드)를 생성해 낸다. 고로 어떤 언어로 내가 프로그램을 작성했건 간에 그 코드는 CLI 규약에 따라 구현된 눈에 보이지 않는 .NET Framework의 CLR과 같은 놈을 통해 중간 언어 (MSIL)로 바뀌어진 다음 실행이 이루어지는 그 순간 (마우스 더블 클릭하는 순간) JIT라 불리우는 컴파일러에 의해 내 환경에 맞는 코드로 재 생성되서 나와서 실행이 이루어 진다는 말.

또한 .NET Framework에는 CTS (Common Type System)이라 불리우는 것이 있는데 이것은 말 그대로 CLI 명세를 따라 구현된 모든 언어가 공통으로 가지는 기본 타입의 구현 및 임의 타임의 구현 약속이다. 고로 C#으로 작성한 코드의 기본 타입들은 Visual Basic이나 C++과 같은 다른 언어로 작성된 기본 타입과 기본적으로 일맥상통하므로 CLR의 입장에서는 다른 언어를 이용한 두 소스 코드를 이해하는데 아무런 문제가 없다는 말.

고로 고 레벨 언어간의 융화와 결합이 용이하고 보다 높은 수준의 안전성 체크 및 형변환을 지원함으로써 한 단계 높은 수준의 프레임워크를 제공한다는 것. 게다가 CLR은 기본적으로 강화된 보안 옵션 (예를 들자면 어셈블리를 공개키와 개인키로 묶는 것)을 제공하며 눈에 띄는 변화가 바로 GC (Garbage Collector)의 등장이다. 

가비지 컬렉터의 등장으로 과거에 사용자에게 메모리 해제의 책임을 전가하는 것에서 벗어나 이제 사용자는 두다리 쭈욱 뻣고 마음껏 새로운 인스턴스를 남발해도 되는 상황이 왔다는 것. 물론 100% 그렇다는 건 아니고... 아직도 여전히 사용자에게 많은 책임이 있으므로 메모리 해제는 늘 머릿 속에 염두해두어야 할 문제다. 어쨌든 가비지 콜렉터는 착하다는 거... ㅋㅋ 


posted by 대갈장군
2013. 4. 25. 01:30 프로그래밍/MSDN

출처: http://msdn.microsoft.com/ko-kr/magazine/cc163681(en-us).aspx


젠장, 이건 한국어 선택해도 한국어로 안나오는 페이지네... 결국 해석해야 할듯...


C++/CLI is a self-contained, component-based dynamic programming language that, like C# or Java, is derived from C++. Unlike those languages, however, we have worked hard to integrate C++/CLI into ISO-C++, using the historical model of evolving the C/C++ programming language to support modern programming paradigms. You can say that C++/CLI is to C++ as C++ is to C. More generally, you can view the evolution leading to C++/CLI in the following historical context:
C++/CLI는 'Self-contained (스스로 모두 내포하고 있는 - 즉, 완전체)'하고 컴포넌트에 기반한 동적 프로그래밍 언어다. 마치 C# 또는 자바처럼 C++에 근간을 두고 있는 언어들 처럼 말이다. 하지만 그런 언어들 (C#이나 Java)과는 달리 우리 (Microsoft사)는 C++/CLI를 ISO-C++ (표준 C++을 말함) 형태로 만들기 위해서 무척이나 노력했단다... 뭐 쉽지 않았을 것 같다. C++ 에서 C++/CLI로 가는 것은 C에서 C++로 가는 것 만큼이나 큰 변화였다는 이야기. 다음 문서들을 보게 되면 얼마나 큰 변화가 있는지 알거다... 허허.. 그걸 언제 다 읽어봐.... 제일 마지막꺼만 읽어보면 괜춘할듯.
  • BCPL (Basic Computer Programming Language)
  • B (Ken Thompson, original UNIX work)
  • C (Dennis Ritchie, adding type and control structure to B)
  • C with Classes (~1979)
  • C84 (~1984)
  • Cfront, release E (~1984-to universities)
  • Cfront, release 1.0 (1985-to the world )—20th birthday
  • Multiple/Virtual Inheritance (MI) programming (~1988)
  • Generic Programming (~1991) (templates)
  • ANSI C++/ ISO-C++ (~1996)
  • Dynamic Component programming (~2005) (C++/CLI)

What is C++/CLI?
C++/CLI represents a tuple. C++ refers, of course, to the C++ programming language invented by Bjarne Stroustrup at Bell Laboratories. It supports a static object model that is optimized for the speed and size of its executables. However, it doesn't support run-time modification of the program other than heap allocation. It allows unlimited access to the underlying machine, but very little access to the types active in the running program and no real access to the associated infrastructure of that program. Herb Sutter, a former colleague of mine at Microsoft and the chief architect of C++/CLI, refers to C++ as a concrete language.
CLI refers to the Common Language Infrastructure, a multitiered architecture supporting a dynamic component programming model. In many ways, this represents a complete reversal of the C++ object model. A runtime software layer, the virtual execution system, runs between the program and the underlying operating system. Access to the underlying machine is fairly constrained. Access to the types active in the executing program and the associated program infrastructure—both as discovery and construction—is supported. The slash (/) represents a binding between C++ and the CLI. The details surrounding this binding make up the general topic of this column.
So, a first approximation of an answer to what is C++/CLI is that it is a binding of the static C++ object model to the dynamic component object model of the CLI. In short, it is how you do .NET programming using C++ rather than C# or Visual Basic®. Like C# and the CLI itself, C++/CLI is undergoing standardization under the European Computer Manufacturers Association (ECMA) and eventually under ISO.
The common language runtime (CLR) is the Microsoft version of the CLI that is specific to the Windows® operating system. Similarly, Visual C++® 2005 is the implementation of C++/CLI.
As a second approximation of an answer, I would say that C++/CLI integrates the .NET programming model within C++ in the same way as, back at Bell Laboratories, we integrated generic programming using templates within the then existing C++. In both of these cases your investment in an existing C++ codebase and in your existing C++ expertise are preserved. This was an essential baseline requirement of the design of C++/CLI.
자, 이제부터 시작이다. 머리 싸매고 읽어보자. C++/CLI는 튜플이다. 여기서 튜플이라 함은 내 생각에 서로 다른 타입이 하나의 오브젝트로 뭉쳐져 있다는 의미로 해석된다. 즉, C++과 CLI 라는 서로 다른 두 타입을 하나로 합쳐놓은 짬뽕이라는 것. C++/CLI이라는 단어에서 C++ 부분은 당연히 유명하신 Bjarne Stroustrup께서 Bell 연구소에서 만드신 것을 지칭하는 것이다. 이거 모르면 당신은 간첩? 이 C++은 기본적으로 실행파일의 속도와 크기를 위해 최적화 되어 있는 'Static Object Model'을 지원한다. Static 이라는 단어가 좀 뜬금없이 들릴수 있다. 사실 내 생각에 이 Static이라는 단어는 상대적인 의미라고 본다. 과거에 없었던 현재 시점에 어떤 기술에 비해 Static (정적)하다는 것이지 C++이 나왔을 당시에는 대박 혁명적 언어였다...

아무튼, 조금 있다 설명할 CLI를 이해하면 왜 C++이 기분나쁘게 '정적'이라고 불리는지 알게 될 것이다. 자, C++의 문제점은 Heal Allocation (힙 메모리 할당)을 제외하고는 Run-time에 변경을 허락하지 않는다. 자, Run-Time이라 함은 프로그램이 실행된 직후부터 멈추는 순간까지를 말한다. 고로 C++은 컴퓨터 자체 하부 구조에는 이른바 'Unlimited Access' 즉 무한 접근을 허락하지만 실행중인 프로그램에는 아주 미미하고 제한적인 접근만을 허락하며 프로그램이 작동하기 위해 사용하는 Infrastructure (예를 들자면 Windows 같은거)에는 실질적인 접근을 제공하지 않는다. Herb Sutter 이라는 내 친구가 있는데 이 친구가 C++/CLI를 설계를 책임진 사람이란다. 아무튼 이 친구가 말하기를 "C++은 Concrete Language다" 라고 말했단다. 직역하자면 C++은 딴딴한 언어다....... 응?? 즉, 정적이다. 별로 유동적이 않다는 것.

이제 우리의 친구 CLI를 살펴보자.  CLI는 Common Language Infrastructure의 줄임말이다. 공용 언어 기반이란 뜻이네? 풀어서 설명하자면 동적 요소 프로그래밍(Dynamic Component Programming) 을 지원하는 멀티스레드를 하는 아키텍쳐란다. 이걸 단박에 이해한다면 당신은 이미 능력자. 이런 정의 자체가 이미 C++과 정반대된다. Runtime Software Layer 다른 말로는 가상 실행 시스템 (Virtual Execution System)은 프로그램과 프로그램을 실행하는 운영체제 가운데 낑겨 있는 녀석이다. 고로 컴퓨터 자체로 접근하는 것은 제한이 된다. (C++과 많이 다르다) CLI에서는 실행중인 프로그램의 타입으로 액티브하게 접근하는 것과 연관된 프로그램 infrastructure에 접근하는 것 모두 지원된다. C++/CLI 사이에 들어가는 /가 의미하는 것은 C++과 CLI의 결합을 의미한단다. 이것에 대해서 이 글에서 이야기 할꺼래.

자, 이제 C++/CLI가 뭐요? 라는 질문의 첫번째 대답을 하겠다. C++/CLI는 정적인 C++ Object Model을 동적인 Component Object Model 인 CLI와 결합한 것이다. 정적 객체 모델 C++ 더하기 동적 요소 객체 모델 CLI. 줄여서 말하자면, C#이나 Visual Basic 대신 C++ 이라는 언어를 사용해서 .NET programming을 하는 것을 말한다. 오오오... 정답! 마음에 드는 말이고 모든 것을 분명하게 선을 긋는 아주 정확한 정의다.

CLR (Common Language Runtime)은 Windows라는 운영체제를 위한 Microsoft사의 CLI라고 보면 된다. 이거 어렵게 들릴지 모르겠지만 내가 예전에 써놓은 .Net Framework에 대한 글을 보면 이해가 쉽게 될것이다. 

두번째 근접한 대답으로는 C++/CLI는 과거에 벨 연구소해서 C에서 C++로 진화할때 제네릭 프로그래밍을 C에 추가한 것 처럼 C++이라는 언어에 .NET 프로그래밍 모델을 추가한 것이다. 이로써 내가 열씸히 작성해 놓은 C++ 코드들이 다 무용지물이 되지 않게 되고 C++/CLI로 흘러감으로써 엄청난 이득을 취할 수 있겠다. 그럴까??? 정말로?? ㅋㅋ

Learning C++/CLI
There are three aspects in the design of a CLI language that hold true across all languages: a mapping of language-level syntax to the underlying Common Type System (CTS), the choice of a level of detail to expose the underlying CLI infrastructure to the manipulation of the programmer, and the choice of additional functionality to provide, beyond that supported directly by the CLI.
The first item is largely the same across all CLI languages. The second and third items are where one CLI language distinguishes itself from another. Depending on the kinds of problems you need to solve, you'll choose one or another language, or possibly combine multiple CLI languages. Learning C++/CLI involves understanding each of these aspects of its design.

CLI 언어의 디자인에는 모든 언어에 공통으로 적용되는 세가지 중요한 관점이있다.
첫째는 CTS (Common Type System)이라 불리는 .Net Framework가 지원하는 공통 타입 체계로 언어 레벨의 문법을 맵핑하는 것이고 둘째는 프로그래머에게 하부 구조인 CLI infrastructure로의 접근 디테일을 얼마만큼 드러낼 것인가이고 셋째는 CLI에 의해 직접적으로 지원되는 것을 넘어서는 추가적인 능력을 선택하는 것이다. 
일반적으로 첫번째 관점은 모든 CLI 언어에 공통적인 요소이다. 둘째와 셋째가 CLI 언어를 구분 짓게 되는 요소들인데 어떤 문제에 직면했느냐에 따라서, 또 어떤 언어를 선택하느냐에 따라서 당신은 여러개의 CLI 언어를 사용할지도 모른다. C++/CLI를 배운다는 것은 이 모든 세가지 디자인 관점을 이해하는 것을 필요로 한다. 즉, 이해력 만렙 찍어야 한다. 
참고로 CTS는 .NET Framework가 가지고 있는 한가지의 요소로써 서로 다른 언어들이 표현하는 같은 형태의 타입들을 모아모아 일관된 형태로 표현하는 일종의 명세(Specification)같은 거라고 생각하면 대략 괜찮다. 사실, 모든 발전된 형태의 언어나 프레임워크는 새로운 것이 아니라 기존의 것을 잘 포장하고 싸놓은 것이다. C++이 추상화와 상속을 통해 객체 지향을 하는 것처럼 .NET Framework도 언어들 간에 추상화와 단일화된 규약을 통해 통합을 시도한 것이라고 보면 된다. 

Mapping C++/CLI to the CTS?
It is important when programming C++/CLI to learn the underlying CTS, which includes these three general class types:
  • The polymorphic reference type, which is what you use for all class inheritance.
  • The non-polymorphic value type, which is used for implementing concrete types requiring runtime efficiency, such as the numeric types.
  • The abstract interface type, which is used for defining a set of operations common to a set of either reference or value types that implement the interface.
CTS는 다음과 같은 세가지 일반적 클래스 타입을 가진다.
- 다형적 레퍼런스 타입: 클래스 상속을 위해 사용되는 것
- 다형적이지 않은 값 타입: 이러한 녀석은 runtime 효율성을 위해서 필요한 concrete type들인데 예를 들자면 숫자 타입들 (int, float)
- 추상적 인터페이스 타입: 이것은 인터페이스를 구현하는 레퍼런스나 값 타입의 공통된 operation들을 정의하기 위해 사용되는 것
말은 참 어렵게 써놓긴 했는데 단순하게 생각하면 C++ 클래스의 상속과 추상 클래스를 떠올리면 거의 맞아 떨어진다. 

와우, 아래의 글을 좀 읽어봤는데 생각보다 내용이 깊다. 물론 CLI와 .NET Framework의 관계를 이해하는데는 엄청난 도움이 될 것이 분명하지만 이 글을 읽게된 이유는 코드 프로젝트 때문이기 때문에 일단 여기서 멈추고 나머지 부분은 차후에 번역해야 겠다.
This design aspect, the mapping of the CTS to a set of built-in language types, is common across all CLI languages although, of course, the syntax varies in each CLI language. So, for example, in C#, you would write
abstract class Shape { ... } // C#
to define an abstract Shape base class from which specific geometric objects are to be derived, while in C++/CLI you write
ref class Shape abstract { ... }; // C++/CLI
to indicate the exact same underlying CLI reference type. The two declarations are represented exactly the same way in the underlying IL. Similarly, in C#, you write
struct Point2D { ... } // C#
to define a concrete Point2D class, while in C++/CLI you write:
value class Point2D { ... }; // C++/CLI
The family of class types supported with C++/CLI represents an integration of the CTS with the native facilities, and that determines your choice of syntax. For example:
class native {};
value class V {};
ref class R {};
interface class I {};
The CTS also supports an enumeration class type that behaves somewhat differently from the native enumeration, and support is provided for both of those as well:
enum native { fail, pass }; 
enum class CLIEnum : char { fail, pass}; 
Similarly, the CTS supports its own array type that again behaves differently from the native array. And again Microsoft provides support for both:
int native[] = { 1,1,2,3,5,8 }; 
array<int>^ managed = { 1,1,2,3,5,8 };
No CLI language is closer to or more nearly a mapping to the underlying CTS than another. Rather, each CLI language represents a view into the underlying CTS object model.

CLI Level of Detail
The second design aspect that must be considered when designing a CLI language is the level of detail of the underlying CLI implementation model to incorporate into the language. What kind of problems will the language be tasked to solve? Does the language have the tools necessary to do this? Also, what sort of programmers is the language likely to attract?
Take, for example, the issue of value types occurring on the managed heap. Value types can find themselves on the managed heap in a number of circumstances:
  • Through implicit boxing, when an instance of a value type is assigned to an Object or when a virtual method is invoked through a value type that is not overridden.
  • When that value type is serving as a member of a reference class type.
  • When that value type is being stored as the element type of a CLI array.
Whether the programmer should be allowed to manipulate the address of a value type of this sort is a CLI language design consideration that must be addressed.

What Are the Issues?
Any object located on the managed heap is subject to relocation during the compaction phase of a sweep of the garbage collector. Any pointers to that object must be tracked and updated by the runtime; the programmer cannot manually track it herself. Therefore, if you were allowed to take the address of a value type that might be on the managed heap, there would need to be a tracking form of pointer in addition to the existing native pointer.
What are the trade-offs to consider? On the one hand, there's simplicity and safety. Directly introducing support in the language for either one or a family of tracking pointers makes it a more complicated language. By not supporting this, the available pool of programmers is expanded because less sophistication is required. In addition, allowing the programmer access to these ephemeral value types increases the possibility of programmer error—she may purposely or inadvertently do dangerous things to the memory. By not supporting tracking pointers, a potentially safer runtime environment is created.
On the other hand, efficiency and flexibility must be considered. Each time you assign the same Object with a value type, a new boxing of the value occurs. Allowing access to the boxed value type allows in-memory update, which may provide significant performance improvements. Without a form of tracking pointer, you cannot iterate over a CLI array using pointer arithmetic. This means that the CLI array cannot participate in the Standard Template Library (STL) iterator pattern and work with the generic algorithms. Allowing access to the boxed value type allows significant design flexibility.
Microsoft chose to provide a collection of addressing modes that handle value types on the managed heap in C++/CLI:
int ival = 1024;
int^ boxedi = ival; 

array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};
interior_ptr<int> begin = &ia[0];

value struct smallInt { int m_ival; ... } si;
pin_ptr<int> ppi = &si.m_ival;
The typical C++/CLI developer is a sophisticated system programmer tasked with providing infrastructure and organizationally critical applications that serve as the foundation over which a business builds its future. She must address both scalability and performance concerns and must therefore have a system-level view into the underlying CLI. The level of detail of a CLI language reflects the face of its programmer.
Complexity is not in itself a negative quality. Human beings are more complicated than single-cell bacteria, and that is certainly not a bad thing. However, when the expression of a simple concept is made complicated, that is usually considered to be a bad thing. In C++/CLI, the CLI team has tried to provide an elegant way to express complex subject matter.

Additional Functionality
A third design aspect is a language-specific layer of functionality above and beyond what is directly supported by the CLI. This may require a mapping between the language-level support and the underlying implementation model of the CLI. In some cases, this just isn't possible because the language cannot intercede with the behavior of the CLI. One example of this is the virtual function resolution in the constructor and destructor of a base class. To reflect ISO-C++ semantics in this case would require a resetting of the virtual table within each base class constructor and destructor. This is not possible because virtual table handling is managed by the runtime and not by the individual language.
So this design aspect is a compromise between what would be preferable to do, and what is feasible. The three primary areas of additional functionality that are provided by C++/CLI are the following:
  • A form of Resource Acquisition is Initialization (RAII) for reference types, in particular, to provide an automated facility for what is referred to as deterministic finalization of garbage- collected types that hold scarce resources.
  • A form of deep-copy semantics associated with the C++ copy constructor and copy assignment operator; however, these semantics could not be extended to value types.
  • Direct support of C++ templates for CTS types in addition to the CLI generic mechanism. In addition, a verifiable version of the STL for CLI types is provided.
Let's look at a brief example: the issue of deterministic finalization. Before the memory associated with an object is reclaimed by the garbage collector, an associated Finalize method, if present, is invoked. You can think of this method as a kind of super-destructor since it is not tied to the program lifetime of the object. This is called finalization. The timing of just when or even whether a Finalize method is invoked is undefined. This is what is meant by nondeterministic finalization of the garbage collector.
Nondeterministic finalization works well with dynamic memory management. When available memory gets sufficiently scarce, the garbage collector kicks in and solves the problem. Nondeterministic finalization does not work well, however, when an object maintains a critical resource such as a database connection, a lock of some sort, or perhaps native heap memory. In this case, it would be great to release the resource as soon as it is no longer needed. The solution that is currently supported by the CLI is for a class to free the resources in its implementation of the Dispose method of the IDisposable interface. The problem here is that Dispose requires an explicit invocation, and therefore is not likely to be invoked.
A fundamental design pattern in C++ is the aforementioned Resource Acquisition is Initialization, which means that a class acquires resources within its constructor. Conversely, a class frees its resources within its destructor. This is managed automatically within the lifetime of the class object.
Here's what reference types should do in terms of the freeing of scarce resources:
  • Use the destructor to encapsulate the necessary code for the freeing of any resources associated with the class.
  • Have the destructor invoked automatically, tied with the lifetime of the class object.
The CLI has no notion of the class destructor for a reference type. So the destructor has to be mapped to something else in the underlying implementation. Internally, then, the compiler performs the following transformations:
  • The class has its base class list extended to inherit from the IDisposable interface.
  • The destructor is transformed into the Dispose method of IDisposable.
That represents half of the goal. A way to automate the invocation of the destructor is still needed. A special stack-based notation for a reference type is supported; that is, one in which its lifetime is associated within the scope of its declaration. Internally, the compiler transforms the notation to allocate the reference object on the managed heap. With the termination of the scope, the compiler inserts an invocation of the Dispose method—the user-defined destructor. Reclamation of the actual memory associated with the object remains under the control of the garbage collector. Figure 1 shows an example.
ref class Wrapper {
    Native *pn;
public:
    // resource acquisition is initialization
    Wrapper( int val ) { pn = new Native( val ); } 

    // this will do our disposition of the native memory
    ~Wrapper(){ delete pn; }

    void mfunc();
protected:

    // an explicit Finalize() method—as a failsafe
    !Wrapper() { delete pn; }
};

void f1() 
{
   // normal treatment of a reference type
   Wrapper^ w1 = gcnew Wrapper( 1024 );

   // mapping a reference type to a lifetime
   Wrapper w2( 2048 ); // no ^ token !

   // just illustrating a semantic difference
   w1->mfunc(); 
   w2.mfunc();

   // w2 is disposed of here
}

// 
// ... later, w1 is finalized at some point, maybe
C++/CLI is not just an extension of C++ into the managed world. Rather, it represents a fully integrated programming paradigm similar in extent to the earlier integration of the multiple inheritance and generic programming paradigms into the language. I think the team has done an outstanding job.

So, What Did You Say About C++/CLI?
C++/CLI represents an integration of native and managed programming. In this iteration, that has been done through a kind of separate but equal community of source-level and binary elements, including Mixed mode (source-level mix of native and CTS types, plus a binary mix of native and CIL object files), Pure mode (source-level mix of native and CTS types, all compiled to CIL object files), Native classes (can hold CTS types through a special wrapper class only), and CTS classes (can hold native types only as pointers). Of course, the C++/CLI programmer can also choose to program in the CLI types alone, and in this way provide verifiable code that can be hosted, for example, as a stored procedure in SQL Server 2005.
So, returning to the question, what is C++/CLI? It is a first-class entry visa into the .NET programming model. With C++/CLI, there is a C++ migration path not just for the C++ source base but for C++ expertise as well. I find great satisfaction in that.

posted by 대갈장군
2013. 4. 13. 05:34 프로그래밍/MSDN

요즘 작업중인 프로젝트는 C++/CLI를 이용한 몇개의 프로젝트인데 중간 중간 C# 폼이 사용된다. 물론 Managed code와 Unmanaged code (Native code)를 함께 사용하기 위해서는 C++/CLI를 사용해야 한 다는 것 쯤은 나도 알고 있지만 왜 그럴까에 대한 진지한 생각은 해본적이 없다.


Stackoverflow 사이트를 돌아다니다 보니 좋은 글이라고 링크해 놓았길래 한번 번역해본다.


출처 - http://msdn.microsoft.com/en-us/magazine/dd315414.aspx


1. 언제 Managed-Native Interop이 사용되어야 하는가?

뭐 주절주절 써놓았는데 중간에 딱 한 문단이 마음에 확 와닿는다. 


These three applications address the three most common reasons to use interop: to allow managed extensibility of preexisting native applications, to allow most of an application to take advantage of the benefits of managed code while still writing the lowest-level pieces in native code, and to add a differentiated, next-generation user experience to an existing native application.


세가지 예시를 들면서 언제가 Managed 코드와 Native 코드를 섞어서 쓰는게 좋은가에 대해서 설명한 다음에 위와 같이 정의했다. 


"이러한 세가지 어플리케이션은 interop (Managed code와 Native code를 섞어쓰는 것을 말함)를 사용하는 가장 흔한 세가지 이유를 나타낸다: 원래 존재하던 native 어플리케이션에 managed 확장 가능성을 허락해 주는 것이 첫째이고, 둘째는 native 코드의 저레벨 코딩을 계속 쓰면서 동시에 managed code의 이점을 취할 수 있는 것이고, 마지막으로 셋째는 현존하는 native 어플리케이션에 차별화된, 또는 차세대 사용자 경험을 추가할 수 있기 때문이다."


영어를 한국어로 번역하다보면 적절한 한국어 찾기가 뜻을 이해하는 것보다 훨씬 더 어렵다. 젠장. 


2. Interop Technologies: 세가지 경우


There are three main interop technologies available in the .NET Framework, and which one you choose will be determined in part by the type of API you are using for interop and in part by your requirements and need to control the boundary. Platform Invoke, or P/Invoke, is primarily a managed-to-native interop technology that allows you to call C-style native APIs from managed code. COM interop is a technology that allows you either to consume native COM interfaces from managed code or export native COM interfaces from managed APIs. Finally there is C++/CLI (formerly known as managed C++), which allows you to create assemblies that contain a mix of managed and native C++ compiled code and is designed to serve as a bridge between managed and native code.


.NET 프레임워크에서는 세가지의 interop 기술들이 사용가능한데 다음과 같다.

첫째는 Platform Invoke 혹은 P/Invoke라고 불리는 기술인데 이것은 주로 managed code에서 C-style의 native APIs (C 함수들)를 불러다 쓰기 위한 용도다. 

둘째는 COM interop인데 이것은 native COM 인터페이스를 managed code로 부터 사용하거나 혹은 native COM 인터페이스를 managed APIs(함수들)로 부터 Export 하기 위해서 쓴다. 마지막으로 대망의 C++/CLI가 있는데 (과거에는 managed C++로 명명되었죠) 이것은 manged code와 native C++ code가 공존하는 형태로써 managed와 native 코드 간의 '다리'역활을 위한 용도로 디자인 되었다.


시간만 많으면 세가지 용도 모두에 대해서 쓰고 싶지만 일단 중요한게 C++/CLI이니까 그것만 자세히 살펴보겠다.


3. Interop Technologies: C++/CLI


C++/CLI is designed to be a bridge between the native and managed world, and it allows you to compile both managed and native C++ into the same assembly (even the same class) and make standard C++ calls between the two portions of the assembly. When you use C++/CLI, you choose which portion of the assembly you want to be managed and which you want to be native. The resulting assembly is a mix of MSIL (Microsoft intermediate language, found in all managed assemblies) and native assembly code. C++/CLI is a very powerful interop technology that gives you almost complete control over the interop boundary. The downside is that it forces you to take almost complete control over the boundary.
C++/CLI can be a good bridge if static-type checking is needed, if strict performance is a requirement, and if you need more predictable finalization. If P/Invoke or COM interop meets your needs, they are generally simpler to use, especially if your developers are not familiar with C++.
There are a few things to keep in mind when considering C++/CLI. The first thing to remember is that if you are planning to use C++/CLI to provide a faster version of COM interop, COM interop is slower than C++/CLI because it does a lot of work on your behalf. If you only loosely use COM in your application and don't require full fidelity COM interop, then this is a good trade-off.
If, however, you use a large portion of the COM spec, you'll likely find that once you add back the pieces of COM semantics you need into your C++/CLI solution, you'll have done a lot of work and will have performance no better than what is provided with COM interop. Several Microsoft teams have gone down this road only to realize this and move back to COM interop.
The second major consideration for using C++/CLI is to remember that this is only intended to be a bridge between the managed and native worlds and not intended to be a technology you use to write the bulk of your application. It is certainly possible to do so, but you'll find that developer productivity is much lower than in a pure C++ or pure C#/Visual Basic environment and that your application runs much slower to boot. So when you use C++/CLI, compile only the files you need with the /clr switch, and use a combination of pure managed or pure native assemblies to build the core functionality of your application.

C++/CLI는 원래 native와 managed 세계를 이어주는 브릿지 (다리) 역활 용으로 디자인 되었다. 그리고 이것은 managed 와 native C++ code 둘 다 동시에 하나의 공통 어셈블리 (심지어는 같은 클래스로) 컴파일 될 수 있게 해주었고 standard C++ 호출을 어셈블리의 두 영역간에 할 수 있게 해주었다. C++/CLI를 사용할때 당신은 어셈블리의 어떤 부분이 managed인지 혹은 native인지 결정할 수 있다. 이로써 결과물로 나오는 어셈블리는 MSIL (Microsoft intermediate language)와 native assembly 코드의 짬뽕이 된다. (MSIL은 managed code를 위해 사용되는 .NET Framework의 기반 언어 기술이다.) C++/CLI는 매우 강력한 interop 기술로써 당신에 거의 완벽에 가까운 interop 경계 컨트롤을 제공한다. (내 맘대로 왔다리 갔다리 할수 있다는 말). 문제는 모든 컨트롤을 제공하므로 일일이 내가 알아서 다 컨트롤 해야 한다.... 젠장 장점이 단점이네?ㅋㅋ


C++/CLI는 만약 어느 정도의 퍼포먼스가 요구되고 보다 예측 가능한 결과가 필요할 때, 그리고 static-type 체킹이 필요한 경우 좋은 다리 역활을 한다. 만약 P/Invoke나 COM interop이 내가 요구하는 것을 충족한다면 C++/CLI보다는 P/Invoke나 COM이 더 사용하기 쉽다. 특히 C++을 잘 모른다면... 더더욱.


C++/CLI을 사용할 때 명심해야 할게 몇가지 있다. 첫째는 C++/CLI가 COM interop보다는 빠르다는 점이다. (왜 같은 의미의 문장을 두번 이어서 썼는지 모르겠지만 암튼 C++/CLI가 COM보다 빠르다는 이야기) 왜냐면 COM은 사용자를 대신해서 많은 업무를 수행하기 때문이다. 만약 COM을 아주 약하게 사용한다면 (쬐끔만), 그러면 COM 쓰는 것도 괜찮다.


하지만 만약 COM의 많은 부분을 가져다 쓰게 되면 C++/CLI interop은 사실상 무용지물이 된다. 속도를 위해서 C++/CLI를 선택했으나 COM이 전체적인 퍼포먼스를 낮추기 때문에 애시당초 걍 COM interop으로 가는게 더 좋다는 말이네. 


C++/CLI를 사용할때 두번째 주의할 것은 이 interop은 다리 역활로 만들어진 것이지 프로그램을 그렇게 작성하시오라고 만든 것이 아니라는 점이다. 뭐 이건 당연한 이야기다. C++로 다 짤수 있는 프로그램을 굳이 C++/CLI로 짜서 다른 managed code랑 썪어 버릴 필요가 없다는 말이다. 이 점이 사실 뼈아프게 들리는 이유는 C++로 작성된 거대한 프로그램에서 몇가지 추가하고 싶은 기능이 있는데 그것이 C#과 같은 managed code로 작성하는 것이 내 입장에서는 더 편하게 느껴질때가 종종 있다. 이때 좀 갈등을 하는데 사실 그냥 좀 불편하더라도 Windows API를 이용하는 것이 C#을 이용하는 것보다 나은것 같다.


C#의 문제점은 폼안에 존재하는 각각의 컨트롤이 폼이 소유하고 있는 스레드를 제외하고는 스레드에 안전하지 않다는 점이다. 즉, C#은 설계 당시부터 다른 언어 (특히 C++)와의 섞어 쓰기에 대해 큰 고려를 안했던 것 같다. 물론 돌아가는 방법을 제공하기는 하지만 좀 완벽하지 못하다.


오늘은 여기까지만 쓰고 다음에 나머지 부분에 대해서 마무리하도록 해야겠다.

posted by 대갈장군
2012. 12. 14. 00:09 프로그래밍/MSDN

http://msdn.microsoft.com/en-us/library/dd293574(v=vs.100).aspx


Visual C++ 2010에서 작성한 C++ 프로그램을 다른 컴퓨터에서 실행하기 위해서는 작성한 어플리케이션, 그리고 어플리케이션을 돌리기 위해 필요한 라이브러리 파일을 같이 제공해야만 한다. 요렇게 하기 위한 방법이 세가지 있다.


1. Central Deployment 

중앙 배포 방식이다. 컴퓨터의 운영체제가 설치된 하드 디스크의 Windows 폴더 안에 보면 System32 폴더가 있다. 요 폴더에 들어가면 각종 DLL들이 무수히 많은데 바로 여기가 윈도우즈 운영체제가 사용하는 '중앙 통제 센터'이다. 여기다가 어플리케이션을 돌리는데 필요한 DLL들을 복사해 넣으면 내가 만든 어플리케이션이 문제 없이 작동한다. 바로 이때 사용되는 것이 VCRedist_archtecture.exe 파일인데 인터넷에서 Visual C++ 2010 Redistribution Package 라고 치면 좌롸롸롹 뜨는 것들이다. 단순히 exe 파일이므로 그냥 다운 받아서 더블 클릭하면 알아서 설치한다. 


이 방식의 장점은 중앙의 통제된 한 곳에 모든 것을 다 집어 넣기 때문에 업데이트나 갱신이 용이하다는 점이다. 보안 취약점이 발견되었을때 단순히 DLL 파일 하나를 업데이트 함으로써 그 DLL을 사용하는 모든 프로그램의 보안 취약점을 한 방에 해결할 수 있다.


2. Local Deployment

지역 배포 방식이다. 어플리케이션이 존재하는 폴더 내부에 필요한 파일들을 복사해서 넣는 방법이다. 


이 방식은 쉽고 간단하지만 문제점으로는 업데이트가 자동으로 안되므로 유지 보수를 위해서는 DLL을 업데이트 하는 방법을 제공해야 한다는 점...


3. Static Linking

이건 정적 링크 방식이다. 어플리케이션 자체에 라이브러리를 심어버리는 방식이다. 장점은 당연히 DLL 문제에서 완전 해방 된다는 점이다. 다만 문제점은 마이크로소프트 업데이트가 만약 내가 링크한 라이브러리를 업데이트 해버리면 어플리케이션이 업데이트된 라이브러리를 사용할 수 없다는 점이다. 결론적으로 보안 취약성이 문제이군.


참, 그리고 재미있는 부분이 있는데, Visual C++ 2008과 2010의 가장 큰 차이점을 설명하고 있는데, 재미있는군.


우선, Visual C++ 라이브러리는 더이상 manifest에 의존하지 않는다. 그리고 더이상 WinSxS 폴더에 설치되지 않는다. 헐, 그래? 그러고 보니 2010으로 컴파일한 실행 파일 폴더에 manifest 가 없네? 


또한 작성된 응용 프로그램이 더이상 manifest 정보를 요구하지 않는단다. 흠, 그렇다면 필요한 라이브러리의 정보가 어디에 포함되는 걸까? 명세서 (manifest) 파일이 존재 하지 않는다면 아마도 실행파일 내부에 어딘가 설명되는가 본데?

posted by 대갈장군
2008. 10. 22. 06:20 프로그래밍/MSDN

참고 자료: http://msdn.microsoft.com/en-us/library/abx4dbyh(VS.80).aspx

관련글: http://diehard98.tistory.com/entry/MSDN-Manifest-파일 (manifest 파일에 대한 설명 및 재배포 방법)


Visual Studio 2005의 C++를 이용해서 프로그램을 작성하다 보면 프로젝트의 Properties 에서 Code Generation 속성에 보면 /MT, /MTd, /MD, /MDd 와 같은 다양한 형태의 옵션 설정이 가능하다.

이것을 어떻게 설정하는가에 따라서 내가 만드는 프로그램이 어떤 DLL과 연결이 되는지정적(Static)으로 라이브러리를 링크 할 것인지 아니면 동적(Dynamic)으로 라이브러리를 링크 할 것인지를 결정할 수 있다.

이 속성을 우습게 보았다가 나는 큰 코 다쳤다. =_+ 헐

Standard C++ Library

Characteristics

Option

Preprocessor directives

LIBCPMT.LIB

Multithreaded, static link

/MT

_MT

MSVCPRT.LIB

Multithreaded, dynamic link (import library for MSVCP80.dll)

/MD

_MT, _DLL

LIBCPMTD.LIB

Multithreaded, static link

/MTd

_DEBUG, _MT

MSVCPRTD.LIB

Multithreaded, dynamic link (import library for MSVCP80D.DLL)

/MDd

_DEBUG, _MT, _DLL


일단, 위의 테이블은 내가 프로그램에서 Standard C++ Library에 해당되는 헤더 파일을 불러왔을 경우에 연결되는 라이브러리들을 보여준다.

Standard C++ Library라 함은 http://en.wikipedia.org/wiki/C_Standard_Library 에 나와 있는 헤더들을 말한다. 아마도 기본적인 프로그램을 작성한다면 최소한 저것 중에 하나는 들어가게 된다.

물론 아닌 사람도 있겠지만... 헐헐헐...

아무튼, 그런 경우가 아니면, 링크 되는 라이브러리가 바뀌긴 한다만 영어 철자 하나 차이로 바뀐다. 일단은 Standard C++ Library를 사용한다고 생각하자.

원래 과거에는 (정확히는 모르겠지만 Visual Studio 6 까지 일것이다.) Single Thread 옵션도 지원이 되었다. 하지만 Visual Studio 2005에서는 무조건 Multi Thread로 프로그램을 작성해야 한다.

앞서 내가 작성한 글에도 있지만 C 런타임 라이브러리가 옛날에 작성될 때 Thread란 개념이 없다보니 Multi Thread로 동작하는 프로그램의 경우 '자원 경쟁'에 의한 오작동 했다.

아마도 이런 문제를 싸그리 날려버리기 위해서 2005에서는 오직 Multi Thread만 지원하나 보다.

표에 보면 /MT와 /MTd가 있는데 이 소문자 d는 Debug 버전을 의미한다. 나도 사실 Debug와 Release의 차이를 전혀 모르다가 다른 컴퓨터에 내 프로그램을 설치하면서 그 차이를 알게 되었다.

간단히 말해서 Debug 버전은 해당 Visual Studio가 설치된 컴퓨터에서만 '테스트'용으로 사용한다. 즉, Visual Studio가 설치안된 컴퓨터에다가 Debug 버젼으로 완성된 .exe 파일 들고가서 실행해봐야 실행이 안되고 The system cannot run this program. 어쩌구 저쩌구 그런다.

왜냐면 Debug 버젼은 실행시 링크되어야 할 DLL 파일이 Visual Studio가 설치되지 않은 컴퓨터의 .NET Framework Assembly 폴더인 Windows/WinSxS 폴더에 존재하지 않기 때문이다.

또한 ELUA인가 뭔가에 의해서 Debug 버전의 DLL 파일은 재배포가 금지되어 있다. 즉, 배포를 목적으로 한다면 Release 버전으로 작성하라~ 이말이다.

아무튼, 테이블에서 보이는 한가지 더 특별한 차이는 Static 과 Dynamic 링크이다. Static 링크는 실행 파일에다가 필요한 DLL 을 같이 묶어서 .exe 파일로 만든다. (개념적으로 그렇긴 한데 DLL 전체를 포함시키는 것인지 아닌지는 잘 모르겠다.)

그리고 Dynamic의 경우는 Static과는 달리 실행을 할때 찾아서 링크를 시키게 되므로 반드시 실행 파일과 같이 제공되어야 한다.

고로 배포를 목적으로 하는 프로그램이라면 반드시 /MD 옵션으로 만들어야 하고 따라서 MSVCPRT.LIB가 프로그램 작성시 링크가 된다.

그런데 프로그램이 만들어진 다음에 다른 컴퓨터에서 이것을 실행하기 위해서는 MSVCPRT.LIB가 필요한 것이 아니고, MSVCP80.DLL이 필요하다.

내가 생각할때 이 두 파일의 차이는 '개발자용'과 '사용자용'의 차이이다.

MSVCPRT.LIB는 Visual Studio를 설치해야만 제공되는 '개발자용' 라이브러리인 반면, MSVCP80.DLLMicrosoft Visual C++ 2005 Redistribution Package SP1 (x86)을 설치하면 Windows/WinSxS 폴더에 자연스럽게 설치가 되는 DLL이다. 즉, '사용자가 프로그램을 돌리기 위해서 배포되는 DLL'이다.

추가로 MSDN에서 주의를 요하는 부분은 프로그램을 작성할 때 동적으로 라이브러리를 링크하는 DLL과 동적으로 라이브러리를 링크하는 DLL 혹은 프로그램이 덩어리로 묶여서 실행될 때 발생할 수 있는 문제점을 지적하고 있다.

물론 정적으로 링크된 라이브러리가 홀로 사용된다면 아무 문제 없겠지만 DLL 간에 데이터 전송이나 호출이 발생하게 되면 링크된 라이브러리가 반드시 일치한다는 보장이 없기 때문에 문제가 될수도 있다는 것을 말하고 있다.

따라서 왠만하면 모든 것을 동적으로 링크하는게 좋다 이말이군... 업데이트하기에도 좋고, 일관성도 있고... 그렇겠군. :)

결론적으로 말하자면,

1. Visual Studio 2005는 Multi Thread로 코드를 작성하는 것만 지원한다.
2. 배포를 목적으로 한다면 반드시 /MD 옵션을 사용하라.
3. 설치할 컴퓨터에 Visual Studio가 설치되어 있지 않다면 Redistribution Package 를 다운 받아서 설치하라.
4. 되도록이면 작성하는 모든 프로그램 및 DLL은 /MD 옵션으로 통일하라.
posted by 대갈장군
2008. 10. 17. 07:00 프로그래밍/MSDN

COM 그 이름만 들어도 살짝 현기증이 날 듯한 이름... Microsoft의 작명 센스는 끝내준다. 몰라도 들어본 것 같고 알아도 헷갈리게 만드는 놀라운 작명 센스... /후아

내 블로그에 남기는 글들은 특정한 기술에 대한 'Teaching' 이 아닌 'Talking' 일 뿐이므로 머리를 쪼그라 들게 하는 깊이 있는 이야기보단 지나가는 이야기이지만 보다 쉽게 이해하기 위해 쓰는 것이므로 이해해 주길 바랍니다.

COM 에 대한 첫 만남은 DirectShow에서 시작되었다. 어찌하다 보니 DirectX를 사용해야 하는 상황이 생겼고 나중에는 DirectShow를 사용해야 하는 상황이 왔던 적이 있다.

물론 전혀 즐겁지 않았다.. -_- DirectShow의 자료를 뒤적거리다 첫 대면을 하게된 COM이란 놈은 DirectShow의 '근간' 이요 '근본' 이며 '바탕'이므로 꼬옥! 알아야 한다는 거였다. 줴엥장.

그때는 시간이 없던 관계로 COM에 대한 이해를 할 시간도 없이 '어떻게' 사용하는가만 집중적으로 판다음 완전히 잊어버리고 살았다.

하지만, 오늘 MSDN에서 COM에 대한 문서를 읽어보니 내가 참 무식했구나 하는 생각이 들었다. 물론 '방법'만 알면 오케이인 일도 많지만 '원리'와 함께 알면 절대 잊어버리지도 않을 뿐더러 연결되는 기술이나 개념에 대한 이해가 훨씬 더 쉽다. 그래서 많은 시간을 투자해가면서 MSDN의 미로를 헤메는 중이다.

COM의 정의는 서로간에 의사 전달이 가능한 프로그램의 구성 요소를 플랫폼에 독립적이고 객체 지향적으로 만들어주는 일종의 System이다.


이말 읽고 바로 이해되는 사람이 있다면 이글을 읽어볼 필요도 없는 사람이라고 생각한다.-_- 기본적인 프로그램만 짜본 사람의 입장으로써는 이글은 '김밥 옆구리 터지는' 소리다. 한마디로 뭔소린지 잘 모른다.

내가 생각하는 COM의 정의는 '뇌를 가진 C++ 클래스' 정도? 헐헐헐.. 이건 또 뭔소리? 라고 할수 있다. 이제 차근 차근 설명해 보겠다. 왜 COM이 뇌를 가진 C++ 클래스라 생각하는지.

우선 Interface와 Methods가 뭔지 알아야 하는데... 흠... 이걸 여기서 설명하게 되면 COM의 개념만 쉽게 풀고자 했던 내 의도가 딱딱한 MSDN 문서를 읽어주는 것과 다를게 없어지는 것 같아서 안쓰는게 좋을것 같다.

우선 COM의 단어적 의미를 집어내 보면 '요소 객체 모델'인데 모델은 어떤 체계를 말하므로 빼버리고 C가 의미하는 Component인 요소에 집중해 보자.

내가 앞서 말했듯이 DirectShow는 COM을 기반으로 하는데 DirectShow로 짜는 프로그램의 구성을 보게 되면 마치 전산시간에 배웠던 Flow Chart를 그리는 것과 똑같다.

전산 시간에 네모를 그러서 그 사이를 화살표로 연결하여 그렸던 흐름도 혹은 논리 흐름도를 기억하는가?

DirectShow의 시작은 입력과 출력을 가지는 Filter 라고 불리는 사각형들이 서로 물고 물리는 관계를 형성함으로써 시작된다. 하나의 Filter 출력이 다른 Filter의 입력으로 들어가게 되고 또 그 출력이 다른 입력으로 들어간다.

이런식으로 Data가 '물 흐르듯이' Stream을 가지게 되고 이처럼 쉽게 하나의 프로그램을 완성 할수 있다.

바로 여기서 보면 각각의 Filter가 하나의 COM이고 바로 '요소' 인 것이다. 프로그램을 구성하는 본질적인 요소임과 동시에 마치 레고 블록처럼 독립적이어서 가져다 붙여주고 서로 잘 이어주기만 하면 되는 것이다.

사실 이것은 JAVA의 상속과 크게 다르지 않다. C++의 상속과 재활용성과도 일맥 상통한다. 하지만 결정적인 차이가 있으니 바로 '뇌를 가진' 이라는 단어다.

COM으로 만들어진 객체는 이른바 QueryInterface라는 함수를 가지고 있다. 이건 무조건 모든 COM 객체가 가지는 함수로써 마치 C++의 생성자/파괴자와 같이 무조건 필요한 놈이다.

이 QueryInterface 함수의 특이점은, 프로그래머는 반드시 이 함수를 이용해서 COM객체의 다른 함수의 포인터 (진입점) 을 얻어와야 한다는 것이다.

Query의 단어 뜻 그 자체가 '질의한다'라는 의미로써 프로그래머가 COM객체에게 '야, 너 이런이런 함수 지원해주니?' 라고 물어보는 것이다.

더 놀라운 것은 이 COM 객체는 프로그래머가 저렇게 물어왔을때, 만약 지원이 가능하면 해당 기능으로 들어가는 문 (포인터)를 알려주고 만약 지원이 안되면 '안되요!' 라고 하면서 NULL 을 리턴한다는 것이다.

이게 뭐가 대단한가? 라고 의아해 한다면 당신은 아마도 잘못된 함수의 호출로 인해 공포의 퍼런 화면을 한번도 안띄워 본 사람! 우후훗!

뇌를 가졌다고 말하는 이유는 바로 이 기능 때문이다. COM 객체는 스스로가 할수 있는 일과 없는 일은 명확히 구분하여 프로그래머의 사용 미숙이나 강제 호출에도 스스로 'NO' 라고 말할 수 있기 때문에 나는 COM이 생각하는 클래스라고 말했다.

사실 여전히 COM 객체는 프로그래머에게 종속적이기는 하지만 스스로의 독립성과 안전성을 지키려고 하는 것은 예전에 C++의 클래스에서는 찾아보기 힘든 점이다.

MSDN에서 말하기를 'COM 객체는 프로그래머 (혹은 클라이언트) 와 Negotiate 한다.' 라고 했다. 즉, '협상'을 한다는 말인데 프로그래머와 대등한 입장에서 서로 대화를 주고 받는다는 의미라 하겠다. 세상 참 좋아졌다... 컴퓨터가 나랑 협상을 다하고... ㅋㅋ

많은 사람이 착각을 하는 것이 COM이 그 자체로 또다른 '언어'라고 생각하는 것인데 (C나 C++처럼) 사실 그렇지 않다고 생각한다.

다만 COM과 같은 '체계' 혹은 '모델'은 사용자에게 보다 나은 방법의 프로그램 설계 방식을 '제안'하고 있을 뿐이다.

그것을 이해한다면 COM이나 MFC에 대해 겁부터 덜컥 먹을 일이 전혀 없다. C와 C++부터 차근 차근 알아나가면 점점 탄력이 붙어 나중에는 자연스레 이해할 수 있게 된다.

이제 결론을 말하자면,

COM은 '생각하는' 혹은 '협상할줄 아는' 함수들의 집합체로써 사용자와 '대화'를 통해 만들어져 있는 수많은 좋은 함수들을 공짜로 '제공'해준다.


다만 이 COM을 제대로 사용하기 위해서 반드시 따라야 하는 조건들만 잘 지키면 무리없이 잘 사용할 수 있다는 점.

그리고 COM의 장점이라면,

무조건 프로그래머의 명령에 'YES"라고 대답하지 않고 우선 할 수 있는건가 아닌가 찾아보는 '똑똑함' 이랄까? :)
posted by 대갈장군
2008. 10. 16. 02:47 프로그래밍/MSDN
앞서 본 DLL 이야기 처럼 DLL은 많은 장점을 가지고 있으나 관리하기 쉽지 않다는 문제가 있다.

사람들이 흔히 만드는 DLL의 오류나 에러는 3가지 타입으로 분류된다고 하는데,

Type 1은 사용자나 일부 잘못된 프로그램이 OS가 사용하는 핵심 DLL의 버전을 지 맘대로 바꿔버릴는 경우를 말하는데 최신 버전의 DLL이 구버전으로 바뀌어 버림으로써 오는 문제다.

이 Type 1 DLL 에러는 과거 Windows 98 시절에는 셀수없이 많았다고 한다. 하지만 지금은 그 자취를 거의 감추었다는데 그 이유는 차차 알아보자.

Type 2Side-Effect 라 불리는 오류인데 이것은 DLL이 업데이트 되면서 (최신 버전으로 바뀌면서) 사용자가 의도와는 다르게 변경된 DLL의 함수들이 예상치 못한 작동을 하는 것이라는데 이것은 뭐 DLL을 만든사람의 실수가 아닌가 싶다만... 구버전의 DLL로 잘 작동하던 프로그램은 반드시 새 버전의 DLL과 잘 작동해야 하지만 100% 그렇지는 않다는게 문제란다.

Type 3는 가장 드물지만 DLL 자체에 버그가 있는 경우다. 물론 이건 아주아주 드물기는 해도 발생할 수 있음을 명심해야 한다. 베타 버젼을 즐겨 사용하는 사람이라면 더더욱 그렇겠지...

이런 3가지 타입의 DLL 오류의 제거를 위해서 Windows 2000에서 아주 기발하고 유용한 방법이 제시되는데 그것이 바로 WFP (Windows File Protection) 이라는 것이다.

우선 이것을 이해하기 위해서는 System DLLPrivate DLL을 알아야 한다. 하나의 컴퓨터에는 무수히 많은 DLL이 존재하는데 이것들은 딱 크게 나눠서 두가지의 DLL로 분류가 된다.

System DLL은 Windows/System32 폴더안에 주로 존재하는 '핵심적인' DLL들이다. 즉, OS가 빈번히 사용하며 여러가지의 프로그램에 대해서 많이 사용하는 DLL을 말한다.

고로 이런 System DLL들은 '잘못' 건드리게 되면 윈도우 전체가 멈춰버리거나 죽음의 '퍼런 화면'을 경험하게 될것이다.

그래서 Windows 2000부터는 이런 System DLL들은 사용자가 변경을 하려해도 DLL에 내장되어 있는 Key를 체크하여 잘못된 변경을 하더라도 윈도우가 알아서 재복사를 해버린다.

즉, Service Pack 과 같은 '시스템이 제공하는 업데이트'가 아니고서는 이 System DLL들을 변경 할수 없다. 고로 사용자의 실수로 인한 시스템 전체의 오류는 완전히 차단하는 것이다.

두번째로 Private DLL이다. 이 Private DLL은 다른말로 하면 Side-by-side DLL이다. 흐흐흐... 나는 이 말을 듣는 순간, Side-by-side Assembly가 빠악! 하고 머리에 떠올랐는데 과연 다른 사람도 그럴까? :)

이 Private DLL은 어떻게 보면 DLL의 기본 원리중에 최대 장점이었던 '통일성'을 완전히 역행하는 반역자이지만 '독립성'을 확보하게 해주는, 즉 소소한 프로그램을 많이 사용하는 나같은 프로그래머에게는 꼬옥 필요한 것이다.

원래 DLL은 통일된 하나의 DLL을 사용함으로써 모든 프로그램이 공유하는 DLL 하나를 관리함으로써 유지 보수 및 관리를 쉽게 한다는 장점이 디스크나 메모리 공간 절약보다 더 크다고 말했다.

하지만 Visual Studio를 써본 사람은 알겠지만 Visual Studio는 하나의 프로젝트를 컴파일 할때 .NET Framework가 제공하는 Assembly 들 (일종의 DLL)을 사용하는데 이놈들의 버전이 컴퓨터마다 달라서 기껏 만들어논 프로그램이 다른 컴에서 돌리면 실행조차 되지 않는 이상한 경험을 해보았을 것이다.

이때는 물론 다른 컴퓨터에 Visual Studio를 설치하거나 .NET Framework를 설치해서 업데이트 하면 되겠지만 만약 그런게 없는 컴퓨터라면 어쩔건가?

바로 이런 점이 '통일성'이 가지는 문제점이다. 다른 환경을 가지는 다른 컴퓨터에 대한 '이식성'이 떨어지는데 이걸 해결하기 위한 것이 'Static한 링크' 즉, 통일성과 반대되는 의미로 '유동성 혹은 프로그램의 독립성'이다.

이 Private DLL은 내가 실행하고자 하는 .exe파일과 같은 폴더에 존재하며 .exe 파일에 필요한 DLL 파일을 말한다. 이것은 당연히 System DLL이 아니기 때문에 사용자가 원하는, 필요로 하는 특정한 버전의 DLL을 .exe 파일과 함께 놔두면 되는 것이다.

내가 참고한 MSDN의 문서에서는 Private DLL을 사용한다는 것을 OS인 윈도우에게 알려주기 위해서는 .exe가 있는 폴더에 (Private DLL에 있는 폴더) 간단하게 실행하고자 하는 실행 파일과 같은 이름 + .local 파일을 만들어주면 된다고 되어 있는데 아직까지 이 룰이 적용되는지는 잘 모르겠다.

내가 알기로는 .exe 파일은 기본적으로 현재 실행 파일이 있는 폴더에 필요로 하는 DLL이 있는지 먼저 검색하는 것으로 알고 있다. 물론 테스트 해본적은 없다... -_-

이 Private DLL을 .exe 파일과 함께 두면 .exe 파일은 실행시 Private DLL을 읽게되고 프로그램 제작자는 이 프로그램이 잘 작동하는 Private DLL을 .exe 파일과 같이 제공하면 그만인 것이다. 이렇게 함으로써 '통일성'은 잃게 되지만 Private DLL을 사용하는 .exe 프로그램은 대신에 System DLL에 영향을 전혀 받지 않는 '독립성'을 가지게 된다.

자, 이 두가지의 DLL 분류와 WFP를 이용하면 위에서 나열한 거의 모든 Type 에러를 해결할 수 있다.

System DLL은 윈도우가 자동으로 관리함으로써 사용자의 미숙으로 인한 잘못을 예방하고, Private DLL을 이용함으로써 환경이 다른 컴퓨터로 이식성 및 독립성을 증가 시켰다.


이 문서에서는 DLL Universal Problem Solver라는 프로그램(DUPS: 둡스!?)을 이용해서 네트워크 상에 있는 모든 연결된 컴퓨터에 대해 DLL 버전을 체크하고 문제가 있는지 검사하는 프로그램에 대해 간단히 설명하고 있는데 사실 나에게는 아직 필요가 없는 듯해서 한번 읽고 넘어 갔지만, 게임방 사장님들에게는 필요한 유틸리티가 아닌가 싶다...

워낙 우리나라 게이머들께서 겜방 PC로 이것 저것 많이 테스트 하시니...  ^^
posted by 대갈장군
2008. 10. 15. 07:00 프로그래밍/MSDN

DLL HELL이라는 단어를 처음 접하고 '풋' 하고 웃었다면 당신은 이미 DLL로 인해 머리 한움큼 정도는 뽑아본 사람이 분명하다..

MSDN에서 머리에 쏙쏙 들어올만한 정보를 찾아 수없는 클릭질을 해댄지 어언 10분. 재미있는 재목의 글을 찾았다. 이름하여 THE END OF DLL HELL.

이름만 봐도 재미있을 것 같아서 차근 차근 읽어보았다.

DLL에 대해서 그 개념 정도는 충분히 이해하고 있었으나 정작 DLL이 일으켜온 문제와 어떤 방식으로 DLL의 문제를 종식시켜 나갔는지는 전혀 몰랐다. 이번을 계기로 확실히 알고 넘어가게 되었다.

DLL은 풀어서 쓰면 Dynamic Library Link인데 한국어로 하면 '동적 라이브러리 링크'이다.

C 프로그램을 작성하게 되면 우리는 생각없이 헤더 파일을 포함하는데 대표적인 헤더 파일이 바로 stdio.h파일이라던가 iostream 같은 파일들이다.

이 헤더 파일들은 실행 코드를 담고 있는, 즉 다시말해서, 실행에 필요한 모든 코드를 가지고 있는 것이 아니라 prototype만 담고 있는 글에 불과하다. 책의 '목차'에 불과한 것이다.

실제 '내용'은 바로 라이브러리가 담고 있는데 라이브러리는 참 많다... -_- 짜증나게 많고 다양하다.

대표적으로 Visual Studio 가 사용하는 라이브러리는 MSVCRT.DLL이다. 걍 이놈이란 놈이 자주 쓰인다고 알고 있자.

이 라이브러리는 수많은 윈도우 프로그램들에서 사용이된다. 프로그래머가 짜는 거의 대다수의 프로그램도 이 라이브러리를 사용하게 되는데 자 그렇다면 여기서 생각을 해보자.

만약에 저 라이브러리를 사용하는 모든 프로그램들이 저 MSVCRT.DLL을 하나씩 소유한다면 얼마나 비효율적일까?

우선 하드드라이브에 저 파일이 수십 수백개 만들어질꺼고, 게다가 내 피같은 RAM (메모리)에 저 파일이 수십 수백개가 생길것이다.

즉, 메모리 낭비요 게다가 하드디스크의 낭비다. 물론 요즘 메모리나 하드디스크가 똥값이긴 해도 그래도 낭비는 낭비다!

DLL을 개발해낸 사람의 최후의 카드는 바로 'Consistency' 즉 '일관성' 혹은 '통일성'에 있다. 바로 이것이 DLL의 가치를 드높여 주는 것인데, 이것이 무엇인가 하면...

많은 프로그램이 공유하게 되는 저 MSVCRT.DLL 파일 하나를 수정함으로써 혹은 업뎃함으로써 모든 프로그램의 동작을 수정할 수 있게된다. 즉, 사용되는 파일을 단 하나로 통일 시킴으로 얻게되는 장점이 바로 그것이다.

허나 반대로 말하면 저 파일 하나가 잘못됨으로써 저것을 사용해서 프로그램을 돌리는 모든 것이 한방에 동작을 안할 수도 있다는 말이다. 허걱. 이 얼마나 위험한 일인가...

바로 이런 문제들이 Windows 98이전에는 수도없이 빈번했으며 인증되지 않은 프로그램에 의해서 혹은 사용자의 실수에 의해서 중요한 DLL 파일이 바뀌어지는 바람에 수많은 프로그램이 갑자기 동작을 멈추게 되었고 이로인해 수많은 머리털이 뽑히거나 하드디스크의 쓸데없는 포멧으로 이어진것이다.... 헐헐헐..

이것을 어떻게 고쳤는가는... 다음 글에서 써야 겠다. 집에 가야 할 시간이 왔군요.. ^^

참조 http://msdn.microsoft.com/en-us/library/ms811694.aspx 

posted by 대갈장군
2008. 10. 14. 06:28 프로그래밍/MSDN

사실 Visual Studio 2005 (VS 2005)를 사용해 오면서 신경쓰지 않았던 부분이 바로 이 manifest file의 존재다.

아마 이글을 본사람들 중에 상당수도 '저게 뭐임?' 이라고 생각하는 사람이 많을 것이라 생각된다.

이 manifest의 중요성은 내가 만든 프로그램을 다른 컴퓨터에서 돌려볼려고 할때 나타난다. 골 깨지는 소리와 함께... 두둥.

전반적인 설명을 위해 자세한 내용은 무시하고 개념만 적어보자. 내가 잘 까먹으므로 이렇게 적어놓는게 훨씬 도움이 됨...

일단 .NET Framework를 알아야 한다. 다 알거는 없고, 이 녀석은 간단히 말해서 컴퓨터의 spec에 관계 없이 VS 2005로 작성한 프로그램이 잘 돌아가도록 해주는 놈이라고 생각하자.

그니까 int가 4바이트인 컴퓨터건 8바이트인 컴퓨터건 (32 비트 컴퓨터냐 64 비트 컴퓨터냐) 관계없이 내가 짠 프로그램이 잘 돌아가도록 해준다.

상식적으로 .NET Framework가 컴퓨터에 관계없이 내 프로그램을 잘 돌아가게 만들어 준다는 것은 .NET Framework가 어떤 공통적인 무언가를 가지고 있거나 아니면 통일된 규정에 따라서 해당 컴퓨터에 맞게 Binary code (실행 코드)를 만들어 낸다는 것이다.

사실 많은 중요하지만 이해하기 쉽지 않은 단어들이 있다. CLR, CRL, CIL, MSIL, 등등등... 이런것들은 VS 2005 책이나 VS 2008 졸라 두꺼운 책 (빨강색) 에 보면 잘 나와있다. 아니면 MSDN 혹은 Wiki를 머리 뽑으면서 읽으면 이해할 수 있다.

이런거 모두 제끼고, .NET Framework Assemblies을 보자. 이놈들은 일부만 컴파일된 dll 파일들이다. 이놈들은 프로그램이 실행되는 시점 (CLR이 Just-in-time complier를 이용해 machine code를 생성해 내는 시점) 에 호출되어 사용된다.

한마디로 필요한 정보가 저장된 도서관(library)이다. 중요한 것은 이제부터인데, VS 2005를 이용해서 프로그램을 작성하게 되면 기본적으로 VS 2005는 manifest 파일이라는 것을 만들어낸다.

이 manifest 파일은 만들어진 프로그램이 어떤 .NET Framework Assemblies를 사용하는지 그리고 그 버젼은 무엇인지, 또 의존성은 어떻게 되는지 등등등을 설명하고 있다.

한번이라도 VS2005를 이용해서 프로그램을 만들어 본 사람이라면 Debug 혹은 Release 폴더에 manifest 라는 단어가 들어가 있는 파일이 생성된 것을 보았을 것이다.

그 파일을 열어서 보게 되면 안에 내용중에 이런 부분이 있다,
<assemblyIdentity type="win32" name="Microsoft.VC80.DebugCRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>

이것이 바로 .NET Assemblies 중에 어떤 버전을 사용하고 있는지 그리고 Debug인지 Release인지, 또 x86 인지 아닌지, 게다가 publickey 까지 자세히 보여주고 있다.

여기서 주목할 것은 name과 version이다. 윈도우 폴더 아래에 있는 WinSxS 라는 폴더를 열어보자. 여기를 열어보면 저 name과 비스무리한 폴더들이 보인다. 그리고 폴더 이름뒤에 version 번호도 보인다.

바로 WinSxS 폴더가 현재 컴퓨터에 설치되어 있는 Shared Side-by-Side Assemblies를 모두 보여준다. 한마디로 이 컴퓨터에서 제공되는 모든 .NET Framework Assemblies를 담고 있는 폴더인 셈이다.



만약 어떤 프로그램을 실행했는데 터무니 없게도 This system cannot execute from this program. 이라든가 위 그림처럼 어이없는 에러가 터진다면 가장 먼저 실행 파일과 함께 있는 manifest 파일을 열어보자. 그리고 그 컴퓨터의 WinSxS 폴더를 열어서 해당 어셈블리가 존재하는지 확인하자.
사용자 삽입 이미지

만약 manifest 파일에 나와있는 Version과 같은 폴더가 존재하지 않는다면 이 컴퓨터는 프로그램을 돌리기 위한 자료가 없기 때문에 실행할 수 없다는 '어이가 없는' 에러 메세지를 내 보낸다.

이것을 해결하기 위한 방법으로는, (확인해 본적은 없지만)
1) 만약 컴퓨터에 VS 2005나 VS 2008이 설치되어 있다면 업데이트를 해라. (업데이트를 하면 새로운 버젼의 어셈블리들이 자동으로 설치된다.)
2) Manifest를 열어서 해당되는 버젼의 DLL 파일을 찾아서 실행파일이 있는 폴더에 복사해 넣어라. 이것은 100% 된다고 장담할 수 없다. .NET Framework가 설치조차 안되어 있다면 아마도 작동 안될 것이다.
3) Microsoft Visual C++ Redistribution (재배포) Package 를 다운받아 설치하라. 

3번이 가장 합리적인 방법인데 구글에서 Visual C++ Redistribution package를 검색하면 Visual Studio 2005 와 2008 버전에 대한 각각의 다운로드들이 뜬다. 이 파일을 다운로드 하여 설치하면 WinSxS 폴더에 해당 버전의 어셈블리들이 자동으로 설치되며 Visual Studio가 없는 사람들도 프로그램을 돌릴수 있게 된다.

재배포 파일의 설치가 가장 중요하다... 이 프로그램은 이 컴퓨터에서 돌아갈수 없다는 말도 안되는 문제를 거의 100% 해결해 준다.


posted by 대갈장군
2008. 7. 21. 23:45 프로그래밍/MSDN
내가 만든 프로그램이 어떤 Visual C++ Library들을 사용하는지 확인하고 싶다면 Property Pages에 있는 General Tab에 들어가보면 쉽게 알수 있다.

예를 들어서 내 프로젝트가 MFC나 ATL을 쓰는지도 쉽게 알수 있다. 만약 내가 MFC를 Dynamic Library에서 사용하고 있다면 내 프로그램은 MFC DLLs에 의존하게 된다. (mfc90.dll) 만약 내 프로그램이 MFC나 ATL을 사용하지 않는다해도 Multi-threaded debug DLL (/MDd)를 Runtime Library로 사용할 경우 프로그램은 여전히 CRT library에 의존하게 된다.

가장 좋은 방법은 depends.exe (Dependency Walker)를 실행해보는 방법이라는데 이것은 /Microsoft Visual Studio 2005/Common7/Tools/bin 에 설치 되어 있단다. 이건 Win32 SDK Tool을 설치한 경우에만 설치되어 있단다.

이 depends.exe를 실행하면 모든 로딩되는 DLLs를 다 알수 있다. (static이건 dynamic 이건간에)

이 프로그램을 실행할때 주의해야 할것은 하나의 DLL은 또다른 DLL 파일 혹은 특정 버젼의 DLL에 의존할수도 있다는 점이다.

모든 DLLs 리스트를 받고나서는 이중에서 어떤 DLLs들이 다른 컴퓨터에서 실행될때 redistricution되어야 하는지를 결정해야 한다는데 대부분의 경우는 system DLLs들은 redistribution 할 필요가 없다. 하지만 Visual C++ library는 redistribution 할 필요가 있다는 군. 아마도 이게 문제 아닐까?

'프로그래밍 > MSDN' 카테고리의 다른 글

[MSDN] /MT, /MTd, /MD, /MDd (C Runtime Library Option)  (12) 2008.10.22
[MSDN] COM (Component Object Model)  (2) 2008.10.17
[MSDN] End of DLL War!  (0) 2008.10.16
[MSDN] DLL HELL!!!! (1)  (0) 2008.10.15
[MSDN] Manifest 파일?!?!  (0) 2008.10.14
posted by 대갈장군
prev 1 next