Emscripten এর সাথে C++ এ জাভাস্ক্রিপ্ট স্নিপেট এম্বেড করা

বাইরের বিশ্বের সাথে যোগাযোগ করার জন্য আপনার WebAssembly লাইব্রেরিতে জাভাস্ক্রিপ্ট কোড কীভাবে এম্বেড করবেন তা শিখুন।

ওয়েবের সাথে WebAssembly ইন্টিগ্রেশনে কাজ করার সময়, আপনার বহিরাগত API যেমন ওয়েব API এবং তৃতীয় পক্ষের লাইব্রেরিগুলিতে কল করার একটি উপায় প্রয়োজন। তারপরে আপনার সেই মানগুলি এবং বস্তুর দৃষ্টান্তগুলি সংরক্ষণ করার একটি উপায় প্রয়োজন যেগুলি সেই APIগুলি ফিরে আসে এবং সেই সঞ্চিত মানগুলিকে পরে অন্যান্য APIগুলিতে প্রেরণ করার একটি উপায়৷ অ্যাসিঙ্ক্রোনাস API-এর জন্য, আপনাকে আপনার সিঙ্ক্রোনাস C/C++ কোডে Asyncify- এর সাথে প্রতিশ্রুতির জন্য অপেক্ষা করতে হতে পারে এবং অপারেশন শেষ হয়ে গেলে ফলাফলটি পড়তে হবে।

এমস্ক্রিপ্টেন এই ধরনের মিথস্ক্রিয়াগুলির জন্য বিভিন্ন সরঞ্জাম সরবরাহ করে:

  • emscripten::val C++ এ জাভাস্ক্রিপ্ট মান সংরক্ষণ এবং পরিচালনার জন্য।
  • জাভাস্ক্রিপ্ট স্নিপেট এম্বেড করার জন্য এবং C/C++ ফাংশন হিসেবে বাইন্ড করার জন্য EM_JS
  • EM_ASYNC_JS যা EM_JS এর ​​মতই, কিন্তু অ্যাসিঙ্ক্রোনাস জাভাস্ক্রিপ্ট স্নিপেট এম্বেড করা সহজ করে তোলে।
  • EM_ASM সংক্ষিপ্ত স্নিপেট এম্বেড করার জন্য এবং একটি ফাংশন ঘোষণা না করেই ইনলাইনে চালানোর জন্য।
  • --js-library উন্নত পরিস্থিতিগুলির জন্য যেখানে আপনি অনেকগুলি জাভাস্ক্রিপ্ট ফাংশনকে একক লাইব্রেরি হিসাবে ঘোষণা করতে চান।

এই পোস্টে আপনি শিখবেন কিভাবে একই ধরনের কাজের জন্য তাদের সবগুলো ব্যবহার করতে হয়।

emscripten::val ক্লাস

emcripten::val ক্লাসটি Embind দ্বারা সরবরাহ করা হয়। এটি গ্লোবাল এপিআই চালু করতে পারে, জাভাস্ক্রিপ্ট মানগুলিকে C++ দৃষ্টান্তে আবদ্ধ করতে পারে এবং মানগুলিকে C++ এবং জাভাস্ক্রিপ্ট প্রকারের মধ্যে রূপান্তর করতে পারে।

কিছু JSON আনয়ন এবং পার্স করতে Asyncify এর .await() এর সাথে এটি কীভাবে ব্যবহার করবেন তা এখানে রয়েছে:

#include <emscripten/val.h>

using namespace emscripten;

val fetch_json(const char *url) {
  // Get and cache a binding to the global `fetch` API in each thread.
  thread_local const val fetch = val::global("fetch");
  // Invoke fetch and await the returned `Promise<Response>`.
  val response = fetch(url).await();
  // Ask to read the response body as JSON and await the returned `Promise<any>`.
  val json = response.call<val>("json").await();
  // Return the JSON object.
  return json;
}

// Example URL.
val example_json = fetch_json("https://httpbin.org/json");

// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();

