I'm making a cross-platform app in Xamarin and currently I'm stuck in trying to get a simple BLE scan up and running. The app crashes ugly, and I've symbolized and hunted the issue down to the file "mini-exceptions.c", line 2360.
Basically, it says;
find_last_handler_block (StackFrameInfo *frame, MonoContext *ctx, gpointer data)
{
int i;
gpointer ip;
FindHandlerBlockData *pdata = data;
MonoJitInfo *ji = frame->ji;
if (!ji)
return FALSE;
So, in short; I'm missing the "MonoJitInfo", returning false which in turn throws an exception and I'm done.
But -- I don't get it. Why this "mono"-stuff? I'm developing for the new Xamarin Unified, and AFAIK there's not a single reference in the project to any classic MonoTouch-stuff?
I'm a Xamarin newbie so I'm trying me best to put the puzzle together here...
As for my application, and crash occurs when I create my CBCentralManager to use the "DefaultGlobalQueue", but I'm not getting any crash when I use the MainQueue as DispatchQueue -- but I'm not getting any discovered peripherals either so I'm guessing that it's something related to my DispatchQueue...?
I'm lost, I guess... Any hints are highly valued. Does CoreBluetooth in Xamarin.iOS require Mono?
EDIT;
I'm actually getting a critical error before my native crash, and I see some strange mono-touch references in there too...;
critical: Stacktrace:
critical: at <unknown> <0xffffffff>
critical: at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <0xffffffff>
critical: at UIKit.UIApplication.Main (string[],intptr,intptr) [0x00005] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:62
critical: at UIKit.UIApplication.Main (string[],string,string) [0x0001c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:45
critical: at iOSMyApp.Application.Main (string[]) [0x00008] in /Users/markus/Xamarin/MyApp/iOS/iOSMyApp/Main.cs:17
critical: at (wrapper runtime-invoke) object.runtime_invoke_dynamic (intptr,intptr,intptr,intptr) <0xffffffff>
Edit -- with code snippets
My BLE implementation is the iOS-native class implementing an interface in a shared-project setup where all the business-logic is managed.
I've tried both a lambda-variant;
private CBCentralManager Central;
readonly AutoResetEvent stateChanged = new AutoResetEvent (false);
async Task WaitForState (CBCentralManagerState state)
{
Console.WriteLine ("Waiting for state: " + state);
while (Central.State != state) {
await Task.Run (() => stateChanged.WaitOne ());
}
}
internal NativeBleCentralTransport ()
{
Central = new CBCentralManager (DispatchQueue.MainQueue);
Central.DiscoveredPeripheral += (object sender, CBDiscoveredPeripheralEventArgs e) => {
Console.WriteLine ("DiscoveredPeripheral: " + e.Peripheral.Name);
};
Central.UpdatedState += (object sender, EventArgs e) => {
Console.WriteLine ("UpdatedState: " + Central.State);
stateChanged.Set();
};
Central.ConnectedPeripheral += (object sender, CBPeripheralEventArgs e) => {
Console.WriteLine ("ConnectedPeripheral: " + e.Peripheral.Name);
};
Central.DisconnectedPeripheral += (object sender, CBPeripheralErrorEventArgs e) => {
Console.WriteLine ("DisconnectedPeripheral: " + e.Peripheral.Name);
};
Central.FailedToConnectPeripheral += (object sender, CBPeripheralErrorEventArgs e) => {
};
}
public async Task<bool> SetupTransport ()
{
//Wait for state...
WaitForState(CBCentralManagerState.PoweredOn);
//Scan for *any* peripheral at the moment...
CBUUID[] uuids = null;
Central.ScanForPeripherals (uuids);
//Wait for things to happen...
await Task.Delay (10000);
return true;
}
... and the other "extend CBCentralManagerDelegate class"-approach;
internal class NativeBleCentralTransport : CBCentralManagerDelegate, ISessionTransport
{
private CBCentralManager BleManager;
internal NativeBleCentralTransport ()
{
//Create BLE manager and hook-up ourselves as delegate
BleManager = new CBCentralManager(this, DispatchQueue.MainQueue);
}
public async Task<bool> SetupTransport ()
{
//The manager will automatically kick "UpdateState" for us to indicate the status of the native BLE-interface (e.g., enabled or disabled?),
//so let's just sit back and await completion of discovery
Task.Delay (10000);
return true;
}
#region BLE delegate overrides
override public void UpdatedState (CBCentralManager mgr)
{
//State change occured! Let's see if we should start
if (BleManager.State == CBCentralManagerState.PoweredOn)
{
CBUUID[] uuids = null;
BleManager.ScanForPeripherals (uuids);
}
}
public override void DiscoveredPeripheral (CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
{
Console.WriteLine ("Discovered: " + peripheral.Name);
}
#endregion
}
Problem summary
The idea is essentially that when a transport is setup (SetupTransport), the BLE interface should kick-in and discover/connect to a peripheral by blocking the invoking thread until it's done and ready for action.
But -- my app crashes when it discovers a peripheral on the iOS-native side. Not in my code -- I never see my debug-messages on the Console, but I've found it when symbolizing the native crash logs...
So, essentially; I'm not doing anything strange, it very straight-forward I'd say, BUT it is a shared-project setup and it's my first Xamarin-project, which makes me less comfortable with the terminology.. I'm using the Unity API. I'm currently suspecting that this is actually a project-setup/constraint/reference-thing and I'm therefore rebuilding a new test-project which is NOT a shared project but a native iOS-thing just to see that I can make a simple BLE discovery without crashing and then I'll take it from there I guess.
Any insights and suggestions are appreciated!
/Markus
Results
I did a test-solution in Xamarin, with only one project; a single-page iOS-template thing and just added my class in the "DidAppear" method in the View-controller, and everything works...
Hence, this is NOT a code/syntax-issue (unless there are rules that I'm not aware of for shared-project setups?), but a shared-project issue/Xamarin-setup issue. Any recommendations out there on how to setup a shared-project. Any missing references that an experienced pair of eyes can find by looking at the crash-traces?
Problem solved. Yes, it was related to shared-project setup and more specifically where my BLE native transport class was created.
Turns out, the CBCentralManager must be created by the main-thread, and I created an instance from a function with async attribute.
So by simply putting my NativeBleCentralTransport instance as a private variable created when the app boot-up, and use that object reference in my async function, everything works (which is also why everything worked nicely when I created a native iOS-project since I just brute-force played with an instance of NativeBleCentralTransport directly from the ViewDidAppear function.
Related
This question already has answers here:
Should I avoid 'async void' event handlers?
(4 answers)
Closed 2 years ago.
I am getting myself confused about async void and button handlers. I heard in a video tutorial that it was not wise to use async void. I have reviewed these questions:
question
question
In many answers it does actually use this syntax. Thus my confusion. 🧐
Here is my handler:
async private void InstallStyleButton_Clicked(object sender, EventArgs e)
{
var customFileType =
new FilePickerFileType(new Dictionary<DevicePlatform, IEnumerable<string>>
{
{DevicePlatform.macOS, new[] {"xsl"} }
});
var pickResult = await FilePicker.PickAsync(new PickOptions
{
FileTypes = customFileType,
PickerTitle = "Select template to install"
});
if(pickResult != null)
{
// Build target path
string targetFile = Path.Combine("/Users/Shared/VisitsRota.MacOS/Styles", pickResult.FileName);
// OK to install?
if(!File.Exists(targetFile))
{
// Install
File.Copy(pickResult.FullPath, targetFile);
// Add it
StylesPicker.ItemsSource.Add(pickResult.FileName);
// Select it
StylesPicker.SelectedItem = pickResult.FileName;
}
}
}
I realise it is not a good idea to hardcode the folder path and I will eventually get around to that. Other than that, my handler appears to operate fine on my development MacBook Pro when I try it.
Is my use of async void with the button handler ok? And if not, how should my handler be adjusted?
I found this link where it states:
To summarize this first guideline, you should prefer async Task to async void. Async Task methods enable easier error-handling, composability and testability. The exception to this guideline is asynchronous event handlers, which must return void.
So my code is OK.
I cannot seem to find a good solution for this issue online. I have a device that is running Windows Embedded Handheld 6.5. I run the solution located at below
C:\Program Files (x86)\Windows Mobile 6.5.3 DTK\Samples\PocketPC\CS\GPS
I deploy the code to my device, not an emulator, and the code breaks with a null reference exception at
Invoke(updateDataHandler);
The solution ive seen recommends changing this to below
BeginInvoke(updateDataHandler);
But now the code breaks at Main with NullRefreceException.
Application.Run(new Form1());
Has anyone found a solution for this?
Did you alter the code? updateDataHandler is initialized in Form_Load:
private void Form1_Load(object sender, System.EventArgs e)
{
updateDataHandler = new EventHandler(UpdateData);
so that object will not be NULL. But there are other annoyances with the code, especially the Samples.Location class. You may instead use http://www.hjgode.de/wp/2010/06/11/enhanced-gps-sample-update/ as a starting point and the older one: http://www.hjgode.de/wp/2009/05/12/enhanced-gps-sampe/
The main issue with the sample is that it does not use a callback (delegate) to update the UI. If an event handler is fired from a background thread, the handler can not directly update the UI. Here is what I always use to update the UI from a handler:
delegate void SetTextCallback(string text);
public void addLog(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.txtLog.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(addLog);
this.Invoke(d, new object[] { text });
}
else
{
txtLog.Text += text + "\r\n";
}
}
I'm unsure about the best practice for obtaining and updating input received from a controller monitored using the GamePad class in UWP.
I've seen a couple of examples of people using Dispatch Timers and async loops inside the GamePadAdded event. In Win32 applications, I would have handled input in the WinMain update/message loop, but in UWP apps I don't know of anything similar.
Is there a loop in UWP apps that input should be collected/handled like in Win32 apps? What is the recommended protocol for polling for input from a input device (nominally a Xbox One controller)?
I'm happy to read more about UWP app development but I'm unsure of any guides that reference something like this.
Edit: It would be productive if, instead of downvoting and moving on, you shared thoughts on why this question deserved a downvote.
I've seen a couple of examples of people using Dispatch Timers and async loops inside the GamePadAdded event
This is the right way in UWP app to read Gamepad data. A little suggestion is, move the loop reading part on UI thread if you need to update UI frequently. See the solution in this blog
Is there a loop in UWP apps that input should be collected/handled like in Win32 apps
You may make a wrapper with custom event, see the open source implementation: XBoxGamepad
public class XBoxGamepad
{
private List<Gamepad> _controllers = new List<Gamepad>();
private bool _running = true;
Task backgroundWorkTask;
public event EventHandler<GamepadButtons> OnXBoxGamepadButtonPressA;
//omitted......
public XBoxGamepad()
{
Gamepad.GamepadAdded += Gamepad_GamepadAdded;
Gamepad.GamepadRemoved += Gamepad_GamepadRemoved;
backgroundWorkTask = Task.Run(() => PollGamepad());
}
//omitted......
private void Start()
{
_running = true;
}
public void Stop()
{
_running = false;
}
public async Task PollGamepad()
{
while (true)
{
if (_running)
{
foreach (Gamepad controller in _controllers)
{
if (controller.GetCurrentReading().Buttons == GamepadButtons.A)
{
OnXBoxGamepadButtonPressA(controller, controller.GetCurrentReading().Buttons);
}
//omitted......
}
}
await Task.Delay(50);
}
}
private void Gamepad_GamepadRemoved(object sender, Gamepad e)
{
_controllers.Remove(e);
}
private void Gamepad_GamepadAdded(object sender, Gamepad e)
{
_controllers.Add(e);
}
}
So i don't actually have a question because i've already solved it, but in case someone else runs into this issue it's always nice to have a neat solution.
And while there is a plentitude of "Can't create handler inside thread which has not called Looper.prepare()" - questions there is none tagged with xamarin. (so theirs is all java and i had 0 matches for "Can't create handler inside thread which has not called Looper.prepare() [xamarin]")
The issue is generated because You tried to do work on UI from other thread. If you want to change UI, Must call UI changes from UI thread.
Xamarin Android:
activity.RunOnUiThread(() =>
{
// Write your code here
});
Xamarin IOS:
nsObject.BeginInvokeOnMainThread(() =>
{
// Write your code here
});
Xamarin Forms:
Device.BeginInvokeOnMainThread(() =>
{
// Write your code here
});
public static class PageExtensions
{
public static Task<bool> DisplayAlertOnUi(this Page source, string title, string message, string accept, string cancel)
{
TaskCompletionSource<bool> doneSource = new TaskCompletionSource<bool>();
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var result = await source.DisplayAlert(title, message, accept, cancel);
doneSource.SetResult(result);
}
catch (Exception ex)
{
doneSource.SetException(ex);
}
});
return doneSource.Task;
}
}
Finally i had a case for using TaskCompletionSource to solve an issue.
Very similar actually happend to me. Previous evening I was developing and testing my app. Next morning, from other computer I got the exception you described. I was remembered from a official Xamarin.Forms documentation that sometimes a bin and obj folder removal solves lot of issues.
I did exactly the same, removed my bin and obj folder from my shared Xamarin.Forms library and also from Xamarin.Android library.
The strange exception disappeared.
I have an app that I'm upgrading from some beta bits - and my map screen is crashing. So to try to get to the bottom of it - I started a brand new - blank "Win Phone Application".
Referenced Caliburn.Micro (just built from new code last night) Version: caliburnmicro_1296ea635677 (from codeplex)
referenced Microsoft.phone.controls.map.dll
and in the MainPage I added
<Grid>
<Maps:Map />
</Grid>
and I add a bootstrapper to app.xaml
<WP7:PhoneBootStrapper x:Name="bootstrapper" />
when the page runs in the phone emulator - the main page renders and I see a map of the world. if I click anywhere on the page - I get an unhandled exception of "The parameter is incorrect"
if I remove the
from the app.xaml - the map works correctly.
What do you think?
Thanks for any advice?
I have found the answer.
The key here - is that I had this setup and wroking with the Beta Templates - and it stopped working when I moved to the WinPhone RTM Templates in VS2010.
Caliburn does some work on my behalf, that was "ADDED" to the RTM templates - which were conflicting with each other. In the end This problem has/had nothing to do with the Bing Maps control - it just so happens that - that was my first screen - so that's where I was trying to solve the problem.
This was the ever so Not-Helpful exception:
The parameter is incorrect
Which, I'm pretty sure would happen on any screen - if you went to the upgrade path of templates, like I did. So here is what I had to remove - to get everything back to normal. In the new App.Xaml.cs - I removed (by commenting) in the App Ctor ...
// Phone-specific initialization
// InitializePhoneApplication();
// Global handler for uncaught exceptions.
// UnhandledException += Application_UnhandledException;
And then I removed these method bodies, because it's just dead code after removing the InitializePhoneApplication() call from ctor ...
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
}
// Code to execute if a navigation fails
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// A navigation has failed; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
// Code to execute on Unhandled Exceptions
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
System.Diagnostics.Debugger.Break();
}
}
#region Phone application initialization
// Avoid double-initialization
private bool phoneApplicationInitialized = false;
// Do not add any additional code to this method
private void InitializePhoneApplication()
{
if (phoneApplicationInitialized)
return;
// Create the frame but don't set it as RootVisual yet; this allows the splash
// screen to remain active until the application is ready to render.
RootFrame = new PhoneApplicationFrame();
RootFrame.Navigated += CompleteInitializePhoneApplication;
// Handle navigation failures
RootFrame.NavigationFailed += RootFrame_NavigationFailed;
// Ensure we don't initialize again
phoneApplicationInitialized = true;
}
// Do not add any additional code to this method
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
RootVisual = RootFrame;
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
#endregion
Special Thanks to Rob for his help solving this mystery!