Firefox Plug-in – JavaScript 연동 (4)

JavaScript에서 Plug-in 내부의 함수를 호출하는 방법을 익혀 봅니다. 서로 다른 형태의 바이너리가 소통할 수 있는 이유는 XPCOM이라는 인터페이스 규약을 따르고 있기 때문입니다.

IDL 파일 생성


그림 1 Add 함수

지금 만들어 보려고 하는 형태는 위 그림에서 설명하는 것처럼 1 + 2를 호출하면 3을 반환하는 함수를 만들어 보려고 하고 있습니다. Function Call처럼 보이는 Add 함수는 Plug-in이 구현하고 있는 함수로서 JavaScript가 호출해서 반환 값을 얻어가고 있는 형태인데요. 바이너리 레벨에서 서로 통신하는 구조는 내부적으로 XPCOM 인터페이스 규칙을 따르고 있기 때문에 가능합니다.

간단해 보이는 Add 함수를 구현함으로써 크게 XPCOM 함수를 구현하는 방법, XPCOM 인터페이스를 구현하는 방법, 인자가 넘어오는 형태, 반환 값을 넘기는 형태 등을 배울 수 있습니다.

가장 첫 번째 단계로 IDL 파일을 만들어 봅시다. IDL 파일은 Interface Description Language로 인터페이스를 정의하는 파일이 되겠습니다. 인터페이스가 필요한 이유는 Add 함수가 어떤 형태로 구현이 되어 있던 간에 외부에서는 인터페이스만 보고 통신할 수 있도록 하기 위함입니다.


그림 2 nsIScriptableObject.idl

위 그림에 작성된 IDL 파일을 보면 #include 부분과 uuid 부분을 제외하면 Class 정의하는 것과 거의 차이가 없어 보입니다. Add 함수는 long 형 2개를 인자로 받고 long형을 반환하는 함수의 형태입니다. 위 내용을 그대로 작성해서 nsIScriptableObject.idl 파일로 저장합니다.


Type Header와 Type Library 파일 생성

앞서 배웠던 xpidl을 이용해서 Type Header와 Type Library를 생성합니다.

xpidl -m header -I .\gecko-sdk\idl nsIScriptableObject.idl
xpidl -m typelib -I .\gecko-sdk\idl nsIScriptableObject.idl

결과 파일로 nsIScriptableObject.h 와 nsIScriptableObject.xpt 파일이 생성됩니다.


Scriptable Object 구현 (XPCOM 프로그래밍)

nsIScriptableObject.h 파일에는 Add 함수에 대한 정의 부분만 들어가 있습니다. 내용을 잠시 보면

... 생략

/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_NSISCRIPTABLEOBJECT \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval);

/* Use this macro to declare functions that forward the behavior of this interface to another object. */
#define NS_FORWARD_NSISCRIPTABLEOBJECT(_to) \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return _to Add(a, b, _retval); }

/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
#define NS_FORWARD_SAFE_NSISCRIPTABLEOBJECT(_to) \
NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return !_to ? NS_ERROR_NULL_POINTER : _to->Add(a, b, _retval); }

… 생략


위 와 같은 내용으로 이루어져 있습니다. Xpild이 자동으로 생성해주는 부분이기 때문에 많은 부분 생략되었습니다. 중요한 부분은 NS_DECL_NSISCRIPTABLEOBJECT 이라고 선언되어 있는 매크로 입니다. Xpidl이 자동으로 헤더 파일을 생성할 때 개발자가 구현을 쉽게 할 수 있도록 몇가지 매크로를 만들어 주는데요. NSISCRIPTABLEOBJECT은 개발자가 구현해야할 함수 원형을 매크로로 선언해놓은 부분입니다. 본 예에서는 함수가 하나밖에 없기 때문에 불필요해 보일 수도 있지만 실제로 헤더에 선언된 매크로들은 많은 편리함을 제공해줍니다.

이 헤더파일의 구현부를 작성해보도록 합시다. 구현 파일의 이름은 nsScriptableObject.h와 nsScriptableObject.cpp로 하겠습니다.

Step 1. nsScriptableObject 클래스를 만들고
Step 2. xpidl이 생성한 nsIScriptableObject 를 상속받습니다.
Step 3. 그 다음에는 AddRef, Release, QueryInterface 함수를 구현해줍니다.
(이 3함수들을 XPCOM 규약에 따르는 형식입니다.)
Step 4. 그리고 마지막으로 Add 함수를 구현해줍니다.

nsScriptableObject.h 파일을 살펴보겠습니다.
#include "nsIScriptableObject.h"

