본문 바로가기

C/C++

NPAPI 메모리 관리 (Memory management in NPAPI)

NPAPI 메모리 관리 (Memory management in NPAPI)

 NPAPI NPObject 객체도 참조 카운트를 사용하여 메모리를 관리하고 있습니다.

NPObject를 다루를 함수들은 아래와 같이 다양합니다.

 - NPN_CreateObject, NPN_RetainObject, NPN_Release, NPN_Evaluate, NPN_GetValue,

NPN_SetValue, NPN_GetProperty, NPN_SetProperty, 등등

 

 

그런데 참조 카운트를 증가하고 감소하는 메커니즘에 대한 이해가 쉽지 않아 구글링을….

다음과 같이 누군가가 작성해 놓았네요 [1] (원문은 맨 아래 링크 클릭) 

 

이해한 바에 따르면,

위에 언급한 NPObject 함수들에서 NPObject를 얻으면작업을 마친 후에 NPObject release 해야 합니다

, get 후에 바로 release 하거나만약 NPObject를 멤버 변수로서 저장하여 사용한다면해당 객체가 파괴될 때 release 하면 됩니다이것은 NPVariant도 마찬가지입니다.

그러나 invoke 함수에 argument로 넘겨질 경우에는 브라우저가 알아서 release 해줍니다.

 

NPN_GetValue를 호출해서 NPObject를 얻는 경우에도 release를 해줘야 합니다브라우저가 NPP_GetValue를 호출할 때 작업을 마친 후에 당신의 NPObject release 할 것입니다.

브라우저가 당신의 NPObject를 얻을려고 NPP_GetValue를 호출할 때마다 새로운 NPObject를 만들고 싶다면, NPN_RetainObject를 호출할 필요가 없습니다. (플러그인 객체가 삭제될 때까지 NPObject가 삭제되지 않도록 NPObject의 복사본을 저장하고 있는다는 가정하에)

 

브라우저가 NPP_GetValue를 호출해서 당신의 NPObject를 얻을 때마다 Release를 호출하기 때문에, NPObject를 리턴하기 전에 ref count가 증가되었는지 확인해야 한다. NPN_CreateObject는 암시적으로 Retain을 호출하기 때문에그것(?)을 두번 호출할 필요가 없다.

(그것이 릴리즈를 뜻하는 걸까..멀 뜻하는 걸까…)

 

 

 



 아래는 [2]의 내용을 번역한 걸로, 보다 자세한 설명입니다. 

 

 

NPObject NPVariant

일반적으로 브라우저에게 정보를 넘겨주는데 사용되는 데이터 타입은 NPObject NPVariant 입니다.

