---
icon: cpu
title: Write Bindings
order: 18
---

In order to use a C/C++ library in python, you need to write bindings for it.

pkpy uses an universal signature to wrap a function pointer as a python function or method that can be called in python code, i.e `NativeFuncC`.

```cpp
typedef PyVar (*NativeFuncC)(VM*, ArgsView);
```
+ The first argument is the pointer of `VM` instance.
+ The second argument is an array-like object indicates the arguments list. You can use `[]` operator to get the element and call `size()` to get the length of the array.
+ The return value is a `PyVar`, which should not be `nullptr`. If there is no return value, return `vm->None`.

## Bind a function or method

Use `vm->bind` to bind a function or method.

+ `PyVar bind(PyVar, const char* sig, NativeFuncC)`
+ `PyVar bind(PyVar, const char* sig, const char* docstring, NativeFuncC)`

```cpp

vm->bind(obj, "add(a: int, b: int) -> int", [](VM* vm, ArgsView args){
    int a = py_cast<int>(vm, args[0]);
    int b = py_cast<int>(vm, args[1]);
    return py_var(vm, a + b);
});

// or you can provide a docstring
vm->bind(obj,
    "add(a: int, b: int) -> int",
    "add two integers", [](VM* vm, ArgsView args){
    int a = py_cast<int>(vm, args[0]);
    int b = py_cast<int>(vm, args[1]);
    return py_var(vm, a + b);
});
```

### How to capture something

By default, the lambda being bound is a C function pointer,
you cannot capture anything! The following example does not compile.

```cpp
int x = 1;
vm->bind(obj, "f() -> int", [x](VM* vm, ArgsView args){
    // error: cannot capture 'x'
    return py_var(vm, x);
});
```

I do not encourage you to capture something in a lambda being bound
because:
1. Captured lambda runs slower and causes "code-bloat".
2. Captured values are unsafe, especially for `PyVar` as they could leak by accident.

However, there are 3 ways to capture something when you really need to.
The most safe and elegant way is to subclass `VM` and add a member variable.

```cpp
class YourVM : public VM{
public:
    int x;
    YourVM() : VM() {}
};

int main(){
    YourVM* vm = new YourVM();
    vm->x = 1;
    vm->bind(obj, "f() -> int", [](VM* _vm, ArgsView args){
        // do a static_cast and you can get any extra members of YourVM
        YourVM* vm = static_cast<YourVM*>(_vm);
        return py_var(vm, vm->x);
    });
    return 0;
}
```

The 2nd way is to use `vm->bind`'s last parameter `userdata`, you can store an `pkpy::any` object.
And use `lambda_get_userdata<T>(args.begin())` to get it inside the lambda body.

```cpp
int x = 1;
vm->bind(obj, "f() -> int", [](VM* vm, ArgsView args){
    // get the userdata
    int x = lambda_get_userdata<int>(args.begin());
    return py_var(vm, x);
}, x);  // capture x
```

## Bind a class or struct

Assume you have a struct `Point` declared as follows.

```cpp
struct Point{
    int x;
    int y;
}
```

You can create a `test` module and use `vm->register_user_class<>` to bind the class to the test module.

```cpp
int main(){
    VM* vm = new VM();
    PyObject* mod = vm->new_module("test");
    vm->register_user_class<Point>(mod, "Point",
        [](VM* vm, PyVar mod, PyVar type){
            // wrap field x
            vm->bind_field(type, "x", &Point::x);
            // wrap field y
            vm->bind_field(type, "y", &Point::y);

            // __init__ method
            vm->bind(type, "__init__(self, x, y)", [](VM* vm, ArgsView args){
                Point& self = _py_cast<Point&>(vm, args[0]);
                self.x = py_cast<int>(vm, args[1]);
                self.y = py_cast<int>(vm, args[2]);
                return vm->None;
            });
        });

    // use the Point class
    vm->exec("import test");
    vm->exec("a = test.Point(1, 2)");
    vm->exec("print(a.x)");         // 1
    vm->exec("print(a.y)");         // 2

    delete vm;
    return 0;
}
```

### Handle gc for container types

If your custom type stores `PyVar` in its fields, you need to handle gc for them.

```cpp
struct Container{
    PyVar a;
    std::vector<PyVar> b;
    // ...
}
```

Add a magic method `_gc_mark(VM*) const` to your custom type.

