728x90
  1. 프로그램 설치.
    1. TortoiseGit를 사용하기 위해서는 Git를 설치해야만 한다.
      Git for windows에 접속해서 최신 버전을 받아서 설치한다.

      특별히 설정할 내용이 없기 때문에 "다음"을 선택하면 된다.

      이로써 Git 기본 프로그램을 설치완료 한다.
    2. TortoiseGit 사이트에 접속한다.
      자신의 Windows 환경에 맞게 32-bit 또는 64-bit을 선택하여 다운로드 받는다.
      TortoiseGit 설치 파일과 언어팩은 함께 받는다.

      기본 설정으로만 설치하기 때문에 "다음" 버튼만 클릭한다.
      먼저, TortoiseGit를 설치하고, 언어팩을 설치한다.

      언어(한국어) 팩을 설치한다.

      언어 변경은 "마우스 우클릭" 후에 "Settings"를 선택한다.

  2. 환경 설정.
    사용자와 메일을 등록해야 한다. 등록된 사용자와 메일은 버전 관리에서 사용되므로 반드시 기술되어야 한다.
    "마우스 우클릭"한 후에

    Git 항목을 클릭해서 등록한다.
  3. 사용법.
    기본 사용을 위해서는 먼저 저장소를 생성해야 한다.
    1. 저장소 생성.
      원하는 폴더에서 "마우스 우클릭"으로 저장소를 생성한다.



      선택한 폴더 내에 ".git"폴더가 생성된다.
    2. 원격 저장소에서 가져오기(Poll).
      저장소 폴더를 선택하고, "마우스 우클릭"해서 Git Poll 명령을 실행한다.



      원격 저장소의 URL을 복사해서 붙여넣기 한다.
      SSH Key를 사용하기 위해서는 "원격 저장소 관리"를 클릭한다.

      "원격"항목에는 Project 이름을 넣고, "URL 입력"항목에는 원격 저장소의 URL을 넣는다. 
      "URL"항목에 BitBucket을 넣었는데, 정확한 이유는 모르겠다.
      "Putty 키"항목에서 SSH Private Key 파일을 지정한다.

      "확인"버튼을 클릭하면, 자동으로 원격 저장소의 프로젝트를 가져온다.

    3. 수정된 저장소를 원격저장소로 보내기(Push).


ps. 개인적으로는 한글패치는 안하는게 더 좋은 것 같다...


출처 : http://kwangho9-develope.blogspot.com/2016/11/git-clienttortoisegit.html



개념 : http://maskkwon.tistory.com/129

728x90

'Dev Tool' 카테고리의 다른 글

Dev Tool - Gold Wave  (0) 2020.04.28
Dev Tool - Inno Setup  (0) 2019.11.15
Dev Tool - CMake  (0) 2018.02.26
Dev Tool - PixelAtro App  (0) 2018.02.22
Dev Tool - VMMap  (0) 2017.12.05
728x90
728x90

'Basic Programming > C++ 11' 카테고리의 다른 글

c++11 - atomic  (2) 2022.10.13
c++11 - constexpr  (0) 2018.08.22
C++11 - final, override 키워드  (0) 2017.09.18
C++11 - std::thread  (0) 2016.12.02
C++11 - "default" and "delete" keyword  (0) 2016.11.29
728x90

.NET Framework에서 새로운 클래스를 만들 때 여러가지 메모리 관리 디자인 패턴과 기법을 적용할 수 있지만, 한시적으로 사용해야 할 필요가 있는 자원들을 묶어서 관리할 때에는 IDisposable 패턴을 적극적으로 활용하는 것이 매우 유용합니다.

하지만 생각보다 IDisposable 패턴을 제대로 구현해서 사용하는 것은 쉽지 않으며 잘못 구현하기 쉽습니다.

이 아티클에서는 IDisposable 패턴을 구현하는 몇 가지 일반적인 전략들을 소개합니다. 잘못 설명된 부분이 있거나 보충이 필요한 부분은 댓글로 피드백을 자세히 남겨주시면 적극 반영하겠습니다.

IDisposable 인터페이스에 대한 이해

