first commit

This commit is contained in:
2023-01-26 13:56:18 +08:00
commit 4abe6bf738
17 changed files with 1105 additions and 0 deletions

20
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,20 @@
set(FIFO_IPC_SOURCES
ipc.cpp
ipc_c.cpp
)
if (WIN32)
list(APPEND FIFO_IPC_SOURCES ipc_win.cpp)
else()
list(APPEND FIFO_IPC_SOURCES ipc_nix.cpp)
endif()
add_library(fifo_ipc STATIC ${FIFO_IPC_SOURCES})
target_link_libraries(fifo_ipc
fmt
)
target_include_directories(fifo_ipc
INTERFACE ${PROJECT_SOURCE_DIR}/include
)

126
src/ipc.cpp Normal file
View File

@@ -0,0 +1,126 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#include <cassert>
#include <fmt/core.h>
#include <thread>
#include "ipc.hpp"
namespace ipc
{
#define set_message(verbose, s, ...) { \
m_message = fmt::format(s __VA_OPT__(,) __VA_ARGS__); \
if (verbose < 0) { \
throw std::runtime_error(m_message); \
} else if (verbose <= m_verbose) { \
fmt::print(m_message); \
fmt::print("\n"); \
} \
}
std::string
default_name();
void
init()
{
init(default_name(), 0600);
}
void
start_external(const std::string& executable, bool is_server)
{
start_external(executable, default_name(), is_server);
}
void
start_external(const std::string& executable, const std::string& ipc_name, bool is_server)
{
auto command = fmt::format("{} {} {}", executable, ipc_name, is_server ? "1" : "0");
fmt::print("execute: {}\n", command);
auto thread_func = [command] {
system(command.c_str());
};
std::thread(thread_func).detach();
}
Ipc::Ipc(bool is_server, int verbose)
: Ipc(default_name(), is_server, verbose)
{
}
void
Ipc::write(const void* buffer, size_t count, size_t size)
{
// TODO simplfy the code, remove loop and use single-write operation only ?
assert(buffer);
const ssize_t bytes_required = size * count;
char* bytes_buffer = (char*)buffer;
ssize_t bytes_current;
ssize_t bytes_sent = 0;
do {
bytes_current = _write(&bytes_buffer[bytes_sent], bytes_required - bytes_sent);
bytes_sent += bytes_current;
// set_message(2, "write {} -> {}/{}\n", bytes_current, bytes_sent, bytes_required);
} while ((bytes_current > 0) && (bytes_sent != bytes_required));
if (bytes_sent != bytes_required) {
set_message(-1, "c : (-) write ({} * {}) = {} bytes error: only {} bytes are sent",
count, size, bytes_required, bytes_sent);
}
set_message(1, "c : (+) write ({} * {}) = {} bytes", count, size, bytes_required);
}
void
Ipc::read(void* buffer, size_t count, size_t size)
{
// TODO simplfy the code, remove loop and use single-read operation only ?
assert(buffer);
const ssize_t bytes_required = size * count;
char* bytes_buffer = (char*)buffer;
ssize_t bytes_current = 0;
ssize_t bytes_received = 0;
do {
bytes_current = _read(&bytes_buffer[bytes_received], bytes_required - bytes_received);
bytes_received += bytes_current;
set_message(2, "read {} -> {}/{}\n", bytes_current, bytes_received, bytes_required);
} while ((bytes_current > 0) && (bytes_received != bytes_required));
if (bytes_received != bytes_required) {
set_message(-1, "c : (-) read ({} * {}) = {} bytes error: only {} bytes are recieved",
count, size, bytes_required, bytes_received);
}
set_message(1, "c : (+) read ({} * {}) = {} bytes", size, count, bytes_required);
}
void
Ipc::write(const char* str)
{
int len = strlen(str);
write(&len, 1, sizeof(len));
write(str, 1, len);
}
void
Ipc::read(char* str)
{
int len;
read(&len, 1, sizeof(len));
read(str, 1, len);
}
}

95
src/ipc.hpp Normal file
View File

@@ -0,0 +1,95 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#pragma once
#include <string>
#if defined(_WIN32)
#include <windows.h>
#endif
namespace ipc
{
void
init();
void
init(const std::string& name, unsigned int mode);
void
start_external(const std::string& executable, bool is_server);
void
start_external(const std::string& executable, const std::string& ipc_name, bool is_server);
class Ipc
{
public:
Ipc(bool is_server, int verbose = 0);
Ipc(const std::string& name, bool is_server, int verbose = 0);
~Ipc();
void
write(const char* str);
template<typename T>
void
write(const T& data)
{
write(&data, 1, sizeof(T));
}
template<typename T>
void
write(const T* data, size_t count)
{
write(data, count, sizeof(T));
}
void
write(const void* buffer, size_t count, size_t size);
void
read(char* str);
template<typename T>
void
read(T& data)
{
read(&data, 1, sizeof(T));
}
template<typename T>
void
read(T* data, size_t count)
{
read(data, count, sizeof(T));
}
void
read(void* buffer, size_t count, size_t size);
protected:
ssize_t
_write(const void* buffer, size_t bytes);
ssize_t
_read(void* buffer, size_t bytes);
protected:
int m_verbose;
std::string m_message;
#if defined(_WIN32)
HANDLE m_pipe;
#else
int m_writer_fd;
int m_reader_fd;
#endif
};
}

