1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-27 14:57:44 +00:00

Removed old database class entirely, some minor cleanups

This commit is contained in:
Mordred 2017-10-28 22:47:25 +02:00
parent dbdf3c0016
commit 42df7b7d13
6 changed files with 40 additions and 720 deletions

View file

@ -1,379 +0,0 @@
#include "Database.h"
#include <stdio.h>
#include <stdarg.h>
#include <thread>
#include <chrono>
#include <sstream>
#include "src/servers/Server_Common/Logging/Logger.h"
extern Core::Logger g_log;
namespace Core {
namespace Db {
QueryResult::QueryResult( MYSQL_RES *res, uint32_t fields, uint32_t rows )
: m_result( res ),
m_fieldCount( fields ),
m_rowCount( rows )
{
m_currentRow = new Field[fields];
}
QueryResult::~QueryResult()
{
mysql_free_result( m_result );
delete[] m_currentRow;
}
bool QueryResult::nextRow()
{
MYSQL_ROW row = mysql_fetch_row( m_result );
auto length = mysql_fetch_lengths( m_result );
if( row == nullptr )
return false;
for( uint32_t i = 0; i < m_fieldCount; ++i )
{
m_currentRow[i].setValue( row[i] );
m_currentRow[i].setLength( 0 );
if( length )
m_currentRow[i].setLength( length[i] );
}
return true;
}
Field *QueryResult::fetch()
{
return m_currentRow;
}
uint32_t QueryResult::getFieldCount() const
{
return m_fieldCount;
}
uint32_t QueryResult::getRowCount() const
{
return m_rowCount;
}
Database::Database()
{
m_port = 0;
m_counter = 0;
m_pConnections = nullptr;
m_connectionCount = -1; // Not connected.
}
Database::~Database()
{
for( int32_t i = 0; i < m_connectionCount; ++i )
{
if( m_pConnections[i].conn != nullptr )
mysql_close( m_pConnections[i].conn );
}
delete[] m_pConnections;
}
bool Database::initialize( const DatabaseParams& params )
{
uint32_t i;
MYSQL * temp;
MYSQL * temp2;
my_bool my_true = true;
g_log.info( "Database: Connecting to " + params.hostname + ", database " + params.databaseName + "..." );
m_pConnections = new DatabaseConnection[params.connectionCount];
for( i = 0; i < params.connectionCount; ++i )
{
temp = mysql_init( nullptr );
if( mysql_options( temp, MYSQL_SET_CHARSET_NAME, "utf8" ) )
g_log.error( "Database: Could not set utf8 character set." );
if( mysql_options( temp, MYSQL_OPT_RECONNECT, &my_true ) )
g_log.error( "Database: MYSQL_OPT_RECONNECT could not be set, "
"connection drops may occur but will be counteracted." );
temp2 = mysql_real_connect( temp,
params.hostname.c_str(),
params.username.c_str(),
params.password.c_str(),
params.databaseName.c_str(),
params.port,
nullptr,
0 );
if( temp2 == nullptr )
{
g_log.fatal( "Database: Connection failed due to: `%s`" + std::string( mysql_error( temp ) ) );
return false;
}
m_pConnections[i].conn = temp2;
}
return true;
}
uint64_t Database::getNextUId()
{
execute( std::string( "INSERT INTO uniqueiddata( IdName ) VALUES( 'NOT_SET' );" ) );
auto res = query( "SELECT LAST_INSERT_ID();" );
if( !res )
return 0;
Db::Field *field = res->fetch();
return field[0].get< uint64_t >();
}
DatabaseConnection * Database::getFreeConnection()
{
uint32_t i = 0;
while( true )
{
DatabaseConnection * con = &m_pConnections[( ( i++ ) % m_connectionCount )];
if( con->lock.try_lock() )
return con;
// sleep every 20 iterations, otherwise this can cause 100% cpu if the db link goes dead
if( !( i % 20 ) )
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}
}
boost::shared_ptr< QueryResult > Database::query( const std::string& QueryString )
{
// Send the query
boost::shared_ptr< QueryResult > qResult( nullptr );
DatabaseConnection * con = getFreeConnection();
if( sendQuery( con, QueryString.c_str(), false ) )
qResult = boost::shared_ptr< QueryResult >( storeQueryResult( con ) );
con->lock.unlock();
return qResult;
}
bool Database::execute( const std::string& QueryString )
{
DatabaseConnection * con = getFreeConnection();
bool Result = sendQuery( con, QueryString, false );
con->lock.unlock();
return Result;
}
void Database::freeQueryResult( QueryResult * p )
{
delete p;
}
std::string Database::escapeString( std::string Escape )
{
char a2[16384] = { 0 };
DatabaseConnection * con = getFreeConnection();
const char * ret;
if( mysql_real_escape_string( con->conn, a2, Escape.c_str(), ( uint32_t ) Escape.length() ) == 0 )
ret = Escape.c_str();
else
ret = a2;
con->lock.unlock();
return std::string( ret );
}
void Database::escapeLongString( const char * str, uint32_t len, std::stringstream& out )
{
char a2[65536 * 3] = { 0 };
DatabaseConnection * con = getFreeConnection();
mysql_real_escape_string( con->conn, a2, str, ( uint32_t ) len );
out.write( a2, ( std::streamsize )strlen( a2 ) );
con->lock.unlock();
}
std::string Database::escapeString( const char * esc, DatabaseConnection * con )
{
char a2[16384] = { 0 };
const char * ret;
if( mysql_real_escape_string( con->conn, a2, ( char* ) esc, ( uint32_t ) strlen( esc ) ) == 0 )
ret = esc;
else
ret = a2;
return std::string( ret );
}
bool Database::sendQuery( DatabaseConnection *con, const std::string &sql, bool Self )
{
int32_t result = mysql_query( con->conn, sql.c_str() );
if( result > 0 )
{
if( Self == false && handleError( con, mysql_errno( con->conn ) ) )
{
// Re-send the query, the connection was successful.
// The true on the end will prevent an endless loop here, as it will
// stop after sending the query twice.
result = sendQuery(con, sql, true);
}
else
{
g_log.error( "Database: query failed " + std::string( mysql_error( con->conn ) ) );
g_log.error( "\t" + std::string( sql ) );
}
}
return ( result == 0 ? true : false );
}
bool Database::handleError( DatabaseConnection *con, uint32_t ErrorNumber )
{
// Handle errors that should cause a reconnect to the CDatabase.
switch( ErrorNumber ) {
case 2006: // Mysql server has gone away
case 2008: // Client ran out of memory
case 2013: // Lost connection to sql server during query
case 2055: // Lost connection to sql server - system error
{
// Let's instruct a reconnect to the db when we encounter these errors.
return reconnect( con );
}
}
return false;
}
QueryResult * Database::storeQueryResult( DatabaseConnection * con )
{
QueryResult* res;
MYSQL_RES* pRes = mysql_store_result( con->conn );
auto uRows = mysql_affected_rows( con->conn );
auto uFields = mysql_field_count( con->conn );
if( uRows == 0 || uFields == 0 || pRes == 0 )
{
if( pRes != nullptr )
mysql_free_result( pRes );
return nullptr;
}
res = new QueryResult( pRes,
static_cast< uint32_t >( uFields ),
static_cast< uint32_t >( uRows ) );
res->nextRow();
return res;
}
bool Database::reconnect( DatabaseConnection * conn )
{
MYSQL * temp;
MYSQL * temp2;
temp = mysql_init( nullptr );
temp2 = mysql_real_connect( temp,
m_hostname.c_str(),
m_username.c_str(),
m_password.c_str(),
m_databaseName.c_str(),
m_port,
nullptr,
0 );
if( temp2 == nullptr )
{
g_log.error( "Database: Could not reconnect to database -> " + std::string( mysql_error( temp ) ) );
mysql_close( temp );
return false;
}
if( conn->conn != nullptr )
mysql_close( conn->conn );
conn->conn = temp;
return true;
}
void Database::cleanupLibs()
{
mysql_library_end();
}
void Database::shutdown()
{
for( int32_t i = 0; i < m_connectionCount; ++i )
{
if( m_pConnections[i].conn != nullptr )
{
mysql_close( m_pConnections[i].conn );
m_pConnections[i].conn = nullptr;
}
}
}
const std::string &Database::getHostName()
{
return m_hostname;
}
const std::string &Database::getDatabaseName()
{
return m_databaseName;
}
void Field::setValue( char *value )
{
m_pValue = value;
}
void Field::setLength( uint32_t value )
{
m_size = value;
}
std::string Field::getString() const
{
if( !m_pValue )
return "";
return std::string( m_pValue );
}
void Field::getBinary( char *dstBuf, uint16_t size ) const
{
if( m_pValue )
memcpy( dstBuf, m_pValue, size );
else
dstBuf = nullptr;
}
float Field::getFloat() const
{
return m_pValue ? static_cast< float >( atof( m_pValue ) ) : 0;
}
bool Field::getBool() const
{
return m_pValue ? atoi( m_pValue ) > 0 : false;
}
uint32_t Field::getLength() const
{
return m_size;
}
}
}