IDisposable 인터페이스가 제공하는 Dispose 메서드는 명시적이고 코드 작성자가 직접 호출할 수 있는 finalizer로, 이 메서드가 불리면 가비지 컬렉터에 의하여 나중에 호출되는 finalizer의 역할을 대체하도록 되어있습니다. 물론, 메모리 상에 할당된 메모리 블록의 해제까지 건너뛴다는 의미는 아닙니다.

그리고 무엇을 Dispose 메서드에서 제거해야 하는지 기준을 세운다면 객체에 대한 소유 권한을 정의하는 것이 필요합니다. 적어도 다음의 경우에는 확실히 Dispose 메서드 내에서 정리가 되어야 합니다.

  • 해당 객체를 Dispose 하게 되면 외부에서 더 이상 사용하는 것이 의미가 없는 객체 (예를 들어 클래스 내부에서 사용하던 파일 입출력 관련 객체, 비동기 작업을 위하여 만들어 놓은 스레드 관련 객체)
  • 외부에서 전달받은 객체이지만 객체의 생명 주기를 위탁하여 관리하도록 지정한 객체 (예를 들어 StreamReader나 StreamWriter가 객체 생성 시 인자로 Stream을 받는 사례)

가장 기본이 되는 IDisposable 구현 패턴

.NET 은 finalizer에 해당되는 멤버를 재정의할 수 있습니다. 하지만 finalizer가 언제 호출이 될 것인지 기약할 수 없으므로 이 finalizer를 대신하여 좀 더 이른 시기에 명시적으로 소멸자와 동등한 효과를 낼 수 있도록 만든 것이 바로 IDisposable.Dispose 메서드가 되겠습니다.

IDisposable 패턴을 처음 구현할 때에는 다음의 사항들이 핵심이 됩니다.

  • protected virtual void Dispose(bool disposing) 메서드를 추가합니다. sealed 클래스에 대해서는 private void Dispose(bool disposing)으로 바꾸어 정의합니다.
  • 객체가 dispose 처리가 이루어진 상태인지를 관리할 수 있는 boolean 필드를 하나 추가하고 이 필드는 기본값을 false로 설정합니다.
  • Dispose(bool disposing) 호출 시 다음의 로직을 구현합니다.
  • 만약 객체가 dispose 상태인 경우에는 함수를 종료합니다.
  • 객체가 dispose 상태임을 필드에 지정합니다. (true로 설정)
  • disposing 매개 변수의 상태와 관계없이 P/Invoke 등을 활용하여 메모리 할당을 받은 나머지 모든 리소스들에 대해 할당을 해제하는 코드를 추가합니다.
  • disposing 매개 변수가 true로 전달되는 경우는 명시적으로 Dispose 메서드를 호출한 경우이며, 이 때에 다른 모든 IDisposable을 구현하는 객체들을 Dispose 처리합니다.
  • IDisposable.Dispose 메서드 구현 시 Dispose(true)를 호출하고 finalizer 호출을 건너뛰기 위하여 GC.SuppressFinalize(this)를 호출합니다.
  • 소멸자에서는 Dispose(false)를 호출합니다.

다음은 코드 예시입니다.

public class DisposableSample : IDisposable
{
public DisposableSample()
{ }
    ~DisposableSample()
{
this.Dispose(false);
}

private bool disposed;
    public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
    protected virtual void Dispose(bool disposing)
{
if (this.disposed) return;
if (disposing)
{
// IDisposable 인터페이스를 구현하는 멤버들을 여기서 정리합니다.
}
// .NET Framework에 의하여 관리되지 않는 외부 리소스들을 여기서 정리합니다.
this.disposed = true;
}
}

IDisposable 객체의 컬렉션에 대한 소거

소켓, 스레드 풀, 혹은 커넥션 풀 같은 컬렉션을 객체 내부에 보관해야 하는 경우도 있습니다. 이러한 경우에도 컬렉션 내의 모든 IDisposable 객체에 대해서 정리를 하는 것이 필요합니다.

  • 컬 렉션 내의 모든 요소가 IDisposable 인터페이스를 구현하고 있다면 Dispose(bool disposing) 메서드에서 disposing이 true일 때 정리를 하면 됩니다. 그렇지 않은 경우, disposing 매개 변수의 상태에 무관하게 정리합니다.
  • Dispose 작업 도중 새로운 항목이 추가되는 것을 방지하기 위하여 disposed 필드를 확인하도록 하는 코드를 추가해야 할 수 있습니다.
  • 컬렉션 내의 모든 요소를 배열에 복사하여 Immutable Collection으로 변환한 다음 하나씩 방문하여 Dispose를 진행하고, 최종적으로 컬렉션의 요소들을 모두 제거합니다.