এই কোডটি ভাল কাজ করে, তবে এটি অনেকগুলি মধ্যবর্তী পদক্ষেপগুলি সম্পাদন করে। val প্রতিটি অপারেশন নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করতে হবে:

  1. আর্গুমেন্ট হিসাবে পাস করা C++ মানগুলিকে কিছু মধ্যবর্তী বিন্যাসে রূপান্তর করুন।
  2. জাভাস্ক্রিপ্টে যান, আর্গুমেন্টগুলিকে জাভাস্ক্রিপ্ট মানগুলিতে পড়ুন এবং রূপান্তর করুন।
  3. ফাংশনটি চালান
  4. জাভাস্ক্রিপ্ট থেকে মধ্যবর্তী বিন্যাসে ফলাফল রূপান্তর করুন।
  5. রূপান্তরিত ফলাফলটি C++ এ ফেরত দিন, এবং C++ অবশেষে এটি আবার পড়ে।

প্রতিটি await() WebAssembly মডিউলের সম্পূর্ণ কল স্ট্যাক খুলে দিয়ে, JavaScript-এ ফিরে, অপেক্ষা করে, এবং অপারেশন সম্পূর্ণ হলে WebAssembly স্ট্যাক পুনরুদ্ধার করে C++ পাশকে বিরতি দিতে হবে।

এই ধরনের কোডের C++ থেকে কিছুর প্রয়োজন নেই। C++ কোড জাভাস্ক্রিপ্ট অপারেশনের একটি সিরিজের জন্য শুধুমাত্র ড্রাইভার হিসেবে কাজ করছে। যদি আপনি জাভাস্ক্রিপ্টে fetch_json সরাতে পারেন এবং একই সময়ে মধ্যবর্তী ধাপের ওভারহেড কমাতে পারেন?

EM_JS ম্যাক্রো

EM_JS macro আপনাকে জাভাস্ক্রিপ্টে fetch_json সরাতে দেয়। Emscripten-এ EM_JS আপনাকে একটি C/C++ ফাংশন ঘোষণা করতে দেয় যা জাভাস্ক্রিপ্ট স্নিপেট দ্বারা প্রয়োগ করা হয়।

WebAssembly নিজেই মত, এটি শুধুমাত্র সাংখ্যিক আর্গুমেন্ট এবং রিটার্ন মান সমর্থন করার একটি সীমাবদ্ধতা আছে. অন্য কোনো মান পাস করার জন্য, আপনাকে সংশ্লিষ্ট API-এর মাধ্যমে ম্যানুয়ালি রূপান্তর করতে হবে। এখানে কিছু উদাহরণঃ.

পাসিং নম্বরের কোনো রূপান্তরের প্রয়োজন নেই:

// Passing numbers, doesn't need any conversion.
EM_JS(int, add_one, (int x), {
  return x + 1;
});

int x = add_one(41);

জাভাস্ক্রিপ্টে এবং থেকে স্ট্রিং পাস করার সময় আপনাকে preamble.js থেকে সংশ্লিষ্ট রূপান্তর এবং বরাদ্দ ফাংশন ব্যবহার করতে হবে:

EM_JS(void, log_string, (const char *msg), {
  console.log(UTF8ToString(msg));
});

EM_JS(const char *, get_input, (), {
  let str = document.getElementById('myinput').value;
  // Returns heap-allocated string.
  // C/C++ code is responsible for calling `free` once unused.
  return allocate(intArrayFromString(str), 'i8', ALLOC_NORMAL);
});

অবশেষে, আরও জটিল, নির্বিচারে, মান প্রকারের জন্য, আপনি পূর্বে উল্লিখিত val ক্লাসের জন্য JavaScript API ব্যবহার করতে পারেন। এটি ব্যবহার করে, আপনি জাভাস্ক্রিপ্ট মান এবং C++ ক্লাসগুলিকে মধ্যবর্তী হ্যান্ডলগুলিতে এবং পিছনে রূপান্তর করতে পারেন:

EM_JS(void, log_value, (EM_VAL val_handle), {
  let value = Emval.toValue(val_handle);
  console.log(value);
});

EM_JS(EM_VAL, find_myinput, (), {
  let input = document.getElementById('myinput');
  return Emval.toHandle(input);
});

val obj = val::object();
obj.set("x", 1);
obj.set("y", 2);
log_value(obj.as_handle()); // logs { x: 1, y: 2 }

val myinput = val::take_ownership(find_input());
// Now you can store the `find_myinput` DOM element for as long as you like, and access it later like:
std::string value = input["value"].as<std::string>();

