My MVCE for SSL relay server:
#pragma once
#include <stdint.h>
#include <iostream>
#include <asio.hpp>
#include <asio/ssl.hpp>
namespace test
{
namespace setup
{
const uint32_t maxMessageSize = 1024 * 1024;
const uint32_t maxSessionsNum = 10;
}
enum class MessageType
{
LOG_ON = 0,
TEXT_MESSAGE = 1
};
class MessageHeader
{
public:
uint32_t messageType;
uint32_t messageLength;
MessageHeader(uint32_t messageType, uint32_t messageLength) : messageType(messageType), messageLength(messageLength) {}
};
class LogOn
{
public:
MessageHeader header;
uint32_t sessionId;
uint32_t isClient0;
LogOn() : header((uint32_t)MessageType::LOG_ON, sizeof(LogOn)) {}
};
class TextMessage
{
public:
MessageHeader header;
uint8_t data[];
TextMessage() : header((uint32_t)MessageType::TEXT_MESSAGE, sizeof(TextMessage)){}
};
class ClientSocket;
class Session
{
public:
ClientSocket* pClient0;
ClientSocket* pClient1;
};
Session* getSession(uint32_t sessionId);
class ClientSocket
{
public:
bool useTLS;
std::shared_ptr<asio::ip::tcp::socket> socket;
std::shared_ptr<asio::ssl::stream<asio::ip::tcp::socket>> socketSSL;
Session* pSession;
bool isClient0;
std::recursive_mutex writeBufferLock;
std::vector<char> readBuffer;
uint32_t readPos;
ClientSocket(asio::ip::tcp::socket& socket) : useTLS(false)
{
this->socket = std::make_shared<asio::ip::tcp::socket>(std::move(socket));
this->readBuffer.resize(setup::maxMessageSize + sizeof(MessageHeader));
this->readPos = 0;
}
ClientSocket(asio::ssl::stream<asio::ip::tcp::socket>& socket) : useTLS(true)
{
this->socketSSL = std::make_shared<asio::ssl::stream<asio::ip::tcp::socket>>(std::move(socket));
this->readBuffer.resize(setup::maxMessageSize + sizeof(MessageHeader));
this->readPos = 0;
}
bool writeSocket(uint8_t* pBuffer, uint32_t bufferSize)
{
try
{
std::unique_lock<std::recursive_mutex>
lock(this->writeBufferLock);
size_t writtenBytes = 0;
if (true == this->useTLS)
{
writtenBytes = asio::write(*this->socketSSL,
asio::buffer(pBuffer, bufferSize));
}
else
{
writtenBytes = asio::write(*this->socket,
asio::buffer(pBuffer, bufferSize));
}
return (writtenBytes == bufferSize);
}
catch (asio::system_error e)
{
std::cout << e.what() << std::endl;
}
catch (std::exception e)
{
std::cout << e.what() << std::endl;
}
catch (...)
{
std::cout << "Some other exception" << std::endl;
}
return false;
}
void asyncReadNextMessage(uint32_t messageSize)
{
auto readMessageLambda = [&](const asio::error_code errorCode, std::size_t length)
{
this->readPos += (uint32_t)length;
if (0 != errorCode.value())
{
//send socket to remove
printf("errorCode= %u, message=%s\n", errorCode.value(), errorCode.message().c_str());
//sendRemoveMeSignal();
return;
}
if ((this->readPos < sizeof(MessageHeader)))
{
asyncReadNextMessage(sizeof(MessageHeader) - this->readPos);
return;
}
MessageHeader* pMessageHeader = (MessageHeader*)this->readBuffer.data();
if (pMessageHeader->messageLength > setup::maxMessageSize)
{
//Message to big - should disconnect ?
this->readPos = 0;
asyncReadNextMessage(sizeof(MessageHeader));
return;
}
if (this->readPos < pMessageHeader->messageLength)
{
asyncReadNextMessage(pMessageHeader->messageLength - this->readPos);
return;
}
MessageType messageType = (MessageType)pMessageHeader->messageType;
switch(messageType)
{
case MessageType::LOG_ON:
{
LogOn* pLogOn = (LogOn*)pMessageHeader;
printf("LOG_ON message sessionId=%u, isClient0=%u\n", pLogOn->sessionId, pLogOn->isClient0);
this->isClient0 = pLogOn->isClient0;
this->pSession = getSession(pLogOn->sessionId);
if (this->isClient0)
this->pSession->pClient0 = this;
else
this->pSession->pClient1 = this;
}
break;
case MessageType::TEXT_MESSAGE:
{
TextMessage* pTextMessage = (TextMessage*)pMessageHeader;
if (nullptr != pSession)
{
if (this->isClient0)
{
if (nullptr != pSession->pClient1)
{
pSession->pClient1->writeSocket((uint8_t*)pTextMessage, pTextMessage->header.messageLength);
}
}
else
{
if (nullptr != pSession->pClient0)
{
pSession->pClient0->writeSocket((uint8_t*)pTextMessage, pTextMessage->header.messageLength);
}
}
}
}
break;
}
this->readPos = 0;
asyncReadNextMessage(sizeof(MessageHeader));
};
if (true == this->useTLS)
{
this->socketSSL->async_read_some(asio::buffer(this->readBuffer.data() + this->readPos, messageSize), readMessageLambda);
}
else
{
this->socket->async_read_some(asio::buffer(this->readBuffer.data() + this->readPos, messageSize), readMessageLambda);
}
}
};
class SSLRelayServer
{
public:
static SSLRelayServer* pSingleton;
asio::io_context ioContext;
asio::ssl::context sslContext;
std::vector<std::thread> workerThreads;
asio::ip::tcp::acceptor* pAcceptor;
asio::ip::tcp::endpoint* pEndpoint;
bool useTLS;
Session* sessions[setup::maxSessionsNum];
SSLRelayServer() : pAcceptor(nullptr), pEndpoint(nullptr), sslContext(asio::ssl::context::tlsv13_server)//sslContext(asio::ssl::context::sslv23)
{
this->useTLS = false;
this->pSingleton = this;
//this->sslContext.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2);
this->sslContext.set_password_callback(std::bind(&SSLRelayServer::getPrivateKeyPEMFilePassword, this));
this->sslContext.use_certificate_chain_file("server_cert.pem");
this->sslContext.use_private_key_file("server_private_key.pem",
asio::ssl::context::pem);
}
static SSLRelayServer* getSingleton()
{
return pSingleton;
}
std::string getPrivateKeyPEMFilePassword() const
{
return "";
}
void addClientSocket(asio::ip::tcp::socket& socket)
{
ClientSocket* pClientSocket = new ClientSocket(socket); // use smart pointers
pClientSocket->asyncReadNextMessage(sizeof(MessageHeader));
}
void addSSLClientToken(asio::ssl::stream<asio::ip::tcp::socket>&sslSocket)
{
ClientSocket* pClientSocket = new ClientSocket(sslSocket); // use smart pointers
pClientSocket->asyncReadNextMessage(sizeof(MessageHeader));
}
void handleAccept(asio::ip::tcp::socket& socket, const asio::error_code& errorCode)
{
if (!errorCode)
{
printf("accepted\n");
if (true == socket.is_open())
{
asio::ip::tcp::no_delay no_delay_option(true);
socket.set_option(no_delay_option);
addClientSocket(socket);
}
}
}
void handleAcceptTLS(asio::ip::tcp::socket& socket, const asio::error_code& errorCode)
{
if (!errorCode)
{
printf("accepted\n");
if (true == socket.is_open())
{
asio::ip::tcp::no_delay no_delay_option(true);
asio::ssl::stream<asio::ip::tcp::socket> sslStream(std::move(socket), this->sslContext);
try
{
sslStream.handshake(asio::ssl::stream_base::server);
sslStream.lowest_layer().set_option(no_delay_option);
addSSLClientToken(sslStream);
}
catch (asio::system_error e)
{
std::cout << e.what() << std::endl;
return;
}
catch (std::exception e)
{
std::cout << e.what() << std::endl;
return;
}
catch (...)
{
std::cout << "Other exception" << std::endl;
return;
}
}
}
}
void startAccept()
{
auto acceptHandler = [this](const asio::error_code& errorCode, asio::ip::tcp::socket socket)
{
printf("acceptHandler\n");
handleAccept(socket, errorCode);
this->startAccept();
};
auto tlsAcceptHandler = [this](const asio::error_code& errorCode, asio::ip::tcp::socket socket)
{
printf("tlsAcceptHandler\n");
handleAcceptTLS(socket, errorCode);
this->startAccept();
};
if (true == this->useTLS)
{
this->pAcceptor->async_accept(tlsAcceptHandler);
}
else
{
this->pAcceptor->async_accept(acceptHandler);
}
}
bool run(uint32_t servicePort, uint32_t threadsNum, bool useTLS)
{
this->useTLS = useTLS;
this->pEndpoint = new asio::ip::tcp::endpoint(asio::ip::tcp::v4(), servicePort);
this->pAcceptor = new asio::ip::tcp::acceptor(ioContext, *pEndpoint);
this->pAcceptor->listen();
this->startAccept();
for (uint32_t threadIt = 0; threadIt < threadsNum; ++threadIt)
{
this->workerThreads.emplace_back([&]() {
#ifdef WINDOWS
SetThreadDescription(GetCurrentThread(), L"SSLRelayServer worker thread");
#endif
this->ioContext.run(); }
);
}
return true;
}
Session* getSession(uint32_t sessionId)
{
if (nullptr == this->sessions[sessionId])
{
this->sessions[sessionId] = new Session();
}
return this->sessions[sessionId];
}
};
SSLRelayServer* SSLRelayServer::pSingleton = nullptr;
Session* getSession(uint32_t sessionId)
{
SSLRelayServer* pServer = SSLRelayServer::getSingleton();
Session* pSession = pServer->getSession(sessionId);
return pSession;
}
class Client
{
public:
asio::ssl::context sslContext;
std::shared_ptr<asio::ip::tcp::socket> socket;
std::shared_ptr<asio::ssl::stream<asio::ip::tcp::socket>> socketSSL;
asio::io_context ioContext;
bool useTLS;
bool isClient0;
uint32_t readDataIt;
std::vector<uint8_t> readBuffer;
std::thread listenerThread;
Client() : sslContext(asio::ssl::context::tlsv13_client)//sslContext(asio::ssl::context::sslv23)
{
sslContext.load_verify_file("server_cert.pem");
//sslContext.set_verify_mode(asio::ssl::verify_peer);
using asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;
sslContext.set_verify_callback(std::bind(&Client::verifyCertificate, this, _1, _2));
this->readBuffer.resize(setup::maxMessageSize);
this->readDataIt = 0;
}
bool verifyCertificate(bool preverified, asio::ssl::verify_context& verifyCtx)
{
return true;
}
void listenerRunner()
{
#ifdef WINDOWS
if (this->isClient0)
{
SetThreadDescription(GetCurrentThread(), L"listenerRunner client0");
}
else
{
SetThreadDescription(GetCurrentThread(), L"listenerRunner client1");
}
#endif
while (1==1)
{
asio::error_code errorCode;
size_t transferred = 0;
if (true == this->useTLS)
{
transferred = this->socketSSL->read_some(asio::buffer(this->readBuffer.data() + this->readDataIt, sizeof(MessageHeader) - this->readDataIt), errorCode);
}
else
{
transferred = this->socket->read_some(asio::buffer(this->readBuffer.data() + this->readDataIt, sizeof(MessageHeader) - this->readDataIt), errorCode);
}
this->readDataIt += transferred;
if (0 != errorCode.value())
{
this->readDataIt = 0;
continue;
}
if (this->readDataIt < sizeof(MessageHeader))
continue;
MessageHeader* pMessageHeader = (MessageHeader*)this->readBuffer.data();
if (pMessageHeader->messageLength > setup::maxMessageSize)
{
exit(1);
}
bool resetSocket = false;
while (pMessageHeader->messageLength > this->readDataIt)
{
printf("readDataIt=%u, threadId=%u\n", this->readDataIt, GetCurrentThreadId());
{
//message not complete
if (true == this->useTLS)
{
transferred = this->socketSSL->read_some(asio::buffer(this->readBuffer.data() + this->readDataIt, pMessageHeader->messageLength - this->readDataIt), errorCode);
}
else
{
transferred = this->socket->read_some(asio::buffer(this->readBuffer.data() + this->readDataIt, pMessageHeader->messageLength - this->readDataIt), errorCode);
}
this->readDataIt += transferred;
}
if (0 != errorCode.value())
{
exit(1);
}
}
MessageType messageType = (MessageType)pMessageHeader->messageType;
switch (messageType)
{
case MessageType::TEXT_MESSAGE:
{
TextMessage* pTextMessage = (TextMessage*)pMessageHeader;
printf("TEXT_MESSAGE: %s\n", pTextMessage->data);
}
break;
}
this->readDataIt = 0;
}
}
void run(uint32_t sessionId, bool isClient0, bool useTLS, uint32_t servicePort)
{
this->useTLS = useTLS;
this->isClient0 = isClient0;
if (useTLS)
{
socketSSL = std::make_shared<asio::ssl::stream<asio::ip::tcp::socket>>(ioContext, sslContext);
}
else
{
socket = std::make_shared<asio::ip::tcp::socket>(ioContext);
}
asio::ip::tcp::resolver resolver(ioContext);
asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(asio::ip::tcp::v4(), "127.0.0.1", std::to_string(servicePort));
asio::ip::tcp::no_delay no_delay_option(true);
if (true == useTLS)
{
asio::ip::tcp::endpoint sslEndpoint = asio::connect(socketSSL->lowest_layer(), endpoints);
socketSSL->handshake(asio::ssl::stream_base::client);
socketSSL->lowest_layer().set_option(no_delay_option);
}
else
{
asio::ip::tcp::endpoint endpoint = asio::connect(*socket, endpoints);
socket->set_option(no_delay_option);
}
this->listenerThread = std::thread(&Client::listenerRunner, this);
LogOn logOn;
logOn.isClient0 = isClient0;
logOn.sessionId = sessionId;
const uint32_t logOnSize = sizeof(logOn);
if (true == useTLS)
{
size_t transferred = asio::write(*socketSSL, asio::buffer(&logOn, sizeof(LogOn)));
}
else
{
size_t transferred = asio::write(*socket, asio::buffer(&logOn, sizeof(LogOn)));
}
uint32_t counter = 0;
while (1 == 1)
{
std::string number = std::to_string(counter);
std::string message;
if (this->isClient0)
{
message = "Client0: " + number;
}
else
{
message = "Client1: " + number;
}
TextMessage textMessage;
textMessage.header.messageLength += message.size() + 1;
if (this->useTLS)
{
size_t transferred = asio::write(*socketSSL, asio::buffer(&textMessage, sizeof(TextMessage)));
transferred = asio::write(*socketSSL, asio::buffer(message.c_str(), message.length() + 1));
}
else
{
size_t transferred = asio::write(*socket, asio::buffer(&textMessage, sizeof(TextMessage)));
transferred = asio::write(*socket, asio::buffer(message.c_str(), message.length() + 1));
}
++counter;
//Sleep(1000);
}
}
};
void clientTest(uint32_t sessionId, bool isClient0, bool useTLS,
uint32_t servicePort)
{
#ifdef WINDOWS
if (isClient0)
{
SetThreadDescription(GetCurrentThread(), L"Client0");
}
else
{
SetThreadDescription(GetCurrentThread(), L"Client1");
}
#endif
Client client;
client.run(sessionId, isClient0, useTLS, servicePort);
while (1 == 1)
{
Sleep(1000);
}
}
void SSLRelayTest()
{
SSLRelayServer relayServer;
const uint32_t threadsNum = 1;
const bool useTLS = true;
const uint32_t servicePort = 777;
relayServer.run(servicePort, threadsNum, useTLS);
Sleep(5000);
std::vector<std::thread> threads;
const uint32_t sessionId = 0;
threads.emplace_back(clientTest, sessionId, true, useTLS, servicePort);
threads.emplace_back(clientTest, sessionId, false, useTLS,servicePort);
for (std::thread& threadIt : threads)
{
threadIt.join();
}
}
}
What this sample does ?
It runs SSL relay server on localhost port 777 which connects two clients and allows exchanging
of text messages between them.
Promblem:
When I run that sample server returns error "errorCode= 167772441, message=decryption failed or bad record mac (SSL routines)" in void "asyncReadNextMessage(uint32_t messageSize)"
I found out this is caused by client which reads and writes to client SSL socket from separate threads (changing variable useTLS to 0 runs it on normal socket which proves that it is SSL socket problem).
Apparently TLS is not full-duplex protocol (I did not know about that). I can't synchronize access to read and write with mutex because when socket enters read state and there is no
incoming message writing to socked will be blocked forever. At this thread Boost ASIO, SSL: How do strands help the implementation?
someone recommended using strands but someone else wrote that asio only synchronizes not concurrent execution of read and write handles which does not fix the problem.
I expect that somehow there is a way to synchronize read and write to SSL socket. I'm 100% sure that problem lies in synchronizing read and writes to socket because when I wrote example with read and write to socket done by one thread it worked. However then client always expects that there is message to read which can block all write if there is not. Can it be solved without using separate sockets for reads and writes ?
Okay I figured it out by writting many diffrent samples of code including SSL sockets.
When asio::io_context is already running you can't simply schedule asio::async_write or asio::async_read from thread which is not
associated with strand connected to that socket.
So when there is:
asio::async_write(*this->socketSSL, asio::buffer(pBuffer, bufferSize), asio::bind_executor(readWriteStrand,writeMessageLambda));
but thread which is executing is not running from readWriteStrand strand then it should be written as:
asio::post(ioContext, asio::bind_executor(readWriteStrand, [&]() {asio::async_read(*this->socketSSL, asio::buffer(readBuffer.data() + this->readDataIt, messageSize), asio::bind_executor(readWriteStrand, readMessageLambda)); }));
I have a problem with a program I have done with Embarcadero C++ Builder.
Part one:
I want to make stand-alone .exe file, and according to that i have disabled "link with dynamic RTL" and disabled "link with runtime packages". But, every time i end my program, I get an error "Abnormal program termination". I've invstigated that and found a partial solution. When I enable link with runtime packages, that error does not appear, but .exe file won't run on PC which don't have C++ builder installed, because some .bpl files missing. So, for now, I have two possibilities: to have "Abnormal program termination" error, or not to have, functional .exe file for all PCs. Except for that error, my program works perfectly.
Part two:
I made fully functional program with 3 units, and I want to translate it on english language using resource DLL wizard. I can preview translated form, but I can't build it, because it say Error but I can't see any more details about error. I really don't know how is it possible to have working program with no errors, and localized version to have an error. I got the advice to do "clean all" and then "build all" but I can't even clean all because of that error.
I really don't know what to do anymore, I am struggling with those problems for a week now and I really hope that someone can help me. Thanks :)
Localization
Not sure if your localization tool is suited for Borland/Embarcadero apps (it might be just for MSVC++ apps which might be the problem).
I do localization on my own (as I needed custom stuff like internal messages and tables translation. So I coded a ~55 KByte parser that takes *.h,*.cpp,*.dfm of all windows as input and creates *.ini file and *.h that will read the selected language ini and translate all forms to it (Caption,Hint,...) then just for each new language I copy the ini and translate the strings. This way I can switch languages on the run without any DLL or restart needed and customers can add new languages on their own without the need for source code or programming skills.
Can not share the code however as it uses some non sharable (corporate) libs of mine for strings,dynamic lists and fast ini file access (they are included in the 55 KByte estimate) but I can share the executable (win32 standalone compiled in BDS2006 and for VCL BDS2006 formatting of code):
Form decode tool v2.70
Use the slow download (it is free without registration). I even add one window in the formulars for testing. You just run the exe and when done use the files from output. However the outputed source files use the fast ini file access (I can not share) so you would need to encode that (or replace with your style access). Here slow winapi based workaround for my ini.h that shoudl work:
#ifndef _ini_h
#define _ini_h
class inifile
{
public:
AnsiString section,filename;
// inline
inifile() {close();}
inifile(inifile& a) { *this=a; }
~inifile() {close();}
inifile* operator = (const inifile *a) { *this=*a; return this; }
//inifile* operator = (const inifile &a) { ...copy... return this; }
int open(AnsiString name) // open ini file
{
close();
if (FileExists(name)) { filename=name; return 1; }
return 0;
}
void close() // close ini file
{
filename="";
section="";
}
int sec_getid(const AnsiString &sec, bool _add) // select section to work with ignoring the returned ID
{
section=sec;
return 0;
}
AnsiString key_get(int section_id,const AnsiString &key,const AnsiString &def) // read key from section of ini with default value
{
if (filename=="") return def;
char dest[1024]; AnsiString val;
GetPrivateProfileString(section.c_str(),key.c_str(),def.c_str(),dest,1024,filename.c_str());
val=dest; return val;
}
};
#endif
So the app has 3 folders:
formulars
put all the *.h,*.cpp;*.dfm files of your forms here. The parsing of *.h is stopping on public: (skip user declarations) to prevent collisions with user code. You can also add comments to end of component line:
// LANGUAGE_SKIP_CAPT
// LANGUAGE_SKIP_TEXT
// LANGUAGE_SKIP_HINT
or any combination to prevent translation is messing with it.
messages
Ignore this it is for *.cpp source files with internal messages _messages[]to translate must be properly encoded using markers in comments like this:
AnsiString _messages[_msg_enum_end+1]=
{
// LANGUAGE_MSG000_TEXT_BEG
"Error: message id out of range !!!",
"OpenGL FBO support is missing !!! ... switching to simplified graphics",
"OpenGL VBO support is missing !!! ... switching to simplified graphics",
"Sorry: ATI graphic card detected !!! ... switching to simplified graphics",
"Sorry: ATI Radeon HD 5450 graphic card detected !!! ... disabling preview features",
"Done.",
// LANGUAGE_MSG000_TEXT_END
"",
""
};
output
the app here outputs 4 files
init.h can ignore this it is just init/exit load/stroing of window state
init.ini ini file for the init can ignore this
into app ini.
language.h this is what you want it is the translation routine
english.ini translation ini file using current window texts
Here example of generated language.h:
#include <math.h>
#include "ini.h" // use the ini.h above
void language_init(AnsiString name)
{
int sec;
inifile ini;
ini.open(name);
sec=ini.sec_getid("Twin_EditorSetup",true);
win_EditorSetup->ck_specchar->Caption=ini.key_get(sec,"C_ck_specchar","Special chars.");
win_EditorSetup->ck_specchar->Hint =ini.key_get(sec,"H_ck_specchar","View special characters on/off");
win_EditorSetup->cb_colors->Hint =ini.key_get(sec,"H_cb_colors","Select color for editing");
win_EditorSetup->ed_tabs->Text =ini.key_get(sec,"T_ed_tabs","1");
win_EditorSetup->ed_tabs->Hint =ini.key_get(sec,"H_ed_tabs","Tabulator size");
win_EditorSetup->ck_smart->Caption=ini.key_get(sec,"C_ck_smart","Smart eol");
win_EditorSetup->ck_smart->Hint =ini.key_get(sec,"H_ck_smart","Delete empty space at end of edited line");
win_EditorSetup->RadioGroup1->Caption=ini.key_get(sec,"C_RadioGroup1","");
win_EditorSetup->RadioGroup1->Hint =ini.key_get(sec,"H_RadioGroup1","");
win_EditorSetup->rb_eol1310->Caption=ini.key_get(sec,"C_rb_eol1310","CR LF");
win_EditorSetup->rb_eol1310->Hint =ini.key_get(sec,"H_rb_eol1310","EOL CR LF 13 10");
win_EditorSetup->rb_eol13->Caption=ini.key_get(sec,"C_rb_eol13","CR");
win_EditorSetup->rb_eol13->Hint =ini.key_get(sec,"H_rb_eol13","EOL CR 13");
win_EditorSetup->rb_eol10->Caption=ini.key_get(sec,"C_rb_eol10","LF");
win_EditorSetup->rb_eol10->Hint =ini.key_get(sec,"H_rb_eol10","EOL LF 10");
win_EditorSetup->txt_tabs->Caption=ini.key_get(sec,"C_txt_tabs","TAB:");
win_EditorSetup->txt_tabs->Hint =ini.key_get(sec,"H_txt_tabs","");
win_EditorSetup->pan_color->Caption=ini.key_get(sec,"C_pan_color","");
win_EditorSetup->pan_color->Hint =ini.key_get(sec,"H_pan_color","Pick color");
sec=ini.sec_getid("Global",true);
ini.close();
}
so in your main app form cpp include language.h but after its pointer is declared (after line like TForm1 *Form1; ) otherwise you will get access violation erros. Now call language_init with selected language ini file on any language change or Apps init like this:
if (Form1->Visible) language_init(ExtractFilePath(Application->ExeName)+"english.ini");
you need to specify full path if winapi is used for the ini.h !!!
Packages
Hard to say without MCVE. You most likely use non standard 3th party package and or have a bug in the code. My bet is you got memory leaks which tend to make a such errors time to time (especially on exit of app) try to use CodeGuard in the Project options which will show you runtime errors and allow you to debug them much better but it will not work for huge projects. Also if you are on older compiler or need to debug the memory leaks see these:
traceback a pointer in c++ code
BDS 2006 C++ hidden memory manager conflicts
They might solve your issues (even those you did not know of yet)
P.S.
If you're interested here is the main source for this tool (but without the supporting *.h files it will not work but you can see how it work in case you want to encode this on your own:
//--- Formular decoder storage class ver 2.40 -------------------------------
#ifndef _frm_decode_h
#define _frm_decode_h
//---------------------------------------------------------------------------
const int _frm_cmp_prop_none =0x00000000;
// language properties
const int _frm_cmp_prop_capt =0x00000001;
const int _frm_cmp_prop_text =0x00000002;
const int _frm_cmp_prop_hint =0x00000004;
// ini properties
const int _frm_cmp_prop_align =0x00010000;
const int _frm_cmp_prop_pos =0x00020000;
const int _frm_cmp_prop_check =0x00040000;
const int _frm_cmp_prop_down =0x00080000;
const int _frm_cmp_prop_page =0x00100000;
class _frm_component
{
public:
AnsiString name; // name in C++
int prop; // component class enum
int action; // if true do not save/load by language
};
_frm_component frm_component[]=
{
// standard
"TMenuItem" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_check,0,
"TLabel" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TEdit" ,_frm_cmp_prop_text|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TMemo" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TButton" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TCheckBox" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_check,0,
"TRadioButton" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_check,0,
"TListBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TComboBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TScrollBar" , _frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_pos ,0,
"TGroupBox" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TRadioGroup" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TPanel" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TAction" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
// additional
"TBitBtn" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TSpeedButton" ,_frm_cmp_prop_capt|_frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_down ,0,
"TStringGrid" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TCategoryButtons" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
// win32
"TProgressBar" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
// system
"TPaintBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
// win3.1
"TTabbedNotebook" , _frm_cmp_prop_hint|_frm_cmp_prop_align|_frm_cmp_prop_page ,0,
"TFileListBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TDirectoryListBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"TDriveComboBox" , _frm_cmp_prop_hint|_frm_cmp_prop_align ,0,
"",0,0
};
//---------------------------------------------------------------------------
class frm_decode
{
public:
AnsiString form; // formular class name in C++
struct _component:_frm_component
{
// language properties
AnsiString text,hint;
// init properties
AnsiString align; // -only for internal init purposes
AnsiString checked,down,position,left,top,width,height;
int skip; // znacky
void reset()
{
text="";
hint="";
align="alNone";
checked="False";
down="False";
position="1";
left="0";
top="0";
width="1";
height="1";
skip=0;
}
};
struct _message { AnsiString id,txt; };
List<_component> cmp;
List<_message> msg;
frm_decode(){ reset(); }
void reset() { form=""; cmp.reset(); msg.reset(); }
void load_form(AnsiString file); // load formular (file.h, file.dfm)
void load_msg(AnsiString file); // load messages (file.h, file.cpp)
void save_lang_ini(inifile &ini); // save language to ini
void save_init_ini(inifile &ini); // save initialisation to ini
void save_lang_cpp(AnsiString &txt,bool end=true); // save language loader (C++) to txt
void save_init_cpp(AnsiString &txt,bool end=true); // save init loader (C++) to txt
void save_exit_cpp(AnsiString &txt,bool end=true); // save exit saver (C++) to txt
};
//---------------------------------------------------------------------------
void frm_decode::load_form(AnsiString file)
{
// nepodporuje "/* ... */"
int hnd,adr,siz,i,j,l,e,_skip;
AnsiString lin,s,s0,ss;
_component a;
BYTE *txt;
reset();
// load list of components from: file.h
hnd=FileOpen(file+".h",fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
txt=new BYTE[siz];
if (txt==NULL) { FileClose(hnd); return; }
FileRead(hnd,txt,siz);
FileClose(hnd);
for (adr=0;adr<siz;)
{
lin=txt_load_lin(txt,siz,adr,true);
l=lin.Length();
for (_skip=0;i<=l;) // osetri zacky este pred odrezanim pokecu
{
s=str_load_str(lin,i,true);
if (s=="LANGUAGE_SKIP_CAPT") _skip|=_frm_cmp_prop_capt;
if (s=="LANGUAGE_SKIP_TEXT") _skip|=_frm_cmp_prop_text;
if (s=="LANGUAGE_SKIP_HINT") _skip|=_frm_cmp_prop_hint;
}
if (str_is_mask(lin,"*//*")) { s=lin; lin=""; for (i=1;i<l;i++) if ((s[i]=='/')&&(s[i+1]=='/')) break; else lin+=char(s[i]); l=lin.Length(); }
s=""; s0=""; e=0;
for (i=1;i<=l;)
{
s0=s;
s=str_load_str(lin,i,true);
if (s=="private:") // stop processing for non formular symbols
{
adr=siz; break;
}
if (!e) { e=1; continue; }
if (s0=="class")
{
form=s;
continue;
}
if (s!="")
for (j=0;;j++)
{
if (frm_component[j].name=="") break;
if (frm_component[j].name==s0)
{
a.reset();
a.name="";
if (s[1]!='*') s=" "+s;
for (int q=2;q<=s.Length();q++) a.name+=s[q];
a.prop=frm_component[j].prop;
a.action=0;
a.skip=_skip;
cmp.add(a);
break;
}
}
}
}
delete txt;
// load component properties from: file.dfm
int cmpid=-1;
hnd=FileOpen(file+".dfm",fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
txt=new BYTE[siz];
if (txt==NULL) { FileClose(hnd); return; }
FileRead(hnd,txt,siz);
FileClose(hnd);
for (adr=0;adr<siz;)
{
lin=txt_load_lin(txt,siz,adr,true);
l=lin.Length();
for (i=1;i<=l;i++) // if cutted '' line then continue
if (lin[i]>32)
{
if (lin[i]!='\'') i=-1;
break;
}
if (i<0) { s=""; s0=""; e=0; } // else new line
for (i=1;i<=l;)
{
int q=1;
if (s!="")
if (s[1]=='\'')
{
if (s[s.Length()]!='\'')
{
s+=" "+str_load_str(lin,i,true);
if (s[s.Length()]!='\'') continue;
}
q=0;
}
if (q)
{
int qqq=0; // handle cutted '' line
ss=str_load_str(lin,i,true);
if ((s0!="")&&((ss!="")&&(ss[1]=='\''))) { qqq=1; s+=ss; }
if ((s0!="")&&(ss=="+")) { qqq=1; }
if (!qqq) { s0=s; s=ss; }
if (s!="")
if (s[1]=='\'')
if (s[s.Length()]!='\'')
e=0;
}
/* // odstran ''
if (s!="")
if (s[1]=='\'')
if (s[s.Length()]=='\'')
{ ss=""; for (q=2;q<s.Length();q++) ss+=s[q]; s=ss; }
*/
if (!e) { e=1; continue; }
if (s!="")
if (s0=="object")
{
cmpid=-1;
if (s[s.Length()]==':')
{
AnsiString t=s;
s=""; for (j=1;j<t.Length();j++) s+=char(t[j]);
}
for (j=0;j<cmp.num;j++)
if (cmp[j].name==s)
{
cmpid=j;
break;
}
continue;
}
if (cmpid<0) continue;
char aa;
bool tt;
int ii,cc,ll=s.Length();
if (ll>0)
{
ss="";
tt=false;
for (ii=1;ii<=ll;ii++)
{
aa=s[ii];
if (aa=='\'') { tt=!tt; continue; }
if (tt) { ss+=aa; continue; }
if (aa!='#'){ ss+=aa; continue; }
cc=0;
for (ii++;ii<=ll;ii++)
{
aa=s[ii];
if (aa=='\'') { ii--; break; }
if (aa< '0') { ii--; break; }
if (aa> '9') { ii--; break; }
cc*=10; cc+=aa-'0';
}
ss+=char(cc);
}
s=ss;
}
if (s0=="Action" ) cmp[cmpid].action=1;
if (s0=="Caption" ) cmp[cmpid].text=s;
if (s0=="Text" ) cmp[cmpid].text=s;
if (s0=="Hint" ) cmp[cmpid].hint=s;
if (s0=="Align" ) cmp[cmpid].align=s;
if (s0=="Checked" ) cmp[cmpid].checked=s;
if (s0=="Down" ) cmp[cmpid].down=s;
if (s0=="Position") cmp[cmpid].position=s;
if (s0=="Left" ) cmp[cmpid].left=s;
if (s0=="Top" ) cmp[cmpid].top=s;
if (s0=="Width" ) cmp[cmpid].width=s;
if (s0=="Height" ) cmp[cmpid].height=s;
}
}
delete txt;
// load list of messages from: file.cpp
load_msg(file+".cpp");
}
//---------------------------------------------------------------------------
void frm_decode::load_msg(AnsiString file)
{
// nepodporuje "/* ... */"
int hnd,adr,siz,i,l,e;
AnsiString lin,s;
BYTE *txt;
hnd=FileOpen(file,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
txt=new BYTE[siz];
if (txt==NULL) { FileClose(hnd); return; }
FileRead(hnd,txt,siz);
FileClose(hnd);
e=msg.num;
for (adr=0;adr<siz;)
{
lin=txt_load_lin(txt,siz,adr,true);
l=lin.Length();
for (i=1;i<=l;)
{
s=str_load_str(lin,i,true);
if (s=="_msg_enum_beg")
for (;adr<siz;)
{
lin=txt_load_lin(txt,siz,adr,true);
l=lin.Length();
for (i=1;i<=l;)
{
s=str_load_str(lin,i,true);
if (s=="_msg_enum_end") { adr=siz; i=l+1; break; }
if (str_is_mask(s,"_msg*"))
{
_message m;
m.id=s;
m.txt="";
msg.add(m);
}
}
}
}
}
for (adr=0;adr<siz;)
{
lin=txt_load_lin(txt,siz,adr,true);
l=lin.Length();
for (i=1;i<=l;)
{
s=str_load_str(lin,i,true);
if (s=="_messages[_msg_enum_end+1]")
for (;adr<siz;)
{
if (e>=msg.num) break;
s="";
for (;adr<siz;adr++)
if (txt[adr]=='"')
{
adr++;
for (;adr<siz;adr++)
if (txt[adr]=='"')
{
adr++;
msg[e].txt=s;
e++;
break;
}
else s+=char(txt[adr]);
break;
}
}
}
}
delete txt;
}
//---------------------------------------------------------------------------
void frm_decode::save_lang_ini(inifile &ini)
{
int i,p,s,sec;
AnsiString key,val;
sec=ini.sec_getid(form,true);
for (i=0;i<msg.num;i++) ini.key_set(sec,msg[i].id,msg[i].txt);
for (i=0;i<cmp.num;i++)
{
if (cmp[i].action) continue;
key=cmp[i].name;
p=cmp[i].prop;
s=cmp[i].skip;
if ((int(p&_frm_cmp_prop_capt)!=0)&&(int(s&_frm_cmp_prop_capt)==0)) ini.key_set(sec,"C_"+key,cmp[i].text);
if ((int(p&_frm_cmp_prop_text)!=0)&&(int(s&_frm_cmp_prop_text)==0)) ini.key_set(sec,"T_"+key,cmp[i].text);
if ((int(p&_frm_cmp_prop_hint)!=0)&&(int(s&_frm_cmp_prop_hint)==0)) ini.key_set(sec,"H_"+key,cmp[i].hint);
}
}
//---------------------------------------------------------------------------
void frm_decode::save_init_ini(inifile &ini)
{
int i,p,sec;
AnsiString key,val;
sec=ini.sec_getid(form,true);
for (i=0;i<cmp.num;i++)
{
if (cmp[i].action) continue;
key=cmp[i].name;
p=cmp[i].prop;
if (cmp[i].align=="alLeft") ini.key_set(sec,"XS_"+key,cmp[i].width);
if (cmp[i].align=="alRight") ini.key_set(sec,"XS_"+key,cmp[i].width);
if (cmp[i].align=="alTop") ini.key_set(sec,"YS_"+key,cmp[i].height);
if (cmp[i].align=="alBottom") ini.key_set(sec,"YS_"+key,cmp[i].height);
if (cmp[i].align=="alClient")
{
}
if (cmp[i].align=="alNone")
{
/*
ini.key_set(sec,"X0_"+key,cmp[i].left);
ini.key_set(sec,"Y0_"+key,cmp[i].top);
ini.key_set(sec,"XS_"+key,cmp[i].width);
ini.key_set(sec,"YS_"+key,cmp[i].height);
*/
}
if (int(p&_frm_cmp_prop_check )!=0) ini.key_set(sec,"CHK_"+key,(cmp[i].checked=="True")?"1":"0");
if (int(p&_frm_cmp_prop_down )!=0) ini.key_set(sec,"DWN_"+key,(cmp[i].down =="True")?"1":"0");
if (int(p&_frm_cmp_prop_pos )!=0) ini.key_set(sec,"POS_"+key, cmp[i].position );
}
}
//---------------------------------------------------------------------------
void frm_decode::save_lang_cpp(AnsiString &txt,bool end)
{
int i,p,s;
AnsiString cpp,lin,key,frm,tabl,endl;
tabl=char(9); endl=char(13); endl+=char(10);
cpp="";
if (txt=="")
{
lin="";
lin+="#include <math.h>"+endl;
lin+="#include \"ini.h\""+endl+endl;
lin+="void language_init(AnsiString name)"+endl;
lin+=tabl+"{"+endl;
lin+=tabl+"int sec;"+endl;
lin+=tabl+"inifile ini;"+endl;
lin+=tabl+"ini.open(name);"+endl;
cpp+=lin;
}
frm=""; for (i=2;i<=form.Length();i++) frm+=form[i];
cpp+=endl;
cpp+=tabl+"sec=ini.sec_getid(\""+form+"\",true);"+endl;
for (i=0;i<msg.num;i++)
{
lin=tabl+"_messages[";
if (i<100) lin+=" ";
if (i<10) lin+=" ";
lin+=i;
lin+="]=ini.key_get(sec,\""+msg[i].id+"\",\""+msg[i].txt+"\");"+endl;
cpp+=lin;
}
for (i=0;i<cmp.num;i++)
{
if (cmp[i].action) continue;
key=cmp[i].name;
p=cmp[i].prop;
s=cmp[i].skip;
lin="";
if ((int(p&_frm_cmp_prop_capt)!=0)&&(int(s&_frm_cmp_prop_capt)==0)) lin+=tabl+frm+"->"+key+"->Caption=ini.key_get(sec,\"C_"+key+"\",\""+cmp[i].text+"\");"+endl;
if ((int(p&_frm_cmp_prop_text)!=0)&&(int(s&_frm_cmp_prop_text)==0)) lin+=tabl+frm+"->"+key+"->Text =ini.key_get(sec,\"T_"+key+"\",\""+cmp[i].text+"\");"+endl;
if ((int(p&_frm_cmp_prop_hint)!=0)&&(int(s&_frm_cmp_prop_hint)==0)) lin+=tabl+frm+"->"+key+"->Hint =ini.key_get(sec,\"H_"+key+"\",\""+cmp[i].hint+"\");"+endl;
cpp+=lin;
}
if (end)
{
lin=endl;
lin+=tabl+"ini.close();"+endl;
lin+=tabl+"}"+endl;
cpp+=lin;
}
txt+=cpp;
}
//---------------------------------------------------------------------------
void frm_decode::save_init_cpp(AnsiString &txt,bool end)
{
int i,p;
AnsiString cpp,lin,key,frm,tabl,endl,endlx;
tabl=char(9); endl=char(13); endl+=char(10); endlx="\\"+endl;
cpp="";
if (txt=="")
{
lin="";
lin+="//---------------------------------------------------------------------------"+endl;
lin+="#include <math.h>"+endl;
lin+="#include \"ini.h\""+endl;
lin+="//---------------------------------------------------------------------------"+endl;
lin+="#define _def_program_init(name) for(;;)"+endlx;
lin+=tabl+"{"+endlx;
lin+=tabl+"int sec;"+endlx;
lin+=tabl+"inifile ini;"+endlx;
lin+=tabl+"ini.open(name);"+endlx;
// lin+=tabl+"if (!ini.open(name)) break;"+endlx;
cpp+=lin;
}
frm=""; for (i=2;i<=form.Length();i++) frm+=form[i];
cpp+=tabl+"sec=ini.sec_getid(\""+form+"\",true);"+endlx;
for (i=0;i<cmp.num;i++)
{
if (cmp[i].action) continue;
key=cmp[i].name;
p=cmp[i].prop;
lin="";
if (cmp[i].align=="alLeft") lin+=tabl+frm+"->"+key+"->Width =ini.key_geti(sec,\"XS_"+key+"\","+cmp[i].width +");"+endlx;
if (cmp[i].align=="alRight") lin+=tabl+frm+"->"+key+"->Width =ini.key_geti(sec,\"XS_"+key+"\","+cmp[i].width +");"+endlx;
if (cmp[i].align=="alTop") lin+=tabl+frm+"->"+key+"->Height=ini.key_geti(sec,\"YS_"+key+"\","+cmp[i].height+");"+endlx;
if (cmp[i].align=="alBottom") lin+=tabl+frm+"->"+key+"->Height=ini.key_geti(sec,\"YS_"+key+"\","+cmp[i].height+");"+endlx;
if (cmp[i].align=="alClient")
{
}
if (cmp[i].align=="alNone")
{
/*
lin+=tabl+frm+"->"+key+"->Left =ini.key_geti(sec,\"X0_"+key+"\","+cmp[i].left +");"+endlx;
lin+=tabl+frm+"->"+key+"->Top =ini.key_geti(sec,\"Y0_"+key+"\","+cmp[i].top +");"+endlx;
lin+=tabl+frm+"->"+key+"->Width =ini.key_geti(sec,\"XS_"+key+"\","+cmp[i].width +");"+endlx;
lin+=tabl+frm+"->"+key+"->Height=ini.key_geti(sec,\"YS_"+key+"\","+cmp[i].height+");"+endlx;
*/
}
if (int(p&_frm_cmp_prop_check )!=0) lin+=tabl+frm+"->"+key+"->Checked =ini.key_geti(sec,\"CHK_"+key+"\","+cmp[i].checked +");"+endlx;
if (int(p&_frm_cmp_prop_down )!=0) lin+=tabl+frm+"->"+key+"->Down =ini.key_geti(sec,\"DWN_"+key+"\","+cmp[i].down +");"+endlx;
if (int(p&_frm_cmp_prop_pos )!=0) lin+=tabl+frm+"->"+key+"->Position=ini.key_geti(sec,\"POS_"+key+"\","+cmp[i].position+");"+endlx;
cpp+=lin;
}
if (end)
{
lin+=tabl+"ini.close();"+endlx;
lin+=tabl+"break;"+endlx;
lin+=tabl+"}"+endl;
cpp+=lin;
}
txt+=cpp;
}
//---------------------------------------------------------------------------
void frm_decode::save_exit_cpp(AnsiString &txt,bool end)
{
int i,p;
AnsiString cpp,lin,key,frm,tabl,endl,endlx;
tabl=char(9); endl=char(13); endl+=char(10); endlx="\\"+endl;
cpp="";
if (txt=="")
{
lin+="//---------------------------------------------------------------------------"+endl;
lin+="#define _def_program_exit(name) for(;;)"+endlx;
lin+=tabl+"{"+endlx;
lin+=tabl+"int sec;"+endlx;
lin+=tabl+"inifile ini;"+endlx;
lin+=tabl+"ini.open(name);"+endlx;
cpp+=lin;
}
frm=""; for (i=2;i<=form.Length();i++) frm+=form[i];
cpp+=tabl+"sec=ini.sec_getid(\""+form+"\",true);"+endlx;
for (i=0;i<cmp.num;i++)
{
if (cmp[i].action) continue;
key=cmp[i].name;
p=cmp[i].prop;
lin="";
if (cmp[i].align=="alLeft") lin+=tabl+"ini.key_set(sec,\"XS_"+key+"\","+frm+"->"+key+"->Width );"+endlx;
if (cmp[i].align=="alRight") lin+=tabl+"ini.key_set(sec,\"XS_"+key+"\","+frm+"->"+key+"->Width );"+endlx;
if (cmp[i].align=="alTop") lin+=tabl+"ini.key_set(sec,\"YS_"+key+"\","+frm+"->"+key+"->Height);"+endlx;
if (cmp[i].align=="alBottom") lin+=tabl+"ini.key_set(sec,\"YS_"+key+"\","+frm+"->"+key+"->Height);"+endlx;
if (cmp[i].align=="alClient")
{
}
if (cmp[i].align=="alNone")
{
/*
lin+=tabl+"ini.key_set(sec,\"X0_"+key+"\","+frm+"->"+key+"->Left );"+endlx;
lin+=tabl+"ini.key_set(sec,\"Y0_"+key+"\","+frm+"->"+key+"->Top );"+endlx;
lin+=tabl+"ini.key_set(sec,\"XS_"+key+"\","+frm+"->"+key+"->Width );"+endlx;
lin+=tabl+"ini.key_set(sec,\"YS_"+key+"\","+frm+"->"+key+"->Height);"+endlx;
*/
}
if (int(p&_frm_cmp_prop_check )!=0) lin+=tabl+"ini.key_set(sec,\"CHK_"+key+"\",int("+frm+"->"+key+"->Checked));"+endlx;
if (int(p&_frm_cmp_prop_down )!=0) lin+=tabl+"ini.key_set(sec,\"DWN_"+key+"\",int("+frm+"->"+key+"->Down ));"+endlx;
if (int(p&_frm_cmp_prop_pos )!=0) lin+=tabl+"ini.key_set(sec,\"POS_"+key+"\", "+frm+"->"+key+"->Position);"+endlx;
cpp+=lin;
}
if (end)
{
lin+=tabl+"ini.save(name);"+endlx;
lin+=tabl+"ini.close();"+endlx;
lin+=tabl+"break;"+endlx;
lin+=tabl+"}"+endl;
lin+="//---------------------------------------------------------------------------"+endl;
cpp+=lin;
}
txt+=cpp;
}
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------