View file

@ -1,160 +0,0 @@
#ifndef _DATABASE_H
#define _DATABASE_H
#include <mutex>
#include <stdio.h>
#include <mysql.h>
#include <boost/shared_ptr.hpp>
#include <string.h>
namespace Core {
namespace Db {
class Field
{
public:
// set value
void setValue( char* value );
void setLength( uint32_t value );
std::string getString() const;
void getBinary( char* dstBuf, uint16_t size ) const;
float getFloat() const;
bool getBool() const;
template< class T >
__inline T get() const
{
if( !m_pValue )
return 0;
return static_cast< T >( atol( m_pValue ) );
}
uint64_t getUInt64()
{
if( m_pValue )
{
#ifdef _WIN32
uint64_t value;
sscanf( m_pValue, "%I64d", &value );
return value;
#else
uint64_t value;
sscanf( m_pValue, "%Lu", &value );
return value;
#endif
}
else
return 0;
}
uint32_t getLength() const;
private:
char *m_pValue;
uint32_t m_size;
};
class QueryResult
{
public:
QueryResult( MYSQL_RES *res, uint32_t fields, uint32_t rows );
~QueryResult();
bool nextRow();
Field* fetch();
uint32_t getFieldCount() const;
uint32_t getRowCount() const;
protected:
uint32_t m_fieldCount;
uint32_t m_rowCount;
Field *m_currentRow;
MYSQL_RES *m_result;
};
struct DatabaseConnection
{
std::mutex lock;
MYSQL *conn;
};
struct DatabaseParams
{
std::string hostname;
std::string username;
std::string password;
std::string databaseName;
uint16_t port;
uint32_t bufferSize;
uint32_t connectionCount;
};
class Database
{
public:
Database();
virtual ~Database();
bool initialize( const DatabaseParams& params );
void shutdown();
boost::shared_ptr< QueryResult > query( const std::string& QueryString );
bool waitExecuteNA( const char* QueryString );//Wait For Request Completion
bool execute( const char* QueryString, ... );
bool execute( const std::string& QueryString );
const std::string& getHostName();
const std::string& getDatabaseName();
std::string escapeString( std::string Escape );
void escapeLongString( const char * str, uint32_t len, std::stringstream& out );
std::string escapeString( const char * esc, DatabaseConnection *con );
void freeQueryResult( QueryResult * p );
DatabaseConnection *getFreeConnection();
void cleanupLibs();
/* database is killed off manually. */
void onShutdown() {}
uint64_t getNextUId();
protected:
// actual query function
bool sendQuery( DatabaseConnection *con, const std::string &sql, bool Self );
QueryResult * storeQueryResult( DatabaseConnection * con );
bool handleError( DatabaseConnection *conn, uint32_t ErrorNumber );
bool reconnect( DatabaseConnection *conn );
DatabaseConnection *m_pConnections;
uint32_t m_counter;
///////////////////////////////
int32_t m_connectionCount;
// For reconnecting a broken connection
std::string m_hostname;
std::string m_username;
std::string m_password;
std::string m_databaseName;
uint32_t m_port;
};
}
}
#endif