সেই APIগুলিকে মাথায় রেখে, জাভাস্ক্রিপ্ট না রেখে বেশিরভাগ কাজ করার জন্য fetch_json উদাহরণটি পুনরায় লেখা যেতে পারে:

EM_JS(EM_VAL, fetch_json, (const char *url), {
  return Asyncify.handleAsync(async () => {
    url = UTF8ToString(url);
    // Invoke fetch and await the returned `Promise<Response>`.
    let response = await fetch(url);
    // Ask to read the response body as JSON and await the returned `Promise<any>`.
    let json = await response.json();
    // Convert JSON into a handle and return it.
    return Emval.toHandle(json);
  });
});

// Example URL.
val example_json = val::take_ownership(fetch_json("https://httpbin.org/json"));

// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();

ফাংশনের এন্ট্রি এবং এক্সিট পয়েন্টগুলিতে আমাদের এখনও কয়েকটি স্পষ্ট রূপান্তর রয়েছে, তবে বাকিগুলি এখন নিয়মিত জাভাস্ক্রিপ্ট কোড। val সমতুল্যের বিপরীতে, এটি এখন জাভাস্ক্রিপ্ট ইঞ্জিন দ্বারা অপ্টিমাইজ করা যেতে পারে এবং সমস্ত অ্যাসিঙ্ক ক্রিয়াকলাপগুলির জন্য শুধুমাত্র একবার C++ দিকে বিরতি দিতে হবে।

EM_ASYNC_JS ম্যাক্রো

শুধুমাত্র একটি বিট বাকি যা সুন্দর দেখায় না তা হল Asyncify.handleAsync র‍্যাপার—এর একমাত্র উদ্দেশ্য হল Asyncify-এর সাথে জাভাস্ক্রিপ্ট ফাংশনগুলিকে async করার অনুমতি দেওয়া। প্রকৃতপক্ষে, এই ব্যবহারের ক্ষেত্রে এতটাই সাধারণ যে এখন একটি বিশেষায়িত EM_ASYNC_JS ম্যাক্রো রয়েছে যা তাদের একত্রিত করে।

fetch উদাহরণের চূড়ান্ত সংস্করণ তৈরি করতে আপনি কীভাবে এটি ব্যবহার করতে পারেন তা এখানে:

EM_ASYNC_JS(EM_VAL, fetch_json, (const char *url), {
  url = UTF8ToString(url);
  // Invoke fetch and await the returned `Promise<Response>`.
  let response = await fetch(url);
  // Ask to read the response body as JSON and await the returned `Promise<any>`.
  let json = await response.json();
  // Convert JSON into a handle and return it.
  return Emval.toHandle(json);
});

// Example URL.
val example_json = val::take_ownership(fetch_json("https://httpbin.org/json"));

// Now we can extract fields, e.g.
std::string author = json["slideshow"]["author"].as<std::string>();

EM_ASM

EM_JS হল জাভাস্ক্রিপ্ট স্নিপেট ঘোষণা করার প্রস্তাবিত উপায়। এটি কার্যকর কারণ এটি ঘোষিত স্নিপেটগুলিকে অন্য জাভাস্ক্রিপ্ট ফাংশন আমদানির মতো সরাসরি আবদ্ধ করে। এটি আপনাকে সমস্ত প্যারামিটারের ধরন এবং নামগুলি স্পষ্টভাবে ঘোষণা করতে সক্ষম করে ভাল এরগনোমিক্স প্রদান করে।

কিছু ক্ষেত্রে, তবে, আপনি console.log কলের জন্য একটি দ্রুত স্নিপেট সন্নিবেশ করতে চান, একটি debugger; বিবৃতি বা অনুরূপ কিছু এবং একটি সম্পূর্ণ পৃথক ফাংশন ঘোষণা সঙ্গে বিরক্ত করতে চান না. এই বিরল ক্ষেত্রে, একটি EM_ASM macros family ( EM_ASM , EM_ASM_INT এবং EM_ASM_DOUBLE ) একটি সহজ পছন্দ হতে পারে৷ এই ম্যাক্রোগুলি EM_JS ম্যাক্রোর অনুরূপ, কিন্তু তারা একটি ফাংশন সংজ্ঞায়িত করার পরিবর্তে কোড ইনলাইনে চালায় যেখানে তারা সন্নিবেশিত হয়।

