Skip to content

Commit bc5d4fe

Browse files
committed
Add example of interfacing with python through a C ABI shared lib.
1 parent 7c1babb commit bc5d4fe

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ M_OBJS := \
108108

109109
include build/host-executable.mk
110110

111+
M_NAME := shared_protocol.so
112+
M_LDFLAGS := --shared
113+
M_OBJS := \
114+
examples/shared.o
115+
116+
include build/host-executable.mk
117+
111118
clean::
112119
@echo clean
113120
@rm -rf $(OUT)

examples/shared.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2018 The Native Object Protocols Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//
16+
// Example of using libnop from python by wrapping operations in a shared
17+
// library. The shared library is loaded using the ctypes module.
18+
//
19+
20+
#include <cstddef>
21+
#include <cstdint>
22+
#include <vector>
23+
24+
#include <nop/protocol.h>
25+
#include <nop/serializer.h>
26+
#include <nop/structure.h>
27+
#include <nop/traits/is_fungible.h>
28+
#include <nop/utility/buffer_reader.h>
29+
#include <nop/utility/buffer_writer.h>
30+
#include <nop/value.h>
31+
32+
namespace {
33+
34+
// Define the basic protocol types. In the real world this would be in a common
35+
// header shared by all the code that wishes to speak this protocol.
36+
37+
// Three component vector of floats.
38+
struct Vec3 {
39+
float x;
40+
float y;
41+
float z;
42+
NOP_STRUCTURE(Vec3, x, y, z);
43+
};
44+
45+
// Triangle composed of three Vec3 structures.
46+
struct Triangle {
47+
Vec3 a;
48+
Vec3 b;
49+
Vec3 c;
50+
NOP_STRUCTURE(Triangle, a, b, c);
51+
};
52+
53+
// Polyhedron composed of a number of Triangle structres.
54+
struct Polyhedron {
55+
std::vector<Triangle> triangles;
56+
NOP_VALUE(Polyhedron, triangles);
57+
};
58+
59+
// This is a special type that is fungible with Polyhedron but fits the C ABI
60+
// required to interface with python. The python ctypes library can't easily
61+
// interface with C++ types, such as std::vector (which is more convenient for
62+
// C++ code to use), so this type may be substituted in the API this library
63+
// exports.
64+
//
65+
// This type is meant to be used as the header of a dynamic C array. C code
66+
// might allocate the header + array as follows:
67+
//
68+
// CPolyhedron* polyhedron =
69+
// malloc(offsetof(CPolyhedron, triangles) + array_size * sizeof(Triangles));
70+
//
71+
// The python code uses ctypes structures and arrays to achieve the equivalent.
72+
//
73+
struct CPolyhedron {
74+
size_t size;
75+
Triangle triangles[1];
76+
NOP_VALUE(CPolyhedron, (triangles, size));
77+
};
78+
79+
// Assert that Polyhedron and CPolyhedron have compatible wire formats.
80+
static_assert(nop::IsFungible<Polyhedron, CPolyhedron>::value, "");
81+
82+
} // anonymous namespace
83+
84+
// Fills the given buffer with a pre-defined Polyhedron.
85+
extern "C" ssize_t GetSerializedPolyhedron(void* buffer, size_t buffer_size) {
86+
nop::Serializer<nop::BufferWriter> serializer{buffer, buffer_size};
87+
88+
Polyhedron polyhedron{
89+
{Triangle{{1.f, 2.f, 3.f}, {4.f, 5.f, 6.f}, {7.f, 8.f, 9.f}},
90+
Triangle{{10.f, 11.f, 12.f}, {13.f, 14.f, 15.f}, {16.f, 17.f, 18.f}},
91+
Triangle{{19.f, 20.f, 21.f}, {22.f, 23.f, 24.f}, {25.f, 26.f, 27.f}}}};
92+
93+
auto status = nop::Protocol<Polyhedron>::Write(&serializer, polyhedron);
94+
if (!status)
95+
return -static_cast<ssize_t>(status.error());
96+
else
97+
return serializer.writer().size();
98+
}
99+
100+
// Serializes the given CPolyhedron into the given buffer.
101+
extern "C" ssize_t SerializePolyhedron(const CPolyhedron* poly, void* buffer,
102+
size_t buffer_size) {
103+
nop::Serializer<nop::BufferWriter> serializer{buffer, buffer_size};
104+
auto status = nop::Protocol<Polyhedron>::Write(&serializer, *poly);
105+
if (!status)
106+
return -static_cast<ssize_t>(status.error());
107+
else
108+
return serializer.writer().size();
109+
}
110+
111+
// Deserializes the given buffer into the given CPolyhedron. The CPolyhedron
112+
// should be allocated with enough capacity to handle the expected number of
113+
// triangles.
114+
extern "C" ssize_t DeserializePolyhedron(CPolyhedron* poly, const void* buffer,
115+
size_t buffer_size) {
116+
nop::Deserializer<nop::BufferReader> deserializer{buffer, buffer_size};
117+
118+
auto status = nop::Protocol<Polyhedron>::Read(&deserializer, poly);
119+
if (!status)
120+
return -static_cast<ssize_t>(status.error());
121+
else
122+
return 0;
123+
}

