非托管C#调用静态库

时间:2016-05-25 22:47:52

标签: ios unity3d mono pinvoke swig

我正在使用swig为C#中使用的某些C代码库生成C#包装器。当我运行swig时,它会生成一个包装器c文件,该文件向生成的PInvoke C#文件公开所有功能...例如:

// This is in KodLogic_wrap.c
SWIGEXPORT void SWIGSTDCALL CSharp_DMGameMode_timeLimit_set(void * jarg1, unsigned short jarg2) { ... }
// This is in KodLogicPInvoke.cs
[global::System.Runtime.InteropServices.DllImport("KodLogic", EntryPoint="CSharp_DMGameMode_timeLimit_set")]

当我构建动态库时,这非常有用。但是,我现在需要支持iOS,所以我准备了一个静态库,并将-dllimport '__Internal'选项传递给swig,以便它可以工作。

不幸的是,我收到的链接错误如下:

"_DMGameMode_timeLimit_set", referenced from:
  RegisterMonoModules() in RegisterMonoModules.o
  (maybe you meant: _CSharp_DMGameMode_timeLimit_set)

的确,我的意思是“CSharp_DMGameMode_timeLimit_set”,但那是“入口点”论点的意思吗?

所以,由于这个错误是由Unity生成的Xcode项目抛出的,我不太确定失败的原因是什么。静态库是否失败?这是在Unity方面还是swig方面修复的东西?

更新:在深入研究之后,我想我对这里发生的事情略有了解......

主要问题似乎来自AOT编译器,它试图将所有CS代码编译为ARM程序集。这似乎是iOS所必需的,因此在Unity的AOT编译期间,它会生成一个文件RegisterMonoModules.cpp,它会尝试定义本机代码的访问函数。 RegisterMonoModules.cpp不支持entrypoint参数,这会导致抛出未定义的符号错误...

仍在尝试寻找合适的解决方法。

1 个答案:

答案 0 :(得分:2)

主要问题似乎来自Unity,而不是Swig和Mono。如上所述,Unity执行的AOT编译并不支持入口点参数。这会生成调用函数名称的cpp代码,而不是入口点名称..

我已经通过将脚本后端切换到IL2cpp来确认这一点,并且那里的入口点名称很受尊重。

让我们切换到回调。与问题不完全相关,但它绝对适合Unity + Native插件+ iOS的上下文。

AFAIK,您无法使用Mono 2x将管理方法封送到iOS上的原生土地。我之前不得不从swig生成的文件中删除所有字符串回调和异常处理程序。幸运的是,经过一些调整后,IL2Cpp支持回调:

  1. 添加using AOT;
  2. 使用[MonoPInvokeCallback(typeof(method_signature))]
  3. 装饰回调

    您可以使用此脚本,只需使用它来处理生成的swig文件:

    def process_csharp_callbacks(pinvoke_file):
      """Process PInvoke file by fixing the decorators for callback methods to use:
      [MonoPInvokeCallback(typeof(method_signature))]
      """
      # prepare requirements
      with open(pinvoke_file) as f:
        content = f.read()
    
      callback_methods_regex = re.compile(r"( +)static (?:void|string) (?:SetPending|CreateString)\w*\([\s\w\,]+\)")
      callback_decorator = "[MonoPInvokeCallback(typeof(ExceptionDelegate))]"
      callback_arg_decorator = "[MonoPInvokeCallback(typeof(ExceptionArgumentDelegate))]"
      callback_str_decorator = "[MonoPInvokeCallback(typeof(SWIGStringDelegate))]"
      # add use AOT
      content = content.replace("\n\n", "\nusing AOT;\n", 1)
      # fix callback methods
      def method_processor(match):
    
        match_string = match.group()
        indentation = match.captures(1)[0]
    
        if match_string.find(",") != -1:
          fix = callback_arg_decorator
        elif match_string.find("static string") != -1:
          fix = callback_str_decorator
        else:
          fix = callback_decorator
    
        return indentation + fix + "\n" + match_string
    
      content = callback_methods_regex.sub(method_processor, content)
      # write it back
      with open(pinvoke_file, "w+") as f:
        f.write(content)
    

    对于寻求帮助的人来说,将生成的swig CSharp PInvoke文件转换为单声道2x脚本后端允许的内容,在生成CSharp文件之后,在构建过程中将其粘贴在某处:

    pinvoke_template = """{extern_prefix} CSharp_{method_signature};
      {normal_prefix} {method_signature} {{
        {return_statement}CSharp_{method_name}({method_args});
      }}"""
    
    def process_csharp_wrapper(csharp_dir):
      """Reads the PINVOKE csharp file, and performs the following:
      1. Remove EntryPoint="xxx" from the decorators
      2. Make the methods match their native counterpart name
      3. Add a C# method with the original name, for compatability
      """
      # prepare requirements
      pinvoke_file = os.path.join(csharp_dir, "KodLogicPINVOKE.cs")
      with open(pinvoke_file) as f:
        content = f.read()
    
      decorator_regex = re.compile(r', EntryPoint=".*?"')
      method_regex = re.compile(r"(public static extern \w+[\w:\.]+)\s(([^S]\w+)\((?:([\w:\. ]+)\,?)*\));")
      # fix decorators
      content = decorator_regex.sub("", content)
      # fix method definitions
      def method_processor(match):
        extern_prefix = match.captures(1)[0]
        return pinvoke_template.format(
          extern_prefix=extern_prefix,
          normal_prefix=extern_prefix.replace("extern ", ""),
          method_signature=match.captures(2)[0],
          return_statement=("return " if extern_prefix.find("void") == -1 else ""),
          method_name=match.captures(3)[0],
          method_args=", ".join(map(lambda s: s.strip().split()[1], match.captures(4)))
        )
    
      content = method_regex.sub(method_processor, content)
      # write it back
      with open(pinvoke_file, "w+") as f:
        f.write(content)