MonoTouch 中的 VarArgs 绑定 / Xamarin.iOS
VarArgs Binding in MonoTouch / Xamarin.iOS
我有一个新的 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——我只是将“”传递给它——我按照 TestFlight binding example per ventayol 并忽略了这一点,只在 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 ();
}
但是后台只看到tag和其他的,没有消息。
这不是 Objective-C 绑定,而是标准的 .NET P/Invoke。这意味着 Xamarin.iOS 不会执行标准的 C# -> Objective-C 类型编组(例如 string -> 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 上的可变参数具有不同的调用约定,所有可变参数都在堆栈上传递。这实际上意味着第一个可变参数参数必须是 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 ();
}
也许你可以在这里查看 TestFlight 对他的绑定做了什么:
https://github.com/mono/monotouch-bindings/blob/master/TestFlight/binding/testflight-cplusplus.cs
它们的功能与您想要做的类似。
我有一个新的 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——我只是将“”传递给它——我按照 TestFlight binding example per ventayol 并忽略了这一点,只在 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 ();
}
但是后台只看到tag和其他的,没有消息。
这不是 Objective-C 绑定,而是标准的 .NET P/Invoke。这意味着 Xamarin.iOS 不会执行标准的 C# -> Objective-C 类型编组(例如 string -> 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 上的可变参数具有不同的调用约定,所有可变参数都在堆栈上传递。这实际上意味着第一个可变参数参数必须是 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 ();
}
也许你可以在这里查看 TestFlight 对他的绑定做了什么: https://github.com/mono/monotouch-bindings/blob/master/TestFlight/binding/testflight-cplusplus.cs
它们的功能与您想要做的类似。