본문 바로가기
프로그래밍/PC

Interface 요약 #5

by 사악신 2012. 4. 19.

지금으로부터 무려 10년전인 2002년 5월경, 델마당 개인게시판에 올렸던 총 9회의 글입니다. 유실된 줄 알았는데 싸이월드 게시판에 있는 걸 확인하고 복구합니다.;;

 

8. 객체 모델과 인터페이스 모델을 혼용하지 말 것.

procedure DoSomethingWithInterface(Intf: IFormattedNumber);
begin
  ShowMessage(Intf.FormattedString);
end;

procedure CreateAndUseObject;
var
  MyInteger: TFormattedInteger;
begin
  MyInteger:= TFormattedInteger.Create(12);
  DoSomethingWithInterface(MyInteger as IFormattedNumber);

  MyInteger.SetValue(10);
end.

 

  • 상기 코드에서 as 연산자는 내부적으로 _AddRef를 호출하여 참조계수를 증가시킨다. 물론 인터페이스의 사용이 끝나는 시점에 _Release가 호출되어 참조계수를 감소시킬 것이다.(참조계수가 0이 되면 인터페이스는 소멸된다.)
  • MyInteger는 객체로서 메모리에 생성된 후, 해당 레퍼런스가 as 연산자에 의하여 인터페이스로 전달된다. 이때 참조계수가 증가되며(값에 의한 호출이므로 함수의 인자가 지역변수로 새로 생성되는 것과 같은 이치로 이해하면 된다. 즉, 해당 인터페이스를 사용하는 변수가 하나 더 늘어난 셈이다.) DoSomethingWithInterface 가 끝나면서 참조계수가 감소되며 인터페이스는 소멸된다. 이때, MyInteger의 레퍼런스가 전해진 것이므로 결국, 인터페이스의 소멸은 MyInteger의 소멸이 된다. 따라서 MyInteger.SetValue(10) 에서 Access Violation 이 발생한다.
  • procedure DoSomethingWithInterface(var Intf: IFormattedNumber) 혹은 procedure DoSomethingWithInterface(const Intf: IFormattedNumber) 로 선언된 경우, 즉 참조에 의한 호출을 할 경우 내부적으로 _AddRef와 _Release가 발생하지 않는다.(마찬가지로 함수의 인자가 자신의 레퍼런스를 전달하므로 새로 생성되지 않는 것과 같은 이치로 생각한다.)
procedure DoSomethingWithInterface(Intf: IFormattedNumber);
begin
  ShowMessage(Intf.FormattedString);
end;

procedure CreateAndUseInterface;
var
  MyInteger: IFormattedInteger;
begin
  MyInteger:= TFormattedInteger.Create(12);
  DoSomethingWithInterface(MyInteger);

  MyInteger.SetValue(10);
end.

 

  • 상기 코드는 인터페이스만을 사용한 것이다. 처음 MyInteger 가 생성되면서 참조 계수가 1 증가하며 DoSomethingWithInterface에서 값에 의한 호출을 하면서 참조 계수가 2로 증가 된다. 그리고 함수를 마치며 다시 1감소한 후, MyInteger.SetValue(10)가 실행된 후 함수를 마치며 참조 계수가 0이 되며 인터페이스는 소멸한다.
  • 상기 코드처럼 인터페이스와 객체를 섞어 사용하지 않는 것이 바람직하다.
procedure DoSomethingWithInterface(Intf: IFormattedNumber);
begin
  ShowMessage(Intf.FormattedString);
end;

procedure CreateAndUseObject;
var
  MyInteger: TFormattedInteger;
begin
  MyInteger:= TFormattedInteger.Create(12)
  MyInteger._AddRef; // - 1

  DoSomethingWithInterface(MyInteger as IFormattedNumber);

  MyInteger.SetValue(10);
  MyInteger._Release; // - 2
end.
  • 상기 코드와 같이 객체와 인터페이스를 혼용할 경우 1, 2에서 처럼 명시적으로 _AddRef와 _Release를 호출하면 에러가 발생하지 않는다.(1에서 참조 계수가 1이 되고 DoSomethingWithInterface가 호출되며 2가 된 후 함수 끝에서 다시 1이 되므로 인터페이스의 소멸로 인한 객체의 소멸을 막을 수 있다.) 하지만, 이 또한 바람직한 방법은 아니다. 즉, 자연스럽지 않다.
TNonRefCountedObject = class(TInterfacedObject, IUnknown)
protected
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
end;

function TNonRefCountedObject._AddRef: Integer;
begin
  Result:= -1;
end;

function TNonRefCountedObject._Release: Integer;
begin
  Result:= -1;
end;

 

  • 새로운 객체를 선언하여 _AddRef와 _Release를 오버라이딩한다.(override를 붙이지 않은 것은 원래 선언에 virtual이 없기 때문인데... virtual 은 동적바인딩을 위한 오버라이딩을 의미한다.)
  • 새로 선언된 _AddRef와 _Release는 무조건 -1을 돌려줌으로 인터페이스는 델파이에 의해 자동으로 소멸되지 않는다.
procedure DoSomethingWithInterface(Intf: IFormattedNumber);
begin
  ShowMessage(Intf.FormattedString);
end;

procedure CreateAndUseObject;
var
  MyInteger: TFormattedInteger.Create(12);
begin
  MyInteger:= TFormattedInteger.Create(12);

  DoSomethingWithInterface(MyInteger as IFormattedNumber);

  MyInteger.SetValue(10);
  MyInteger.Free; // - 1
end.
  • _AddRef와 _Release가 무조건 -1 을 반환하므로 1에서처럼 직접 소멸시켜주어야 한다.
  • 마찬가지로 바람직한 방법은 아니다.
 

 

 

반응형

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

Interface 요약 #7  (0) 2012.04.19
Interface 요약 #6  (0) 2012.04.19
Interface 요약 #4  (0) 2012.04.19
Interface 요약 #3  (0) 2012.04.19
Interface 요약 #2  (0) 2012.04.19

댓글