NPObject는 스크립트 가능한 (scriptable) 기본 데이터 타입입니다, javascript 객체와 javascript에게 말하는(접근하는객체를 의미하죠.

NPVariant  NPObject로 파라미를 넘겨주거나리턴 값을 받는 데이터 타입입니다.

 

경험상브라우저로부터 무언가를 리턴 값으로 받는다면, , 자신이 브라우저 API 호출을 통해 release 해줘야 합니다반대로 브라우저로 무언가를 리턴한다면브라우저가 브라우저 API 호출을 통해 release 할 것입니다.

 

 

NPObject

 

struct NPObject {
  NPClass *_class;
  uint32_t referenceCount;
  /*
   * Additional space may be allocated here by types of NPObjects
   */
};

 

NPN_(browser API) 함수 중에 NPObject를 사용할 때 반드시 알아야 할 3가지 함수가 있습니다.

 NPN_CreateObject : NPObject를 만들고 referenceCount 1로 세팅해서 리턴

 NPN_RetainObject : referenceCount 증가

 NPN_ReleaseObject : referenceCount 감소만약 0이면 객체 삭제

 

 

NPObject 생성

NPN_CreateObject referenceCount 1로 초기화해서 리턴한다는 것이 중요합니다.

만약 객체를 한번만 사용할 것이라면새로 생성된 NPObject NPN_RetainObject를 호출할 필요는 없습니다그렇게 하기 위해서는 객체가 release되지 못하도록 막아야 합니다.

그런데 gecko 소스 코드에 npruntime 샘플 소스를 보면(아래 소스 코드),  NPN_CreatObject 후에 NPN_RetainObject를 호출했을까요

 

NPObject *
CPlugin::GetScriptableObject()
{
    if (!m_pScriptableObject) {
        m_pScriptableObject = NPN_CreateObject(m_pNPInstance, GET_NPOBJECT_CLASS(ScriptablePluginObject));
    }
 
    if (m_pScriptableObject) {
        NPN_RetainObject(m_pScriptableObject);
    }
 
    return m_pScriptableObject;
}

< from mozilla_central/modules/plugin/sdk/samples/npruntime/plugin.cpp  일부>

 

위 코드를 보면혼동될 여지가 있습니다. referenceCount가 이미 1로 세팅되어 있는데 왜??? Retain을 호출 했을까?? 간단히 말하면초기 referenceCount this->m_pScriptableObject내의 NPObject를 저장한다는 사실을 설명하는 것입니다. (NPObejct release하기 위해서는 소멸자에서 NPN_ReleaseObject를 호출해야 합니다.)

그렇다면 Retain을 호출 했느냐?? (몇번 물어보냐)

브라우저로 NPObject를 리턴하기 때문입니다.

(이 경우에는 NPPVariant NPPVpluginScriptableNPObject값과 함께 NPP_GetValue를 호출)

브라우저는 적절히 NPObject를 초기화 할 것이고작업을 끝낸 후에는 NPN_ReleaseObject를 호출 할 것이기 때문입니다.

 

위에 언급했던 것을 기억하세요

브라우저로 NPObject를 리턴하면브라우저가 release 할 것입니다물론!, 리턴하기 전에 retain해야 합니다만약 그 NPObject 객체의 사본을 유지할 필요가 없을 때는 retain 할 필요는 없습니다. (단지 생성하고매번 리턴하는 식)

상태를 유지하는 일종의 트릭이 있는데FireBreath 의 JSAPI 클래스를 참고하세요.

( NPJavascriptObject.cpp and NPJavascriptObject.h)

 

 

NPVariants

 

typedef struct _NPVariant {
  NPVariantType type;
  union {
    bool boolValue;
    int32_t intValue;
    double_t doubleValue;
    NPString stringValue;
    NPObject *objectValue;
  } value;
} NPVariant;

메모리 관리를 위해 처리 해야 할 다음 단계는 NPVariant가 어떻게 동작하는지어떻게 메모리를 할당 및 해제하는지 이해하는 일입니다.

 

NPVariant는 NPObject 메소드에 파라미터나 리턴 값으로 사용됩니다위에 언급한 NPVariant의 구조체를 보면 알 수 있듯이대부분의 NPVariant 타입은 원시(primitive)코드 입니다그래서 특별한 메모리 관리가 필요하지 않죠우리가 걱정해야 할 두가지 타입은 NPString NPObject 입니다.

 

NPString과 관련된 3가지 중요한 NPN_(browser API) 함수가 있습니다.

 NPN_MemAlloc : 브라우저에 의해 컨트롤되는 메모리 chunk 할당 및 포인터 리턴

 NPN_MemFree : 브라우저에 의해 컨트롤 되는 메모리 해제

 NPN_ReleaseVariantValue : variant내의 값을 해제

(VariantType가 NPVariantType_String, calls NPN_MemFree; if it’s NPVariantType_Object, calls NPN_ReleaseObject인 경우)

 

?? malloc free는 모든 C 컴파일러에 있는 표준인데메모리를 할당 및 해제하기 위해 브라우저 API를 호출해야 할까요?

그 이유는 윈도우를 포함한 많은 플랫폼에서 각각의 DLL이나 EXE는 자신만의 메모리 관리 루틴과 힙을 갖고 있기 때문입니다.그래서 플러그인 DLL에서 메모리를 할당하고 브라우저에서 메모리를 해제하면 문제가 발생합니다. (반대도 마찬가지)

 

또 경험상, ;;

브라우저에서 리턴값을 리턴했다면브라우저 API를 사용해서 release 해야 합니다그리고 브라우저에게 string을 리턴해야 한다면, NPN_MemAlloc를 사용해야 메모리를 할당해줘야 합니다.

다음은 FireBreath 코드 입니다.

 

// We have:
// std::string str as the string to store
// NPVariant *dst as the destination NPVariant
char *outStr = (char*)this->MemAlloc(str.size() + 1);
memcpy(outStr, str.c_str(), str.size() + 1);
dst->type = NPVariantType_String;             
dst->value.stringValue.utf8characters = outStr;
dst->value.stringValue.utf8length = str.size();

 

브라우저가 NPN_Invoke를 호출 할 때마지막 파라미터(NPVariant *result)에서 NPString을 저장하기 위해 그 메소드를 사용하며브라우저는 작업을 마치고 메모리를 해제할 것입니다.

마찬가지로당신이 NPN_Invoke를 호출한다면작업을 마쳤을 때 리턴된 NPVariant에 대해 NPN_ReleaseVariantValue를 호출해야 합니다.

또한당신이 NPN_Invoke로 파라미터를 넘겨주는 NPVariant array에 대한 생성과 해제에도 책임을 져야 합나다이것은 NPN_SetProperty에서 사용된 NPVariant도 마찬가지 입니다.

 

메모리 해제에 책임을 지지 않을 유일한 경우는당신의 함수가 끝났을 때메모리를 컨트롤 하지 않는 방식으로 파라미터를 넘겨줄 때 입니다당신의 데이터가 함수가 끝난 이후에도 유효하려면그것을 release 해야 합니다.

 

 

<참고>

1. http://stackoverflow.com/questions/1955073/when-to-release-object-in-npapi-plugin

2. http://colonelpanic.net/2009/12/memory-management-in-npapi/