examples/shared.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Copyright 2018 The Native Object Protocols Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
#
16+
# Example of using libnop from python by wrapping operations in a shared
17+
# library. The shared library is loaded using the ctypes module.
18+
#
19+
20+
from ctypes import *
21+
import sys
22+
import os
23+
24+
# Define ctypes structures that mirror the basic protocol types.
25+
26+
# Three component vector of floats.
27+
class Vec3(Structure):
28+
_fields_ = (('x', c_float), ('y', c_float), ('z', c_float))
29+
30+
def __str__(self):
31+
return '(%.1f, %.1f, %.1f)' % (self.x, self.y, self.z)
32+
33+
# Triangle composed of three Vec3 structures.
34+
class Triangle(Structure):
35+
_fields_ = (('a', Vec3), ('b', Vec3), ('c', Vec3))
36+
37+
def __str__(self):
38+
return 'Triangle{%s, %s, %s}' % (self.a, self.b, self.c)
39+
40+
# Base Polyhedron structure. This carries the size of the Triangle array
41+
# appended to the end of the structure.
42+
class PolyhedronBase(Structure):
43+
_fields_ = (('size', c_size_t),)
44+
45+
# Builds a Polyhedron given either a list of Triangles or a capacity to reserve.
46+
#
47+
# This is equivalent to the following C header struct + dynamic array idiom:
48+
#
49+
# struct Polyhedron {
50+
# size_t size;
51+
# Triangle triangles[1];
52+
# };
53+
#
54+
# Polyhedron* p = malloc(offsetof(Polyhedron, triangles) + reserve * sizeof(Triangle));
55+
#
56+
def Polyhedron(elements=[], reserve=None):
57+
if reserve is None:
58+
reserve = len(elements)
59+
60+
# Triangle array of the required size.
61+
class ArrayType(Array):
62+
_type_ = Triangle
63+
_length_ = reserve
64+
65+
# A subclass of PolyhedronBase with an array of Triangles appended.
66+
class PolyhedronType(PolyhedronBase):
67+
_fields_ = (('triangles', ArrayType),)
68+
69+
def __str__(self):
70+
return 'Polyhedron{' + ', '.join(
71+
[str(self.triangles[i]) for i in range(self.size)]
72+
) + '}'
73+
74+
# Return an initialized instance of the Polyhedron.
75+
return PolyhedronType(reserve, ArrayType(*elements))
76+
77+
def LoadProtocolLibrary():
78+
global ProtocolLibrary
79+
global GetSerializedPolyhedron
80+
global SerializePolyhedron
81+
global DeserializePolyhedron
82+
83+
# Load the shared library.
84+
ProtocolLibrary = cdll.LoadLibrary('out/shared_protocol.so')
85+
86+
# Load the exported API and setup the function pointer types.
87+
GetSerializedPolyhedron = ProtocolLibrary.GetSerializedPolyhedron
88+
GetSerializedPolyhedron.argtypes = (c_void_p, c_size_t)
89+
GetSerializedPolyhedron.restype = c_ssize_t
90+
91+
SerializePolyhedron = ProtocolLibrary.SerializePolyhedron
92+
SerializePolyhedron.argtypes = (POINTER(PolyhedronBase), c_void_p, c_size_t)
93+
SerializePolyhedron.restype = c_ssize_t
94+
95+
DeserializePolyhedron = ProtocolLibrary.DeserializePolyhedron
96+
DeserializePolyhedron.argtypes = (POINTER(PolyhedronBase), c_void_p, c_size_t)
97+
DeserializePolyhedron.restype = c_ssize_t
98+
99+
def main():
100+
LoadProtocolLibrary()
101+
102+
# Create a buffer to hold the serialized payload.
103+
payload_buffer = create_string_buffer(1024)
104+
105+
# Create a Polyhedron and serialize it to the buffer.
106+
polyhedron = Polyhedron(
107+
(
108+
Triangle(Vec3(0, 0, 0), Vec3(1, 1, 1), Vec3(2, 2, 2)),
109+
Triangle(Vec3(3, 3, 3), Vec3(4, 4, 4), Vec3(5, 5, 5)),
110+
Triangle(Vec3(6, 6, 6), Vec3(7, 7, 7), Vec3(8, 8, 8))
111+
)
112+
)
113+
114+
count = SerializePolyhedron(polyhedron, payload_buffer, len(payload_buffer))
115+
if count >= 0:
116+
print count, 'bytes:', payload_buffer[0:count].encode('hex')
117+
else:
118+
print 'Error:', -count
119+
120+
# Create an empty Polyhedron that can hold up to 10 triangles read the payload.
121+
polyhedron = Polyhedron(reserve=10)
122+
123+
count = DeserializePolyhedron(polyhedron, payload_buffer, len(payload_buffer))
124+
if count >= 0:
125+
print polyhedron
126+
else:
127+
print 'Error:', -count
128+
129+
# Get a serialized Polyhedron from the library and then deserialize it.
130+
count = GetSerializedPolyhedron(payload_buffer, len(payload_buffer))
131+
if count < 0:
132+
print 'Error:', -count
133+
return;
134+
135+
count = DeserializePolyhedron(polyhedron, payload_buffer, len(payload_buffer))
136+
if count >= 0:
137+
print polyhedron
138+
else:
139+
print 'Error:', -count
140+
141+
if __name__ == '__main__':
142+
main()

0 commit comments

Comments
 (0)