View file

@ -1,12 +1,11 @@
#include "LinkshellMgr.h"
#include <Server_Common/Database/Database.h>
#include <Server_Common/Logging/Logger.h>
#include <boost/make_shared.hpp>
#include <servers/Server_Common/Database/DatabaseDef.h>
#include "Linkshell.h"
extern Core::Logger g_log;
extern Core::Db::Database g_database;
Core::LinkshellMgr::LinkshellMgr()
{
@ -16,47 +15,47 @@ Core::LinkshellMgr::LinkshellMgr()
bool Core::LinkshellMgr::loadLinkshells()
{
auto res = g_database.query( "SELECT LinkshellId, MasterCharacterId, CharacterIdList, "
"LinkshellName, LeaderIdList, InviteIdList "
"FROM infolinkshell "
"ORDER BY LinkshellId ASC;" );
auto res = g_charaDb.query( "SELECT LinkshellId, MasterCharacterId, CharacterIdList, "
"LinkshellName, LeaderIdList, InviteIdList "
"FROM infolinkshell "
"ORDER BY LinkshellId ASC;" );
// we do not really need linkshells to function...
if( !res )
return true;
Db::Field *field = res->fetch();
do
while( res->next() )
{
uint64_t linkshellId = field[0].get< uint64_t >();
uint32_t masterId = field[1].get< uint32_t >();
std::string name = field[3].getString();
uint64_t linkshellId = res->getUInt64( 1 );
uint32_t masterId = res->getUInt( 2 );
std::string name = res->getString( 4 );
auto func = []( std::set< uint64_t >& outList, Db::Field * pField )
auto func = []( std::set< uint64_t >& outList, std::vector< char >& inData )
{
if( pField->getLength() )
if( inData.size() )
{
std::vector< uint64_t > list( pField->getLength() / 8 );
pField->getBinary( reinterpret_cast< char * >( &list[0] ), pField->getLength() );
std::vector< uint64_t > list( inData.size() / 8 );
outList.insert( list.begin(), list.end() );
}
};
std::set< uint64_t > members;
func( members, &field[2] );
std::vector< char > membersBin;
membersBin = res->getBlobVector( 3 );
func( members, membersBin );
std::set< uint64_t > leaders;
func( members, &field[4] );
std::vector< char > leadersBin;
leadersBin = res->getBlobVector( 5 );
func( members, leadersBin );
std::set< uint64_t > invites;
func( members, &field[5] );
std::vector< char > invitesBin;
invitesBin = res->getBlobVector( 6 );
func( members, invitesBin );
auto lsPtr = boost::make_shared< Linkshell >( linkshellId, name, masterId, members, leaders, invites );
m_linkshellIdMap[linkshellId] = lsPtr;
m_linkshellNameMap[name] = lsPtr;
} while( res->nextRow() );
}
return true;

View file

@ -1,7 +1,7 @@
#include <src/servers/Server_Common/Logging/Logger.h>
#include <chaiscript/chaiscript.hpp>
#include <src/servers/Server_Common/Script/ChaiscriptStdLib.h>
#include <Server_Common/Logging/Logger.h>
#include <Server_Common/Script/ChaiscriptStdLib.h>
#include "src/servers/Server_Zone/Script/ScriptManager.h"
@ -15,11 +15,6 @@
#include "src/servers/Server_Zone/StatusEffect/StatusEffect.h"
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/format.hpp>
#include <boost/foreach.hpp>
extern Core::Logger g_log;

View file

@ -1,35 +1,29 @@
#include <thread>
#include <chrono>
#include <boost/lexical_cast.hpp>
#include "ServerZone.h"
#include <src/servers/Server_Common/Version.h>
#include <src/servers/Server_Common/Logging/Logger.h>
#include <src/servers/Server_Common/Config/XMLConfig.h>
#include <src/servers/Server_Common/Database/Database.h>
#include <src/servers/Server_Common/Version.h>
#include <MySqlBase.h>
#include <Connection.h>
#include <Statement.h>
#include <ResultSet.h>
#include <PreparedStatement.h>
#include <PreparedResultSet.h>
#include <src/servers/Server_Common/Network/Connection.h>
#include <src/servers/Server_Common/Network/Hive.h>
#include <src/servers/Server_Common/Network/Acceptor.h>
#include <Server_Common/Network/Connection.h>
#include <Server_Common/Network/Hive.h>
#include <src/servers/Server_Common/Exd/ExdData.h>
#include <src/servers/Server_Common/Network/PacketContainer.h>
#include <Server_Common/Exd/ExdData.h>
#include <Server_Common/Network/PacketContainer.h>
#include <Server_Common/Database/DbLoader.h>
#include <Server_Common/Database/CharaDbConnection.h>
#include <Server_Common/Database/DbWorkerPool.h>
#include <Server_Common/Database/PreparedStatement.h>
#include "src/servers/Server_Zone/Network/GameConnection.h"
#include "Network/GameConnection.h"
#include "Session.h"
#include "src/servers/Server_Zone/Zone/ZoneMgr.h"
#include "Zone/ZoneMgr.h"
#include "src/servers/Server_Zone/DebugCommand/DebugCommandHandler.h"
#include "DebugCommand/DebugCommandHandler.h"
#include "Script/ScriptManager.h"
#include "Linkshell/LinkshellMgr.h"
@ -38,15 +32,9 @@
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <Server_Common/Database/DbLoader.h>
#include <Server_Common/Database/CharaDbConnection.h>
#include <Server_Common/Database/DbWorkerPool.h>
#include <Server_Common/Database/PreparedStatement.h>
#include <thread>
Core::Logger g_log;
Core::Db::Database g_database;
Core::DebugCommandHandler g_gameCommandMgr;
Core::Scripting::ScriptManager g_scriptMgr;
Core::Data::ExdData g_exdData;
@ -54,7 +42,7 @@ Core::ZoneMgr g_zoneMgr;
Core::LinkshellMgr g_linkshellMgr;
Core::Db::DbWorkerPool< Core::Db::CharaDbConnection > g_charaDb;
Core::ServerZone::ServerZone( const std::string& configPath, uint16_t serverId )
Core::ServerZone::ServerZone( const std::string& configPath )
: m_configPath( configPath ),
m_bRunning( true )
{
@ -95,7 +83,7 @@ bool Core::ServerZone::registerBnpcTemplate( std::string templateName, uint32_t
Core::Entity::BattleNpcTemplatePtr Core::ServerZone::getBnpcTemplate( std::string templateName )
{
auto it = m_bnpcTemplates.find(templateName);
auto it = m_bnpcTemplates.find( templateName );
if (it != m_bnpcTemplates.end())
return nullptr;
@ -190,131 +178,8 @@ bool Core::ServerZone::loadSettings( int32_t argc, char* argv[] )
if( !loader.initDbs() )
return false;
// execute() runs asynchronous
g_charaDb.execute( "INSERT INTO zoneservers ( id, ip, port ) VALUES ( 101, '127.0.0.1', 54555);" );
g_charaDb.execute( "DELETE FROM zoneservers WHERE id = 101" );
// query runs synchronous
auto res = g_charaDb.query( "SELECT id,ip,port FROM zoneservers" );
while( res->next() )
{
g_log.info( "id: " + std::to_string( res->getUInt( "id" ) ) );
g_log.info( "ip: " + res->getString( "ip" ) );
g_log.info( "port: " + std::to_string( res->getUInt( "port" ) ) );
}
//stmt->setUInt( 1, 245 );
//stmt->setString( 2, "12.12.12.12" );
//stmt->setUInt( 3, 3306 );
//CharacterDatabase.execute( stmt );
try
{
// // bunch of test cases for db wrapper
boost::shared_ptr< Mysql::MySqlBase > base( new Mysql::MySqlBase() );
g_log.info( base->getVersionInfo() );
Mysql::optionMap options;
options[ MYSQL_OPT_RECONNECT ] = "1";
auto con = base->connect( "127.0.0.1", "root", "", options, 3306 );
// if( con->getAutoCommit() )
// g_log.info( "autocommit active" );
// con->setAutoCommit( false );
// if( !con->getAutoCommit() )
// g_log.info( "autocommit inactive" );
// con->setAutoCommit( true );
// if( con->getAutoCommit() )
// g_log.info( "autocommit active" );
con->setSchema( "sapphire" );
// boost::scoped_ptr< Mysql::Statement > stmt( con->createStatement() );
// bool t1 = stmt->execute( "DELETE FROM zoneservers WHERE id = 101" );
// t1 = stmt->execute( "INSERT INTO zoneservers ( id, ip, port ) VALUES ( 101, '127.0.0.1', 54555);" );
// // t1 = stmt->execute( "INSERT INTO zoneservers ( id, ip, port ) VALUES ( 101, '127.0.0.1', 54555);" ); // throws duplicate entry
// t1 = stmt->execute( "DELETE FROM zoneservers WHERE id = 101" );
// t1 = stmt->execute( "INSERT INTO zoneservers ( id, ip, port ) VALUES ( 101, '127.0.0.1', 54555);" );
// //t1 = stmt->execute( "DELETE FROM zoneservers WHERE id = 101" );
// //boost::scoped_ptr< Mysql::Statement > stmt1( con->createStatement() );
// //bool t2 = stmt1->execute( "INSERT INTO BLARGH!" ); // throws error
// boost::scoped_ptr< Mysql::Statement > stmt2( con->createStatement() );
// boost::scoped_ptr< Mysql::ResultSet > res( stmt2->executeQuery( "SELECT id,ip,port FROM zoneservers" ) );
// while( res->next() )
// {
// g_log.info( "id: " + std::to_string( res->getUInt( "id" ) ) );
// g_log.info( "ip: " + res->getString( "ip" ) );
// g_log.info( "port: " + std::to_string( res->getUInt( "port" ) ) );
// // alternatively ( slightly faster )
// // g_log.info( "id: " + std::to_string( res->getUInt( 1 ) ) );
// // g_log.info( "ip: " + res->getString( 2 ) );
// // g_log.info( "port: " + std::to_string( res->getUInt( 3 ) ) );
// }
// // binary data test
// boost::scoped_ptr< Mysql::Statement > stmt3( con->createStatement() );
// boost::scoped_ptr< Mysql::ResultSet > res1( stmt3->executeQuery( "SELECT * FROM charabase" ) );
// while( res1->next() )
// {
// auto blob = res1->getBlobVector( "Customize" );
// }
// pstmt2->setInt( 1, 1001 );
// pstmt2->execute();
// boost::scoped_ptr< Mysql::PreparedStatement > pstmt( con->prepareStatement( "INSERT INTO zoneservers ( id, ip, port ) VALUES ( ?, ?, ?);" ) );
// pstmt->setInt( 1, 1001 );
// pstmt->setString( 2, "123.123.123.123" );
// pstmt->setInt( 3, 5454 );
// pstmt->execute();
// pstmt->setInt( 1, 1021 );
// pstmt->setString( 2, "173.173.173.173" );
// pstmt->setInt( 3, 5151 );
// pstmt->execute();
// boost::scoped_ptr< Mysql::PreparedStatement > pstmt1( con->prepareStatement( "DELETE FROM zoneservers WHERE id = ?" ) );
// pstmt->setInt( 1, 1021 );
// pstmt->execute();
// pstmt->setInt( 1, 1001 );
// pstmt->execute();
}
catch( std::runtime_error e )
{
g_log.error( e.what() );
}
Db::DatabaseParams params;
params.bufferSize = 16384;
params.connectionCount = 3;
params.databaseName = m_pConfig->getValue< std::string >( "Settings.General.Mysql.Database", "sapphire" );
params.hostname = m_pConfig->getValue< std::string >( "Settings.General.Mysql.Host", "127.0.0.1" );
params.password = m_pConfig->getValue< std::string >( "Settings.General.Mysql.Pass", "" );
params.port = m_pConfig->getValue< uint16_t >( "Settings.General.Mysql.Port", 3306 );
params.username = m_pConfig->getValue< std::string >( "Settings.General.Mysql.Username", "root" );
if( !g_database.initialize( params ) )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 5000 ) );
return false;
}
m_port = m_pConfig->getValue< uint16_t >( "Settings.General.ListenPort", 54992 );
m_ip = m_pConfig->getValue< std::string >( "Settings.General.ListenIp", "0.0.0.0" );;
m_ip = m_pConfig->getValue< std::string >( "Settings.General.ListenIp", "0.0.0.0" );
return true;
}

View file

@ -15,7 +15,7 @@ namespace Core {
class ServerZone
{
public:
ServerZone( const std::string& configPath, uint16_t serverId = 0 );
ServerZone( const std::string& configPath );
~ServerZone();
void run( int32_t argc, char* argv[] );