Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cbindings): adding tiny Waku Relay example in Golang #1794

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ else
echo -e $(BUILD_MSG) "build/$@.so" && \
$(ENV_SCRIPT) nim libwakuDynamic $(NIM_PARAMS) $(EXPERIMENTAL_PARAMS) waku.nims
endif
cp build/libwaku.so examples/golang/

cwaku_example: | build libwaku
echo -e $(BUILD_MSG) "build/$@" && \
Expand Down
59 changes: 59 additions & 0 deletions examples/golang/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

## Summary

This is a simple example on how to integrate the `libwaku.so` library in a
Golang app.

[cgo](https://pkg.go.dev/cmd/cgo) is used in this example.

There are comments in the file `waku.go` that are considered by the cgo itself and
shouldn't be arbitrarily changed unless required.

The important comments in the `waku.go` are the ones above the `import "C"` statement and
above the `eventHandler` function.

## libwaku.so

Before running the example, make sure to properly export the LD_LIBRARY_PATH
with the path that contains the target `libwaku.so` library.

e.g.
From the repo's root folder:
```code
export LD_LIBRARY_PATH=build
```

In order to build the `libwaku.so`, go to the repo's root folder and
invoke the next command, which will create `libwaku.so` under the `build` folder:

```code
make libwaku
```
This will both generate the `libwaku.so` file under the `build` and
the `examples/golang/` fodler.
It is important to notice that there should be a `libwaku.so` at the same level as the `waku.go` file as per how `waku.go` is implemented.

## libwaku.h & nimbase.h

This is the header associated with the `libwaku.so`.

The `libwaku.h` is auto-generated when building the `libwaku.so`.

Everytime a new `libwaku.so` is built, the `libwaku.h` file gets stored in
<REPO_ROOT_DIR>/nimcache/release/libwaku/libwaku.h.

However, the `libwaku.h` is kept version-controlled just for commodity.
It might be needed to update the `libwaku.h` header if a new function
is added to the `libwaku.so` or any signature is modified.

## Running the example

- Open a terminal
- cd <...>/nwaku/
- ```code
export LD_LIBRARY_PATH=build
```
- ```code
go run examples/golang/waku.go
```
note: `--help` can be appended to the end of the previous command to get better insight of possible params
45 changes: 45 additions & 0 deletions examples/golang/libwaku.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* Generated by Nim Compiler v1.6.11 */
#ifndef __libwaku__
#define __libwaku__
#define NIM_INTBITS 64

#include "nimbase.h"
#undef LANGUAGE_C
#undef MIPSEB
#undef MIPSEL
#undef PPC
#undef R3000
#undef R4000
#undef i386
#undef linux
#undef mips
#undef near
#undef far
#undef powerpc
#undef unix
typedef struct ConfigNode ConfigNode;
typedef struct NimStringDesc NimStringDesc;
typedef struct TGenericSeq TGenericSeq;
struct TGenericSeq {NI len;
NI reserved;
};
struct NimStringDesc { TGenericSeq Sup;NIM_CHAR data[SEQ_DECL_SIZE];
};
typedef N_CDECL_PTR(void, tyProc__5cp59bim9aJ4WupX5aVaD1Sg) (NCSTRING signal_0);
N_LIB_PRIVATE N_NOCONV(void, signalHandler)(int sign);
N_LIB_PRIVATE N_NIMCALL(NI, getRefcount)(void* p);
N_LIB_IMPORT N_CDECL(NIM_BOOL, waku_new)(ConfigNode* config, NimStringDesc** jsonResp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of defining the API in terms of nim types, it's generally better to declare it in terms of c types in the nim code (ie cint, cstring etc) then import these "standard" types into the other languages

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for the same reason as below, ie the generated Nim code is not ABI-stable this way except certain parts

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comments @arnetheduck !

All of these variables get translated into C types thanks to the nimbase.h header (included in libwaku.h.)

nimbase.h brings the next definitions:

  • # define N_LIB_IMPORT extern
  • # define N_LIB_PRIVATE __attribute__((visibility("hidden")))
  • # define N_CDECL(rettype, name) rettype name
  • #define NIM_BOOL _Bool, where _Bool is the C99 standard representation of a boolean.
  • typedef NI64 NI; -> typedef int64_t NI64; -> ··· -> typedef signed long int __int64_t;

Personally, given that the libwaku.h file is auto-generated by the Nim compiler, I wouldn't touch it.

Of course, I'm open to any other option that suits better from your point of view.
Kindly let me know if the above definitions are good enough :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but please understand: nimbase.h changes between Nim versions: if you compile this code with nim 1.6 and nim 2.0, you will not get the same result: you will get a different, incompatible, ABI: the result of this is that the consumer of the API will need to adjust as well: when they import the code in their code, their types will change depending on which version of Nim the code was compiled with. This is highly undesireable.

The above point is why all languages tend to use C as the "lingua franca": developers have informally agreed that plain C types are good enough for this purpose, so the "pipeline" to consume something in one language from another is that one language translates its own native types to C types and the other language does the same journey in reverse.

NimStringDesc is a good example: it's no longer part of nim 2.0: it has been replaced by a different type - NimStringDesc, NIM_BOOL and so on, as their names suggest, are internal implementation details of the nim compiler - they are not meant to be exposed to other languages or outside of Nim.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep in mind that exporting code from Nim is only half of the story: the code also needs to be imported on the other side.

When importing code on the other side, this is done by translating C types seen in libwaku.h to "native" types: if, between nim versions, NIM_BOOL changes from int to char (this is a valid change for Nim to make), the import into the target language also must change - there's no reason this should be necessary: the aim of a "C bindings" module is to remove this variability by explicitly using "C FFI" types that exist for this purpose on the Nim side.

N_LIB_IMPORT N_CDECL(NCSTRING, waku_version)(void);
N_LIB_IMPORT N_CDECL(void, waku_set_event_callback)(tyProc__5cp59bim9aJ4WupX5aVaD1Sg callback);
N_LIB_IMPORT N_CDECL(void, waku_content_topic)(NCSTRING appName, NU appVersion, NCSTRING contentTopicName, NCSTRING encoding, NimStringDesc** outContentTopic);
N_LIB_IMPORT N_CDECL(void, waku_pubsub_topic)(NCSTRING topicName, NimStringDesc** outPubsubTopic);
N_LIB_IMPORT N_CDECL(void, waku_default_pubsub_topic)(NimStringDesc** defPubsubTopic);
N_LIB_IMPORT N_CDECL(NIM_BOOL, waku_relay_publish)(NCSTRING pubSubTopic, NCSTRING jsonWakuMessage, NI timeoutMs, NimStringDesc** jsonResp);
N_LIB_IMPORT N_CDECL(void, waku_start)(void);
N_LIB_IMPORT N_CDECL(void, waku_stop)(void);
N_LIB_IMPORT N_CDECL(NIM_BOOL, waku_relay_subscribe)(NCSTRING pubSubTopic, NimStringDesc** jsonResp);
N_LIB_IMPORT N_CDECL(NIM_BOOL, waku_relay_unsubscribe)(NCSTRING pubSubTopic, NimStringDesc** jsonResp);
N_LIB_IMPORT N_CDECL(NIM_BOOL, waku_connect)(NCSTRING peerMultiAddr, NU timeoutMs, NimStringDesc** jsonResp);
N_LIB_IMPORT N_CDECL(void, waku_poll)(void);
N_LIB_IMPORT N_CDECL(void, NimMain)(void);
#endif /* __libwaku__ */
Loading