다음은 코드 예시입니다.

public class DisposableSample : IDisposable
{
public DisposableSample()
{
this.items = new List<IDisposable>();
}
    ~DisposableSample()
{
this.Dispose(false);
}

private bool disposed;
private List<IDipsosable> items;
    public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
    protected virtual void Dispose(bool disposing)
{
if (this.disposed) return;
if (disposing)
{
// IDisposable 인터페이스를 구현하는 멤버들을 여기서 정리합니다.
IDisposable[] targetList = new IDisposable[this.items.Count];
this.items.CopyTo(targetList);
foreach (IDisposable eachItem in targetList)
{
eachItem.Dispose();
}
this.items.Clear();
}
// .NET Framework에 의하여 관리되지 않는 외부 리소스들을 여기서 정리합니다.
this.disposed = true;
}
}

Dispose 메서드 내의 예외 처리

만약 Dispose 메서드를 실행하는 도중에 예외가 발생한다면, CA1065의 지침에 따라 명시적인 Dispose 호출이었든 아니었든 예외를 전파하지 않도록 처리하는 것이 필요합니다. 다만 명시적으로 Dispose를 호출하면서 예외가 발생했다면 예외를 전파하지 않는 대신 적절한 예외 처리는 필요합니다.

이 부분에 대한 자세한 내용은 https://msdn.microsoft.com/ko-kr/library/bb386039.aspx 페이지의 내용을 참고하시면 도움이 될 것입니다.

컬렉션 내의 모든 요소들을 Dispose 하는 코드를 조금 더 보강하면 다음과 같이 고쳐쓸 수 있겠습니다.

public class DisposableSample : IDisposable
{
public DisposableSample()
{
this.items = new List<IDisposable>();
}
    ~DisposableSample()
{
this.Dispose(false);
}
    private bool disposed;
private List<IDisposable> items;
    public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
    protected virtual void Dispose(bool disposing)
{
if (this.disposed) return;
try
{
if (disposing)
{
// IDisposable 인터페이스를 구현하는 멤버들을 여기서 정리합니다.
IDisposable[] targetList = new IDisposable[this.items.Count];
this.items.CopyTo(targetList);
foreach (IDisposable eachItem in targetList)
{
try { eachItem.Dispose(); }
catch (Exception ex) { /* 예외 처리를 수행합니다. */ }
finally { /* 정리 작업을 수행합니다. */ }
}
this.items.Clear();
}
try { /* .NET Framework에 의하여 관리되지 않는 외부 리소스들을 여기서 정리합니다. */ }
catch { /* 예외 처리를 수행합니다. */ }
finally
{
/* 정리 작업을 수행합니다. */
this.disposed = true;
}
}
finally { /* 정리 작업을 수행합니다. */ }
}
}

소유하고 있는 객체들에 대한 Dispose 또는 정리 작업들을 각각 try, catch, finally 블록안에 두어 예외가 발생하면 적절한 예외 처리를 할 수 있게 하고, Dispose 메서드 전체에 대해서 try, finally 블록 안에 두어 예외가 전파되지 않도록 하였습니다.

결론

.NET Framework 기반의 응용프로그램이 안정적으로 장시간 실행될 수 있게 만들어야 할 때 고려해야 할 요소들 가운데에서 가장 비중있게 다루어야 할 부분이 바로 메모리 관리입니다. IDisposable 인터페이스를 통한 명시적인 finalizer 호출은 적절하게 활용하면 응용프로그램의 메모리 관리를 단순하게 만드는데 큰 도움을 줍니다.


출처 : https://medium.com/rkttu/idisposable-%ED%8C%A8%ED%84%B4%EC%9D%98-%EC%98%AC%EB%B0%94%EB%A5%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EB%B2%95-4fa0fcf0e67a

728x90

