I need to have a callback function to listen for some events in native module and transfers data from native to javascript and I want to call this javascript function from native directly in React Native iOS app without sending events to NativeEventEmitter.
How to implement this with JSI (JavaScript Interface)?
First, your function must be defined globally in javascript e.g.:
App.js
global.greeting = function(param) {
return "Hello " + param + "!";
};
Then you should find and call it with React Native Runtime in native:
AppDelegate.mm
#include <jsi/jsi.h>
#import <React/RCTBridge+Private.h>
using namespace facebook::jsi;
using namespace std;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// Runtime notification
[NSNotificationCenter.defaultCenter addObserverForName:RCTJavaScriptDidLoadNotification object:nil queue:nil
usingBlock:^(NSNotification* notification) {
// Get runtime
RCTCxxBridge* cxxbridge = (RCTCxxBridge*)notification.userInfo[#"bridge"];
if (cxxbridge.runtime) {
Runtime& runtime = *(Runtime*)cxxbridge.runtime;
// Get global function
Function greeting = runtime.global().getPropertyAsFunction(runtime, "greeting");
// Call with param
Value param = Value(runtime, String::createFromUtf8(runtime, "JSI"));
Value result = greeting.call(runtime, move(param), 1);
string str = result.asString(runtime).utf8(runtime);
printf("Result: %s", str.c_str());
}
}];
return YES;
}
Outputs:
Result: Hello JSI!
Note: Since the sample uses JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible. Instead, you should use Flipper for debugging your JS code.
Related
I am trying to implement in-app purchase into my WinRT appliation using desktop bridge and Windows App SDK with WinUI 3. I have simple method:
class TransactionService
{
void buyOrRestore() {
....
ComPtr<IAsyncOperation<StorePurchaseResult*>> purchaseOperation;
hr = storeContext->RequestPurchaseWithPurchasePropertiesAsync(HStringReference(kItemStoreId).Get(), purchaseProperties.Get(), &purchaseOperation);
CheckHr(hr);
}
}
Following code always print error into output:
info:StoreContextServer::Initialize: packageFullName = PurchaseTester, productStoreId = 123, isLicensed = true, isAppContainer = false [Windows::Services::Store::Internal::StoreContextServer::Initialize]
info:Windows::Services::Store::StoreContext::RequestPurchaseWithPurchasePropertiesAsync(abc) invoked. (CV:8glMygpFo0+UMRKk.2.3) [Windows::Services::Store::StoreContext::RequestPurchaseWithPurchasePropertiesAsync]
Exception thrown at 0x00007FFE7BB5474C (KernelBase.dll) in PurchaseTester.exe: WinRT originate error - 0x80070578 : 'This function must be called from a UI thread'.
buyOrRestore method is called directly from MainWindow.xaml.cpp after button click like:
void MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
auto transactionService = new TransactionService();
transactionService->buyOrRestore();
}
I use RN 0.66.3 and want to make direct sync calls from javascript to native code in my iOS React Native project to share data without using the React Native Bridge for performance purposes so that I need to have a shared global object and access to its properties and methods from javascript.
I know that is possible with JSI (JavaScript Interface) but there are no docs and few tutorials about so what the simple steps or sample code to implement this?
To expose your object to javascript over React Native JSI you should make next steps:
Make your c++ class inherited from HostObject
Override get and set methods to implement access to its properties and methods.
Install your object globally on React Native runtime.
Look at NativeStorage sample that can store key/value pairs persistently to NSUserDefaults across launches of your app:
NativeStorage class
#include <jsi/jsi.h>
#import <React/RCTBridge+Private.h>
using namespace facebook::jsi;
using namespace std;
// Store key-value pairs persistently across launches of your app.
class NativeStorage : public HostObject {
public:
/// Stored property
int expirationTime = 60 * 60 * 24; // 1 day
// Helper function
static NSString* stringValue(Runtime &runtime, const Value &value) {
return value.isString()
? [NSString stringWithUTF8String:value.getString(runtime).utf8(runtime).c_str()]
: nil;
}
Value get(Runtime &runtime, const PropNameID &name) override {
auto methodName = name.utf8(runtime);
// `expirationTime` property getter
if (methodName == "expirationTime") {
return this->expirationTime;
}
// `setObject` method
else if (methodName == "setObject") {
return Function::createFromHostFunction(runtime, PropNameID::forAscii(runtime, "setObject"), 2,
[](Runtime &runtime, const Value &thisValue,const Value *arguments, size_t count) -> Value {
NSString* key = stringValue(runtime, arguments[0]);
NSString* value = stringValue(runtime, arguments[1]);
if (key.length && value.length) {
[NSUserDefaults.standardUserDefaults setObject:value forKey:key];
return true;
}
return false;
});
}
// `object` method
else if (methodName == "object") {
return Function::createFromHostFunction(runtime, PropNameID::forAscii(runtime, "object"), 1,
[](Runtime &runtime, const Value &thisValue,const Value *arguments, size_t count) -> Value {
NSString* key = stringValue(runtime, arguments[0]);
NSString* value = [NSUserDefaults.standardUserDefaults stringForKey:key];
return value.length
? Value(runtime, String::createFromUtf8(runtime, value.UTF8String))
: Value::undefined();
});
}
return Value::undefined();
}
void set(Runtime& runtime, const PropNameID& name, const Value& value) override {
auto methodName = name.utf8(runtime);
// ExpirationTime property setter
if (methodName == "expirationTime") {
if (value.isNumber()) {
this->expirationTime = value.asNumber();
}
}
}
// Install `nativeStorage` globally to the runtime
static void install(Runtime& runtime) {
NativeStorage nativeStorage;
shared_ptr<NativeStorage> binding = make_shared<NativeStorage>(move(nativeStorage));
auto object = Object::createFromHostObject(runtime, binding);
runtime.global().setProperty(runtime, "nativeStorage", object);
}
};
AppDelegate.mm
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// Runtime notification
[NSNotificationCenter.defaultCenter addObserverForName:RCTJavaScriptDidLoadNotification object:nil queue:nil
usingBlock:^(NSNotification* notification) {
RCTCxxBridge* cxxbridge = (RCTCxxBridge*)notification.userInfo[#"bridge"];
if (cxxbridge.runtime) {
NativeStorage::install(*(Runtime*)cxxbridge.runtime);
}
}];
return YES;
}
App.js
nativeStorage.expirationTime = 1000;
console.log(nativeStorage.expirationTime);
const key = "greeting";
nativeStorage.setObject(key, "Hello JSI!");
const text = nativeStorage.object(key);
console.log(text);
Outputs:
1000
Hello JSI!
Future React Native's TurboModules & CodeGen makes all of this cleaner & easier but it's the low level JSI implementation of the native module that can be called directly from JavaScript without going through the React Native Bridge.
Note: Since the sample uses JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible. Instead, you should use Flipper for debugging your JS code.
Using VS2013 I created a very simple web browser Control application that navigates to http://demos.dojotoolkit.org/demos/calendar/demo.html
When no FEATURE_BROWSER_EMULATION is set for this application in the registry the site functions correctly, When adding the application to this registry key (under HKLM) it is working till IE9 emulation but fails with IE10 and IE11 values (i have IE11 on my machine).
example:
FEATURE_BROWSER_EMULATION
myApp=9999 - works
myApp=10001 - doesn't work
doesn't work = month date picker is not working
Any suggestion what can be the problem?
Thanks,
Guy
Below is my WebBrowser playground application (in C#) which works well with your URL (http://demos.dojotoolkit.org/demos/calendar/demo.html).
Disabling FEATURE_NINPUT_LEGACY_MODE is what made the difference, I believe. There is a couple of other settings I enabled, as well. It also shows how to use HKEY_CURRENT_USER instead of HKLM, so the app doesn't require admin rights.
using Microsoft.Win32;
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WebBrowserApp
{
public partial class MainForm : Form
{
const int POLL_DELAY = 250;
WebBrowser _webBrowser;
// set WebBrowser features, more info: http://stackoverflow.com/a/18333982/1768303
static void SetWebBrowserFeatures()
{
// don't change the registry if running in-proc inside Visual Studio
if (LicenseManager.UsageMode != LicenseUsageMode.Runtime)
return;
var appName = System.IO.Path.GetFileName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
var featureControlRegKey = #"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\";
Registry.SetValue(featureControlRegKey + "FEATURE_BROWSER_EMULATION",
appName, GetBrowserEmulationMode(), RegistryValueKind.DWord);
// enable the features which are "On" for the full Internet Explorer browser
Registry.SetValue(featureControlRegKey + "FEATURE_ENABLE_CLIPCHILDREN_OPTIMIZATION",
appName, 1, RegistryValueKind.DWord);
Registry.SetValue(featureControlRegKey + "FEATURE_AJAX_CONNECTIONEVENTS",
appName, 1, RegistryValueKind.DWord);
Registry.SetValue(featureControlRegKey + "FEATURE_GPU_RENDERING",
appName, 1, RegistryValueKind.DWord);
Registry.SetValue(featureControlRegKey + "FEATURE_WEBOC_DOCUMENT_ZOOM",
appName, 1, RegistryValueKind.DWord);
Registry.SetValue(featureControlRegKey + "FEATURE_NINPUT_LEGACYMODE",
appName, 0, RegistryValueKind.DWord);
}
static UInt32 GetBrowserEmulationMode()
{
int browserVersion = 0;
using (var ieKey = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Internet Explorer",
RegistryKeyPermissionCheck.ReadSubTree,
System.Security.AccessControl.RegistryRights.QueryValues))
{
var version = ieKey.GetValue("svcVersion");
if (null == version)
{
version = ieKey.GetValue("Version");
if (null == version)
throw new ApplicationException("Microsoft Internet Explorer is required!");
}
int.TryParse(version.ToString().Split('.')[0], out browserVersion);
}
if (browserVersion < 7)
{
throw new ApplicationException("Unsupported version of Microsoft Internet Explorer!");
}
UInt32 mode = 11000; // Internet Explorer 11. Webpages containing standards-based !DOCTYPE directives are displayed in IE11 Standards mode.
switch (browserVersion)
{
case 7:
mode = 7000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE7 Standards mode.
break;
case 8:
mode = 8000; // Webpages containing standards-based !DOCTYPE directives are displayed in IE8 mode.
break;
case 9:
mode = 9000; // Internet Explorer 9. Webpages containing standards-based !DOCTYPE directives are displayed in IE9 mode.
break;
case 10:
mode = 10000; // Internet Explorer 10.
break;
}
return mode;
}
// static constructor, runs first
static MainForm()
{
SetWebBrowserFeatures();
}
public MainForm()
{
InitializeComponent();
_webBrowser = new WebBrowser() { Dock = DockStyle.Fill };
this.Controls.Add(_webBrowser);
this.Size = new System.Drawing.Size(800, 600);
this.Load += MainForm_Load;
}
// start the task
async void MainForm_Load(object sender, EventArgs e)
{
try
{
dynamic document = await LoadDynamicPage("http://demos.dojotoolkit.org/demos/calendar/demo.html",
CancellationToken.None);
MessageBox.Show(new { document.documentMode, document.compatMode }.ToString());
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// navigate and download
async Task<object> LoadDynamicPage(string url, CancellationToken token)
{
// navigate and await DocumentCompleted
var tcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = (s, arg) =>
tcs.TrySetResult(true);
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false))
{
this._webBrowser.DocumentCompleted += handler;
try
{
this._webBrowser.Navigate(url);
await tcs.Task; // wait for DocumentCompleted
}
finally
{
this._webBrowser.DocumentCompleted -= handler;
}
}
// get the root element
var documentElement = this._webBrowser.Document.GetElementsByTagName("html")[0];
// poll the current HTML for changes asynchronosly
var html = documentElement.OuterHtml;
while (true)
{
// wait asynchronously, this will throw if cancellation requested
await Task.Delay(POLL_DELAY, token);
// continue polling if the WebBrowser is still busy
if (this._webBrowser.IsBusy)
continue;
var htmlNow = documentElement.OuterHtml;
if (html == htmlNow)
break; // no changes detected, end the poll loop
html = htmlNow;
}
// consider the page fully rendered
token.ThrowIfCancellationRequested();
return this._webBrowser.Document.DomDocument;
}
}
}
This works for me:
yourWebBrowser.ScriptErrorsSuppressed = true;
Please review the following website for information on ScriptErrorsSuppressed
Excerpt from website:
Remarks
Set this property to false to debug Web pages that you display in the WebBrowser control. This is useful when you use the control to add Web-based controls and scripting code to your application. It is less useful when you use the control as a generic browser. When you have finished debugging your application, set this property to true to suppress script errors.
Note
When ScriptErrorsSuppressed is set to true, the WebBrowser control hides all its dialog boxes that originate from the underlying ActiveX control, not just script errors. Occasionally you might need to suppress script errors while displaying dialog boxes such as those used for browser security settings and user login. In this case, set ScriptErrorsSuppressed to false and suppress script errors in a handler for the HtmlWindow.Error event. For more information, see the code example in this topic.
I'm struggling with Adobe Air native process communication. I'd like to pass an c++ exe shellcommands stored at processArgs when started. The c++ program uses this arguments to choose device channel and symbolrate of an CAN-Bus-Adapter. The c program itself creates an json database for an html page. While the c program is processing i'd like to get some feedback of the program and if it exits adobe air should create a link for the html page with the function onExit. The c program uses standart output of iostream lib ("cout", "cerr") to send messages to adobe air.
Adobe Air script:
var process;
function launchProcess()
{
if(air.NativeProcess.isSupported)
{
setupAndLaunch();
}
else
{
air.Introspector.Console.log("NativeProcess not supported.");
}
}
function setupAndLaunch()
{
var cpp_device = $( "#device option:selected" ).val();
var cpp_channel= $("#channel option:selected").text();
var cpp_symbolRate= $("#symbolRate option:selected").val();
air.Introspector.Console.log("CHANNEL",cpp_channel);
air.Introspector.Console.log("Device",cpp_device);
air.Introspector.Console.log("Symbol Rate",cpp_symbolRate);
var nativeProcessStartupInfo = new air.NativeProcessStartupInfo();
var file = air.File.applicationDirectory.resolvePath("InteractiveDocumentation.exe");
nativeProcessStartupInfo.executable = file;
var processArgs = new air.Vector["<String>"]();
processArgs.push(cpp_device);
processArgs.push(cpp_channel);
processArgs.push(cpp_symbolRate);
nativeProcessStartupInfo.arguments = processArgs;
process = new air.NativeProcess();
process.start(nativeProcessStartupInfo);
process.addEventListener(air.ProgressEvent.STANDARD_OUTPUT_DATA, onOutputData);
process.addEventListener(air.ProgressEvent.STANDARD_ERROR_DATA, onErrorData);
process.addEventListener(air.IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, onIOError);
process.addEventListener(air.IOErrorEvent.STANDARD_ERROR_IO_ERROR, onIOError);
process.addEventListener(air.NativeProcessExitEvent.EXIT, onExit);
}
function onOutputData(event)
{
air.Introspector.Console.log("Got: ", process.standardOutput.readUTFBytes(process.standardOutput.bytesAvailable));
}
function onErrorData(event)
{
air.Introspector.Console.log("ERROR -", process.standardError.readUTFBytes(process.standardError.bytesAvailable));
}
function onExit(event)
{
air.Introspector.Console.log("Process exited with ",event.exitCode);
$("#output").html("Start");
}
function onIOError(event)
{
air.Introspector.Console.log(event.toString());
}
$(function()
{
$("#start").click(function()
{
air.Introspector.Console.log("START");
launchProcess();
});
});
the c program is quite long, and here i post only the main part
int main( int argc, char *argv[])
{
//! get shell commands for init
// echo shell commands
for( int count = 0; count < argc; count++ )
{
cout << " argv[" << count << "] "
<< argv[count];
}
// handle data
intDevice = (int)(argv[1][0] - '0');
intChannel = (int)(argv[2][0] - '0');
intChannel -= 1;
intSymbolRate = atoi(argv[3]);
//! class function calls
useDll CanFunction;
try
{
CanFunction.initProg();
CanFunction.ecuScan();
CanFunction.openSession();
CanFunction.readECU();
CanFunction.stopProg();
return 0;
}
//! exception handling
catch(int faultNumber)
{
....
}
}
First I used only the onExit listener and everything works fine. After I used start conditions adobe air only responses if i call no class functions except the CanFunction.initProg() and the onExit function was called but skipped the jQuery command to create the link. If I add the class function calls, the c program is called and the json database created but Adobe Air doesnt received any responses. The json database is still created. This confuses me.
My c program uses some *.dll files to communicate with the bus so i could imagine that it is a windows problem. But it is still weird.
Has anybody an idea why adobe air communication doesnt work with my c program if i call my class functions or why the jQuery command is skipped?
Or is there a better solution to call a c++ exe from adobe air?
I am buiding a simple XPCOM component. but firefox crashes everytime when I am trying to call a method of this xpcom component.
the file hierarchy is below:
HelloXpcom.xpi
---chrome.manifest
---install.rdf
---chrome
------content
---------sample.xul
---components
------HelloXpcom.dll
------nsISample.xpt
chrome.manifest snippet
content sample chrome/content/
interfaces components/nsISample.xpt
binary-component components/HelloXpcom.dll
overlay chrome://browser/content/browser.xul chrome://sample/content/sample.xul
my interface idl:
#include "nsISupports.idl"
[scriptable, uuid(F7F48977-382E-461B-B04A-44567EE6D45A)]
interface nsISample : nsISupports
{
attribute string value;
void writeValue(in string aPrefix);
void poke(in string aValue);
};
use xpidl.exe to generate npISample.h and npISample.xpt.
and all the implementation methods are almost nothing, just do some simple thing.
sample.xul
<?xml version="1.0"?>
<overlay id="sample"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<statusbar id="status-bar">
<script>
function DoXPCOM()
{
try {
alert("1");
var sample = Components.classes["#baofeng/sample;1"].createInstance();
alert("2");
sample = sample.QueryInterface(Components.interfaces.nsISample);
alert("3");
dump("sample = " + sample + "\n");
}
catch(err) {
alert(err);
return;
}
var res = sample.SetValue("123");
var str = "";
obj.GetValue(str);
alert(str);
}
</script>
<button label="xpcom" oncommand="DoXPCOM();"/>
</statusbar>
</overlay>
"alert("1");" is executed, It crashes at "Components.classes["#baofeng/sample;1"].createInstance();" the contact ID "#baofeng/sample;1" is recognized by firefox, if not, there should be some info like "#baofeng/sample;1" undefined pops out. So the createInstance() method is the point.
can someone help me out of this?