【问题标题】:VarArgs Binding in MonoTouch / Xamarin.iOSMonoTouch / Xamarin.iOS 中的 VarArgs 绑定
【发布时间】:2015-04-16 01:34:26
【问题描述】:

我有一个新的 MonoTouch 绑定部分工作。但是变量参数函数在运行时会导致崩溃。 .h 文件:

FOUNDATION_EXPORT void __BFLog(NSInteger lineNumber, NSString *method, NSString *file, 
    BFLogLevel level, NSString *tag, NSString *format, ...);

C#:

internal static class CFunctions
{
    // extern void __BFLog (NSInteger lineNumber, NSString * method, 
    // NSString * file, BFLogLevel level, NSString * tag, NSString * format, ...);
    [DllImport ("__Internal", EntryPoint = "__BFLog")]
    internal static extern void BFLog (nint lineNumber, string method, string file, 
        LogLevel level, string tag, string format, string arg0);
}

因为我会在 arg0 中传递 "" 并真正在格式部分传递字符串。但是在调用时,我看到了这个崩溃:

critical:   at <unknown> <0xffffffff>
critical:   at (wrapper managed-to-native) BugfenderSDK.CFunctions.BFLog (System.nint,string,string,BugfenderSDK.LogLevel,string,string,string) <0xffffffff>
...

Objective Sharpie 默认将 IntPtr varArgs 作为最后一个参数。我尝试了这个字符串 arg0 并改为传入 IntPtr.Zero ,但仍然崩溃。

编辑 #1:不用担心第一个 vararg ——我只是将 "" 传递给它——我按照每个 ventayol 遵循 TestFlight binding example 并忽略了这一点,只在 Dllimport 中定义格式:

 [DllImport ("__Internal", EntryPoint = "__BFLog")]
 internal static extern void BFLog(
     nint lineNumber, /* nint will be marshalled correctly */
     IntPtr method, /* NSString must be declared as IntPtr */
     IntPtr file, /* NSString */
     LogLevel level, /* This may be wrong, depending on the exact LogLevel type */
     IntPtr tag, /* NSString */
     IntPtr format /* NSString */
 );

还有包装器:

 public static void Log(LogLevel level, nint lineNumber, string method, string file, 
     string tag, string format, params object[] args)
 {
     var nsMethod = new NSString (method);
     var nsFile = new NSString (file);
     var nsTag = new NSString (tag);
     string msg = String.Format(format, args);
     var nsMsg = new NSString(msg);

     BFLog (lineNumber, nsMethod.Handle, nsFile.Handle, level, nsTag.Handle, nsMsg.Handle);

     nsMethod.Dispose ();
     nsFile.Dispose ();
     nsTag.Dispose ();
     nsMsg.Dispose ();            
 }

但我在后端只看到标签和其他,没有消息。

【问题讨论】:

    标签: xamarin xamarin.ios


    【解决方案1】:

    这不是一个 Objective-C 绑定,而是一个标准的 .NET P/Invoke。这意味着 Xamarin.iOS 不会执行标准 C# -> Objective-C 类型编组(例如字符串 -> NSString)。

    所以你需要像这样编写 P/Invoke:

    [DllImport ("__Internal", EntryPoint = "__BFLog")]
    internal static extern void BFLog (
        nint lineNumber, /* nint will be marshalled correctly */
        IntPtr method, /* NSString must be declared as IntPtr */
        IntPtr file, /* NSString */
        LogLevel level, /* This may be wrong, depending on the exact LogLevel type */
        IntPtr tag, /* NSString */
        IntPtr format, /* NSString */
        string arg0 /* this is a C-style string, char* */)]
    

    并像这样使用它:

    BFLog (
        0, /* nint */
        new NSString (method).Handle, /* IntPtr */
        new NSString (file).Handle, /* IntPtr */
        level, /* LogLevel */
        new NSString (tag).Handle,  /* IntPtr */
        new NSString (format).Handle,  /* IntPtr */
        "arg0" /* string */);
    

    您还需要为您使用的每个可变参数变体创建一个单独的 P/Invoke。因此,如果您需要一个带有两个 C 风格字符串的字符串,请执行以下操作:

    [DllImport ("__Internal", EntryPoint = "__BFLog")]
    internal static extern void BFLog (
        nint lineNumber, /* nint will be marshalled correctly */
        IntPtr method, /* NSString must be declared as IntPtr */
        IntPtr file, /* NSString */
        LogLevel level, /* This may be wrong, depending on the exact LogLevel type */
        IntPtr tag, /* NSString */
        IntPtr format, /* NSString */
        string arg0, /* this is a C-style string, char* */
        string arg1 /* second C-style string, char* */)]
    

    还要记住,arm64 上的可变参数有不同的调用约定,所有可变参数都在堆栈上传递。这在实践中意味着第一个 varargs 参数必须是 P/Invoke 中的参数 #8:

    [DllImport ("__Internal", EntryPoint = "__BFLog")]
    internal static extern void BFLog_arm64 (
        nint lineNumber, /* 1st */
        IntPtr method, /* 2nd */
        IntPtr file, /* 3rd */
        LogLevel level, /* 4th */
        IntPtr tag, /* 5th */
        IntPtr format, /* 6th */
        IntPtr dummy1, /* 7th */
        string arg0 /* 8th, the first varargs parameter */)]
    

    然后您可以为 BFLog 导出提供一个包装器,该包装器将根据架构执行正确的操作:

    using ObjCRuntime;
    
    internal static void Log (nint lineNumber, string method, string file, LogLevel level, string tag, string format, string arg0)
    {
        var nsMethod = new NSString (method);
        var nsFile = new NSString (file);
        var nsTag = new NSString (tag);
        var nsFormat = new NSString (format);
    
        if (Runtime.Arch == ARCH.Device && IntPtr.Size == 8) {
            BFLog_arm64 (lineNumber, nsMethod.Handle, nsFile.Handle, level, nsTag.Handle, nsFormat.Handle, IntPtr.Zero, arg0);
        } else {
            BFLog (lineNumber, nsMethod.Handle, nsFile.Handle, level, nsTag.Handle, nsFormat.Handle, arg0);
        }
    
        nsMethod.Dispose ();
        nsFile.Dispose ();
        nsTag.Dispose ();
        nsFormat.Dispose ();
    }
    

    【讨论】:

    • 谢谢罗尔夫。这很有效,只是我很难通过格式/参数获取消息。我假设后端库——我没有源代码——只允许我做 format = "%s" 然后我可以通过 arg0 传递消息。没有骰子。但我可以在尝试使用此服务时使用标签 (bugfender.com)。
    【解决方案2】:

    也许你可以在这里查看 TestFlight 对他的绑定做了什么: https://github.com/mono/monotouch-bindings/blob/master/TestFlight/binding/testflight-cplusplus.cs

    它们的功能与您想要做的类似。

    【讨论】:

    • 仍然不行(见上面的编辑#1)。我确实认为在这样的 API 中提供非格式化版本是最佳实践(例如,我必须转义诸如 %s 之类的东西,不是吗?)。 TestFlight 实际上在他们的 SDK 中提供了这样一个简单的接口:void TFLogPreFormatted(NSString *message)。这就是我在电子邮件中所追求的 :-)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多