After trawling the internet for material on boostrapping CEF and Xamarin Mac, I came up with dead-ends on every turn.
Some problems i encoutered were:
- Nothing worked with the multi-threaded message loop (CEF v75+) which is recommended approach to get Chrome-like performance.
- Nothing gave developers a susccint understanding of what exactly was required to acheive the integration
Everything seemed possible, so i started down the rabbit hole and have come out the other side wanting to share my experience with the commuinity and make this task straight forward for those who come after me.
My goal:
- Integrating CEF with Xamarin MacoS in the lightest way possible
- Use C# across the stack
- Allow the application to customise the look and feel of the CEF window as if it was its own
- Provide a nuget package that can be added to any Xamarin MacS Application, that bootstraps it for plug & play operation with CEF.
- To run CEF, you need to integrate with its processing model on a platform it supports.
- This means, you need to implement a CefClient that understands how to implement the CEF protocal
- This client can be in any lanuage, but it must support the platform protocal (swizzel events etc)
- By implementing this client, at runtime CEF will provide your App with a OpenGlView window/view which is the browser and u can paint into various surfaces
My experience coming into this project was using CEFSharp which uses a C++ layer
to smooth out the CEF abstraction and make it user friendly with a drop-and-drag canvas. You dont need to worry about anything threading related, and they provide a great interface for quickly boostrapping your project. but.... they were never going to be cross-platform.
Enter CefGlue. It ran on .NET Standard and i found demo projects on integrating it with CEF ~v30-50 & MonoMac.
- These all relied on a single threaded messages loop to work correctly. This option is now deprecated CEF.
- They fell over on startup constantly. Something had changed between CEF versions, and i didnt know what..
- This was the closest i got with to a running CEF app on XamMac
A picture was starting to form in my head about what it will take to integrate CEF cross-platform... I had future goals of pushing the limits of code sharing, my ultimate goal being running the same code on Windows/Mac/Andriod/Linux/MacOs...?
By this stage I knew i needed to strap myself in and hold on for the ride. I needed to somehow mash all the peices of the puzzle into something that worked. I was experienced with Xamarin/Andriod/iOS but new to Xamarin Mac... MacOS... and also new to the inner workings of CEF... and suddenly I was faced with the challenge of having to write a CefClient layer that talked the CEF protocal... My project deadline was nearing... Could i do it with XamMac? hmmmm!
So i started digging through all the resources i had found for a *working sample *of a C#/Mono CefClient & CEFGlue... but again, nothing worked with the latest CEF API where the protocal had evolved... and then i found Chromely & its chromely_mac.mm.
- I didnt know
Obj-C
, but i knew this worked with the latest CEF version so it implemented the protocal correctly - It took the approach of creating a native window with
obj-c
, and then handing off toCefGlue
with the bare essentials boostrapped.
This topic deserves a special callout. Over and over and over and over and over and.... over you will read doco on CEF / Xamarin / p/inokves and other topics of interop'ing. It will tell you Place CEF next to your dll
. I saw many lost souls along the way aganonising over this fact. "Why doesnt it detect CEF!? I followed the DOCS!" they would say in desperate rants.
- Your not going crazy. Apparently this is common knowledge to those old veterans of mono but if CEF is your first dip in the water, then may get lost too
Chromely got the furthest of any platform but was built for .Net Core
, not Xam.Mac
/mono
. Whats the differences? They both implement .Net standard 2.0+? Why did it crash on startup? Time to deep dive on CEF
Xam.Mac
runs onmono
and is more mature then.net core
which will morph into.NET 5
. Fun fact Eto maintains a fork ofXam.Mac
to run on.net core
.- I used
sudo opensnoop
to detect whatxam.mac
was trying todo during compile and what paths it was probing - I saw it was probing every dir BUT
frameworks
for a libary calledlibcef
notChrominium Embedded Framework
- CEF is hardcoded to probe for
assets
on certain paths. Some assets it needs next tolibcef
some needed inside offrameworks
The lessons i learnt were:
- CEF requires assets to be in certain folders.
- CEF hardcodes these expectations like any other Mac app, renaming or [DllImports] dont effect that.
- If you follow the required dir layout, placing CEF inside the
my.app/contents/frameworks
it will fail to detect! libcef
needs to be placed inside ofmy.app/contents/monobundle
for Xam.mac to detect it There are other folders also but its safest here as thats what most others do- Placing a copy of
libcef
in bothmonobundle
&frameworks
will result in a STARTUP CRASH! why? no idea! It just does! - You need to keep all the libcef/assets/resources ONLY in
frameworks
and then another copy of everything PLUS libcef inside ofmonobundle
- I personally think this is a bug in Xamarin mac. Xam.Mac should use
frameworks
as a probing path!?!
- I personally think this is a bug in Xamarin mac. Xam.Mac should use
This time though, I was getting different error codes. These were related to the GPU sub-processes that CEF attempts to launch after it has Initailised
(...and beleive me, after a week of watching CEF crash on startup over and over, that first time you initalise its pure bliss)
Why did it crash?
- Xamarin mac creates a
MacOS.app
that is natively executed but lies at a different execution path then the calling assembly CEFGlue
needs to be told about this so it sets CEF'ssub process path
. We set thisto the .app.
. By default, it will call into the executing process... Which in Xam mac world, is not what we want (mono etc in the between)
I had to make a few quick hacks *here and there *to get through this spike and to make this process streamlined for others. Ive collated all these fixes
into this repository so checkout the history to get more context.
I hope this helps anyone who may follow in attempting to make CEF do something others havnt before. Hopefully my methodology to debugging will help you also.
I plan on maintaining this fork going foward and/or integrating these changes back into Chromely and providing an nuget package to consume to faciliate that fabled - plug and play CEF
integration - in any cross platform .net app.
This nuget package will provide
- All the hacks i just decribed to copy CEF and its assets to the right dirs, allows you to clean and rebuild with no delay in your flow.
- Distribute the supported CEF runtime with the libary instead of having it as a seperate download. This has a MAJOR pain when ramping up on CEF, its hard to understand all the version numbers and what depends on what! Clean and rebuild and** download** and unzip is not fun.
- Supplies the window handle to your App so you can style it using platform functions if you wish
- Implements the Chromely NativeHost
.MessageBox
dialogs to allow cross-platform message boxes via Chromelys APIs (using Xamarin / .Net MUAIs APIs)
I hope the information I have presented helps you launch your CEF project. Cross-platform rocks, and I am actively working on updating another project of mine, Reactions to be plug / play & dependency free with .NET5!
In the world of tomorrow, the expectation is that your code will run on any device. I love open source. I love the possibilities of cross-platform. Ive been devv'ing since JDK 1.1 and moved to .NET @ 2.0. After building Java, Silverlight, Xamarin, Angular/React/TS/Progressive Apps/Services I found myself doing the same things over and over again.
I want to use that experience to help your code run on any device using the patterns that are portable to produce apps which are reliable, scalable, and maintainable over the well after the hype cycle fades