```cpp
struct Container{
    PyVar a;
    std::vector<PyVar> b;
    // ...

    void _gc_mark(VM* vm) const{
        // mark a
        vm->obj_gc_mark(a);

        // mark elements in b
        for(PyVar obj : b){
            vm->obj_gc_mark(obj);
        }
    }
}
```

For global objects, use the callback in `vm->heap`.
```cpp
void (*_gc_marker_ex)(VM*) = nullptr;
```
It will be invoked before a GC starts. So you can mark objects inside the callback to keep them alive.

## Others

For some magic methods, we provide specialized binding function.
They do not take universal function pointer as argument.
You need to provide the detailed `Type` object and the corresponding function pointer.

```cpp
PyVar f_add(VM* vm, PyVar lhs, PyVar rhs){
    int a = py_cast<int>(vm, lhs);
    int b = py_cast<int>(vm, rhs);
    return py_var(vm, a + b);
}

vm->bind__add__(vm->tp_int, f_add);
```

This specialized binding function has optimizations and result in better performance when calling from python code.

For example, `vm->bind__add__` is preferred over `vm->bind_func(type, "__add__", 2, f_add)`.


## Further reading

See [random.cpp](https://github.com/pocketpy/pocketpy/blob/main/src/random.cpp) for an example used by `random` module.

See [collections.cpp](https://github.com/pocketpy/pocketpy/blob/main/src/collections.cpp) for a modern implementation of `collections.deque`.

## Reuse Lua bindings

pkpy provides a lua bridge to reuse lua bindings.
It allows you to run lua code and call lua functions in python
by embedding a lua virtual machine.

Add `lua_bridge.hpp` and `lua_bridge.cpp` in [3rd/lua_bridge](https://github.com/pocketpy/pocketpy/tree/main/3rd/lua_bridge) to your project.
Make sure `lua.h`, `lualib.h` and `lauxlib.h` are in your include path
because `lua_bridge.hpp` needs them.

The lua bridge is based on lua 5.1.5 for maximum compatibility.
lua 5.2 or higher should also work.

### Setup

Use `initialize_lua_bridge(VM*, lua_State*)` to initialize the lua bridge.
This creates a new module `lua` in your python virtual machine.

You can use `lua.dostring` to execute lua code and get the result.
And use `lua.Table()` to create a lua table.
A `lua.Table` instance in python is a dict-like object which provides a bunch of
magic methods to access the underlying lua table.

```python
class Table:
    def keys(self) -> list:
        """Return a list of keys in the table."""

    def values(self) -> list:
        """Return a list of values in the table."""

    def items(self) -> list[tuple]:
        """Return a list of (key, value) pairs in the table."""

    def __len__(self) -> int:
        """Return the length of the table."""

    def __contains__(self, key) -> bool:
        """Return True if the table contains the key."""

    def __getitem__(self, key): ...
    def __setitem__(self, key, value): ...
    def __delitem__(self, key): ...
    def __getattr__(self, key): ...
    def __setattr__(self, key, value): ...
    def __delattr__(self, key): ...
```

Only basic types can be passed between python and lua.
The following table shows the type mapping.
If you pass an unsupported type, an exception will be raised.

| Python type   | Lua type  | Allow create in Python? | Reference? |
| -----------   | --------  | ---------------------- |  --------- |
| `None`        | `nil`     | YES                    |  NO        |
| `bool`        | `boolean` | YES                    |  NO        |
| `int`         | `number`  | YES                    |  NO        |
| `float`       | `number`  | YES                    |  NO        |
| `str`         | `string`  | YES                    |  NO        |
| `tuple`       | `table`   | YES                    |  NO        |
| `list`        | `table`   | YES                    |  NO        |
| `dict`        | `table`   | YES                    |  NO        |
| `lua.Table`   | `table`   | YES                    |  YES       |
| `lua.Function`| `function`| NO                     |  YES       |

### Example
```cpp
#include "lua_bridge.hpp"

using namespace pkpy;

int main(){
    VM* vm = new VM();

    // create lua state
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    // initialize lua bridge
    initialize_lua_bridge(vm, L);

    // dostring to get _G
    vm->exec("import lua");
    vm->exec("g = lua.dostring('return _G')");

    // create a table
    vm->exec("t = lua.Table()");
    vm->exec("t.a = 1");
    vm->exec("t.b = 2");

    // call lua function
    vm->exec("g.print(t.a + t.b)");     // 3
    
    return 0;
}
```