class nsScriptableObject  : public nsIScriptableObject
{
public:
nsScriptableObject();
virtual ~nsScriptableObject();

public:
// native methods callable from JavaScript
NS_DECL_NSISCRIPTABLEOBJECT


///////////////////////////////////////////////////////
// XPCOM Support
///////////////////////////////////////////////////////

// methods from nsISupports
NS_IMETHOD QueryInterface(const nsIID& aIID, void** aInstancePtr);
NS_IMETHOD_(nsrefcnt) AddRef();
NS_IMETHOD_(nsrefcnt) Release();

protected:
nsrefcnt m_nRefCnt;

};

NS_DECL_NSISCRIPTABLEOBJECT은 Add 함수의 원형이 들어있는 매크로 입니다. 그리고 아래는 AddRef, Release, QueryInterface 함수가 선언되어 있습니다.

nsScriptableObject.cpp 파일을 살펴보겠습니다.
// AddRef, Release and QueryInterface are common methods and must
// be implemented for any interface
NS_IMETHODIMP_(nsrefcnt) nsScriptableObject::AddRef()
{
++m_nRefCnt;
return m_nRefCnt;
}

NS_IMETHODIMP_(nsrefcnt) nsScriptableObject::Release()
{
--m_nRefCnt;
if (m_nRefCnt == 0)
{
delete this;
return 0;
}
return m_nRefCnt;
}

static NS_DEFINE_IID(kIScriptableIID, NS_ISCRIPTABLEOBJECT_IID);
static NS_DEFINE_IID(kIClassInfoIID, NS_ICLASSINFO_IID);
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);

// here nsScriptablePeer should return three interfaces it can be asked for by their iid's
// static casts are necessary to ensure that correct pointer is returned
NS_IMETHODIMP nsScriptableObject::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if(!aInstancePtr)
return NS_ERROR_NULL_POINTER;

if(aIID.Equals(kIScriptableIID)) {
*aInstancePtr = static_cast<nsIScriptableObject*>(this);
AddRef();
return NS_OK;
}

if(aIID.Equals(kIClassInfoIID)) {
*aInstancePtr = static_cast<nsIClassInfo*>(this);
AddRef();
return NS_OK;
}

if(aIID.Equals(kISupportsIID)) {
*aInstancePtr = static_cast<nsISupports*>(static_cast<nsIScriptableObject*>(this));
AddRef();
return NS_OK;
}

  return NS_NOINTERFACE;
}

NS_IMETHODIMP nsScriptableObject::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
int nResult = a + b ;

*_retval = nResult ;

return NS_OK;
}

AddRef, Release, QueryInterface 함수는 위 내용대로 구현하면 됩니다. 자세한 내용은 추후에 알아보도록 합니다.

Add 함수 구현부를 보면 인자로 a, b , _retval 이 있습니다. a와 b는 idl에서 선언해준 그대로의 형태로 되어 있으며, 반환값은 정수형 포인터로 _retval 로 지정되어 있는 것을 볼 수 있습니다. 일반 Function Call과 약간은 다른 형태로 보입니다.


nsIClassInfo 구현

JavaScript에서 Plug-in 함수를 호출을 허용하기 위해서는 nsIClassInfo를 상속받아야 합니다.


// We must implement nsIClassInfo because it signals the
// Mozilla Security Manager to allow calls from JavaScript.
#include "nsIClassInfo.h"
class nsClassInfoMixin : public nsIClassInfo
{
  // These flags are used by the DOM and security systems to signal that
  // JavaScript callers are allowed to call this object's scritable methods.
  NS_IMETHOD GetFlags(PRUint32 *aFlags)
  {*aFlags = nsIClassInfo::PLUGIN_OBJECT | nsIClassInfo::DOM_OBJECT;
    return NS_OK;}
  NS_IMETHOD GetImplementationLanguage(PRUint32 *aImplementationLanguage)
  {*aImplementationLanguage = nsIProgrammingLanguage::CPLUSPLUS;
    return NS_OK;}
  // The rest of the methods can safely return error codes...
  NS_IMETHOD GetInterfaces(PRUint32 *count, nsIID * **array)
  {return NS_ERROR_NOT_IMPLEMENTED;}
  NS_IMETHOD GetHelperForLanguage(PRUint32 language, nsISupports **_retval)
  {return NS_ERROR_NOT_IMPLEMENTED;}
  NS_IMETHOD GetContractID(char * *aContractID)
  {return NS_ERROR_NOT_IMPLEMENTED;}
  NS_IMETHOD GetClassDescription(char * *aClassDescription)
  {return NS_ERROR_NOT_IMPLEMENTED;}
  NS_IMETHOD GetClassID(nsCID * *aClassID)
  {return NS_ERROR_NOT_IMPLEMENTED;}
  NS_IMETHOD GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
  {return NS_ERROR_NOT_IMPLEMENTED;}
};