'Basic Programming > C#' 카테고리의 다른 글

C# - 작업 스케줄러에 등록하기  (0) 2021.12.10
C# - Steam Game 실행하기  (0) 2020.07.21
C# - WeakReference  (0) 2018.10.11
C# - 공부하기 좋은 사이트  (0) 2018.09.05
C# - Attibute  (0) 2018.07.09
728x90

WeakReference Class를 사용하여 메모리를 관리해보자. 


C#의 가장 큰 특징중 하나는 가비지 컬렉터(Garbage Collector)가 자동으로 메모리를 관리해준다는 것입니다.

상식적으로 가비지 컬렉터가 작동된다면 사용하지 않은 메모리는 자동으로 반환해야되지만

실제로 메모리가 반환되지 않고 계속 늘어나기도 합니다.


계속 늘어나는 메모리를 해제하고 싶지만 C#에는 따로 메모리를 해제하는 명령어가 없어서 메모리 릭(Memory leak)이 발생하기도 합니다.


이렇게 메모리가 계속 늘어나는 이유가 뭘까요?


가비지(Garbage)는 더 이상 참조가 없는 메모리를 뜻합니다.

메모리가 반환되지 않고 계속 늘어나고 있다면 어디선가 반환되야될 메모리를 의도치 않게 참조 하고 있어서 

가바지 컬렉터가 반환메모리로 인식하지 않아 청소를 못하고 자꾸 쌓이고만 있는것입니다.


저 또한 겪었던 문제로


하나의 UserControl을 만들었습니다.

이 UserControl은 DB에서 Select문으로 데이터를 끌어올때마다 생성하여 UserControl내부 로직을 타고 화면에 뿌려줍니다.

그 후 사용이 끝난 UserControl은 Dispose()를 해주지만 이상하게 검색을 할때마다 메모리가 늘어납니다.

결국 메모리 릭(Memory leak)이 발생하더군요.

저는 분명 모든 리소스를 해제했다고 생각하고 있었지만 어디선가 참조가 되고 있었는것이죠.

(결국 문제점을 발견하지 못했습니다. ㅠㅠ Garbage 강제 콜을 해도 메모리가 2%늘었다 1%줄었다 반복하더니 메모리 릭이 발생하더군요.)


이럴때 WeakReference Class를 사용해주면 됬었지만! 

그때 당시 저는 이 방법을 몰라서 한참을 돌아돌아 프로그램을 완성했었습니다.

(어찌했는지 기억도 잘 안나네요 ㅠㅠ)


WeakReference는 가바지 수집에 의한 개체 회수를 허용하면서 개체를 참조하는 일명 약한 참조를 만들어 냅니다.


아래 예제 소스를 보면서 설명하겠습니다.

(예제소스를 실행해보고 싶으시면 라벨 두개와 버튼 하나를 만들어서 복사후 연결만 시켜주면됩니다.)



<p>
using System;
using System.Windows.Forms;
 
namespace WeakReferenceTest
{
    public partial class WeakReference_Form : Form
    {
        stock phone;
        stock notebook;
        stock stock1;
        WeakReference stock2;
 
        public WeakReference_Form()
        {
            InitializeComponent();
            //Stock라는 클래스를 만들어 각각 Phone, NoteBook라는 물품을 담게 하였습니다.
            phone = new stock("Phone");
            notebook = new stock("NoteBook");
             
            //각각 참조를 거는데 phone은 일반적으로 사용하는 강한참조
            //notebook은 WeakReference를 이용한 약한 참조를 걸어보도록 하겠습니다.
            stock1 = phone;
            stock2 = new WeakReference(notebook);
 
            //각각 라벨에 출력해보겠습니다.
            labelControl1.Text = stock1 == null ? "null" : stock1.name;
            labelControl2.Text = stock2.Target == null ? "null" : (stock2.Target as stock).name;
 
            //당연한 이야기겠지만
            //------------  label1 = Phone
            //------------  label2 = NoteBook
            //가 출력됩니다.
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            //버튼 클릭시 phone과 notebook에 null을 넣어 초기화 시켜줍니다.
            phone = null;
            notebook = null;
 
            //가바지 컬렉터를 강제로 작동시켜보겠습니다.
            System.GC.Collect(0, GCCollectionMode.Forced);
            System.GC.WaitForFullGCComplete();
 
            //다시한번 참조한 값을 각각 라벨에 출력해보겠습니다.
            labelControl1.Text = stock1 == null ? "null" : stock1.name;
            labelControl2.Text = stock2.Target == null ? "null" : (stock2.Target as stock).name;
 
            //어떻게 나올지 예상하셨나요?
            //필자는 stock1과 stock2 모두 참조 객체가 null로 변경이 되었으니 null이 출력된다고 생각했습니다.
            //하지만 결과는
            //----------  label1 = Phone
            //----------  label2 = null
            //과 같이 출력됩니다.
            //강한참조는 참조 객체를 초기화 시켜도 Data를 붙잡고 있어서 메모리 회수가 안되는 것입니다.
        }
    }
 