যেহেতু তারা একটি ফাংশন প্রোটোটাইপ ঘোষণা করে না, তাদের রিটার্ন টাইপ নির্দিষ্ট করার এবং আর্গুমেন্ট অ্যাক্সেস করার একটি ভিন্ন উপায় প্রয়োজন।

রিটার্ন টাইপ বেছে নিতে আপনাকে সঠিক ম্যাক্রো নাম ব্যবহার করতে হবে। EM_ASM ব্লকগুলি void ফাংশনের মতো কাজ করবে বলে আশা করা হচ্ছে, EM_ASM_INT ব্লকগুলি একটি পূর্ণসংখ্যার মান প্রদান করতে পারে এবং EM_ASM_DOUBLE ব্লকগুলি অনুরূপভাবে ফ্লোটিং-পয়েন্ট নম্বরগুলি প্রদান করে৷

যেকোনো পাস করা আর্গুমেন্ট জাভাস্ক্রিপ্ট বডিতে $0 , $1 এবং আরও কিছু নামে পাওয়া যাবে। সাধারণভাবে EM_JS বা WebAssembly-এর মতো, আর্গুমেন্টগুলি শুধুমাত্র সাংখ্যিক মানগুলির মধ্যে সীমাবদ্ধ - পূর্ণসংখ্যা, ফ্লোটিং-পয়েন্ট সংখ্যা, পয়েন্টার এবং হ্যান্ডলগুলি।

কনসোলে নির্বিচারে JS মান লগ করার জন্য আপনি কীভাবে একটি EM_ASM ম্যাক্রো ব্যবহার করতে পারেন তার একটি উদাহরণ এখানে দেওয়া হল:

val obj = val::object();
obj.set("x", 1);
obj.set("y", 2);
// executes inline immediately
EM_ASM({
  // convert handle passed under $0 into a JavaScript value
  let obj = Emval.fromHandle($0);
  console.log(obj); // logs { x: 1, y: 2 }
}, obj.as_handle());

--js-লাইব্রেরি

অবশেষে, Emscripten একটি নিজস্ব লাইব্রেরি বিন্যাসে একটি পৃথক ফাইলে জাভাস্ক্রিপ্ট কোড ঘোষণা সমর্থন করে:

mergeInto(LibraryManager.library, {
  log_value: function (val_handle) {
    let value = Emval.toValue(val_handle);
    console.log(value);
  }
});

তারপরে আপনাকে C++ পাশে ম্যানুয়ালি সংশ্লিষ্ট প্রোটোটাইপগুলি ঘোষণা করতে হবে:

extern "C" void log_value(EM_VAL val_handle);

একবার উভয় দিকে ঘোষণা করা হলে, জাভাস্ক্রিপ্ট লাইব্রেরীকে --js-library option মাধ্যমে মূল কোডের সাথে সংযুক্ত করা যেতে পারে, সংশ্লিষ্ট জাভাস্ক্রিপ্ট বাস্তবায়নের সাথে প্রোটোটাইপগুলিকে সংযুক্ত করে।

যাইহোক, এই মডিউল বিন্যাসটি অ-মানক এবং এর জন্য সতর্ক নির্ভরতা টীকা প্রয়োজন। যেমন, এটি বেশিরভাগই উন্নত পরিস্থিতির জন্য সংরক্ষিত।

উপসংহার

এই পোস্টে আমরা WebAssembly এর সাথে কাজ করার সময় C++ এ JavaScript কোড একীভূত করার বিভিন্ন উপায় দেখেছি।

এই ধরনের স্নিপেটগুলি অন্তর্ভুক্ত করা আপনাকে একটি পরিষ্কার এবং আরও দক্ষ উপায়ে অপারেশনগুলির দীর্ঘ ক্রম প্রকাশ করতে এবং তৃতীয় পক্ষের লাইব্রেরি, নতুন জাভাস্ক্রিপ্ট API এবং এমনকি জাভাস্ক্রিপ্ট সিনট্যাক্স বৈশিষ্ট্যগুলিতে ট্যাপ করতে দেয় যা এখনও C++ বা এম্বিন্ডের মাধ্যমে প্রকাশযোগ্য নয়।