Tài liệu này cung cấp những kiến thức cơ bản nhất giúp các nhà phát triển nhanh chóng làm quen và ứng dụng Mojo trong môi trường Chromium. Để tìm hiểu sâu hơn về các ràng buộc C++ của Mojo, bạn có thể tham khảo thêm các nguồn tài liệu chuyên sâu khác.
Thuật Ngữ Cơ Bản
Trong Mojo, một kênh truyền tin (message pipe) bao gồm hai điểm cuối (endpoints). Mỗi điểm cuối sở hữu một hàng đợi riêng để nhận thông điệp. Khi một thông điệp được gửi từ một điểm cuối, nó sẽ được thêm vào hàng đợi của điểm cuối đối tác. Điều này đảm bảo tính hai chiều của kênh truyền tin.
Một tệp .mojom có chức năng định nghĩa các giao diện (interfaces), mô tả cấu trúc thông điệp với kiểu dữ liệu chặt chẽ, tương tự như cách các tệp .proto hoạt động.
Khi kết hợp một giao diện mojom và một kênh truyền tin, hai điểm cuối của kênh sẽ được gán nhãn là InterfacePtr và Binding. Lúc này, kênh truyền tin được định hình để chuyên chở các thông điệp tuân theo định nghĩa của giao diện mojom đó. Cụ thể, InterfacePtr là điểm cuối thực hiện việc 'gửi' các thông điệp, trong khi Binding là điểm cuối 'nhận' chúng. Cần lưu ý rằng bản thân kênh truyền tin vẫn hoạt động hai chiều, và một thông điệp gửi đi có thể bao gồm một hàm gọi lại (callback) để nhận phản hồi, hàm này sẽ được InterfacePtr tiếp nhận.
Có thể hình dung đơn giản hơn: một InterfacePtr có khả năng thực hiện các lời gọi từ xa (remote calls) đến một cài đặt (implementation) của giao diện mojom mà Binding đang đại diện.
Bản thân Binding đóng vai trò là cầu nối, liên kết hàng đợi thông điệp của điểm cuối với một cài đặt cụ thể của giao diện, do lập trình viên cung cấp.
Ví Dụ Thực Hành
Để minh họa, hãy cùng xem xét một kịch bản trong Chrome: làm thế nào để gửi một thông điệp "Ping" từ tiến trình Trình duyệt (Browser process) đến tiến trình Kết xuất (Renderer process). Bước đầu tiên là định nghĩa giao diện mojom.
module my_app.remote_comm; // Định nghĩa module
interface RemotePinger { // Định nghĩa giao diện
// Nhận yêu cầu "Ping" và gửi lại một số nguyên ngẫu nhiên.
SendPing() => (int result_value); // Phương thức SendPing với phản hồi
};
Tiếp theo, chúng ta tạo một kênh truyền tin (MessagePipe):
my_app::remote_comm::RemotePingerPtr remote_client_proxy; // Khởi tạo con trỏ giao diện
my_app::remote_comm::RemotePingerRequest client_request = mojo::MakeRequest(&remote_client_proxy); // Tạo yêu cầu
Trong ví dụ này, remote_client_proxy là một InterfacePtr, còn client_request là một InterfaceRequest – tiền thân của một Binding sẽ được thiết lập sau này. Bây giờ, chúng ta có thể gửi thông điệp Ping.
auto response_handler = base::BindOnce(&HandlePongResponse); // Định nghĩa hàm callback
remote_client_proxy->SendPing(std::move(response_handler)); // Gửi Ping và chờ phản hồi
Lưu ý quan trọng: Để có thể nhận được phản hồi, đối tượng remote_client_proxy phải được giữ cho hoạt động. Đây chỉ là một lớp bao bọc quanh điểm cuối của kênh truyền tin; nếu nó bị hủy, không có gì có thể nhận được phản hồi.
Vậy là xong bước gửi! Tuy nhiên, mọi thứ sẽ không đơn giản đến mức tài liệu này không cần thiết. Chúng ta đã biến vấn đề phức tạp là gửi thông điệp từ Browser sang Renderer thành một bài toán đơn giản hơn: cần chuyển đối tượng client_request sang tiến trình Renderer, chuyển đổi nó thành một Binding, và cài đặt giao diện.
Trong kiến trúc Chrome, các tiến trình thường cung cấp các dịch vụ (services), và các dịch vụ này được kết nối với Trình quản lý dịch vụ (Service Manager) thông qua các kênh truyền tin. Việc chuyển client_request đến tiến trình Renderer mong muốn qua Service Manager là khá thuận tiện, nhưng điều này đòi hỏi phải khai báo rõ ràng ý định của chúng ta trong các tệp kê khai (manifest files). Với ví dụ này, chúng ta sẽ điều chỉnh các tệp kê khai mẫu cho Browser và Renderer.
renderer_app_manifest.json:
...
"interface_provider_specs": {
"service_manager:connector": {
"provides": {
"remote_ping_service": [ // Tên capability mà Renderer cung cấp
"my_app::remote_comm::RemotePinger" // Giao diện được cung cấp
]
}
}
},
...
browser_client_manifest.json:
...
"interface_provider_specs": {
"service_manager:connector": {
"requires": {
"content_renderer": [ "remote_ping_service" ] // Browser yêu cầu capability này từ Renderer
}
}
},
...
Những thay đổi này cho biết dịch vụ content_renderer cung cấp giao diện RemotePinger dưới khả năng (capability) có tên "remote_ping_service". Đồng thời, dịch vụ content_browser có ý định sử dụng khả năng này. Hàm trợ giúp content::BindInterface sẽ nhận client_request và gửi nó đến tiến trình renderer thông qua Service Manager.
content::RenderProcessHost* target_renderer_host = FindTargetRendererHost(); // Tìm host của tiến trình renderer đích
content::BindInterface(target_renderer_host, std::move(client_request)); // Gửi yêu cầu binding
Tổng hợp lại toàn bộ luồng xử lý trong tiến trình trình duyệt:
// Đảm bảo giữ đối tượng này hoạt động để nhận phản hồi.
my_app::remote_comm::RemotePingerPtr remote_ping_client_ptr;
my_app::remote_comm::RemotePingerRequest ping_request_obj = mojo::MakeRequest(&remote_ping_client_ptr);
// Gọi phương thức và cung cấp callback xử lý phản hồi.
remote_ping_client_ptr->SendPing(base::BindOnce(&HandlePongResponse));
// Lấy RenderProcessHost phù hợp và gửi yêu cầu.
content::RenderProcessHost* renderer_process_host = GetActiveRendererHost();
content::BindInterface(renderer_process_host, std::move(ping_request_obj));
Trong tiến trình Renderer, chúng ta cần triển khai giao diện RemotePinger và đảm bảo rằng một Binding được tạo ra bằng cách sử dụng client_request đã được chuyển đến. Đối với một dịch vụ Mojo độc lập, điều này sẽ đòi hỏi việc cài đặt phương thức service_manager::Service::OnBindInterface(). Tuy nhiên, trong môi trường Chrome, cơ chế này được trừu tượng hóa thông qua content::ConnectionFilters và service_manager::BinderRegistry. Việc thiết lập này thường được thực hiện trong hàm khởi tạo của Renderer, ví dụ RendererRuntimeContext::Initialize.
class RemotePingServiceImpl : public my_app::remote_comm::RemotePinger {
public:
// Phương thức để thiết lập binding khi nhận yêu cầu.
void EstablishBinding(my_app::remote_comm::RemotePingerRequest endpoint_request) {
service_binding_.reset(
new mojo::Binding(this, std::move(endpoint_request)));
}
// Triển khai phương thức SendPing từ giao diện RemotePinger.
void SendPing(SendPingCallback completion_callback) override {
// Thực hiện logic Ping tại đây, ví dụ trả về một số ngẫu nhiên.
std::move(completion_callback).Run(42); // Gửi phản hồi
}
private:
std::unique_ptr> service_binding_;
};
// ... trong một phương thức khởi tạo của Renderer (ví dụ RendererRuntimeContext::Initialize)
void RendererRuntimeContext::Initialize() {
// ... (các khởi tạo khác)
// Khởi tạo đối tượng triển khai dịch vụ và đăng ký nó.
// Đảm bảo `this->ping_implementation_instance` có tuổi thọ dài hơn `binder_registry`.
this->ping_implementation_instance = std::make_unique<RemotePingServiceImpl>();
auto binder_registry = std::make_unique();
binder_registry->AddInterface(
base::BindRepeating(&RemotePingServiceImpl::EstablishBinding,
base::Unretained(this->ping_implementation_instance.get())));
service_manager::GetServiceManagerConnection()->AddConnectionFilter(
std::make_unique<CustomServiceConnectionFilter>(std::move(binder_registry)));
// ... (tiếp tục khởi tạo)
}