    public class stock
    {
        public string name = "";
        public stock(string name)
        {
            this.name = name;
        }
    }
}
</p>


C#은 메모리를 컨트롤 안해도 된다는 생각을 가지고 있었지만 (필자가 배울때는 그렇게 배워서 ㅠㅠ)

실제로는 C나 C++만큼은 아니지만 메모리 관리를 해야된다고 생각합니다.


출처 : http://mirwebma.tistory.com/142

728x90

'Basic Programming > C#' 카테고리의 다른 글

C# - 작업 스케줄러에 등록하기  (0) 2021.12.10
C# - Steam Game 실행하기  (0) 2020.07.21
C# - Dispose  (0) 2018.10.12
C# - 공부하기 좋은 사이트  (0) 2018.09.05
C# - Attibute  (0) 2018.07.09
728x90
[in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기



지난 글에 이어서.

[in, out] 배열을 C#에서 C/C++로 넘기는 방법
; http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&detail=1&wid=810


마이크로소프트가 원래 의도한 바는 아니었겠지만, tlbimp.exe 의 기능을 보정할 수 있는 방법을 별도로 제공하고 있습니다.

.NET Framework Developer's Guide
- Customizing Runtime Callable Wrappers
; http://msdn.microsoft.com/en-us/library/e753eftz.aspx


위의 글에 포함된 그림이 재미있습니다. ^^

[그림 1: RCW DLL 생성 방법]
how_to_customize_rcw_1.gif

RCW를 생성하는 3가지 방법 중에서 중간 그림이 의미가 있는데, 일단 한번 tlbimp.exe에 의해서 생성된 DLL 을 역어셈블한 뒤, 원하는 데로 마샬링 정보를 바꾸고 다시 ilasm을 이용해서 DLL을 생성하고 있습니다. 오호... 이 정도면 훌륭한 대안이죠. ^^




이것을 이용해서 지난 번 예제를 개선해 보겠습니다.

IDL 은 원래 정의된 그대로 사용하고,

[
	object,
	uuid(1A38076B-3D6D-4F20-8B4D-C72EF6AE1204),
	dual,
	nonextensible,
	helpstring("IMyTest Interface"),
	pointer_default(unique)
]
interface IMyTest : IDispatch
{
	[id(0x3003), helpstring("method PrepareBuf")] 
	HRESULT PrepareBuf([in, out, size_is(bufLength)] __int64 buffer [], [in] int bufLength);
};


마샬링 정보가 올바르진 않겠지만 일단 tlbimp.exe (또는 Visual Studio의 DLL 참조)를 이용하여 interop DLL을 생성합니다.

tlbimp testatl.dll /out:interop.testatl.dll


ildasm 으로 DLL을 역어셈블하면 해당 메서드가 다음과 같이 선언된 것을 볼 수 있습니다.

ildasm interop.testatl.dll /out:interop.testatl.il

.method public hidebysig newslot virtual 
      instance void  PrepareBuf([in][out] int64& buffer,
                                 [in] int32 bufLength) runtime managed internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 03 30 00 00 00 00 )   // ...0....
.override interop.testatl.IMyTest::PrepareBuf
} // end of method MyTestClass::PrepareBuf