#include "nsIScriptableObject.h"
class nsScriptableObject  : public nsIScriptableObject ,
     public nsClassInfoMixin
{

.. 생략

위 같이 nsClassInfoMixin 클래스를 작성해주시고, 구현한 nsScriptableObject 클래스에 상속을 시켜주세요. 이제 JavaScript에서 Plug-in에 있는 함수를 호출할 때 사용할 함수에 대한 구현이 완료되었습니다. 다음은 plug-in에서 ScriptableObject를 Firefox 브라우저에 넘겨주는 코드가 작성되어야 합니다.




CleanSample에서 GetValue 함수 추가 구현

이전에 작성하였던 CleanSample에서는 GetValue함수가 구현이 되어 있지 않습니다. 이 함수를 구현해야 Firefox 브라우저에게 ScriptableObject를 넘길 수가 있고, 그제서야 JavaScript에서 Plug-in 내부의 함수를 호출 할 수 있게 됩니다.

가장 먼저 fillPluginFunctionTable 함수에서 GetValue 함수 포인터를 넘겨주는 코드를 수정합니다.
static NPError fillPluginFunctionTable(NPPluginFuncs* aNPPFuncs)
{
  if(aNPPFuncs == NULL)
  return NPERR_INVALID_FUNCTABLE_ERROR;

  // Set up the plugin function table that Netscape will use to
  // call us. Netscape needs to know about our version and size  
  // and have a UniversalProcPointer for every function we implement.

  aNPPFuncs->version       = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
  aNPPFuncs->newp          = NPP_New;    // 플러그인 인스턴스가 생성될때마다 호출된다.
// aNPPFuncs->destroy       = NPP_Destroy;   // 플러그인 인스턴스가 소멸될때마다 호출된다.
  aNPPFuncs->setwindow     = NPP_SetWindow;   // 플러그인의 윈도우 상태가 변경될때마다 호출된다.
// aNPPFuncs->newstream     = NPP_NewStream;
// aNPPFuncs->destroystream = NPP_DestroyStream;
// aNPPFuncs->asfile        = NPP_StreamAsFile;
// aNPPFuncs->writeready    = NPP_WriteReady;
// aNPPFuncs->write         = NPP_Write;
// aNPPFuncs->print         = NPP_Print;
// aNPPFuncs->event         = NPP_HandleEvent;
// aNPPFuncs->urlnotify     = NPP_URLNotify;
  aNPPFuncs->getvalue      = NPP_GetValue;
// aNPPFuncs->setvalue      = NPP_SetValue;

  return NPERR_NO_ERROR;
}

그리고 다음으로 NPP_GetValue 함수를 구현합니다. 함수 내용은 대략적으로 Firefox가 ScriptableObject의 인스턴스를 요청처리와 Interface 요청 처리하는 코드로 구성되어 있습니다.
NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value)
{
if(instance == NULL)
return NPERR_INVALID_INSTANCE_ERROR;

NPError rv = NPERR_NO_ERROR;

if (variable == NPPVpluginScriptableInstance)
{
// addref happens in getter, so we don't addref here
nsScriptableObject * scriptablePeer = getScriptablePeer();
if (scriptablePeer)
{
  *(nsScriptableObject **)value = scriptablePeer;
}
else
{
  rv = NPERR_OUT_OF_MEMORY_ERROR;
}  
}

else if (variable == NPPVpluginScriptableIID)
{
static nsIID scriptableIID = NS_ISCRIPTABLEOBJECT_IID;
nsIID* ptr = (nsIID *)NPNFuncs.memalloc(sizeof(nsIID));

if (ptr)
{
  *ptr = scriptableIID;
  *(nsIID **)value = ptr;
}
else
{
  rv = NPERR_OUT_OF_MEMORY_ERROR;
}
}
return rv ;
}


작동개시!


ㅋ ㅑ ~ 에러없이 컴파일이 되는데 성공하셨나요? 저는 여기까지 오는데 무수한 삽질을 했습니다;; 마지막으로 Firefox에서 돌려보면서 결과를 보는 일만 남았습니다. 아래와 같은 HTML 페이지를 작성하신 다음에 %Firefox 설치 폴더%plugins 폴더에 NPPlugIn.dll 파일과 NPPlugIn.xpt 파일을 복사하신 다음 HTML 페이지를 Firefox로 열어보세요.

<embed id=sample type="application/sample-plugin" width=600 height=40>


<script>
var obj = document.getElementById( "sample" ) ;
var result = obj.Add( 2 , 1 ) ;
alert( result ) ;
</script>


후후후! Alert 창으로 1 + 2 = 3 의 결과 창이 뜨는 것이 보이시나요? 이제 plug-in 뭐 별거냐 싶으시죠?

 

 
본 글의 출처는 다음과 같습니다.

작성자 : 이은규
홈페이지 : http://unkyulee.net
작성일 : 2006-02-20
블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,