Reputation: 1626
I have a new MonoTouch binding partially working. But a variable arguments function is causing a crash when run. The .h file:
FOUNDATION_EXPORT void __BFLog(NSInteger lineNumber, NSString *method, NSString *file,
BFLogLevel level, NSString *tag, NSString *format, ...);
The 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);
}
Because I would pass "" in arg0 and really pass the string in format section. But when calling, I see this crash:
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 put IntPtr varArgs as the last argument by default. I tried this string arg0 and passing in IntPtr.Zero instead, but still crash.
EDIT #1: insteading of worrying about the first vararg -- I was going to just pass "" to it -- I followed TestFlight binding example per ventayol and ignored this, only defining the format in the 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 */
);
And the wrapper:
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 ();
}
But I only see tag and others on the backend, no message.
Upvotes: 2
Views: 388
Reputation: 635
Maybe you can check what TestFlight did on his binding here: https://github.com/mono/monotouch-bindings/blob/master/TestFlight/binding/testflight-cplusplus.cs
They have a similar function to what you want to do.
Upvotes: 1
Reputation: 19335
This isn't an Objective-C binding, but a standard .NET P/Invoke. This means that Xamarin.iOS will not do the standard C# -> Objective-C type marshalling (like string -> NSString for instance).
So you need to write the P/Invoke like this:
[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* */)]
and use it like this:
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 */);
You'll also need to create a separate P/Invoke for every varargs variation you're using. So if you need one that takes two C-style strings, do:
[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* */)]
Also have in mind that varargs on arm64 has a different calling convention, all varargs are passed on the stack. What this means in practice is that the first varargs argument must be argument #8 in the P/Invoke:
[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 */)]
and then you can provide a wrapper around the BFLog export that will do the right thing depending on the architecture:
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 ();
}
Upvotes: 6