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을 호출하기 때문에, 그것(?)을 두번 호출할 필요가 없다.
(그것이 릴리즈를 뜻하는 걸까..멀 뜻하는 걸까…)
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/