UDN
Search public documentation:

DLLBindKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 언리얼스크립트 > 언리얼스크립트에서 DLL 호출하기 (DLLBind)

언리얼스크립트에서 DLL 호출하기 (DLLBind)


개요


2009년 12월 버전부터 언리얼스크립트에서 윈도우 DLL에 구현된 함수를 호출할 수 있게 되었습니다. 이 기능은 UDK 개발자 및 언리얼 엔진 3 제품의 확장 모드를 만드는 제 3사 용으로 고안된 것입니다. 언리얼 엔진 3 풀 라이선스 개발자들은 네이티브 클래스 생성 을 통해 네이티브 코드를 직접 생성할 수 있으므로 이 기능을 사용할 필요가 없을 것입니다. 고로 라이선시의 소스 코드에는 이 기능이 비활성화되어 있습니다만, UE3BuildWin32.cs의 libffi 부분에서 활성화시킬 수 있습니다.

언리얼스크립트 측


하나의 언리얼스크립트 클래스는 하나의 DLL에만 바인드 가능합니다. DLL을 바인드할 곳은 DLLBind 디렉티브에 지정되어 있으며, 바인드할 DLL명은 접두사에 지정되어 있습니다. 경로나 .DLL 확장자를 포함하지 마십시오. DLL은 Binaries\Win32\UserCode 폴더에서만 로드 가능합니다.

임포트된 함수는 dllimport 함수 디렉티브를 사용하여 언리얼스크립트 함수처럼 선언됩니다.

DLL 임포트 함수는 final로 선언해야 하며, DLLBind 클래스의 하위클래스는 만들 수 없습니다.

예로:

class TestDLLPlayerController extends PlayerController
   DLLBind(TestDLL);

dllimport final function CallDLL1(out string s);
dllimport final function vector CallDLL2(float x, float y, float z);
dllimport final function bool CallDLL3(string s, int i[2], out float f, out vector v);

TestDLLPlayerController 클래스가 로드되면, Binaries\Win32\UserCode\TestDLL.dll로 바인드됩니다. DLL 바인드가 불가능하면, 로그에 경고가 출력됩니다. DLL의 세 익스포트 지점(CallDLL1, CallDLL2, CallDLL3)에서 도입 지점 검색을 시도합니다. 바인드되면, 이 함수는 다른 언리얼스크립트 함수와 마찬가지로 호출 가능해 집니다. 바인드가 실패하면, 이 함수를 호출해도 아무 효과가 없습니다.

DLL 측


이 함수는 다음과 같이 C++ DLL에 구현 가능합니다:

extern "C"
{
   struct FVector
   {
      float x,y,z;
   };

   __declspec(dllexport) void CallDLL1(wchar_t* s)
   {
      MessageBox(0, s, L"Inside the DLL: CallDLL1", MB_OK);
      // reverse the out parameter string
      int len = wcslen(s);
      for(int i=0; i<len>>1;i++)
      {
         wchar_t temp = s[i];
         s[i] = s[len-i-1];
         s[len-i-1] = temp;
      }
   }

   __declspec(dllexport) FVector* CallDLL2(float x, float y, float z)
   {
      static FVector result;   // declared static so that the struct's memory is still valid after the function returns.
      result.x = x;
      result.y = y;
      result.z = z;
      return &result;
   }

   __declspec(dllexport) bool CallDLL3(wchar_t* s, int i[2], float* f, FVector* V)
   {
      wchar_t temp[1024];
      swprintf_s(temp, 1024, L"string: %s, i: {%d,%d}, float: %f, V was (%f,%f,%f)", s, i[0], i[1], *f, V->x, V->y, V->z);
      V->x = (float)i[0];
      V->y = (float)i[1];
      V->z = (*f);
      return (MessageBox(0, temp, L"Inside the DLL: CallDLL3", MB_OKCANCEL) == IDOK);
   }
}

언로드


DLLBind 클래스의 클래스 오브젝트가 소멸되면, DLL이 FreeLibrary API를 통해 언로드됩니다. 해당 클래스 오브젝트로의 모든 참조가 삭제된 경우에 일어나는 일이며, 가비지 콜렉션도 잇따릅니다. 정리 작업을 DLL 내부의 DllMain() 함수를 통해, DLL_PROCESS_DETACH fdwReason 에 응답하는 식으로 수행할 수도 있습니다.

지원 파라미터 유형


다음 파라미터 유형이 지원됩니다:

언리얼스크립트 유형 C 유형  
int 값 int 값 (32비트) 값 전달
int 값[..] int* 값 (32비트) 참조 전달
out int 값 int* 값 (32비트) 참조 전달
float 값 float 값 값 전달
float 값[..] float* 값 참조 전달
out float 값 float* 값 참조 전달
byte 값 unsigned char 값 값 전달
byte 값[..] unsigned char* 값 참조 전달
out byte 값 unsigned char* 값 참조 전달
string 값 wchar_t* 값 참조 전달
out string 값 wchar_t* 값 참조 전달 아래 경고 참고
struct foo struct foo* 값 참조 전달. DLL에서 구조체를 선언하여 언리얼스크립트 구조체에 일치시켜야 합니다. 구조체 내부의 문자열에 관한 정보는 아래를 참고하십시오.
out struct foo struct foo* 값 참조 전달

다음 반환값 유형이 지원됩니다:

언리얼스크립트 유형 C 유형  
int int (32비트) 값 직접 반환
float float 값 직접 반환
byte unsigned char 값 직접 반환
bool unsigned int (32비트) 값 직접 반환, non-zero=true
string wchar_t* 포인터 값이 언리얼스크립트 문자열에 strcpy됩니다. 아래 경고 참고
struct foo struct foo* 구조체 데이터가 언리얼스크립트 구조체에 memcpy됩니다. 아래 경고 참고

