Skip to content

Commit 4d8bc92

Browse files
committed
grpc-native: Add grpcpp-c C/C++ library to grpc/
Signed-off-by: Johannes Zottele <[email protected]>
1 parent 00efcec commit 4d8bc92

File tree

7 files changed

+1229
-0
lines changed

7 files changed

+1229
-0
lines changed

grpc/grpcpp-c/.bazelrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# we build the cc_static library bundled with all dependencies
2+
build --experimental_cc_static_library
3+
4+
build:release --compilation_mode=opt --strip=always

grpc/grpcpp-c/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# gitignore template for Bazel build system
2+
# website: https://bazel.build/
3+
4+
# Ignore all bazel-* symlinks. There is no full list since this can change
5+
# based on the name of the directory bazel is cloned into.
6+
/bazel-*
7+
8+
# Directories for the Bazel IntelliJ plugin containing the generated
9+
# IntelliJ project files and plugin configuration. Separate directories are
10+
# for the IntelliJ, Android Studio and CLion versions of the plugin.
11+
/.ijwb/
12+
/.aswb/
13+
/.clwb/
14+
.idea/

grpc/grpcpp-c/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_library")
2+
3+
cc_library(
4+
name = "grpcpp_c",
5+
srcs = ["src/grpcpp_c.cpp"],
6+
hdrs = glob(["include/**/*.h"]),
7+
copts = ["-std=c++20"],
8+
includes = ["include"],
9+
visibility = ["//visibility:public"],
10+
deps = [
11+
"@com_github_grpc_grpc//:grpc++",
12+
"@com_google_protobuf//:protobuf",
13+
],
14+
)
15+
16+
cc_static_library(
17+
name = "grpcpp_c_static",
18+
deps = [
19+
"grpcpp_c",
20+
],
21+
)

grpc/grpcpp-c/MODULE.bazel

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module(
2+
name = "grpcpp_c",
3+
version = "0.1",
4+
)
5+
6+
# rules_cc for cc_library support
7+
bazel_dep(
8+
name = "rules_cc",
9+
version = "0.1.1",
10+
)
11+
12+
# Protobuf
13+
bazel_dep(
14+
name = "protobuf",
15+
version = "31.1",
16+
repo_name = "com_google_protobuf",
17+
)
18+
19+
# gRPC C++ library
20+
bazel_dep(
21+
name = "grpc",
22+
version = "1.73.1",
23+
repo_name = "com_github_grpc_grpc",
24+
)

grpc/grpcpp-c/MODULE.bazel.lock

Lines changed: 899 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

grpc/grpcpp-c/include/grpcpp_c.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Created by Johannes Zottele on 11.07.25.
3+
//
4+
5+
#ifndef GRPCPP_C_H
6+
#define GRPCPP_C_H
7+
8+
#include <stdint.h>
9+
#include <grpc/slice.h>
10+
#include <grpc/byte_buffer.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
typedef struct grpc_client grpc_client_t;
17+
typedef struct grpc_method grpc_method_t;
18+
typedef struct grpc_context grpc_context_t;
19+
20+
typedef enum StatusCode {
21+
GRPC_C_STATUS_OK = 0,
22+
GRPC_C_STATUS_CANCELLED = 1,
23+
GRPC_C_STATUS_UNKNOWN = 2,
24+
GRPC_C_STATUS_INVALID_ARGUMENT = 3,
25+
GRPC_C_STATUS_DEADLINE_EXCEEDED = 4,
26+
GRPC_C_STATUS_NOT_FOUND = 5,
27+
GRPC_C_STATUS_ALREADY_EXISTS = 6,
28+
GRPC_C_STATUS_PERMISSION_DENIED = 7,
29+
GRPC_C_STATUS_UNAUTHENTICATED = 16,
30+
GRPC_C_STATUS_RESOURCE_EXHAUSTED = 8,
31+
GRPC_C_STATUS_FAILED_PRECONDITION = 9,
32+
GRPC_C_STATUS_ABORTED = 10,
33+
GRPC_C_STATUS_OUT_OF_RANGE = 11,
34+
GRPC_C_STATUS_UNIMPLEMENTED = 12,
35+
GRPC_C_STATUS_INTERNAL = 13,
36+
GRPC_C_STATUS_UNAVAILABLE = 14,
37+
GRPC_C_STATUS_DATA_LOSS = 15,
38+
GRPC_C_STATUS_DO_NOT_USE = -1
39+
} grpc_status_code_t;
40+
41+
grpc_client_t *grpc_client_create_insecure(const char *target);
42+
void grpc_client_delete(const grpc_client_t *client);
43+
44+
grpc_method_t *grpc_method_create(const char *method_name);
45+
void grpc_method_delete(const grpc_method_t *method);
46+
47+
const char *grpc_method_name(const grpc_method_t *method);
48+
49+
grpc_context_t *grpc_context_create();
50+
void grpc_context_delete(const grpc_context_t *context);
51+
52+
grpc_status_code_t grpc_client_call_unary_blocking(grpc_client_t *client, const char *method,
53+
grpc_slice req_slice, grpc_slice *resp_slice);
54+
55+
void grpc_client_call_unary_callback(grpc_client_t *client, grpc_method_t *method, grpc_context_t *context,
56+
grpc_byte_buffer **req_buf, grpc_byte_buffer **resp_buf, void* callback_context, void (*callback)(grpc_status_code_t,void*));
57+
58+
uint32_t pb_decode_greeter_sayhello_response(grpc_slice response);
59+
60+
grpc_status_code_t grpc_byte_buffer_dump_to_single_slice(grpc_byte_buffer *byte_buffer, grpc_slice *slice);
61+
62+
#ifdef __cplusplus
63+
}
64+
#endif
65+
66+
#endif //GRPCPP_C_H