여기에서 "[in][out] int64& buffer" 구문을 "[in][out] int64[] marshal([]) buffer" 과 같이 바꿔줍니다. (또 한군데 더 정의되어 있기 때문에 그 부분도 마저 바꿔줍니다.) 이렇게 변경하고 다시 ilasm으로 어셈블해주면, C# 에서 정상적으로 long [] 으로 사용할 수 있습니다.

ilasm interop.testatl.il /dll

public virtual void PrepareBuf(long[] buffer, int bufLength);


우와~~~ ^^ 깔끔하죠! (첨부된 파일은 위의 예제와 지난 번 글의 예제를 함께 테스트한 프로젝트입니다.)

그러고 보니, 이걸 하면서 예전에 쓴 글이 하나 생각났습니다.

COM 개체의 이벤트를 구독하는 코드 제작
; http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&detail=1&wid=589


위의 방법 역시 interop DLL 코드가 잘못 생성된 경우인데, ildasm/ilasm 조합으로 해결할 수도 있었지 않았을까 싶네요. ^^


출처 : http://www.sysnet.pe.kr/2/0/811


728x90
728x90
[in,out] 배열을 C# 에서 C/C++ 로 넘기는 방법



가령, long 형 배열을 C/C++에 넘겨주고, C/C++ 측에서 해당 배열의 내용을 채운 후 반환해 주는 메서드라면 다음과 같이 IDL 정의를 해줄 수 있습니다.

[
	object,
	uuid(1A38076B-3D6D-4F20-8B4D-C72EF6AE1204),
	dual,
	nonextensible,
	helpstring("IMyTest Interface"),
	pointer_default(unique)
]
interface IMyTest : IDispatch
{
	[id(0x3003), helpstring("method PrepareBuf1")] 
	HRESULT PrepareBuf([in, out, size_is(bufLength)] __int64 buffer [], [in] int bufLength);
};


그런데, tlbimp.exe (또는 Visual Studio의 DLL 참조)를 이용하여 interop DLL을 생성해 보면, PrepareBuf 의 함수 형식이 다음과 같이 정의되는 것을 볼 수 있습니다.

tlbimp testatl.dll /out:interop.testatl.dll

public virtual void PrepareBuf(ref long buffer, int bufLength);


오호... tlbimp.exe로써는, 감당이 안되는 IDL 구문이라는 것인데요. 그렇다면 이를 해결하기 위해서 생각해 볼 수 있는 것이 배열 자체를 포인터로 넘겨보는 정도일텐데, 약간 찜찜하긴 해도 4byte(혹은 8byte) 값으로 넘기는 것은 ^^ 너무 잘 동작합니다.

그래서 C# 측에서, 배열 자체를 IntPtr로 변경하고,

[STAThread]
static void Main(string[] args)
{
    interop.testatl.MyTestClass mtc = new interop.testatl.MyTestClass();
    
    long [] test = new long[5];
    IntPtr ptr = Marshal.UnsafeAddrOfPinnedArrayElement(test, 0);
    mtc.PrepareBuf(ptr.ToInt64(), 5);
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(test[i]);
    }
}


C/C++ 에서는 넘겨받은 정수값을 간단하게 포인터로 형변환해서 처리해 주면 됩니다.

STDMETHOD(PrepareBuf2)(__int64 buffer, int bufLength)
{
	__int64 *pBuffer = (__int64 *)buffer;

	for (int i = 0; i < bufLength; i ++ )
	{
		pBuffer[i] = i;
	}

	return S_OK;
}


물론, 이 방법은 out-of-process COM 개체로 만들면 프로세스 주소 공간이 달라지기 때문에 동작하지 않습니다. 하지만, In-proc COM 개체만으로 사용하실 분들이라면 이 방법이 나쁘다고 볼 수는 없습니다.



출처 : http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0&detail=1&wid=810

728x90
728x90

저의 경우, VC++ 8.0 의 C++/CLI 에 대한 매력을 느끼게 된 것은 문맥 구문이니... 뭐 그런 것들이 아니었습니다. 바로 명시적인 delete 구문의 지원이었습니다. 어떻게 그것이 가능할까... 뭔가 Interop 간에 대단한 비밀이 있지 않나 싶었는데요. 역시 파고들어 보니... 결국 ^^; IL 코드내에서 해결할 수 있을 뿐이더군요. (이제와서는, 명시적인 종료자보다 IJW 를 구현한 것에 대해 더 칭찬을 해주고 싶습니다.)

