server:服务端转发功能基本完成。
This commit is contained in:
parent
cb125d20f4
commit
0ed55b478d
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -121,6 +121,7 @@
|
|||||||
"typeindex": "cpp",
|
"typeindex": "cpp",
|
||||||
"valarray": "cpp",
|
"valarray": "cpp",
|
||||||
"charconv": "cpp",
|
"charconv": "cpp",
|
||||||
"compare": "cpp"
|
"compare": "cpp",
|
||||||
|
"format": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(RelayFile VERSION 0.1.0 LANGUAGES CXX)
|
project(RelayFile VERSION 0.1.0 LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(CMAKE_PREFIX_PATH "D:/Program/Dev/wxWidgets")
|
set(CMAKE_PREFIX_PATH "D:/Program/Dev/wxWidgets")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(ClientCore LANGUAGES CXX)
|
project(ClientCore LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(wxWidgets CONFIG REQUIRED)
|
find_package(wxWidgets CONFIG REQUIRED)
|
||||||
|
@ -4,20 +4,23 @@
|
|||||||
#include <cereal/archives/binary.hpp>
|
#include <cereal/archives/binary.hpp>
|
||||||
#include <cereal/types/memory.hpp>
|
#include <cereal/types/memory.hpp>
|
||||||
#include <cereal/types/vector.hpp>
|
#include <cereal/types/vector.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
MSG_TYPE_ASK_CLIENTS = 1,
|
MSG_TYPE_ASK_CLIENTS = 1,
|
||||||
|
MSG_TYPE_FORWORD_FAILED
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InfoCommunicate {
|
struct InfoCommunicate {
|
||||||
MessageType type;
|
MessageType type;
|
||||||
|
std::string fromID;
|
||||||
std::string toID;
|
std::string toID;
|
||||||
std::string UUID;
|
std::string UUID;
|
||||||
std::string data;
|
std::string data;
|
||||||
char mark{};
|
uint8_t mark{};
|
||||||
template <class Archive> void serialize(Archive& archive)
|
template <class Archive> void serialize(Archive& archive)
|
||||||
{
|
{
|
||||||
archive(CEREAL_NVP(type), CEREAL_NVP(toID), CEREAL_NVP(UUID), CEREAL_NVP(data), CEREAL_NVP(mark));
|
archive(CEREAL_NVP(type), CEREAL_NVP(fromID), CEREAL_NVP(toID), CEREAL_NVP(UUID), CEREAL_NVP(data), CEREAL_NVP(mark));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(Protocol LANGUAGES CXX)
|
project(Protocol LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(wxWidgets CONFIG REQUIRED)
|
find_package(wxWidgets CONFIG REQUIRED)
|
||||||
|
@ -10,6 +10,7 @@ Communicate::Communicate()
|
|||||||
/*
|
/*
|
||||||
【 transm TCP 数据协议 】
|
【 transm TCP 数据协议 】
|
||||||
header 2 char: 0xFF 0xFE
|
header 2 char: 0xFF 0xFE
|
||||||
|
unpack 1 char;
|
||||||
from 32 char:
|
from 32 char:
|
||||||
to 32 char:
|
to 32 char:
|
||||||
len 4 char:
|
len 4 char:
|
||||||
@ -26,25 +27,26 @@ FrameBuffer* Communicate::ParseBuffer(MutBuffer& buffer)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
std::memcpy(&len, buffer.GetData() + find + sizeof(gHeader) + 64, sizeof(len));
|
std::memcpy(&len, buffer.GetData() + find + sizeof(gHeader) + sizeof(uint8_t) + 64, sizeof(len));
|
||||||
if (buffer.Length() < (find + sizeof(gHeader) + 64 + len + sizeof(len) + sizeof(gTail)) || len < 0) {
|
if (buffer.Length() < (find + sizeof(gHeader) + sizeof(uint8_t) + 64 + len + sizeof(len) + sizeof(gTail)) || len < 0) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::memcmp(buffer.GetData() + find + sizeof(gHeader) + 64 + sizeof(len) + len, gTail, sizeof(gTail)) != 0) {
|
if (std::memcmp(buffer.GetData() + find + sizeof(gHeader) + sizeof(uint8_t) + 64 + sizeof(len) + len, gTail, sizeof(gTail)) !=
|
||||||
|
0) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame = new FrameBuffer();
|
frame = new FrameBuffer();
|
||||||
frame->fid = std::string(buffer.GetData() + find + sizeof(gHeader), 32);
|
frame->fid = std::string(buffer.GetData() + find + sizeof(gHeader) + sizeof(uint8_t), 32);
|
||||||
frame->tid = std::string(buffer.GetData() + find + sizeof(gHeader) + 32, 32);
|
frame->tid = std::string(buffer.GetData() + find + sizeof(gHeader) + sizeof(uint8_t) + 32, 32);
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
frame->data = new char[len];
|
frame->dataMut = new char[len];
|
||||||
std::memcpy(frame->data, buffer.GetData() + find + sizeof(gHeader) + 64 + sizeof(len), len);
|
std::memcpy(frame->dataMut, buffer.GetData() + find + sizeof(gHeader) + sizeof(uint8_t) + 64 + sizeof(len), len);
|
||||||
frame->len = len;
|
frame->len = len;
|
||||||
}
|
}
|
||||||
buffer.RemoveOf(0, find + sizeof(gHeader) + 64 + len + sizeof(len) + sizeof(gTail));
|
buffer.RemoveOf(0, find + sizeof(gHeader) + sizeof(uint8_t) + 64 + len + sizeof(len) + sizeof(gTail));
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,24 +55,39 @@ bool Communicate::PackBuffer(FrameBuffer* frame, char** buf, int& len)
|
|||||||
if (frame == nullptr) {
|
if (frame == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (frame->data == nullptr) {
|
const char* dataPtr = nullptr;
|
||||||
|
if (frame->dataMut == nullptr) {
|
||||||
frame->len = 0;
|
frame->len = 0;
|
||||||
}
|
}
|
||||||
len = sizeof(gHeader) + 64 + sizeof(len) + frame->len + sizeof(gTail);
|
if (frame->dataConst) {
|
||||||
|
dataPtr = frame->dataConst;
|
||||||
|
} else {
|
||||||
|
dataPtr = frame->dataMut;
|
||||||
|
}
|
||||||
|
len = sizeof(gHeader) + sizeof(uint8_t) + 64 + sizeof(len) + frame->len + sizeof(gTail);
|
||||||
*buf = new char[len];
|
*buf = new char[len];
|
||||||
std::memcpy(*buf, gHeader, sizeof(gHeader));
|
std::memcpy(*buf, gHeader, sizeof(gHeader));
|
||||||
std::memcpy(*buf + sizeof(gHeader), frame->fid.c_str(), 32);
|
std::memcpy(*buf + sizeof(gHeader), &frame->unpack, sizeof(uint8_t));
|
||||||
std::memcpy(*buf + sizeof(gHeader) + 32, frame->tid.c_str(), 32);
|
std::memcpy(*buf + sizeof(gHeader) + sizeof(uint8_t), frame->fid.c_str(), 32);
|
||||||
std::memcpy(*buf + sizeof(gHeader) + 64, &frame->len, sizeof(len));
|
std::memcpy(*buf + sizeof(gHeader) + sizeof(uint8_t) + 32, frame->tid.c_str(), 32);
|
||||||
|
std::memcpy(*buf + sizeof(gHeader) + sizeof(uint8_t) + 64, &frame->len, sizeof(len));
|
||||||
if (frame->len > 0) {
|
if (frame->len > 0) {
|
||||||
std::memcpy(*buf + sizeof(gHeader) + 64 + sizeof(len), frame->data, frame->len);
|
std::memcpy(*buf + sizeof(gHeader) + 64 + sizeof(len), dataPtr, frame->len);
|
||||||
}
|
}
|
||||||
std::memcpy(*buf + sizeof(gHeader) + 64 + sizeof(len) + frame->len, gTail, sizeof(gTail));
|
std::memcpy(*buf + sizeof(gHeader) + 64 + sizeof(len) + frame->len, gTail, sizeof(gTail));
|
||||||
|
frame->dataConst = nullptr;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FrameBuffer::FrameBuffer()
|
||||||
|
{
|
||||||
|
dataConst = nullptr;
|
||||||
|
dataMut = nullptr;
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
FrameBuffer::~FrameBuffer()
|
FrameBuffer::~FrameBuffer()
|
||||||
{
|
{
|
||||||
delete[] data;
|
delete[] dataMut;
|
||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,16 @@
|
|||||||
#define COMMUNICATE_H
|
#define COMMUNICATE_H
|
||||||
|
|
||||||
#include <Util.h>
|
#include <Util.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
struct FrameBuffer {
|
struct FrameBuffer {
|
||||||
|
FrameBuffer();
|
||||||
~FrameBuffer();
|
~FrameBuffer();
|
||||||
|
uint8_t unpack{};
|
||||||
std::string fid;
|
std::string fid;
|
||||||
std::string tid;
|
std::string tid;
|
||||||
char* data{};
|
const char* dataConst;
|
||||||
|
char* dataMut;
|
||||||
int len{};
|
int len{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(RelayServer LANGUAGES CXX)
|
project(RelayServer LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(wxWidgets CONFIG REQUIRED)
|
find_package(wxWidgets CONFIG REQUIRED)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "RelayServer.h"
|
#include "RelayServer.h"
|
||||||
#include <Communicate.h>
|
#include <InfoClient.hpp>
|
||||||
#include <InfoCommunicate.hpp>
|
|
||||||
|
|
||||||
RemoteServer::RemoteServer()
|
RemoteServer::RemoteServer()
|
||||||
{
|
{
|
||||||
@ -8,7 +7,7 @@ RemoteServer::RemoteServer()
|
|||||||
|
|
||||||
bool RemoteServer::Init(const wxString& ip, unsigned short port)
|
bool RemoteServer::Init(const wxString& ip, unsigned short port)
|
||||||
{
|
{
|
||||||
thRun_= true;
|
thRun_ = true;
|
||||||
wxIPV4address addr;
|
wxIPV4address addr;
|
||||||
|
|
||||||
if (!addr.Hostname(ip)) {
|
if (!addr.Hostname(ip)) {
|
||||||
@ -27,7 +26,7 @@ bool RemoteServer::Init(const wxString& ip, unsigned short port)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
wxLogMessage(wxT("Server socket created on %s:%d"), addr.IPAddress(), addr.Service());
|
wxLogMessage(wxT("Server socket created on %s:%d"), addr.IPAddress(), addr.Service());
|
||||||
//wxLogInfo(wxT("Server socket created on %s:%d"), addr.IPAddress(), addr.Service());
|
// wxLogInfo(wxT("Server socket created on %s:%d"), addr.IPAddress(), addr.Service());
|
||||||
|
|
||||||
serverId_ = wxNewId();
|
serverId_ = wxNewId();
|
||||||
server_->SetFlags(wxSOCKET_WAITALL);
|
server_->SetFlags(wxSOCKET_WAITALL);
|
||||||
@ -118,11 +117,107 @@ void RemoteServer::thClientThread(const std::shared_ptr<wxSocketBase>& wxSock, c
|
|||||||
if (!frame) {
|
if (!frame) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::stringstream ss;
|
if (frame->unpack != 0) {
|
||||||
ss.write(frame->data, frame->len);
|
std::stringstream ss;
|
||||||
cereal::BinaryInputArchive inputArchive(ss);
|
ss.write(frame->dataMut, frame->len);
|
||||||
inputArchive(info);
|
cereal::BinaryInputArchive inputArchive(ss);
|
||||||
|
inputArchive(info);
|
||||||
|
Reply(client->wxSock, info);
|
||||||
|
delete frame;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Forword(client->wxSock, frame);
|
||||||
delete frame;
|
delete frame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RemoteServer::Forword(const sockPtr& wxSock, FrameBuffer* buf)
|
||||||
|
{
|
||||||
|
std::shared_ptr<TranClient> fcl = nullptr;
|
||||||
|
std::shared_ptr<TranClient> tcl = nullptr;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock<std::shared_mutex> lock(clientsMutex_);
|
||||||
|
if (clients_.count(buf->fid)) {
|
||||||
|
fcl = clients_[buf->fid];
|
||||||
|
}
|
||||||
|
if (clients_.count(buf->tid)) {
|
||||||
|
tcl = clients_[buf->tid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool farward = false;
|
||||||
|
if (tcl) {
|
||||||
|
farward = Send(tcl->wxSock, buf);
|
||||||
|
}
|
||||||
|
if (farward) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (fcl) {
|
||||||
|
RpyForwordFailed(fcl->wxSock);
|
||||||
|
}
|
||||||
|
return farward;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteServer::Reply(const sockPtr& wxSock, InfoCommunicate& info)
|
||||||
|
{
|
||||||
|
switch (info.type) {
|
||||||
|
case MSG_TYPE_ASK_CLIENTS: {
|
||||||
|
std::swap(info.fromID, info.toID);
|
||||||
|
info.fromID = strID_;
|
||||||
|
RpyOnline(wxSock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wxLogWarning(_("Unknow message type: %d"), info.type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteServer::RpyOnline(const sockPtr& wxSock)
|
||||||
|
{
|
||||||
|
InfoClientVec infoClients;
|
||||||
|
{
|
||||||
|
std::shared_lock<std::shared_mutex> lock(clientsMutex_);
|
||||||
|
for (const auto& client : clients_) {
|
||||||
|
InfoClient infoClient;
|
||||||
|
infoClient.id = client.first;
|
||||||
|
infoClient.name = client.second->name;
|
||||||
|
infoClients.vec.push_back(infoClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
cereal::BinaryOutputArchive archive(ss);
|
||||||
|
archive(infoClients);
|
||||||
|
return Send<InfoClientVec>(wxSock, infoClients);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteServer::RpyForwordFailed(const sockPtr& wxSock)
|
||||||
|
{
|
||||||
|
InfoCommunicate info;
|
||||||
|
info.type = MSG_TYPE_FORWORD_FAILED;
|
||||||
|
return Send<InfoCommunicate>(wxSock, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteServer::Send(const sockPtr& wxSock, FrameBuffer* buf)
|
||||||
|
{
|
||||||
|
if (buf == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char* od = nullptr;
|
||||||
|
int odLen = 0;
|
||||||
|
if (!Communicate::PackBuffer(buf, &od, odLen)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxSock->Write(od, odLen);
|
||||||
|
if (wxSock->Error()) {
|
||||||
|
delete[] od;
|
||||||
|
wxLogError(wxT("Send error: %s"), wxSock->LastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete[] od;
|
||||||
|
return true;
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
#ifndef REMOTE_SERVER_H
|
#ifndef REMOTE_SERVER_H
|
||||||
#define REMOTE_SERVER_H
|
#define REMOTE_SERVER_H
|
||||||
|
|
||||||
|
#include <Communicate.h>
|
||||||
|
#include <InfoCommunicate.hpp>
|
||||||
|
|
||||||
#include <Util.h>
|
#include <Util.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@ -17,12 +20,14 @@
|
|||||||
// constexpr int gBufferSize = 1024 * 1024;
|
// constexpr int gBufferSize = 1024 * 1024;
|
||||||
constexpr int gBufferSize = 256;
|
constexpr int gBufferSize = 256;
|
||||||
using highClock_t = std::chrono::time_point<std::chrono::high_resolution_clock>;
|
using highClock_t = std::chrono::time_point<std::chrono::high_resolution_clock>;
|
||||||
|
using sockPtr = std::shared_ptr<wxSocketBase>;
|
||||||
struct TranClient {
|
struct TranClient {
|
||||||
std::shared_ptr<wxSocketBase> wxSock;
|
sockPtr wxSock;
|
||||||
std::array<char, gBufferSize> buf;
|
|
||||||
MutBuffer buffer;
|
MutBuffer buffer;
|
||||||
int64_t onlineTime;
|
int64_t onlineTime;
|
||||||
|
std::string name;
|
||||||
highClock_t lastRecvTime;
|
highClock_t lastRecvTime;
|
||||||
|
std::array<char, gBufferSize> buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RemoteServer : public wxEvtHandler
|
class RemoteServer : public wxEvtHandler
|
||||||
@ -37,9 +42,31 @@ public:
|
|||||||
private:
|
private:
|
||||||
void OnServerEvent(wxSocketEvent& event);
|
void OnServerEvent(wxSocketEvent& event);
|
||||||
void thClientThread(const std::shared_ptr<wxSocketBase>& wxSock, const wxString& id);
|
void thClientThread(const std::shared_ptr<wxSocketBase>& wxSock, const wxString& id);
|
||||||
|
bool Forword(const sockPtr& wxSock, FrameBuffer* buf);
|
||||||
|
bool Reply(const sockPtr& wxSock, InfoCommunicate& info);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool RpyOnline(const sockPtr& wxSock);
|
||||||
|
bool RpyForwordFailed(const sockPtr& wxSock);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T> bool Send(const sockPtr& wxSock, const T& info)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
cereal::BinaryOutputArchive archive(ss);
|
||||||
|
archive(info);
|
||||||
|
|
||||||
|
auto buf = std::make_shared<FrameBuffer>();
|
||||||
|
buf->dataConst = ss.view().data();
|
||||||
|
buf->len = ss.str().size();
|
||||||
|
|
||||||
|
return Send(wxSock, buf.get());
|
||||||
|
}
|
||||||
|
bool Send(const sockPtr& wxSock, FrameBuffer* buf);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool thRun_{false};
|
bool thRun_{false};
|
||||||
|
std::string strID_;
|
||||||
wxWindowID serverId_;
|
wxWindowID serverId_;
|
||||||
std::shared_mutex clientsMutex_;
|
std::shared_mutex clientsMutex_;
|
||||||
std::unique_ptr<wxSocketServer> server_;
|
std::unique_ptr<wxSocketServer> server_;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(RelayFile LANGUAGES CXX)
|
project(RelayFile LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(wxWidgets CONFIG REQUIRED)
|
find_package(wxWidgets CONFIG REQUIRED)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(Util LANGUAGES CXX)
|
project(Util LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
find_package(wxWidgets CONFIG REQUIRED)
|
find_package(wxWidgets CONFIG REQUIRED)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user