grpc/grpcpp-c/src/grpcpp_c.cpp

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
//
2+
// Created by Johannes Zottele on 11.07.25.
3+
//
4+
5+
#include <grpcpp_c.h>
6+
7+
#include <memory>
8+
#include <grpcpp/grpcpp.h>
9+
#include <grpcpp/generic/generic_stub.h>
10+
#include <grpcpp/impl/client_unary_call.h>
11+
#include <google/protobuf/io/coded_stream.h>
12+
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
13+
14+
namespace pb = google::protobuf;
15+
16+
struct grpc_client {
17+
std::shared_ptr<grpc::Channel> channel;
18+
std::unique_ptr<grpc::GenericStub> stub;
19+
};
20+
21+
struct grpc_method {
22+
std::string name_str;
23+
std::unique_ptr<grpc::internal::RpcMethod> method;
24+
};
25+
26+
struct grpc_context {
27+
std::unique_ptr<grpc::ClientContext> context;
28+
};
29+
30+
extern "C" {
31+
32+
grpc_client_t *grpc_client_create_insecure(const char *target) {
33+
std::string target_str = target;
34+
auto client = new grpc_client;
35+
client->channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials());
36+
client->stub = std::make_unique<grpc::GenericStub>(client->channel);
37+
return client;
38+
}
39+
40+
void grpc_client_delete(const grpc_client_t *client) {
41+
delete client;
42+
}
43+
44+
grpc_method_t *grpc_method_create(const char *method_name) {
45+
auto *method = new grpc_method;
46+
method->name_str = method_name;
47+
method->method = std::make_unique<grpc::internal::RpcMethod>(method->name_str.c_str(), grpc::internal::RpcMethod::NORMAL_RPC);
48+
return method;
49+
}
50+
51+
void grpc_method_delete(const grpc_method_t *method) {
52+
delete method;
53+
}
54+
55+
const char *grpc_method_name(const grpc_method_t *method) {
56+
return method->method->name();
57+
}
58+
59+
grpc_context_t *grpc_context_create() {
60+
auto *context = new grpc_context;
61+
context->context = std::make_unique<grpc::ClientContext>();
62+
return context;
63+
}
64+
65+
void grpc_context_delete(const grpc_context_t *context) {
66+
delete context;
67+
}
68+
69+
static grpc_status_code_t status_to_c(grpc::StatusCode status);
70+
71+
grpc_status_code_t grpc_client_call_unary_blocking(grpc_client_t *client, const char *method,
72+
grpc_slice req_slice, grpc_slice *resp_slice) {
73+
74+
if (!client || !method) return GRPC_C_STATUS_INVALID_ARGUMENT;
75+
76+
grpc::Slice cc_req_slice(req_slice, grpc::Slice::ADD_REF);
77+
grpc::ByteBuffer req_bb(&cc_req_slice, 1);
78+
79+
grpc::ClientContext context;
80+
grpc::ByteBuffer resp_bb;
81+
82+
const std::string method_path = "/Greeter/SayHello";
83+
grpc::internal::RpcMethod rpc(method_path.c_str(),
84+
grpc::internal::RpcMethod::NORMAL_RPC);
85+
86+
grpc::Status st =
87+
grpc::internal::BlockingUnaryCall<grpc::ByteBuffer, grpc::ByteBuffer>(
88+
client->channel.get(), rpc, &context, req_bb, &resp_bb);
89+
90+
91+
if (!st.ok()) {
92+
// if not ok, no resp_buf is left null
93+
return status_to_c(st.error_code());
94+
}
95+
96+
grpc::Slice cc_resp_slice;
97+
resp_bb.DumpToSingleSlice(&cc_resp_slice);
98+
*resp_slice = cc_resp_slice.c_slice();
99+
100+
grpc::Slice test_slice(*resp_slice, grpc::Slice::ADD_REF);
101+
pb::io::ArrayInputStream ais(test_slice.begin(), test_slice.size());
102+
pb::io::CodedInputStream cis(&ais);
103+
104+
105+
cis.ReadTag();
106+
uint32_t id = 0;
107+
if (!cis.ReadVarint32(&id)) {
108+
std::cerr << "Failed to read id field\n";
109+
}
110+
111+
return status_to_c(st.error_code());
112+
}
113+
114+
void grpc_client_call_unary_callback(grpc_client_t *client, grpc_method_t *method, grpc_context_t *context,
115+
grpc_byte_buffer **req_buf, grpc_byte_buffer **resp_buf, void* callback_context, void (*callback)(grpc_status_code_t,void*)) {
116+
// the grpc::ByteBuffer representation is identical to (* grpc_byte_buffer) so we can safely cast it.
117+
// so a **grpc_byte_buffer can be cast to *grpc::ByteBuffer.
118+
static_assert(sizeof(grpc::ByteBuffer) == sizeof(grpc_byte_buffer*),
119+
"ByteBuffer must have same representation as "
120+
"grpc_byte_buffer*");
121+
const auto req_bb = reinterpret_cast<grpc::ByteBuffer *>(req_buf);
122+
const auto resp_bb = reinterpret_cast<grpc::ByteBuffer *>(resp_buf);
123+
grpc::internal::CallbackUnaryCall<grpc::ByteBuffer, grpc::ByteBuffer>(client->channel.get(), *method->method, context->context.get(), req_bb, resp_bb, [callback, callback_context](grpc::Status st) {
124+
const auto c_st = status_to_c(st.error_code());
125+
callback(c_st, callback_context);
126+
});
127+
}
128+
129+
grpc_status_code_t status_to_c(grpc::StatusCode status) {
130+
switch (status) {
131+
case grpc::OK:
132+
return GRPC_C_STATUS_OK;
133+
case grpc::CANCELLED:
134+
return GRPC_C_STATUS_CANCELLED;
135+
case grpc::UNKNOWN:
136+
return GRPC_C_STATUS_UNKNOWN;
137+
case grpc::INVALID_ARGUMENT:
138+
return GRPC_C_STATUS_INVALID_ARGUMENT;
139+
case grpc::DEADLINE_EXCEEDED:
140+
return GRPC_C_STATUS_DEADLINE_EXCEEDED;
141+
case grpc::NOT_FOUND:
142+
return GRPC_C_STATUS_NOT_FOUND;
143+
case grpc::ALREADY_EXISTS:
144+
return GRPC_C_STATUS_ALREADY_EXISTS;
145+
case grpc::PERMISSION_DENIED:
146+
return GRPC_C_STATUS_PERMISSION_DENIED;
147+
case grpc::UNAUTHENTICATED:
148+
return GRPC_C_STATUS_UNAUTHENTICATED;
149+
case grpc::RESOURCE_EXHAUSTED:
150+
return GRPC_C_STATUS_RESOURCE_EXHAUSTED;
151+
case grpc::FAILED_PRECONDITION:
152+
return GRPC_C_STATUS_FAILED_PRECONDITION;
153+
case grpc::ABORTED:
154+
return GRPC_C_STATUS_ABORTED;
155+
case grpc::UNIMPLEMENTED:
156+
return GRPC_C_STATUS_UNIMPLEMENTED;
157+
case grpc::OUT_OF_RANGE:
158+
return GRPC_C_STATUS_OUT_OF_RANGE;
159+
case grpc::INTERNAL:
160+
return GRPC_C_STATUS_INTERNAL;
161+
case grpc::UNAVAILABLE:
162+
return GRPC_C_STATUS_UNAVAILABLE;
163+
case grpc::DATA_LOSS:
164+
return GRPC_C_STATUS_DATA_LOSS;
165+
case grpc::DO_NOT_USE:
166+
return GRPC_C_STATUS_DO_NOT_USE;
167+
}
168+
}
169+
170+
171+
uint32_t pb_decode_greeter_sayhello_response(grpc_slice response) {
172+
grpc::Slice cc_resp_slice(response, grpc::Slice::ADD_REF);
173+
pb::io::ArrayInputStream asi(cc_resp_slice.begin(), cc_resp_slice.size());
174+
pb::io::CodedInputStream cis(&asi);
175+
176+
const auto tag = cis.ReadTag();
177+
if (tag != 8) {
178+
std::cerr << "Failed to read tag. Got: " << tag << std::endl;
179+
}
180+
181+
uint32_t result;
182+
if (!cis.ReadVarint32(&result)) {
183+
std::cerr << "Failed to read result" << std::endl;
184+
} else {
185+
186+
}
187+
return result;
188+
}
189+
190+
191+
grpc_status_code_t grpc_byte_buffer_dump_to_single_slice(grpc_byte_buffer *byte_buffer, grpc_slice *slice) {
192+
auto bb = reinterpret_cast<grpc::ByteBuffer*>(&byte_buffer);
193+
grpc::Slice cc_slice;
194+
bb->DumpToSingleSlice(&cc_slice);
195+
*slice = cc_slice.c_slice();
196+
return GRPC_C_STATUS_OK;
197+
}
198+
199+
}
200+
201+

0 commit comments

Comments
 (0)