Unity에서 제공하는 문서는 단순히 함수나 변수에 관한 것으로서, 그다지 유용하지 않다. 저기서 사용되는 수준은 기존의 C#으로도 충분히 할 수 있는 코드이고(굳이 JavaScript로 할 필요가 없음) 다른 언어를 사용해야 할 정도로 중요한 객체나 콜백을 다루는 방법등에 대해서는 다루지 않기 때문.
emscripten 공식 문서에서 이 규칙을 상세하게 제공하지만, 이것을 처음부터 보기엔 난이도가 있고, 거의 대부분이 지금 당장 사용하지 않을 기능이기 때문에, 필요한 부분만 순서대로 확인하려고 한다.
다음 링크에서 제공하는 예시문을 살펴본다.
using System;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;
public class JsCallCsTest : MonoBehaviour
{
[DllImport("__Internal")]
public static extern void ProvideCallback(Action action);
public void Start()
{
ProvideCallback(Callback);
}
[MonoPInvokeCallback(typeof(Action))]
public static void Callback()
{
Debug.Log("Callback called");
}
}
var LibraryJsCallCsTest = {
$JsCallCsTest: {},
ProvideCallback: function(obj)
{
console.log("ProvideCallback");
console.log(obj);
JsCallCsTest.callback = obj;
Runtime.dynCall('v', obj, 0);
},
};
autoAddDeps(LibraryJsCallCsTest, '$JsCallCsTest');
mergeInto(LibraryManager.library, LibraryJsCallCsTest);
[MonoPInvokeCallback(typeof(Action))] 라는 attribute가 붙어있는 Callback이라는 함수가 있다.
DllImport(여기서는 jslib)안에서 ProvideCallback을 가져온다.
이 Instance가 Start할 때, 2.의 DllImport로 갖고온 ProvideCallback을 1.의 Callback객체(함수)를 Export할 JsCallCsTest라는 객체의 callback이라는 프로퍼티로 한다.
그 다음, Runtime.dynCall을 사용한다. 무언가 argument가 있지만, 무엇을 뜻하는지는 모른다.
어떤 방식으로 작성되는지는 대충 알겠지만, 여전히 모르는것이 많다.
다른 사이트에서 제공하는 이해하기 쉬운 설명을 참조한다.
Runtime.dynCall(signature,functionPtr,arguments);
이는 보일러플레이트 코드(반복되는 코드)이며, JavaScript에서 C# 함수를 사용할 수 있게 한다.
파라미터의 의미는 다음과 같다.
return type followed by each argument type.
리턴타입과 이후의 파라미터 타입순이다.
v : void
i : int
f : float
d : double
'들'이라고 하였다. 여러개가 붙어서 나올 수 있다.
예시1) 상기 기재된 예시
[MonoPInvokeCallback(typeof(Action))]
public static void Callback()
{
Debug.Log("Callback called");
}
위 함수를 가져오려면 "v"를 사용해야 한다.
(리턴타입 void, 파라미터 없음)
예시2)
public static void SomeCallback(int id, string data)
위 함수를 가져오고 싶다면, "vii"를 사용해야 한다.
분명히 마지막 파라미터는 string이지만, 처리되는 것은 string에 대한 포인터라서 int로 받는다고 한다.
링크에서 제공한 예제는 다음과 같다.
public class JSAPI{
[DllImport("__Internal")]
private static extern void JSExample(Action callback);
[MonoPInvokeCallback(typeof(Action))]
public static void DefaultCallback(){
//This fires from javascript
}
public void YourMethod(){
JSExample(DefaultCallback);
}
}
var myLib={
$dependencies:{},
JSExample: function(functionPtr){
Runtime.dynCall("v",functionPtr,[]);
}
};
autoAddDeps(myLib,'$dependencies');
mergInto(LibraryManager.library,myLib);
Unity는 C#에서 wasm으로 바꾸는 과정 중 emscripten을 사용한다. 2019년 버전의 emscripten 부터, JavaScript 함수를 invokable한 method 포인터로 wrap할 수 있는 기능을 추가했다.
emscripten은 v.1.38.26 버전부터 다음과 같은 기능을 제공한다.
Runtime.addFunction(jsFunction,signature):IntPtr
예시는 다음과 같다.
//상기의 JSExample 코드
JSCallbackExample: function(){
var callback=function(){
alert("Received a callback!");
};
var ptr=Runtime.addFunction(callback,'v');
return ptr;
}
//상기의 JSExample 코드
[DllImport("__Internal")]
private static extern Action JSCallbackExample();
void ExampleUsage(){
Action a = JSCallbackExample();
a.Invoke();
}
Emscripten에서 제공하는 Dependency 관련 정보
JavaScript libraries can declare dependencies (
__deps
), however those are only for other JavaScript libraries.
자바스크립트 라이브러리는 dependency들을 선언할 수 있지만(__deps
키워드를 뒤에 붙이는 것으로), 이것은 JavaScript 라이브러리끼리나 가능한 것이다. (즉, C, C++, C#과는 다름)
You can add dependencies for all your methods using autoAddDeps(myLibrary, name) where myLibrary is the object with all your methods, and name is the thing they all depend upon.
모든 메소드를 감싸는 myLibrary 개체가 있을 때, autoAddDeps(myLibrary, name)함수를 사용해 모든 함수들에 의존성을 추가할 수 있다. name이 의존할 개체의 이름이다.
This is useful when all the implemented methods use a JavaScript singleton containing helper methods.
이 기능은 모든 구현된 메소드들이 JavaScript 싱글톤 헬퍼 메소드를 사용할 때 유용하다.
예제를 다시 확인해본다.
public class JSAPI{
[DllImport("__Internal")]
private static extern void JSExample(Action callback);
[MonoPInvokeCallback(typeof(Action))]
public static void DefaultCallback(){
//This fires from javascript
}
public void YourMethod(){
JSExample(DefaultCallback);
}
}
var myLib={
$dependencies:{},
JSExample: function(functionPtr){
Runtime.dynCall("v",functionPtr,[]);
}
};
autoAddDeps(myLib,'$dependencies');
mergInto(LibraryManager.library,myLib);
이 예시를 보면, 최고 myLib 개체에 $dependencies가 있고, 그것을 의존하여 추가한다.
참고할 만한 자료들