우선, 본격적으로 들어가기에 앞서 미리 다음의 토픽을 먼저 봐주시기 바랍니다.

.NET IDisposable 처리 정리 
http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0&detail=1&wid=347

사실, 도움말에도 나타난 위의 토픽을 굳이 한번 더 언급한 것은 이번 토픽을 위해서였습니다.



그럼, 하나씩 한번 알아볼까요? ^^

우선 예제 코드가 하나 있어야 할테니, 다음과 같은 클래스를 만들어서 예로 들어보겠습니다.

public ref class MyClass 
{
public:

	MyClass()
	{
	}
};


아래는 위의 코드를 사용하느 main 함수입니다.

int main(array ^args)
{
	MyClass ^myClass = gcnew MyClass();
    return 0;
}


일단, 여기 까지는 크게 일반 C#/VB.NET 클래스와 다른 점이 없습니다. 그런데, 여러분이 (또는 과거의 제가) 어렴풋이 들었던 것 중에, C++/CLI 에서는 delete 구문으로 개체를 명시적으로 해제하는 것을 지원한다고 했지요. 그래서 다음과 같이 delete 를 추가해 보겠습니다.

int main(array ^args)
{
	MyClass ^myClass = gcnew MyClass();
	delete myClass;
    return 0;
}


과연 정말 그럴까요? 위와 같이 하면 CLR Heap 에 할당된 개체가 즉시 삭제되어 메모리 관리 효율이 C++ 과 같아질 수 있을까요? 물론... 대답은 "아니오" 입니다. 실제로, 위의 코드 부분에 대해서 ".NET Reflector" 로 보면 다음과 같이 delete 코드가 확장되어 나오는 것을 확인할 수 있습니다.

internal static int main(string[] args)
{
      MyClass class1 = null;
      class1 = new MyClass();
      IDisposable disposable1 = class1 as IDisposable;
      if (disposable1 != null)
      {
            disposable1.Dispose();
      }

      return 0;
}


위의 코드를 보시고, ^^; 쓴 웃음을 짓는 분도 계실 텐데요. 그렇습니다. "delete myClass;" 구문은 실제로는 해당 개체의 IDisposable 인터페이스 구현여부를 알아내서 그것의 Dispose 메서드를 호출해주는 코드로 대체되어 삽입되는 것 뿐입니다. 이건 사실 기술이라고 부를 수도 없지요. 단지 언어적인 확장에 기인한 것 뿐이니까요. 마치 C# 의 경우 using 예약어를 지원하지만, 결국 생성된 코드는 try / finally 에 Dispose 메서드를 불러주는 코드로 확장되는 것과 다를 바 없습니다. 실제로, 우리가 기대했던 CLR Heap 의 메모리 정리 상황은 발생하지 않는 다는 것이 중요할 것입니다. 위의 방식을 아셨으니, 이제 쓰게 될 나머지 부분은 다 그러한 부분의 확장으로 받아들이시면 이해가 금방 되실 텐데요.

가만 있자... 그럼 또 뭐가 있을까요? 그렇군요. 우리가 알고 있던 얘기 중에, C++/CLI 는 파괴자를 구현하면 scope 을 벗어나는 경우 자동으로 호출된다고 들었지요. 그럼 그 부분도 한번 살펴볼까요? 예를 위해 위의 코드를 다음과 같이 수정해 보겠습니다.

public ref class MyClass 
{
	char *m_pBuf;
public:

	MyClass()
	{
		m_pBuf = new char[ 4096 ];
	}

	~MyClass()
	{
		delete [] m_pBuf;
	}
};


scope 을 벗어나는 것을 테스트 하기 위해서 MyClass 에 대한 명시적인 delete 없이 다음과 같은 스택 방식으로 수정을 하겠습니다.

int main(array ^args)
{
	MyClass myClass;
	return 0;
}


역시 이번에도 생성된 IL 코드를 ".NET Reflector" 로 확인해 보겠습니다.