121
src/ipc_c.cpp Normal file
View File

@@ -0,0 +1,121 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#include <cassert>
#include "ipc.hpp"
#include "ipc_c.h"
void
ipc_init_default()
{
ipc::init();
}
void
ipc_init(const char* name, unsigned int mode)
{
ipc::init(name, mode);
}
void*
ipc_create_default(int is_server, int verbose)
{
return new ipc::Ipc(is_server, verbose);
}
void*
ipc_create(const char* name, int is_server, int verbose)
{
return new ipc::Ipc(name, is_server, verbose);
}
void
ipc_destroy(void* ipc)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
delete i;
}
void
ipc_start_external_default(const char* executable, int is_server)
{
ipc::start_external(executable, is_server);
}
void
ipc_start_external(const char* executable, const char* ipc_name, int is_server)
{
ipc::start_external(executable, ipc_name, is_server);
}
void
ipc_write_any(void* ipc, const void* data, size_t count, size_t size)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->write(data, count, size);
}
void
ipc_read_any(void* ipc, void* data, size_t count, size_t size)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->read(data, count, size);
}
void
ipc_write_string(void* ipc, const char* str)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->write(str);
}
void
ipc_read_string(void* ipc, char* str)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->read(str);
}
template<typename T>
void
ipc_write(void* ipc, const T* data, size_t count)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->write(data, count);
}
template<typename T>
void
ipc_read(void* ipc, T* data, size_t count)
{
assert(ipc);
auto i = (ipc::Ipc*)ipc;
i->read(data, count);
}
#define _IPC_WRITE_FN(data_type) _IPC_WRITE_FN_NAME(data_type) { return ipc_write(ipc, data, count); }
#define _IPC_READ_FN(data_type) _IPC_READ_FN_NAME (data_type) { return ipc_read (ipc, data, count); }
_IPC_WRITE_FN(char);
_IPC_WRITE_FN(int);
_IPC_WRITE_FN(long);
_IPC_WRITE_FN(double);
_IPC_READ_FN(char);
_IPC_READ_FN(int);
_IPC_READ_FN(long);
_IPC_READ_FN(double);

58
src/ipc_c.h Normal file
View File

@@ -0,0 +1,58 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void
ipc_init_default();
void
ipc_init(const char* name, unsigned int mode);
void*
ipc_create_default(int is_server, int verbose);
void*
ipc_create(const char* name, int is_server, int verbose);
void
ipc_destroy(void* ipc);
void
ipc_start_external_default(const char* executable, int is_server);
void
ipc_start_external(const char* executable, const char* ipc_name, int is_server);
void
ipc_write_any(void* ipc, const void* data, size_t count, size_t size);
void
ipc_read_any(void* ipc, void* data, size_t count, size_t size);
void
ipc_write_string(void* ipc, const char* str);
void
ipc_read_string(void* ipc, char* str);
#define _IPC_WRITE_FN_NAME(type) void ipc_write_ ## type (void* ipc, const type * data, size_t count)
#define _IPC_READ_FN_NAME(type) void ipc_read_ ## type (void* ipc, type * data, size_t count)
_IPC_WRITE_FN_NAME(char);
_IPC_WRITE_FN_NAME(int);
_IPC_WRITE_FN_NAME(long);
_IPC_WRITE_FN_NAME(double);
_IPC_READ_FN_NAME(char);
_IPC_READ_FN_NAME(int);
_IPC_READ_FN_NAME(long);
_IPC_READ_FN_NAME(double);
#ifdef __cplusplus
}
#endif

75
src/ipc_nix.cpp Normal file
View File

@@ -0,0 +1,75 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#include <fcntl.h>
#include <fmt/core.h>
#include <sys/stat.h>
#include <unistd.h>
#include "ipc.hpp"
namespace ipc
{
std::string
default_name()
{
return fmt::format("/tmp/fifo_ipc_{}", getpid());
}
void
make_fifo(const std::string& name, unsigned int mode)
{
if (mkfifo(name.c_str(), mode) < 0)
{
auto message = fmt::format("mkfifo '{}' : {}", name, strerror(errno));
throw std::runtime_error(message);
}
}
void
init(const std::string& name, unsigned int mode)
{
make_fifo(name + "_s", mode);
make_fifo(name + "_c", mode);
}
Ipc::Ipc(const std::string& name, bool is_server, int verbose)
: m_verbose(verbose)
{
auto fifo_open = [](std::string name, int flags) -> int {
int fd = open(name.c_str(), flags);
if (fd == -1) {
auto message = fmt::format("open '{}' : {}", name, strerror(errno));
throw std::runtime_error(message);
}
return fd;
};
if (is_server) {
m_reader_fd = fifo_open(name + "_c", O_RDONLY);
m_writer_fd = fifo_open(name + "_s", O_WRONLY);
} else {
m_writer_fd = fifo_open(name + "_c", O_WRONLY);
m_reader_fd = fifo_open(name + "_s", O_RDONLY);
}
}
Ipc::~Ipc()
{
}
ssize_t
Ipc::_write(const void* buffer, size_t bytes)
{
return ::write(m_writer_fd, buffer, bytes);
}
ssize_t
Ipc::_read(void* buffer, size_t bytes)
{
return ::read(m_reader_fd, buffer, bytes);
}
}

