0326 컴파일언어스크립팅용으로쓰기

비슷한 글을 적은 적이 있던 것 같은데 
어딨는지 기억이 안나서 다시 적는다.

DLL로 컴파일 될 수 있는 언어라면, 
어떤 언어던 스크립팅용으로 쓸 수 있다. 

DLL을 사용하는 방법에는 두가지가 있는데,
    첫째는 헤더와 mylib-d.lib을 참조해서 (오프라인) 링킹하는 것이고,
    둘째는 플랫폼에 따라 LoadLibrary()같은걸로
        핸들을 받아오고 그 핸들과 프로시저 이름을 이용해서 프로시저 주소를 가져오는 것이다.
        (물론 받아오는 게 실패할 수도 있다)

        1. dllexport와 extern"c"로 dll에 함수내용 포함 및 이름 충돌 방지
            extern "C" __declspec(dllexport) void MyFunction();
        2. 로딩
            HINSTANCE hinstLib = LoadLibrary(TEXT("MyDll.dll"));
        3. 프로시저 가져오기
            typedef void (*MYPROC)();
            MYPROC pMyFunction = 
                (MYPROC)GetProcAddress(hinstLib, "MyFunction");
        4. 해제
            FreeLibrary(hinstLib);

C언어로 스크립팅을 한다 치면, 
    1. DLL안에 "export된 특정 이름의 함수"가 있다고 가정.
    2. DLL을 컴파일할 때, 엔진 측에서 제공해주는
        수학 / 글로벌서버 / 로깅 인터페이스를 사용함.
    3. DLL을 로딩한 바로 직후에,
        엔진 측에서는 DLL의 비어 있는 "인터페이스 슬롯"에
        자신이 가진 함수 포인터를 건네줌.

예를 들어 현재 씬에 "MySprite"라는 오브젝트가 있다고 하면,
C언어 안에서는 다음과 같이 쓴다.

extern "C" __declspec(dllexport) void MySprite_onUpdate(double dt) {      // 참고로 MySprite의 아이디를 얻어올 방법도 생각해놓자.

}

그리고 C언어에서는 게임 엔진에 메세지를 로깅하는 함수 슬롯이 정의되어 있다.

typedef void (*LogMessageFunc)(const char* msg);
// DLL 내의 전역 변수는 __declspec(dllexport)를 붙이지 않아도 
//  일단 DLL내부의 함수에서 접근이 가능하다고 알고 있다(아닐수도???)
extern "C" static LogMessageFunc logMessage = NULL;

그리고 C언어에서는 게임 엔진의 함수들을 받아오는 함수가 있다.

// DLL의 진입점
extern "C" 
__declspec(dllexport) void Initialize_C_Interface(LogMessageFunc logFunc) {
    logMessage = func; // 로깅 함수 포인터를 받아놓는다.
}





이제 게임엔진에서 "C언어로 쓰인 DLL"을 로딩하고, 초기화한다.

typedef void (*LogMessageFunc)(const char* msg);
typedef void (*C_Script_Init)(LogMessageFunc); // 스크립트 인터페이스를 초기화하는 함수
typedef void (*Sprite_on_update)(double); // 모든 스프라이트 오브젝트에서 쓰이는 함수

void LogMessage_requested_from_C(const char* msg) {
    std::cout << "Calling Engine from C : " << msg << "\n";
}

int main() {
    auto hInst = LoadLibrary("C_Script");

    // C스크립트에 "전역" 게임 엔진 함수를 제공해준다.
    // 나중에는 이거 구조체로 바꿔서 한방에 보내주자.
    C_Script_Init init_fn = (C_Script_Init)GetProcAddress(hInst, "Initialize_C_Interface");
    assert(init_fn != NULL);
    init_fn(reinterpref_cast<LogMessageFunc>(LogMessage_requested_from_C)); // 로깅 함수 건네주기

    // 이제 모든 오브젝트와 그에 상응하는 C스크립트를 연결한다.
    const std::string objName = "MySprite";
    Sprite_on_update mySpriteUpdate = (Sprite_on_update)GetProcAddress(hInst, 
        (objName + "_on_update").c_str()
    );
}



추가적으로 C#같은 클래스 기반 언어의 경우에는, 
이런 식으로 한다.

1. 인터페이스가 있다.
class MonoBehavior
    public size_t m_ID;

2. 인터페이스를 상속받는 커스텀 스크립트를 만든다.
class MyBehavior : public MonoBehavior
    constructor(size_t ID) : m_ID(ID) {

    }
    on_update(double dt) { _EngineInternal_Log("mysprite update"); }

3. 인터페이스 내부에 모든 오브젝트가 들어가 있는 전역 리스트와
    그 전역 리스트를 관리할 수 있는 전역 함수가 있다.
    여기서 중요한 점은, 리플렉션을 통해서 클래스 이름으로 인스턴스를 생성하고,
    부모 클래스로 관리해두는 것이다.

unordered_map<size_t, MonoBehavior> objects {};

void AddBehavior(string class_name, size_t ID)
    Type a = Type.GetType(class_name);
        var myA = Activator.CreateInstance(a);

        MonoBehavior behav = (MonoBehavior)myA;

    behav.m_ID = ID;

    objects[ID] = behav;

void UpdateBehavior(size_t ID, double dt)
    objects[ID].on_update(dt);

4. 추가적으로 이러한 함수도 존재할 것이다..
bool ComponentExists(size_t ID, string comp_name)
    return _EngineInternal_ComponentExists(ID, comp_name);



*유의할 점은, DLL은 DLL내부의 함수를 엔진이 부르기 전까지는
    스스로 아무것도 할 수 없다. 응답만 가능하다.