Mojo C++ 바인딩 API는 C++ 시스템 API를 기반으로 Mojo 메시지 파이프를 통한 통신을 위한 더 자연스러운 기본 요소를 제공합니다. Mojom IDL 및 바인딩 생성기에서 생성된 코드와 결합하여 사용자는 임의의 프로세스 내 및 프로세스 간 경계를 통해 인터페이스 클라이언트와 구현을 쉽게 연결할 수 있습니다.
이 문서는 예제 코드를 포함한 바인딩 API 사용법에 대한 상세 가이드를 제공합니다. 자세한 API 참조는 //mojo/public/cpp/bindings의 헤더를 참조하십시오.
시작하기
Mojom IDL 파일이 바인딩 생성기에 의해 처리되면 입력 .mojom 파일의 이름을 기반으로 하는 일련의 .h 및 .cc 파일로 C++ 코드가 생성됩니다. //services/db/public/mojom/db.mojom에 다음과 같은 Mojom 파일을 생성한다고 가정해 보겠습니다:
module db.mojom;
interface Table {
AddRow(int32 key, string data);
};
interface Database {
CreateTable(Table& table);
};
그리고 //services/db/public/mojom/BUILD.gn에 바인딩을 생성하는 GN 타겟을 추가합니다:
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"db.mojom",
]
}
이 인터페이스가 필요한 모든 타겟이 이에 의존하도록 해야 합니다. 예를 들어 다음과 같은 줄을 추가합니다:
deps += [ '//services/db/public/mojom' ]
그런 다음 이 타겟을 빌드합니다:
ninja -C out/r services/db/public/mojom
그러면 C++ 바인딩과 관련된 여러 생성된 소스 파일이 생성됩니다. 그중 두 파일은 다음과 같습니다:
out/gen/services/db/public/mojom/db.mojom.cc
out/gen/services/db/public/mojom/db.mojom.h
위에서 생성된 헤더를 소스에 포함하여 그 안에 정의된 정의를 사용할 수 있습니다:
#include "services/business/public/mojom/factory.mojom.h"
class TableImpl : public db::mojom::Table {
// ...
};
이 문서는 Mojom IDL이 C++ 소비자를 위해 생성하는 다양한 종류의 정의와 이를 메시지 파이프를 통해 통신하는 데 효과적으로 사용하는 방법을 다룹니다.
참고: Blink 코드 내에서 C++ 바인딩을 사용하는 것은 일반적으로 다른 생성된 헤더를 사용해야 하는 특별한 제약 조건을 따릅니다. 자세한 내용은 Blink 타입 매핑을 참조하십시오.
인터페이스
Mojom IDL 인터페이스는 생성된 헤더에서 해당 C++ (순수 가상) 클래스 인터페이스 정의로 변환됩니다. 이 정의는 인터페이스의 각 요청 메시지에 대해 하나의 생성된 메서드 시그니처로 구성됩니다. 내부적으로는 메시지 직렬화 및 역직렬화를 위한 코드도 생성되지만, 이 세부 사항은 바인딩 소비자에게 숨겨집니다.
기본 사용법
클라이언트가 간단한 문자열 메시지를 기록하는 데 사용할 수 있는 간단한 로깅 인터페이스를 정의하는 새로운 //sample/logger.mojom을 고려해 보겠습니다:
module sample.mojom;
interface Logger {
Log(string message);
};
이것을 바인딩 생성기에 실행하면 중요하지 않은 세부 사항을 제외하고 다음과 같은 정의가 포함된 logging.mojom.h가 생성됩니다:
namespace sample {
namespace mojom {
class Logger {
virtual ~Logger() {}
virtual void Log(const std::string& message) = 0;
};
using LoggerPtr = mojo::InterfacePtr<Logger>;
using LoggerRequest = mojo::InterfaceRequest<Logger>;
} // namespace mojom
} // namespace sample
말이 됩니다. 끝에 있는 해당 타입 별칭을 자세히 살펴보겠습니다.
InterfacePtr 및 InterfaceRequest
LoggerPtr 및 LoggerRequest에 대한 타입 별칭이 C++ 바인딩 라이브러리에서 가장 기본적인 템플릿 타입 중 두 가지인 InterfacePtr<T> 및 InterfaceRequest<T>를 사용하고 있음을 알 수 있습니다.
Mojo 바인딩 라이브러리의 세계에서 이것들은 효과적으로 강력한 타입의 메시지 파이프 엔드포인트입니다. InterfacePtr<T>가 메시지 파이프 엔드포인트에 바인딩되면 역참조하여 불투명한 T 인터페이스에 대한 호출을 할 수 있습니다. 이러한 호출은 즉시 인수(생성된 코드 사용)를 직렬화하고 해당 메시지를 파이프에 씁니다.
InterfaceRequest<T>는 기본적으로 InterfacePtr<T> 파이프의 다른 쪽 끝(수신 끝)을 바인딩할 일부 구현으로 라우팅될 수 있을 때까지 보유하는 타입이 지정된 컨테이너일 뿐입니다. InterfaceRequest<T>는 파이프 엔드포인트를 보유하고 유용한 컴파일 타임 타입 정보를 전달하는 것 외에는 실제로 아무 작업도 수행하지 않습니다.
인터페이스 파이프 생성
이를 수행하는 한 가지 방법은 수동으로 파이프를 만들고 각 끝을 강력한 타입의 객체로 래핑하는 것입니다:
#include "sample/logger.mojom.h"
mojo::MessagePipe pipe;
sample::mojom::LoggerPtr logger(
sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0));
sample::mojom::LoggerRequest request(std::move(pipe.handle1));
꽤 장황하지만, C++ 바인딩 라이브러리는 동일한 작업을 수행하는 더 편리한 방법을 제공합니다. interface_request.h는 MakeRequest 함수를 정의합니다:
sample::mojom::LoggerPtr logger;
auto request = mojo::MakeRequest(&logger);
이 두 번째 코드 조각은 첫 번째 코드 조각과 동일합니다.
참고: 위의 첫 번째 예제에서 LoggerPtrInfo 타입을 사용하는 것을 볼 수 있습니다. 이는 mojo::InterfacePtrInfo<Logger>의 생성된 별칭입니다. 이것은 단지 파이프 핸들을 보유하고 실제로 파이프에서 메시지를 읽거나 쓸 수 없다는 점에서 InterfaceRequest<T>와 유사합니다. 이 타입과 InterfaceRequest<T>는 모두 시퀀스 간에 자유롭게 이동할 수 있는 반면, 바인딩된 InterfacePtr<T>는 단일 시퀀스에 바인딩됩니다.
InterfacePtr<T>는 PassInterface() 메서드를 호출하여 바인딩을 해제할 수 있으며, 이 메서드는 새 InterfacePtrInfo<T>를 반환합니다. 반대로 InterfacePtr<T>는 InterfacePtrInfo<T>를 바인딩(따라서 소유권을 가져옴)하여 파이프에서 인터페이스 호출을 할 수 있습니다.
InterfacePtr<T>의 시퀀스 바인딩 특성은 message responses 및 connection error notifications의 안전한 디스패치를 지원하는 데 필요합니다.
LoggerPtr이 바인딩되면 즉시 Logger 인터페이스 메서드를 호출하기 시작할 수 있으며, 이는 즉시 메시지를 파이프에 씁니다. 이러한 메시지는 누군가 바인딩하여 읽기 시작할 때까지 파이프의 수신 끝에서 대기열에 남아 있습니다.
logger->Log("Hello!");
이것은 실제로 파이프에 Log 메시지를 씁니다.
그러나 앞서 언급했듯이 InterfaceRequest는 실제로 아무 작업도 수행하지 않으므로 해당 메시지는 파이프에 영원히 남아 있습니다. 파이프의 다른 쪽 끝에서 메시지를 읽고 디스패치하는 방법이 필요합니다. 인터페이스 요청을 바인딩해야 합니다.
인터페이스 요청 바인딩
바인딩 라이브러리에는 메시지 파이프의 수신 끝을 바인딩하기 위한 다양한 도우미 클래스가 있습니다. 그중 가장 기본적인 것은 적절하게 이름이 지정된 mojo::Binding<T>입니다. mojo::Binding<T>는 T의 구현을 단일 바인딩된 메시지 파이프 엔드포인트(mojo::InterfaceRequest<T>를 통해)와 연결하여 읽기 가능 상태를 지속적으로 모니터링합니다.
바인딩된 파이프를 읽을 수 있게 될 때마다 Binding은 작업을 예약하여 사용 가능한 모든 메시지를 읽고, 역직렬화하고(생성된 코드 사용), 바인딩된 T 구현으로 디스패치합니다. 다음은 Logger 인터페이스의 샘플 구현입니다. 구현 자체가 mojo::Binding을 소유하고 있음을 확인하십시오. 바인딩된 구현은 이를 바인딩하는 mojo::Binding보다 오래 지속되어야 하므로 이는 일반적인 패턴입니다.
#include "base/logging.h"
#include "base/macros.h"
#include "sample/logger.mojom.h"
class LoggerImpl : public sample::mojom::Logger {
public:
// 참고: 클라이언트당 하나의 인스턴스를 갖는 인터페이스 구현의 일반적인 패턴은
// 생성자에서 InterfaceRequest를 사용하는 것입니다.
explicit LoggerImpl(sample::mojom::LoggerRequest request)
: binding_(this, std::move(request)) {}
~Logger() override {}
// sample::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
mojo::Binding<sample::mojom::Logger> binding_;
DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
};
이제 보류 중인 LoggerRequest에 대해 LoggerImpl을 구성할 수 있으며, 이전에 대기 중이던 Log 메시지는 LoggerImpl의 시퀀스에서 가능한 한 빨리 디스패치됩니다:
LoggerImpl impl(std::move(request));
아래 다이어그램은 위의 코드 한 줄로 시작되는 다음과 같은 일련의 이벤트를 보여줍니다:
LoggerImpl생성자가 호출되어LoggerRequest를Binding에 전달합니다.Binding은LoggerRequest의 파이프 엔드포인트 소유권을 가져와 읽기 가능 상태를 모니터링하기 시작합니다. 파이프를 즉시 읽을 수 있으므로 보류 중인Log메시지를 가능한 한 빨리 파이프에서 읽도록 작업이 예약됩니다.Log메시지를 읽고 역직렬화하여Binding이 바인딩된LoggerImpl에서Logger::Log구현을 호출하도록 합니다.
결과적으로 구현은 결국 클라이언트의 "Hello!" 메시지를 LOG(ERROR)를 통해 기록합니다.
참고: 메시지는 파이프를 바인딩하는 객체(위 예제의 mojo::Binding)가 살아 있는 동안에만 파이프에서 읽고 디스패치됩니다.
응답 수신
일부 Mojom 인터페이스 메서드는 응답을 예상합니다. 마지막으로 기록된 줄을 다음과 같이 쿼리할 수 있도록 Logger 인터페이스를 수정한다고 가정해 보겠습니다:
module sample.mojom;
interface Logger {
Log(string message);
GetTail() => (string message);
};
생성된 C++ 인터페이스는 이제 다음과 같습니다:
namespace sample {
namespace mojom {
class Logger {
public:
virtual ~Logger() {}
virtual void Log(const std::string& message) = 0;
using GetTailCallback = base::OnceCallback<void(const std::string& message)>;
virtual void GetTail(GetTailCallback callback) = 0;
}
} // namespace mojom
} // namespace sample
이전과 마찬가지로 이 인터페이스의 클라이언트와 구현 모두 GetTail 메서드에 대해 동일한 시그니처를 사용합니다. 구현은 callback 인수를 사용하여 요청에 응답하고, 클라이언트는 callback 인수를 전달하여 비동기적으로 응답을 수신합니다. 다음은 업데이트된 구현입니다:
class LoggerImpl : public sample::mojom::Logger {
public:
explicit LoggerImpl(sample::mojom::LoggerRequest request)
: binding_(this, std::move(request)) {}
~Logger() override {}
// sample::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
lines_.push_back(message);
}
void GetTail(GetTailCallback callback) override {
std::move(callback).Run(lines_.back());
}
private:
mojo::Binding<sample::mojom::Logger> binding_;
std::vector<std::string> lines_;
DISALLOW_COPY_AND_ASSIGN(LoggerImpl);
};
그리고 업데이트된 클라이언트 호출:
void OnGetTail(const std::string& message) {
LOG(ERROR) << "Tail was: " << message;
}
logger->GetTail(base::BindOnce(&OnGetTail));
내부적으로 구현 측 콜백은 실제로 응답 인수를 직렬화하고 클라이언트로 다시 전달하기 위해 파이프에 작성합니다. 한편 클라이언트 측 콜백은 들어오는 응답 메시지에 대해 파이프를 감시하고, 도착하면 읽고 역직렬화한 다음 역직렬화된 매개변수로 콜백을 호출하는 일부 내부 로직에 의해 호출됩니다.
연결 오류
파이프 연결이 끊어지면 두 엔드포인트 모두 연결 오류를 관찰할 수 있습니다(연결 끊김이 엔드포인트를 닫거나 파괴하여 발생한 경우 해당 엔드포인트는 해당 알림을 받지 못함). 연결 끊김 시 엔드포인트에 남아 있는 수신 메시지가 있으면 메시지가 소진될 때까지 연결 오류가 트리거되지 않습니다.
파이프 연결 끊김은 다음과 같은 이유로 발생할 수 있습니다:
- Mojo 시스템 수준 원인: 프로세스 종료, 리소스 부족 등.
- 수신된 메시지 처리 중 유효성 검사 오류로 인해 바인딩이 파이프를 닫습니다.
- 피어 엔드포인트가 닫혔습니다. 예를 들어, 원격 측이 바인딩된
mojo::InterfacePtr<T>이고 파괴된 경우입니다.
기본 원인에 관계없이 바인딩 엔드포인트에서 연결 오류가 발생하면 해당 엔드포인트의 연결 오류 처리기(설정된 경우)가 호출됩니다. 이 처리기는 간단한 base::Closure이며 엔드포인트가 동일한 파이프에 바인딩되어 있는 한 한 번만 호출될 수 있습니다. 일반적으로 클라이언트와 구현은 이 처리기를 사용하여 일종의 정리를 수행하거나, 특히 오류가 예상치 못한 경우 새 파이프를 만들어 새 연결을 설정하려고 시도합니다.
모든 메시지 파이프 바인딩 C++ 객체(예: mojo::Binding<T>, mojo::InterfacePtr<T> 등)는 set_connection_error_handler 메서드를 통해 연결 오류 처리기 설정을 지원합니다.
오류 처리기 호출을 보여주기 위해 또 다른 종단 간 Logger 예제를 설정할 수 있습니다:
sample::mojom::LoggerPtr logger;
LoggerImpl impl(mojo::MakeRequest(&logger));
impl.set_connection_error_handler(base::BindOnce([] { LOG(ERROR) << "Bye."; }));
logger->Log("OK cool");
logger.reset(); // 클라이언트 끝을 닫습니다.
impl이 여기서 살아 있는 한, 결국 Log 메시지를 수신하고 즉시 "Bye."를 출력하는 바인딩된 콜백이 호출됩니다. 다른 모든 바인딩 콜백과 마찬가지로 연결 오류 처리기는 해당 바인딩 객체가 파괴되면 절대 호출되지 않습니다.
실제로 LoggerImpl이 생성자 내에서 다음과 같은 오류 처리기를 설정했다고 가정해 보겠습니다:
LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request)
: binding_(this, std::move(request)) {
binding_.set_connection_error_handler(
base::BindOnce(&LoggerImpl::OnError, base::Unretained(this)));
}
void LoggerImpl::OnError() {
LOG(ERROR) << "Client disconnected! Purging log lines.";
lines_.clear();
}
base::Unretained의 사용은 안전합니다. 오류 처리기는 binding_의 수명을 넘어 절대 호출되지 않으며, this는 binding_을 소유하기 때문입니다.
엔드포인트 수명 및 콜백에 대한 참고 사항
mojo::InterfacePtr<T>가 파괴되면 보류 중인 콜백과 연결 오류 처리기(등록된 경우)가 호출되지 않음이 보장됩니다.
mojo::Binding<T>가 파괴되면 더 이상 메서드 호출이 구현에 디스패치되지 않고 연결 오류 처리기(등록된 경우)가 호출되지 않음이 보장됩니다.
프로세스 충돌 및 콜백 처리 모범 사례
콜백을 사용하는 mojo 인터페이스 메서드를 호출할 때 일반적인 상황은 호출자가 다른 엔드포인트가 종료되었는지(예: 충돌로 인해) 알고 싶어한다는 것입니다. 이 경우 소비자는 일반적으로 응답 콜백이 실행되지 않을지 알고 싶어합니다. InterfacePtr<T>가 유지되는 방식에 따라 이 문제에 대한 다양한 솔루션이 있습니다:
- 소비자가
InterfacePtr<T>를 소유하는 경우:set_connection_error_handler를 사용해야 합니다. - 소비자가
InterfacePtr<T>를 소유하지 않는 경우: 호출자가 원하는 동작에 따라 두 가지 도우미가 있습니다. 호출자가 오류 처리기가 실행되도록 하려면mojo::WrapCallbackWithDropHandler를 사용해야 합니다. 호출자가 콜백이 항상 실행되도록 하려면mojo::WrapCallbackWithDefaultInvokeIfNotRun도우미를 사용해야 합니다. 두 도우미 모두 일반적인 콜백 주의 사항을 따라 콜백이 소비자가 소멸된 후에 실행되지 않도록 해야 합니다(예:InterfacePtr<T>의 소유자가 소비자보다 오래 지속되기 때문에). 여기에는base::WeakPtr또는base::RefCounted사용이 포함됩니다. 또한 이러한 도우미를 사용하면 InterfacePtr이 재설정되거나 파괴되는 동안 콜백이 동기적으로 실행될 수 있다는 점에 유의해야 합니다.
순서에 대한 참고 사항
이전 섹션에서 언급했듯이 파이프의 한쪽 끝을 닫으면 결국 다른 쪽 끝에서 연결 오류가 트리거됩니다. 그러나 이 이벤트 자체는 파이프의 다른 이벤트(예: 메시지 쓰기)와 관련하여 순서가 지정된다는 점에 유의하는 것이 중요합니다.
즉, 다음과 같은 인위적인 코드를 작성하는 것이 안전합니다:
void GoBindALogger(sample::mojom::LoggerRequest request) {
LoggerImpl impl(std::move(request));
base::RunLoop loop;
impl.set_connection_error_handler(loop.QuitClosure());
loop.Run();
}
void LogSomething() {
sample::mojom::LoggerPtr logger;
bg_thread->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger)));
logger->Log("OK Computer");
}
logger가 범위를 벗어나면 즉시 메시지 파이프의 끝을 닫지만, 구현 측은 전송된 Log 메시지를 수신할 때까지 이를 인지하지 못합니다. 따라서 위의 impl은 먼저 메시지를 기록하고 그런 다음 연결 오류를 확인하고 실행 루프를 중단합니다.
타입
열거형
Mojom 열거형은 int32_t를 기본 타입으로 사용하는 동등한 강력한 타입의 C++11 열거형 클래스로 직접 변환됩니다. 타입 이름과 값 이름은 Mojom과 C++ 간에 동일합니다. Mojo는 또한 항상 가장 높은 열거자의 값을 공유하는 특수 열거자 kMaxValue를 정의합니다. 이렇게 하면 Mojo 열거형을 히스토그램에 기록하고 레거시 IPC와 상호 운용하기가 쉬워집니다.
예를 들어 다음 Mojom 정의를 고려하십시오:
module business.mojom;
enum Department {
kEngineering,
kMarketing,
kSales,
};
이는 다음 C++ 정의로 변환됩니다:
namespace business {
namespace mojom {
enum class Department : int32_t {
kEngineering,
kMarketing,
kSales,
kMaxValue = kSales,
};
} // namespace mojom
} // namespace business
구조체
Mojom 구조체는 필드의 논리적 그룹을 새로운 복합 타입으로 정의하는 데 사용할 수 있습니다. 모든 Mojom 구조체는 동일한 이름의 대표적인 C++ 클래스를 생성하며, 해당 C++ 타입의 동일한 이름의 public 필드와 여러 유용한 public 메서드를 제공합니다.
예를 들어 다음 Mojom 구조체를 고려하십시오:
module business.mojom;
struct Employee {
int64 id;
string username;
Department department;
};
그러면 다음과 같은 C++ 클래스가 생성됩니다:
namespace business {
namespace mojom {
class Employee;
using EmployeePtr = mojo::StructPtr<Employee>;
class Employee {
public:
// 기본 생성자 - 기본값(Mojom 내에서 명시적으로 지정된 값 포함)을 적용합니다.
Employee();
// 값 생성자 - 구조체의 각 필드에 대한 명시적 인수(Mojom 정의 순서대로).
Employee(int64_t id, const std::string& username, Department department);
// 이 구조체 값의 새 복사본을 만듭니다.
EmployeePtr Clone();
// 동일한 타입의 다른 구조체 값과 동등성을 테스트합니다.
bool Equals(const Employee& other);
// Mojom과 동일한 이름의 동등한 public 필드입니다.
int64_t id;
std::string username;
Department department;
};
} // namespace mojom
} // namespace business
메시지 매개변수로 사용되거나 다른 Mojom 구조체 내의 필드로 사용될 때 struct 타입은 move-only mojo::StructPtr 도우미로 래핑됩니다. 이는 몇 가지 추가 유틸리티 메서드가 있는 std::unique_ptr와 거의 동일합니다. 이를 통해 구조체 값을 nullable로 만들고 구조체 타입이 잠재적으로 자기 참조가 될 수 있습니다.
생성된 모든 구조체 클래스에는 New의 인수를 전달하여 구성된 클래스의 새 인스턴스를 래핑하는 새 mojo::StructPtr<T>를 반환하는 정적 New() 메서드가 있습니다. 예:
mojom::EmployeePtr e1 = mojom::Employee::New();
e1->id = 42;
e1->username = "mojo";
e1->department = mojom::Department::kEngineering;
다음과 동일합니다:
auto e1 = mojom::Employee::New(42, "mojo", mojom::Department::kEngineering);
이제 다음과 같은 인터페이스를 정의하면:
interface EmployeeManager {
AddEmployee(Employee e);
};
구현할 다음 C++ 인터페이스를 얻습니다:
class EmployeeManager {
public:
virtual ~EmployeManager() {}
virtual void AddEmployee(EmployeePtr e) = 0;
};
그리고 다음과 같이 C++ 코드에서 이 메시지를 보낼 수 있습니다:
mojom::EmployeManagerPtr manager = ...;
manager->AddEmployee(
Employee::New(42, "mojo", mojom::Department::kEngineering));
// 또는
auto e = Employee::New(42, "mojo", mojom::Department::kEngineering);
manager->AddEmployee(std::move(e));
공용체
구조체와 유사하게 태그된 공용체는 동일한 이름의 대표적인 C++ 클래스를 생성하며 일반적으로 mojo::StructPtr<T>로 래핑됩니다.
구조체와 달리 생성된 모든 공용체 필드는 비공개이며 접근자를 사용하여 검색하고 조작해야 합니다. foo 필드는 foo()로 액세스할 수 있고 set_foo()로 설정할 수 있습니다. 또한 각 필드에 대해 공용체가 현재 다른 모든 공용체 필드를 배제하고 필드 foo의 값을 취하고 있는지 여부를 나타내는 부울 is_foo()가 있습니다.
마지막으로 생성된 모든 공용체 클래스에는 명명된 모든 공용체 필드를 열거하는 중첩된 Tag 열거형 클래스도 있습니다. Mojom 공용체 값의 현재 타입은 Tag를 반환하는 which() 메서드를 호출하여 확인할 수 있습니다.
예를 들어, 다음 Mojom 정의를 고려하십시오:
union Value {
int64 int_value;
float float_value;
string string_value;
};
interface Dictionary {
AddValue(string key, Value value);
};
이것은 다음 C++ 인터페이스를 생성합니다:
class Value {
public:
~Value() {}
};
class Dictionary {
public:
virtual ~Dictionary() {}
virtual void AddValue(const std::string& key, ValuePtr value) = 0;
};
그리고 다음과 같이 사용할 수 있습니다:
ValuePtr value = Value::New();
value->set_int_value(42);
CHECK(value->is_int_value());
CHECK_EQ(value->which(), Value::Tag::INT_VALUE);
value->set_float_value(42);
CHECK(value->is_float_value());
CHECK_EQ(value->which(), Value::Tag::FLOAT_VALUE);
value->set_string_value("bananas");
CHECK(value->is_string_value());
CHECK_EQ(value->which(), Value::Tag::STRING_VALUE);
마지막으로, 공용체 값이 현재 특정 필드로 채워져 있지 않은 경우 해당 필드에 액세스하려고 하면 DCHECK가 발생합니다:
ValuePtr value = Value::New();
value->set_int_value(42);
LOG(INFO) << "Value is " << value->string_value(); // DCHECK!
인터페이스를 통한 인터페이스 전송
인터페이스 파이프를 만들고 Ptr 및 Request 엔드포인트를 흥미로운 방식으로 사용하는 방법을 알았습니다. 이것이 여전히 흥미로운 IPC로 이어지지는 않습니다! Mojo IPC의 핵심은 다른 인터페이스를 통해 인터페이스 엔드포인트를 전송하는 기능이므로 이를 수행하는 방법을 살펴보겠습니다.
인터페이스 요청 전송
//sample/db.mojom의 새로운 예제 Mojom을 고려하십시오:
module db.mojom;
interface Table {
void AddRow(int32 key, string data);
};
interface Database {
AddTable(Table& table);
};
Mojom IDL 문서에 설명된 대로 Table& 구문은 Table 인터페이스 요청을 나타냅니다. 이것은 위 섹션에서 논의된 InterfaceRequest<T> 타입과 정확히 일치하며, 실제로 이러한 인터페이스에 대한 생성된 코드는 다음과 같습니다:
namespace db {
namespace mojom {
class Table {
public:
virtual ~Table() {}
virtual void AddRow(int32_t key, const std::string& data) = 0;
}
using TablePtr = mojo::InterfacePtr<Table>;
using TableRequest = mojo::InterfaceRequest<Table>;
class Database {
public:
virtual ~Database() {}
virtual void AddTable(TableRequest table);
};
using DatabasePtr = mojo::InterfacePtr<Database>;
using DatabaseRequest = mojo::InterfaceRequest<Database>;
} // namespace mojom
} // namespace db
이제 Table 및 Database의 구현과 함께 이 모든 것을 종합할 수 있습니다:
#include "sample/db.mojom.h"
class TableImpl : public db::mojom:Table {
public:
explicit TableImpl(db::mojom::TableRequest request)
: binding_(this, std::move(request)) {}
~TableImpl() override {}
// db::mojom::Table:
void AddRow(int32_t key, const std::string& data) override {
rows_.insert({key, data});
}
private:
mojo::Binding<db::mojom::Table> binding_;
std::map<int32_t, std::string> rows_;
};
class DatabaseImpl : public db::mojom::Database {
public:
explicit DatabaseImpl(db::mojom::DatabaseRequest request)
: binding_(this, std::move(request)) {}
~DatabaseImpl() override {}
// db::mojom::Database:
void AddTable(db::mojom::TableRequest table) {
tables_.emplace_back(std::make_unique<TableImpl>(std::move(table)));
}
private:
mojo::Binding<db::mojom::Database> binding_;
std::vector<std::unique_ptr<TableImpl>> tables_;
};
꽤 간단합니다. AddTable에 대한 Table& Mojom 매개변수는 mojo::InterfaceRequest<db::mojom::Table>에서 별칭이 지정된 C++ db::mojom::TableRequest로 변환되며, 이는 강력한 타입의 메시지 파이프 핸들입니다. DatabaseImpl이 AddTable 호출을 받으면 새 TableImpl을 생성하고 이를 수신된 TableRequest에 바인딩합니다.
이것이 어떻게 사용될 수 있는지 살펴보겠습니다.
db::mojom::DatabasePtr database;
DatabaseImpl db_impl(mojo::MakeRequest(&database));
db::mojom::TablePtr table1, table2;
database->AddTable(mojo::MakeRequest(&table1));
database->AddTable(mojo::MakeRequest(&table2));
table1->AddRow(1, "hiiiiiiii");
table2->AddRow(2, "heyyyyyy");
TableRequest 엔드포인트가 아직 전송 중인 동안에도 새 Table 파이프를 즉시 사용할 수 있습니다.
InterfacePtr 전송
물론 InterfacePtr도 전송할 수 있습니다:
interface TableListener {
OnRowAdded(int32 key, string data);
};
interface Table {
AddRow(int32 key, string data);
AddListener(TableListener listener);
};
이것은 다음과 같은 Table::AddListener 시그니처를 생성합니다:
virtual void AddListener(TableListenerPtr listener) = 0;
그리고 다음과 같이 사용할 수 있습니다:
db::mojom::TableListenerPtr listener;
TableListenerImpl impl(mojo::MakeRequest(&listener));
table->AddListener(std::move(listener));
기타 인터페이스 바인딩 타입
위의 인터페이스 섹션에서는 가장 일반적인 바인딩 객체 타입인 InterfacePtr, InterfaceRequest 및 Binding의 기본 사용법을 다룹니다. 이러한 타입이 실제로 가장 일반적으로 사용되지만 클라이언트 측 및 구현 측 인터페이스 파이프를 바인딩하는 다른 여러 방법이 있습니다.
강력한 바인딩
강력한 바인딩은 인터페이스 구현을 소유하고 바인딩된 인터페이스 엔드포인트가 오류를 감지하면 자동으로 정리하는 독립 실행형 객체로 존재합니다. MakeStrongBinding 함수는 이러한 바인딩을 만드는 데 사용됩니다.
class LoggerImpl : public sample::mojom::Logger {
public:
LoggerImpl() {}
~LoggerImpl() override {}
// sample::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
// 참고: Binding 객체를 소유하지 않습니다!
};
db::mojom::LoggerPtr logger;
mojo::MakeStrongBinding(std::make_unique<LoggerImpl>(),
mojo::MakeRequest(&logger));
logger->Log("NOM NOM NOM MESSAGES");
이제 시스템 어딘가에 logger가 열려 있는 한 반대쪽 끝에 바인딩된 LoggerImpl은 계속 활성 상태로 유지됩니다.
바인딩 세트
단일 구현 인스턴스를 여러 클라이언트와 공유하는 것이 유용한 경우가 있습니다. BindingSet을 사용하면 쉽게 할 수 있습니다. 다음 Mojom을 고려하십시오:
module system.mojom;
interface Logger {
Log(string message);
};
interface LoggerProvider {
GetLogger(Logger& logger);
};
BindingSet을 사용하여 여러 Logger 요청을 단일 구현 인스턴스에 바인딩할 수 있습니다:
class LogManager : public system::mojom::LoggerProvider,
public system::mojom::Logger {
public:
explicit LogManager(system::mojom::LoggerProviderRequest request)
: provider_binding_(this, std::move(request)) {}
~LogManager() {}
// system::mojom::LoggerProvider:
void GetLogger(LoggerRequest request) override {
logger_bindings_.AddBinding(this, std::move(request));
}
// system::mojom::Logger:
void Log(const std::string& message) override {
LOG(ERROR) << "[Logger] " << message;
}
private:
mojo::Binding<system::mojom::LoggerProvider> provider_binding_;
mojo::BindingSet<system::mojom::Logger> logger_bindings_;
};
InterfacePtr 세트
위의 BindingSet과 유사하게, 일부 이벤트를 관찰하는 클라이언트 세트와 같은 InterfacePtr 세트를 유지 관리하는 것이 유용한 경우가 있습니다. InterfacePtrSet이 도움이 됩니다. 다음 Mojom을 사용하십시오:
module db.mojom;
interface TableListener {
OnRowAdded(int32 key, string data);
};
interface Table {
AddRow(int32 key, string data);
AddListener(TableListener listener);
};
Table의 구현은 다음과 같을 수 있습니다:
class TableImpl : public db::mojom::Table {
public:
TableImpl() {}
~TableImpl() override {}
// db::mojom::Table:
void AddRow(int32_t key, const std::string& data) override {
rows_.insert({key, data});
listeners_.ForEach([key, &data](db::mojom::TableListener* listener) {
listener->OnRowAdded(key, data);
});
}
void AddListener(db::mojom::TableListenerPtr listener) {
listeners_.AddPtr(std::move(listener));
}
private:
mojo::InterfacePtrSet<db::mojom::Table> listeners_;
std::map<int32_t, std::string> rows_;
};
연결된 인터페이스
연결된 인터페이스는 다음과 같은 인터페이스입니다:
- 단일 메시지 파이프를 통해 여러 인터페이스를 실행하면서 메시지 순서를 유지할 수 있습니다.
- 바인딩이 여러 시퀀스에서 단일 메시지 파이프에 액세스할 수 있도록 합니다.
Mojom
인터페이스 포인터/요청 필드에 대해 새로운 키워드 associated가 도입되었습니다. 예:
interface Bar {};
struct Qux {
associated Bar bar3;
};
interface Foo {
// 연결된 인터페이스 포인터를 사용합니다.
SetBar(associated Bar bar1);
// 연결된 인터페이스 요청을 사용합니다.
GetBar(associated Bar& bar2);
// 연결된 인터페이스 포인터가 있는 구조체를 전달합니다.
PassQux(Qux qux);
// 콜백에서 연결된 인터페이스 포인터를 사용합니다.
AsyncGetBar() => (associated Bar bar4);
};
이는 인터페이스 구현/클라이언트가 연결된 인터페이스 포인터/요청이 전달되는 동일한 메시지 파이프를 사용하여 통신함을 의미합니다.
C++에서 연결된 인터페이스 사용
C++ 바인딩을 생성할 때 Bar의 연결된 인터페이스 포인터는 BarAssociatedPtrInfo(mojo::AssociatedInterfacePtrInfo<Bar>의 별칭)에 매핑되고, 연결된 인터페이스 요청은 BarAssociatedRequest(mojo::AssociatedInterfaceRequest<Bar>의 별칭)에 매핑됩니다.
// mojom:
interface Foo {
...
SetBar(associated Bar bar1);
GetBar(associated Bar& bar2);
...
};
// C++:
class Foo {
...
virtual void SetBar(BarAssociatedPtrInfo bar1) = 0;
virtual void GetBar(BarAssociatedRequest bar2) = 0;
...
};
연결된 인터페이스 요청 전달
이미 InterfacePtr<Foo> foo_ptr가 있고 여기서 GetBar()를 호출한다고 가정합니다. 다음과 같이 할 수 있습니다:
BarAssociatedPtrInfo bar_ptr_info;
BarAssociatedRequest bar_request = MakeRequest(&bar_ptr_info);
foo_ptr->GetBar(std::move(bar_request));
// BarAssociatedPtr은 AssociatedInterfacePtr<Bar>의 별칭입니다.
BarAssociatedPtr bar_ptr;
bar_ptr.Bind(std::move(bar_ptr_info));
bar_ptr->DoSomething();
먼저 코드는 Bar 타입의 연결된 인터페이스를 생성합니다. 연결되지 않은 인터페이스를 설정하기 위해 수행하는 작업과 매우 유사해 보입니다. 중요한 차이점은 두 연결된 엔드포인트(bar_request 또는 bar_ptr_info) 중 하나가 다른 인터페이스를 통해 전송되어야 한다는 것입니다. 이것이 인터페이스가 기존 메시지 파이프와 연결되는 방식입니다.
bar_request를 전달하기 전에 bar_ptr->DoSomething()을 호출할 수 없다는 점에 유의해야 합니다. 이는 FIFO 보장에 필요합니다. 수신자 측에서 DoSomething 호출 메시지가 도착하면 후속 메시지를 처리하기 전에 해당 AssociatedBinding<Bar>로 디스패치하려고 합니다. bar_request가 후속 메시지에 있으면 메시지 디스패치가 교착 상태에 빠집니다. 반면 bar_request가 전송되는 즉시 bar_ptr을 사용할 수 있습니다. bar_request가 원격 측의 구현에 바인딩될 때까지 기다릴 필요가 없습니다.
AssociatedInterfacePtrInfo 포인터 대신 AssociatedInterfacePtr 포인터를 사용하는 MakeRequest 오버로드가 제공되어 코드를 좀 더 짧게 만듭니다. 다음 코드도 동일한 목적을 달성합니다:
BarAssociatedPtr bar_ptr;
foo_ptr->GetBar(MakeRequest(&bar_ptr));
bar_ptr->DoSomething();
Foo의 구현은 다음과 같습니다:
class FooImpl : public Foo {
...
void GetBar(BarAssociatedRequest bar2) override {
bar_binding_.Bind(std::move(bar2));
...
}
...
Binding<Foo> foo_binding_;
AssociatedBinding<Bar> bar_binding_;
};
이 예제에서 bar_binding_의 수명은 FooImpl의 수명에 연결됩니다. 하지만 그렇게 할 필요는 없습니다. 예를 들어 bar2를 다른 시퀀스로 전달하여 거기에서 AssociatedBinding<Bar>에 바인딩할 수 있습니다.
기본 메시지 파이프의 연결이 끊어지면(예: foo_ptr 또는 foo_binding_이 파괴됨) 모든 연결된 인터페이스 엔드포인트(예: bar_ptr 및 bar_binding_)는 연결 오류를 수신합니다.
연결된 인터페이스 포인터 전달
마찬가지로 이미 InterfacePtr<Foo> foo_ptr가 있고 여기서 SetBar()를 호출한다고 가정합니다. 다음과 같이 할 수 있습니다:
AssociatedBinding<Bar> bar_binding(some_bar_impl);
BarAssociatedPtrInfo bar_ptr_info;
BarAssociatedRequest bar_request = MakeRequest(&bar_ptr_info);
foo_ptr->SetBar(std::move(bar_ptr_info));
bar_binding.Bind(std::move(bar_request));
다음 코드도 동일한 목적을 달성합니다:
AssociatedBinding<Bar> bar_binding(some_bar_impl);
BarAssociatedPtrInfo bar_ptr_info;
bar_binding.Bind(&bar_ptr_info);
foo_ptr->SetBar(std::move(bar_ptr_info));
성능 고려 사항
마스터 시퀀스(마스터 인터페이스가 있는 곳)와 다른 시퀀스에서 연결된 인터페이스를 사용하는 경우:
- 메시지 전송: 호출 시퀀스에서 직접 전송이 발생합니다. 따라서 시퀀스 홉이 없습니다.
- 메시지 수신: 마스터 인터페이스와 다른 시퀀스에 바인딩된 연결된 인터페이스는 디스패치 중에 추가 시퀀스 홉이 발생합니다.
따라서 성능 면에서 연결된 인터페이스는 메시지 수신이 마스터 시퀀스에서 발생하는 시나리오에 더 적합합니다.
테스트
연결된 인터페이스를 사용하려면 먼저 마스터 인터페이스와 연결되어야 합니다. 즉, 연결된 인터페이스의 한쪽 끝은 마스터 인터페이스의 한쪽 끝을 통해, 또는 이미 마스터 인터페이스가 있는 다른 연결된 인터페이스의 한쪽 끝을 통해 전송되어야 합니다.
먼저 연결하지 않고 연결된 인터페이스 엔드포인트를 테스트하려면 mojo::MakeIsolatedRequest()를 사용할 수 있습니다. 이렇게 하면 실제로 다른 것과 연결되지 않은 작동하는 연결된 인터페이스 엔드포인트가 생성됩니다.
더 읽어보기
- 설계: Mojo 연결된 인터페이스
동기 호출
이 문서를 참조하십시오.
TODO: 위 문서를 저장소 마크다운 문서로 이동합니다.
타입 매핑
많은 경우 생성된 C++ 바인딩이 인터페이스 메서드에서 특정 Mojom 타입을 나타내기 위해 더 자연스러운 타입을 사용하는 것을 선호할 수 있습니다. 한 가지 예로 아래 Rect와 같은 Mojom 구조체를 고려하십시오:
module gfx.mojom;
struct Rect {
int32 x;
int32 y;
int32 width;
int32 height;
};
interface Canvas {
void FillRect(Rect rect);
};
Canvas Mojom 인터페이스는 일반적으로 다음과 같은 C++ 인터페이스를 생성합니다:
class Canvas {
public:
virtual void FillRect(RectPtr rect) = 0;
};
그러나 Chromium 트리에는 이미 의미는 동일하지만 유용한 도우미 메서드도 있는 기본 gfx::Rect가 정의되어 있습니다. 모든 메시지 경계에서 gfx::Rect와 Mojom 생성 RectPtr 간에 수동으로 변환하는 대신 Mojom 바인딩 생성기가 대신 다음을 생성할 수 있다면 좋지 않을까요?
class Canvas {
public:
virtual void FillRect(const gfx::Rect& rect) = 0;
}
정답은 "네! 좋을 것 같아요!"입니다. 그리고 다행히도 가능합니다!
전역 구성
이 기능은 매우 강력하지만 빌드 시스템에 피할 수 없는 복잡성을 도입합니다. 이는 타입 매핑이 본질적으로 전염성 있는 개념이기 때문입니다. gfx::mojom::Rect가 어디에서 gfx::Rect에 매핑되면 매핑이 모든 곳에 적용되어야 합니다.
이러한 이유로 chromium_bindings_configuration.gni 및 blink_bindings_configuration.gni에 정의된 몇 가지 전역 타입맵 구성이 있습니다. 이것들은 저장소에서 지원되는 두 가지 변형의 Mojom 생성 바인딩을 구성합니다. 다음 섹션에서 이에 대해 자세히 알아보십시오.
지금은 gfx::mojom::Rect에서 gfx::Rect로의 매핑을 표현하는 방법을 살펴보겠습니다.
StructTraits 정의
생성된 바인딩 코드에 임의의 기본 타입 T를 임의의 Mojom 타입 mojom::U로 직렬화하는 방법을 알려주기 위해 mojo::StructTraits 템플릿의 적절한 특수화를 정의해야 합니다.
StructTraits의 유효한 특수화는 다음 정적 메서드를 정의해야 합니다:
-
Mojom 구조체의 각 필드에 대해 정확히 동일한 이름을 가진 단일 정적 접근자입니다. 이러한 접근자는 모두 기본 타입의 객체에 대한 const 참조를 사용해야 하며 Mojom 구조체 필드의 타입과 호환되는 값을 반환해야 합니다. 이는 메시지 직렬화 중에 추가 복사 비용을 발생시키지 않고 기본 타입에서 데이터를 안전하고 일관되게 추출하는 데 사용됩니다.
-
Mojom 구조체의 직렬화된 표현이 주어지면 기본 타입의 인스턴스를 초기화하는 단일 정적
Read메서드입니다.Read메서드는 들어오는 데이터가 허용되는지(true) 또는 거부되는지(false)를 나타내기 위해bool을 반환해야 합니다.
StructTraits 특수화가 덜 일반적인 요구 사항을 충족하기 위해 정의할 수 있는 다른 메서드가 있습니다. 자세한 내용은 고급 StructTraits 사용법을 참조하십시오.
gfx::Rect에 대한 매핑을 정의하기 위해 //ui/gfx/geometry/mojo/geometry_struct_traits.h에 정의할 다음 StructTraits 특수화가 필요합니다:
#include "mojo/public/cpp/bindings/struct_traits.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/mojo/geometry.mojom.h"
namespace mojo {
template <>
class StructTraits<gfx::mojom::RectDataView, gfx::Rect> {
public:
static int32_t x(const gfx::Rect& r) { return r.x(); }
static int32_t y(const gfx::Rect& r) { return r.y(); }
static int32_t width(const gfx::Rect& r) { return r.width(); }
static int32_t height(const gfx::Rect& r) { return r.height(); }
static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect);
};
} // namespace mojo
그리고 //ui/gfx/geometry/mojo/geometry_struct_traits.cc에서:
#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
namespace mojo {
template <>
bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read(
gfx::mojom::RectDataView data,
gfx::Rect* out_rect) {
if (data.width() < 0 || data.height() < 0)
return false;
out_rect->SetRect(data.x(), data.y(), data.width(), data.height());
return true;
};
} // namespace mojo
Read() 메서드는 들어오는 width 또는 height 필드가 음수이면 false를 반환합니다. 이는 역직렬화 중에 유효성 검사 단계로 작동합니다. 클라이언트가 음수 너비 또는 높이를 가진 gfx::Rect를 보내면 해당 메시지가 거부되고 파이프가 닫힙니다. 이러한 방식으로 타입 매핑은 호출 사이트와 인터페이스 구현을 더 편리하게 만드는 것 외에도 사용자 정의 유효성 검사 논리를 활성화하는 역할을 할 수 있습니다.
새 타입 매핑 활성화
필요한 StructTraits를 정의했지만, 바인딩 생성기(따라서 빌드 시스템)에 매핑에 대해 알려야 합니다. 이를 위해 친숙한 GN 구문을 사용하여 새 타입 매핑을 설명하는 typemap 파일을 만들어야 합니다.
Mojom 파일과 함께 이 geometry.typemap 파일을 배치해 보겠습니다:
mojom = "//ui/gfx/geometry/mojo/geometry.mojom"
public_headers = [ "//ui/gfx/geometry/rect.h" ]
traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ]
sources = [
"//ui/gfx/geometry/mojo/geometry_struct_traits.cc",
"//ui/gfx/geometry/mojo/geometry_struct_traits.h",
]
public_deps = [ "//ui/gfx/geometry" ]
type_mappings = [
"gfx.mojom.Rect=gfx::Rect",
]
위의 각 변수를 살펴보겠습니다:
mojom: typemap이 적용되는mojom파일을 지정합니다. 여러 typemap이 동일한mojom파일에 적용될 수 있지만, 특정 typemap은 단일mojom파일에만 적용될 수 있습니다.public_headers: typemap이 적용된 후gfx.mojom.Rect의 Mojom 정의에 의존하는 모든 코드에 필요한 추가 헤더입니다. 기본 대상 타입 정의에 필요한 모든 헤더는 여기에 나열되어야 합니다.traits_headers: 이 파일에서 설명하는 모든 타입 매핑에 대한 관련StructTraits특수화를 포함하는 헤더입니다.sources:StructTraits정의에 필요한 모든 구현 소스 및 헤더입니다. 이러한 소스는 이 typemap을 적용하는mojom파일에 대해 생성된 C++ 바인딩 타겟으로 직접 컴파일됩니다.public_deps:public_headers및traits_headers에 의해 노출되는 타겟 종속성입니다.deps:sources에 의해 노출되지만public_deps에 아직 포함되지 않은 타겟 종속성입니다.type_mappings: 이 typemap에 대해 적용될 타입 매핑 목록입니다. 이 목록의 문자열은"MojomType=CppType"형식입니다. 여기서MojomType은 정규화된 Mojom 타입 이름이어야 하고CppType은 정규화된 C++ 타입 이름이어야 합니다.CppType다음에 대괄호로 추가 속성을 지정할 수 있습니다:move_only:CppType은 move-only이며 생성된 모든 메서드 시그니처에서 값으로 전달되어야 합니다.move_only는 전이적이므로MojomType의 컨테이너는 값으로 전달되는CppType의 컨테이너로 변환됩니다.copyable_pass_by_value:CppType값을 이동하지 않고 값으로 전달하도록 강제합니다.move_only와 달리 이는 전이적이지 않습니다.nullable_is_same_type: 기본적으로 nullable이 아닌MojomType은CppType에 매핑되고 nullableMojomType?은base::Optional<CppType>에 매핑됩니다. 이 속성이 설정되면 nullableMojomType?값에 대해base::Optional래퍼가 생략되지만, 이 타입 매핑에 대한StructTraits정의는 추가IsNull및SetToNull메서드를 정의해야 합니다. 아래 Nullability 특수화를 참조하십시오.force_serialize: typemap은 지연 직렬화와 호환되지 않습니다(예:base::StringPiece에 대한 typemap을 고려하십시오. 여기서 복사본을 유지하는 것은 안전하지 않습니다). 이 타입을 전달하는 모든 메시지는 즉시 직렬화 경로를 따르도록 강제됩니다.
이제 typemap 파일이 있으므로 이를 전역 구성에 추가할 수 있는 로컬 typemap 목록에 추가해야 합니다. 다음 내용으로 새 //ui/gfx/typemaps.gni 파일을 만듭니다:
typemaps = [
"//ui/gfx/geometry/mojo/geometry.typemap",
]
마지막으로 이 파일을 chromium_bindings_configuration.gni의 _typemap_imports에 추가하여 전역 기본(Chromium) 바인딩 구성에서 참조할 수 있습니다:
_typemap_imports = [
...,
"//ui/gfx/typemaps.gni",
...,
]
StructTraits 참조
StructTraits 특수화의 각 정적 getter 메서드(구조체 필드당 하나)는 직렬화 중에 필드의 데이터 소스로 사용할 수 있는 타입을 반환해야 합니다. 다음은 Mojom 필드 타입을 유효한 getter 반환 타입에 매핑하는 빠른 참조입니다:
| Mojom 필드 타입 | C++ Getter 반환 타입 |
|---|---|
bool |
bool |
int8 |
int8_t |
uint8 |
uint8_t |
int16 |
int16_t |
uint16 |
uint16_t |
int32 |
int32_t |
uint32 |
uint32_t |
int64 |
int64_t |
uint64 |
uint64_t |
float |
float |
double |
double |
handle |
mojo::ScopedHandle |
handle<message_pipe> |
mojo::ScopedMessagePipeHandle |
handle<data_pipe_consumer> |
mojo::ScopedDataPipeConsumerHandle |
handle<data_pipe_producer> |
mojo::ScopedDataPipeProducerHandle |
handle<shared_buffer> |
mojo::ScopedSharedBufferHandle |
FooInterface |
FooInterfacePtr |
FooInterface& |
FooInterfaceRequest |
associated FooInterface |
FooAssociatedInterfacePtr |
associated FooInterface& |
FooAssociatedInterfaceRequest |
string |
mojo::StringTraits 특수화가 정의된 모든 타입 T에 대한 값 또는 참조입니다. 기본적으로 std::string, base::StringPiece 및 WTF::String(Blink)이 포함됩니다. |
array<T> |
mojo::ArrayTraits 특수화가 정의된 모든 타입 T에 대한 값 또는 참조입니다. 기본적으로 std::vector<T>, mojo::CArray<T> 및 WTF::Vector<T>(Blink)가 포함됩니다. |
map<K, V> |
mojo::MapTraits 특수화가 정의된 모든 타입 T에 대한 값 또는 참조입니다. 기본적으로 std::map<T>, mojo::unordered_map<T> 및 WTF::HashMap<T>(Blink)가 포함됩니다. |
FooEnum |
적절한 EnumTraits 특수화가 정의된 모든 타입의 값입니다. 기본적으로 생성된 FooEnum 타입만 포함됩니다. |
FooStruct |
적절한 StructTraits 특수화가 정의된 모든 타입에 대한 값 또는 참조입니다. 기본적으로 생성된 FooStructPtr 타입만 포함됩니다. |
FooUnion |
적절한 UnionTraits 특수화가 정의된 모든 타입에 대한 값의 참조입니다. 기본적으로 생성된 FooUnionPtr 타입만 포함됩니다. |
생성된 DataView 타입 사용
StructTraits 특수화의 정적 Read 메서드는 들어오는 메시지 내용 내에서 직렬화된 Mojom 구조체의 직접 보기를 노출하는 생성된 FooDataView 인수(위 예제의 RectDataView와 같은)를 가져옵니다. 이를 가능한 한 쉽게 작업할 수 있도록 생성된 FooDataView 타입에는 각 구조체 필드에 해당하는 생성된 메서드가 있습니다:
-
POD 필드 타입(예: bool, float, 정수)의 경우 필드 이름과 동일한 이름의 간단한 접근자 메서드입니다. 따라서
Rect예제에서는data.x()및data.width()와 같은 것에 액세스할 수 있습니다. 반환 타입은 StructTraits 참조 아래 위 표에 나열된 매핑과 정확히 일치합니다. -
핸들 및 인터페이스 타입(예:
handle또는FooInterface&)의 경우TakeFieldName(필드 이름이field_name인 경우)으로 명명되며 값으로 적절한 move-only 핸들 타입을 반환합니다. 반환 타입은 StructTraits 참조 아래 위 표에 나열된 매핑과 정확히 일치합니다. -
다른 모든 필드 타입(예: 열거형, 문자열, 배열, 맵, 구조체)의 경우
ReadFieldName(필드 이름이field_name인 경우)으로 명명되며bool(읽기 성공 또는 실패를 나타내기 위해)을 반환합니다. 성공하면 역직렬화된 필드 값으로 출력 인수를 채웁니다. 출력 인수는 StructTraits 참조 아래 위 표에 언급된 대로 적절한StructTraits특수화가 정의된 모든 타입에 대한 포인터일 수 있습니다.
예제가 여기서 유용할 것입니다. 새 Mojom 구조체를 도입했다고 가정합니다:
struct RectPair {
Rect left;
Rect right;
};
그리고 해당 C++ 타입:
class RectPair {
public:
RectPair() {}
const gfx::Rect& left() const { return left_; }
const gfx::Rect& right() const { return right_; }
void Set(const gfx::Rect& left, const gfx::Rect& right) {
left_ = left;
right_ = right;
}
// ... 다른 것들
private:
gfx::Rect left_;
gfx::Rect right_;
};
gfx::mojom::RectPair를 gfx::RectPair에 매핑하는 특성은 다음과 같습니다:
namespace mojo {
template <>
class StructTraits<gfx::mojom::RectPairDataView, gfx::RectPair> {
public:
static const gfx::Rect& left(const gfx::RectPair& pair) {
return pair.left();
}
static const gfx::Rect& right(const gfx::RectPair& pair) {
return pair.right();
}
static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) {
gfx::Rect left, right;
if (!data.ReadLeft(&left) || !data.ReadRight(&right))
return false;
out_pair->Set(left, right);
return true;
}
};
} // namespace mojo
생성된 ReadFoo 메서드는 항상 multi_word_field_name 필드를 ReadMultiWordFieldName 메서드로 변환합니다.
변형
이제 Mojom이 처리될 때 추가 C++ 소스가 생성된다는 것을 눈치챘을 것입니다. 이는 타입 매핑으로 인해 발생하며, 이 문서 전체에서 참조하는 소스 파일(즉, foo.mojom.cc 및 foo.mojom.h)은 실제로 특정 Mojom 파일에 대한 C++ 바인딩의 하나의 변형(기본 또는 chromium 변형)일 뿐입니다.
현재 트리에 정의된 유일한 다른 변형은 blink 변형으로, 몇 가지 추가 파일을 생성합니다:
out/gen/sample/db.mojom-blink.cc
out/gen/sample/db.mojom-blink.h
이러한 파일은 기본 변형의 정의를 미러링하지만 특정 기본 제공 필드 및 매개변수 타입 대신 다른 C++ 타입을 사용합니다. 예를 들어, Mojom 문자열은 std::string 대신 WTF::String으로 표시됩니다. 기호 충돌을 피하기 위해 변형의 기호는 추가 내부 네임스페이스에 중첩됩니다. 따라서 Blink 소비자는 인터페이스를 다음과 같이 작성할 수 있습니다:
#include "sample/db.mojom-blink.h"
class TableImpl : public db::mojom::blink::Table {
public:
void AddRow(int32_t key, const WTF::String& data) override {
// ...
}
};
기본 제공 문자열, 배열 및 맵에 대해 다른 C++ 타입을 사용하는 것 외에도 기본 및 "blink" 변형에 대한 전역 typemap 구성은 완전히 별개입니다. Blink 구성에 대한 typemap을 추가하려면 blink_bindings_configuration.gni를 수정할 수 있습니다.
모든 변형은 타입 매핑 구성의 차이에 영향을 받지 않는 일부 정의(예: 열거형)를 공유합니다. 이러한 정의는 공유 소스에서 생성됩니다:
out/gen/sample/db.mojom-shared.cc
out/gen/sample/db.mojom-shared.h
out/gen/sample/db.mojom-shared-internal.h
두 변형 중 하나의 헤더(db.mojom.h 또는 db.mojom-blink.h)를 포함하면 암시적으로 공유 헤더가 포함되지만, 경우에 따라 공유 헤더만 포함할 수도 있습니다.
마지막으로 mojom GN 타겟의 경우 지원되는 바인딩 구성에 대해 암시적으로 해당 mojom_{variant} 타겟이 정의됩니다. 예를 들어 //sample/BUILD.gn에 정의한 경우:
import("mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"db.mojom",
]
}
생성된 Blink 변형 정의를 사용하려는 Blink 코드는 "//sample:mojom_blink"에 의존해야 합니다.
버전 관리 고려 사항
Mojom IDL의 버전 관리에 대한 일반적인 문서는 버전 관리를 참조하십시오.
이 섹션에서는 버전이 지정된 Mojom 타입과 관련된 몇 가지 C++ 관련 고려 사항에 대해 간략히 설명합니다.
인터페이스 버전 쿼리
InterfacePtr은 원격 인터페이스 버전을 쿼리하거나 어설션하는 다음 메서드를 정의합니다:
void QueryVersion(const base::Callback<void(uint32_t)>& callback);
이것은 바인딩의 버전 번호에 대해 원격 엔드포인트를 쿼리합니다. 응답이 수신되면 callback이 원격 버전 번호와 함께 호출됩니다. 이 값은 중복 쿼리를 피하기 위해 InterfacePtr 인스턴스에 의해 캐시됩니다.
void RequireVersion(uint32_t version);
클라이언트에 version의 최소 버전이 필요함을 원격 엔드포인트에 알립니다. 원격 엔드포인트가 해당 버전을 지원할 수 없으면 즉시 파이프의 끝을 닫아 다른 요청을 수신하지 못하도록 합니다.
버전이 지정된 열거형
편의를 위해 모든 확장 가능한 열거형에는 수신된 열거형 값이 열거형 정의의 현재 구현 버전에서 알려진 값인지 확인하는 생성된 도우미 함수가 있습니다. 예:
[Extensible]
enum Department {
SALES,
DEV,
RESEARCH,
};
생성된 C++ 열거형 타입과 동일한 네임스페이스에 함수를 생성합니다:
inline bool IsKnownEnumValue(Department value);
Chrome에서 Mojo 바인딩 사용
레거시 Chrome IPC를 Mojo로 변환을 참조하십시오.
추가 문서
Blink에서 Mojo 호출: Blink 코드 내에서 Mojom C++ 바인딩을 사용하는 방법에 대한 간략한 개요입니다.