UDN
Search public documentation:

DLLBindCH
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主页 > UnrealScript > 从UnrealScript中调用DLL (DLLBind)

从UnrealScript 调用DLLs (DLLBind)


概述


从2009年12月份的版本开始,UnrealScript便可以调用在Windows DLL文件中实现的函数。这个功能的设计目的是供UDK开发人员以及开发第三方虚幻引擎3产品的扩展mod制作人员使用的。完全授权的虚幻引擎3开发人员可以通过创建 Native 类来直接地调用native函数,并且他们不应该使用这个功能。这个功能默认情况下在授权用户源代码中是禁用的。要想启用它,可以通过启用UE3BuildWin32.cs的libffi部分来实现。

UnrealScript方面


一个单独的UnrealScript类仅能绑定到一个单独的DLL上。要绑定到的DLL通过 DLLBind 指令指出,要绑定到的DLL的名称在圆括号中指出。请不要包含一个路径或.DLL扩展名。DLLs仅可以从Binaries\Win32\UserCode文件夹中进行加载。

使用 dllimport 函数指令,像声明UnrealScript函数那样来声明导入的函数。

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中找出到3个导出的入口点(CallDLL1, CallDLL2 and CallDLL3)。一旦绑定上后,可以像从其它UnrealScript函数中那样正常地调用这些函数。如果绑定失败,对这些函数中任何函数的调用将没有效果。

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);
      //反转输出参数字符串
      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;   //声明为static ,以便struct(结构体)的内存在函数返回后仍然有效。
      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 类别的类对象,将会使用FreeLibrary API卸载DLL。当到那个类对象的最后一个引用被删除并且进行垃圾回收时,应该发生这个动作。通过响应DLL_PROCESS_DETACH fdwReason,您应该可以在您DLL的DllMain()函数中执行清除动作。

支持的参数类型


支持以下参数类型:

UnrealScript 类型 C 类型  
int值 int 值 (32-bit) 值传递
int值[..] int* 值(32-bit) 引用传递
out int值 int* 值(32-bit) 引用传递
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应该声明一个struct来和UnrealScript 的struct 相匹配。请参照相面获得关于struct内的字符串的信息。
out struct foo struct foo*值 引用传递

支持以下返回类型:

UnrealScript 类型 C 类型  
int int (32-bit) 直接返回值。
float float value 直接返回值。
byte unsigned char 直接返回值。
bool unsigned int (32-bit) 直接返回值,非零值为真。
string wchar_t* 将指针值通过strcpy (字符串拷贝)函数拷贝到UnrealScript 字符串中。*参照以下警告* 。
struct foo struct foo* 通过memcpy函数将struct(结构体)数据拷贝到UnrealScript struct (结构体)中。*参照以下警告* 。

限制


  • 仅支持Unicode(UTF-16)字符串。当编译您的DLL时,请启用Unicode。
  • 因为目前仅32-位的UDK可用,所以支持任何32-位的DLLs。当提供64-位的UDK时,那么将支持64-位的DLLs。
  • DLLs仅能从Binaries\Win32\UserCode进行加载。
  • 在Win32目录下,仅支持stdcall。(请参照这个链接来获得关于函数调用规则的更多信息)。

调试


  • 您的DLL的调试版本应该可以正确绑定,并且您应该可以通过调试器附加到 UDK.exe上,并在DLL中设置断点。
  • 如果您想发布该DLL文件给其他人,您应该以调试模式编译您的DLL,否则您将会遇到问题,因为没有C运行时系统的调试版本。

关于UnrealScript字符串及在结构体中使用字符串


Unreal在内部将动态字符串表示为一个结构体,称为FString。它包含三个成员:

  • Data, 一个32位的指针,指向为该字符串分配的内存。
  • ArrayNum, 一个32-位的整型数,存放着字符串(包括NULL终止符)中16-位单词(UTF-16字符)的当前数量。除了空字符串外,这个值一般等于 wcslen(Data)+1 ,当字符串为空时该值为0。
  • ArrayMax, 一个32位的整型数值,存放了当前为这个字符串的16-位单词分配的内存的数量。

字符串的长度可以增加或缩减ArrayMax-1个单词,那时Ureal将会为Data 重新分配数据内存。 空字符串是特殊情况;Data指针为NULL,并且ArrayNum = ArrayMax = 0。

这对于DLLBind来说会产生一个问题。当前的DLLiBind代码寻找任何字符串参数或返回值,并把FString的Data指针传递给DLL。当返回时,Unreal判断任何输出字符串参数的当前长度,并根据需要调整ArrayNum 的值。 对于wchar_t*返回值,它在DLL返回后将数据复制给一个新的Ureal字符串。

但是,这个过程在通过引用传递给DLL的结构体内的字符串中并不执行。这个问题的解决方案是DLL声明它自己的局部FString类来镜像Unreal的内存布局。

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;
};

注意

  • 如果结构体是输出(out)参数,假设您调整ArrayNum来向后匹配,那么可以把结构体内的字符串的值最多改为ArrayMax的长度。请参照附件中的StringDLL 示例。
  • 请不要尝试分配或重新分配Data指针,因为您的DLL将不会使用Unreal的内存分配器。
  • 请不要调整ArrayMax的值。
  • 请不要返回具有字符串的结构体,因为当结构体变量销毁时Unreal将尝试释放Data的内存。

警告


  • 当通过输出参数来修改字符串时,新的字符串必须等于小于传入的字符串的值的长度。这可以通过在UnrealScript中初始化字符串为某些比DLL将存储的最大值长的东西来达到目的。如果这样做失败,那么这可能会覆盖内存并导致崩溃。
  • 当返回structs(结构体)或字符串时,DLL将返回一个指针,Unreal将会把那个值复制回到UnrealScript中。然而,DLL负责分配内存来存储所指向的数据。比如,它返回了一个到静态变量的指针,正如在CallDLL2所完成的。返回一个指向声明在栈上的变量的指针将会导致返回错误数据或崩溃,因为当DLL函数返回时那个栈就已经销毁了那个栈。
  • 在UnrealScript中,struct(结构体)成员4个字节的分界进行排列的,所以当您编译DLL时,您应该使用[[]编译器选项。
  • 声明包含DLL不能理解的Unreal数据类型的structs是可以的。但是我们不推荐您这么做!
  • 请不要尝试返回包含UnrealScript 字符串的结构体。如果遵守了上面的指示,那么Out(输出)参数是可以的。
  • 在2010年2月份的UDK之前的版本中,当前的工作目录设置在Binaries\Win32文件夹而不是UserCode。自从2010年2月份的版本后,在调用任何DLLBind函数之前工作目录变为UserCode文件夹。

示例


  • DLLBind_Example.zip - 基本的DLLBind示例
  • StringDLL.zip - 展示如何使用具有字符串的结构体的示例。
  • ArrayDLL.zip - 展示了在DLL中使用动态数组的高级示例,包括内存重新分配问题。