189
src/ipc_win.cpp Normal file
View File

@@ -0,0 +1,189 @@
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
// All rights reserved.
#include <chrono>
#include <fmt/core.h>
#include <string>
#include <thread>
#include <windows.h>
#include "ipc.hpp"
#define UNUSED(x) (void)(x)
namespace ipc
{
// https://stackoverflow.com/a/17387176
// Returns the last Win32 error, in string format.
// Returns an empty string if there is no error.
std::string
getLastErrorAsString()
{
// Get the error message ID, if any.
DWORD errorMessageID = ::GetLastError();
if(errorMessageID == 0) {
return std::string(); //No error message has been recorded
}
LPSTR messageBuffer = nullptr;
// Ask Win32 to give us the string version of that message ID.
// The parameters we pass in, tell Win32 to create the buffer that holds the
// message for us (because we don't yet know how long the message string will be).
//
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea
//
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags
NULL, // lpSource
errorMessageID, // dwMessageId
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), // dwLanguageId
(LPSTR)&messageBuffer, // lpBuffer
0, // nSize
NULL // *Arguments
);
// Copy the error message into a std::string.
std::string message(messageBuffer, size);
// Free the Win32's string's buffer.
LocalFree(messageBuffer);
return message;
}
std::string
default_name()
{
return fmt::format("fifo_ipc_{}", getpid());
}
void
init(const std::string& name, unsigned int mode)
{
UNUSED(name);
UNUSED(mode);
}
// https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-operations
// https://peter.bloomfield.online/introduction-to-win32-named-pipes-cpp/
// https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-client
// https://habr.com/ru/post/166155/
Ipc::Ipc(const std::string& name, bool is_server, int verbose)
: m_verbose(verbose)
{
using namespace std::chrono_literals;
auto full_name = fmt::format("\\\\.\\pipe\\{}", name);
// fmt::print("full name = {}\n", full_name);
auto throw_failed = [](const std::string& name) {
auto message = fmt::format("{}() failed: {}", name, getLastErrorAsString());
throw std::runtime_error(message);
};
if (is_server) {
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea
m_pipe = CreateNamedPipe(
(LPSTR)full_name.c_str(), // lpName
PIPE_ACCESS_DUPLEX, // dwOpenMode
PIPE_TYPE_BYTE, // dwPipeMode
1, // nMaxInstances
0, // nOutBufferSize
0, // nInBufferSize
0, // nDefaultTimeOut
NULL // lpSecurityAttributes
);
if (m_pipe == INVALID_HANDLE_VALUE) {
throw_failed("CreateNamedPipe");
}
auto result = ConnectNamedPipe(m_pipe, NULL);
if (!result) {
throw_failed("ConnectNamedPipe");
}
return;
}
auto time_step = 500ms;
auto time_current = 0ms;
while (time_current < 10s) {
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
m_pipe = CreateFile(
(LPSTR)full_name.c_str(), // lpFileName
GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess
0, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
NULL // hTemplateFile
);
if (m_pipe != INVALID_HANDLE_VALUE) {
break;
}
std::this_thread::sleep_for(time_step);
time_current += time_step;
}
if (m_pipe == INVALID_HANDLE_VALUE) {
throw_failed("CreateFile");
}
}
Ipc::~Ipc()
{
CloseHandle(m_pipe);
}
ssize_t
Ipc::_write(const void* buffer, size_t bytes)
{
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
DWORD numBytesWritten = 0;
auto result = WriteFile(
m_pipe, // hFile
buffer, // lpBuffer
bytes, // nNumberOfBytesToWrite
&numBytesWritten, // lpNumberOfBytesWritten
NULL // lpOverlapped
);
if (!result) {
// FIXME
}
return numBytesWritten;
}
ssize_t
Ipc::_read(void* buffer, size_t bytes)
{
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
DWORD numBytesRead = 0;
auto result = ReadFile(
m_pipe, // hFile
buffer, // lpBuffer
bytes, // nNumberOfBytesToRead
&numBytesRead, // lpNumberOfBytesRead
NULL // lpOverlapped
);
if (!result) {
// FIXME
}
return numBytesRead;
}
}