제한사항


  • 유니코드(UTF-16) 문자열만 지원됩니다. 유니코드를 활성화시키고 DLL을 컴파일하십시오.
  • 현재 UDK는 32비트 버전만 가능하므로, 32비트 DLL만 지원됩니다. UDK 64비트 버전이 나오면, 64비트 DLL도 지원될 것입니다.
  • DLL은 Binaries\Win32\UserCode 에서만 로드 가능합니다.
  • Win32에서는 stdcall만 지원됩니다. (호출 용례에 대한 정보)

디버깅


  • DLL 의 Debug 빌드가 올바르게 바인드되면, UDK.exe 에 디버거를 부착할 수 있을 것이며, DLL 내에 브레이크포인트를 설정할 수 있을 것입니다.
  • 다른 이에게 배포하려면 DLL 을 Release 모드로 컴파일해야 하며, 그렇지 않으면 C 런타임의 디버그 버전이 사용할 수 없는 상태가 되기에 문제가 생길 것입니다.

언리얼스크립트 문자열, 구조체에서의 문자열 사용 관련


언리얼은 동적 문자열을 내부적으로 FString이라 불리는 구조체로 표현합니다. 그 3개 멤버는:

  • Data, 문자열에 할당된 메모리를 가리키는 32비트 포인터
  • ArrayNum, 현재 문자열의 (NULL 종료자를 포함한) 16비트 워드(UTF-16 글자) 수를 저장하는 32비트 int. 보통 wcslen(Data)+1 이며, 빈 문자열은 0.
  • ArrayMax, 현재 문자열에 할당된 메모리의 16비트 워드 수를 저장하는 a 32비트 int.

문자열은 ArrayMax-1 워드 길이까지 늘이거나 줄일 수 있으며, 그 시점에서 언리얼이 Data 메모리를 재할당합니다. 빈 문자열은 특별한 경우입니다. Data 포인터는 NULL, ArrayNum = ArrayMax = 0 입니다.

이는 DLLBind에 문제를 유발시킵니다. 현 DLLBind 코드는 문자열 파라미터나 반환값을 찾아서 FString의 Data 포인터를 DLL에 넘겨줍니다. 반환되면, 언리얼은 출력 파라미터 문자열의 현 길이를 검사하여 필요하면 ArrayNum을 조절합니다. wchar_t* 반환값에 대해서는, DLL이 반환된 후에 데이터를 새 언리얼 문자열에 복사합니다.

그러나 DLL에 참조 전달된 구조체 내부의 문자열에 대해서는, 이 작업들 중 수행되는 것이 아무것도 없습니다. 이 문제의 해결책은 DLL이 자신의 로컬 FString 클래스를 언리얼의 메모리 레이아웃 그대로 선언해야 합니다.

C++:

struct FString
{
   wchar_t* Data;
   int ArrayNum;
   int ArrayMax;
};

struct MyPlayerStruct
{
   FString PlayerName;
   FString PlayerPassword;
   float Health;
   float Score;         
};

UnrealScript:

struct MyPlayerStruct
{
   var string PlayerName;
   var string PlayerPassword;
   var float Health;
   var float Score;
};

알림

  • 구조체가 출력 파라미터인 경우, 구조체 내부 문자열의 값을 ArrayMax 길이까지 바꿀 수 있습니다. 나중에 ArrayNum을 맞춰주기만 하면요. 첨부된 StringDLL 예제를 참고하십시오.
  • Data 포인터를 배치 및 재배치 시도하지 마십시오. DLL이 언리얼의 메모리 할당기를 사용하지 않습니다.
  • ArrayMax 값을 수정하지 마십시오.
  • 문자열이 포함된 구조체를 반환하지 마십시오. 구조체 변수가 소멸되면 언리얼이 Data 메모리를 해방(free)하려 하기 때문입니다.

경고


  • 출력 파라미터의 문자열을 수정할 때, 바뀌는 문자열 길이는 전달된 기존 문자열 길이 이하여야 합니다. 이는 언리얼스크립트의 문자열에다 DLL이 저장하게될 최대값보다 긴 초기값을 설정하여 수행할 수 있습니다. 이렇게 하지 못하면 메모리를 덮어쓰게 되어 크래시가 생길 수 있습니다.
  • 구조체나 문자열을 반환할 때, DLL은 포인터를 반환하며 언리얼은 그 값을 언리얼스크립트에 다시 복사합니다. 그러나 지정된 데이터 저장 메모리 할당을 관장하는 것은 DLL입니다. 예로 CallDLL2 에서처럼 정적 변수에 포인터를 반환할 수도 있습니다. 스택에 선언된 변수에다 포인터를 반환하면 데이터 유실되거나 크래시가 납니다. 왜냐면 스택은 DLL 함수가 반환될 때 소멸되기 때문입니다.
  • 언리얼스크립트에서 구조체 멤버는 4바이트 경계로 정렬되므로, DLL을 빌드할 때는 /Zp4 컴파일러 옵션을 사용해야 합니다.
  • DLL에 안쓰이는 언리얼 데이터 유형을 포함하여 구조체를 선언할 수는 있습니다. 물론 비추입니다!
  • 언리얼스크립트 문자열을 포함하는 구조체를 반환하려 하지 마십시오. 위의 지침만 따른다면 출력 파라미터에는 괜찮습니다.
  • 2010년 2월 UDK 이전에, 현 작업 디렉토리는 UserCode가 아닌 Binaries\Win32 폴더로 설정되어 있었습니다. 2010년 2월 버전부터 DLLBind 함수 호출 이전에는 작업 디렉토리가 UserCode 폴더로 바뀌었습니다.

예제