internal static int main(string[] args)
{
      int num2;
      MyClass class1 = null;
      MyClass modopt(IsConst) local1 = (MyClass modopt(IsConst)) new MyClass();
      try
      {
            class1 = local1;
            num2 = 0;
      }
      fault
      {
            class1.Dispose();
      }
      class1.Dispose();
      
      return num2;
}


오... 이런... ^^; 역시 이번에도 실제로는 Managed 클래스를 스택상에 생성하지 않고 CLR Heap 에 할당하고는 fault 처리기에서 Dispose 가 명시적으로 불릴 수 있도록 하는 IL 코드로 확장이 되었습니다. 
재미있는 것은, MyClass 를 IL 코드로 열어보면 C++ 구문의 파괴자를 구현한 것이 실제로는 다음과 같이 IDisposable 패턴으로 확장된 것을 볼 수 있습니다.

public class MyClass : IDisposable
{
      public MyClass();
      private void ~MyClass();
      public sealed override void Dispose();
      protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool);

      private unsafe sbyte modopt(IsSignUnspecifiedByte)* m_pBuf;
}


패턴은 이전 토픽에서 살펴봤던 C# 과 동일한 구조를 따르고 있습니다.

정리해 보면, C++/CLI 의 자원해제 기능은 결국 C++ 과 유사한 구문을 그대로 유지하되 내부적으로는 IDisposable 구문으로 확장해주는 것에 불과하다는 것입니다.

즉, C++/CLI 에서 다음과 같이 코딩을 한 것은,

	MyClass ^myClass = gcnew MyClass();
	delete myClass;
	
	또는,
	
	MyClass myClass;


C# 으로 다음과 같이 코딩을 해줄 수가 있습니다.

  using ( MyClass myClass = new MyClass() )
  {
  }  


C++/CLI 에서는 단지 구문을 단순하게 해준다는 장점만 있을 뿐, 그 이외의 어떠한 장점도 없다는 것입니다.



이미 앞에서 다뤘던 .NET IDisposable 처리 정리의 토픽과 비교해 보시면 아직 얘기하지 않은 이야기가 하나 있음을 아시게 될 텐데요. 바로 Managed 자원 해제와 Unmanaged 자원해제를 구분지어 해제하는 방법이 과연 무엇이냐는 것입니다. C# 의 경우, IDisposable 인터페이스를 구현하고 내부적으로 Dispose(bool disposing) 메서드를 호출하는데, C++/CLI 에서는 IDisposable 인터페이스를 자동으로 구현하기 때문에 bool disposing 에 대한 처리를 하는 부분이 모호해 지기 때문입니다. 이를 위해, C++/CLI 에서는 다음과 같은 특별한 구문의 파괴자를 하나 더 지원합니다.

public ref class MyClass 
{
	char *m_pBuf;
public:

	MyClass()
	{
		m_pBuf = new char[ 4096 ];
	}

	~MyClass()
	{
		// Managed 자원과 Unmanaged 자원을 모두 해제
		delete [] m_pBuf;
	}

	!MyClass()
	{
		// Unmanaged 자원을 해제
		delete [] m_pBuf;
	}
};


이 부분에서 약간 재미난 현상을 만나게 되는데요. Unmanaged 자원에 대한 해제 코드를 중복시키지 않기 위해 ~MyClass 에서 !MyClass 를 아래와 같이 호출하게 되면 컴파일 오류가 발생하게 됩니다.

	~MyClass()
	{
		!MyClass(); // error C2088: '!' : illegal for class	d:\...\Test.h
	}

	!MyClass()
	{
		delete [] m_pBuf;
	}


대신에 다음과 같이 포인터 구문을 쓰게 되면 정상적으로 호출이 됩니다.

	~MyClass()
	{
		this->!MyClass(); // 또는 MyClass::!MyClass();
	}

	!MyClass()
	{
		delete [] m_pBuf;
	}




이로써, C++/CLI 의 명시적인 종료자에 대해서 왠만큼 살펴본 것 같은데요. 
결론을 내려보면. C++/CLI 가 자원 해제 면에서 결코 우수한 언어가 아님을 알게 됩니다. 그저 Interop 만 우수한 언어일 뿐.


출처 : http://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=1&title=%25c%2B%2B%25&wid=349&detail=1

728x90
728x90
728x90

+ Recent posts