c++:改造cmdline用於MSVC下的命令行參數解析


cmdline是一個輕量級的c++命令行參數解析工具,全部源碼只有一個cmdline.h頭文件,使用起來非常方便,關於如何使用它,不是本文討論的重點,本文要說的是如何解決cmdline在MSVC下不能編譯的問題。

你打開下面的cmdline項目托管地址只看首頁上的說明就會使用了,另外,如果你看英文比較吃力,這篇博文有全面詳細的中文翻譯《cmdline —— 輕量級的C++命令行解析庫》
cmdline項目托管地址Github:https://github.com/tanakh/cmdline

MSVC下編譯報錯

我在使用cmdline的時候,在gcc下編譯都正常,但在MSVC環境下,是不能編譯的,因為缺少頭文件cxxabi.h,這個頭文件MSVC是沒有的。開始發現這個問題后,打算繞過它,只在gcc下編譯,但因為其他原因逼得我只能想辦法在MSVC下編譯,所以就要解決這個問題。
怎么辦呢?我嘗試着注釋掉cmdline.h中#include <cxxabi.h>這一行代碼,發現只有一個地方報錯,就是下面的函數,也就是說#include <cxxabi.h>中的函數只在這一處被用到,貌似問題不那么大,所以我就繼續深入研究下去。

static inline std::string demangle(const std::string &name)
{
int status=0;
char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status);//用到cxxabi.h中的函數
std::string ret(p);
free(p);
return ret;
}
template <class T>
std::string readable_typename() // 獲取類型T名稱
{
return demangle(typeid(T).name());// 調用demangle返回類型T的真實名字
}

原因分析

C/C++語言在編譯以后,函數和數據類型的名字會被編譯器修改,改成編譯器內部的名字,這個名字會在鏈接的時候用到。如果用backtrace之類的函數打印堆棧時,顯示的就是被編譯器修改過的名字,比如說_Z3foov ,
數據類型名稱也是一樣,比如在gcc下double的類型內部名字就變成了’d’,

gcc下調用typeid(double).name()返回的結果是’d’ 。

那么這個函數或類型真實的名字是什么呢?
如何在運行時獲取類型或函數真實的名稱呢?
上面這個demangle函數中調用的abi::__cxa_demangle的作用就是將編譯器內部使用的名字反向轉換(demangle)為源代碼中定義的名字。
MSVC為什么沒有提供abi::__cxa_demangle類似的功能呢?因為MSVC編譯器編譯的代碼typeid返回的是demangle后的結果。
也就是說,在MSVC下typeid(double).name()返回的就是”double”。所以不需要類似的功能。

解決辦法

找到原因就好辦了,只需要用宏定義改造代碼就好了,只需要修改兩處代碼
1.修改#include部分

//當編譯器非gcc時,不包含cxxabi.h頭文件
#ifdef __GNUC__
#include <cxxabi.h>
#endif

2.修改demangle函數,當編譯器為MSVC時直接將輸入參數返回

static inline std::string demangle(const std::string &name)
{
#ifdef _MSC_VER
return name; // 為MSVC編譯器時直接返回name
#elif defined(__GNUC__)
// 為gcc編譯器時還調用原來的代碼
int status=0;
char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status);
std::string ret(p);
free(p);
return ret;
#else
// 其他不支持的編譯器需要自己實現這個方法
#error unexpected c complier (msc/gcc), Need to implement this method for demangle
#endif
}

參考資料

《如何識別C++編譯以后的函數名(demangle)》
《C++類名demangle》
《abi::__cxa_demangle》

修改后的cmdline完整代碼:

下面是修改后的cmdline完整代碼:

/*
Copyright (c) 2009, Hideyuki Tanaka
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


#pragma once

#include <iostream>
#include <sstream>
#include <vector>
#include <map>
#include <string>
#include <stdexcept>
#include <typeinfo>
#include <cstring>
#include <algorithm>
//當編譯器非gcc時,不包含cxxabi.h頭文件
#ifdef __GNUC__
#include <cxxabi.h>
#endif
#include <cstdlib>

namespace cmdline{

namespace detail{

template <typename Target, typename Source, bool Same>
class lexical_cast_t{
public:
static Target cast(const Source &arg){
Target ret;
std::stringstream ss;
if (!(ss<<arg && ss>>ret && ss.eof()))
throw std::bad_cast();

return ret;
}
};

template <typename Target, typename Source>
class lexical_cast_t<Target, Source, true>{
public:
static Target cast(const Source &arg){
return arg;
}
};

template <typename Source>
class lexical_cast_t<std::string, Source, false>{
public:
static std::string cast(const Source &arg){
std::ostringstream ss;
ss<<arg;
return ss.str();
}
};

template <typename Target>
class lexical_cast_t<Target, std::string, false>{
public:
static Target cast(const std::string &arg){
Target ret;
std::istringstream ss(arg);
if (!(ss>>ret && ss.eof()))
throw std::bad_cast();
return ret;
}
};

template <typename T1, typename T2>
struct is_same {
static const bool value = false;
};

template <typename T>
struct is_same<T, T>{
static const bool value = true;
};

template<typename Target, typename Source>
Target lexical_cast(const Source &arg)
{
return lexical_cast_t<Target, Source, detail::is_same<Target, Source>::value>::cast(arg);
}

static inline std::string demangle(const std::string &name)
{
#ifdef _MSC_VER
return name; // 為MSVC編譯器時直接返回name
#elif defined(__GNUC__)
// 為gcc編譯器時還調用原來的代碼
int status=0;
char *p=abi::__cxa_demangle(name.c_str(), 0, 0, &status);
std::string ret(p);
free(p);
return ret;
#else
// 其他不支持的編譯器需要自己實現這個方法
#error unexpected c complier (msc/gcc), Need to implement this method for demangle
#endif
}

template <class T>
std::string readable_typename()
{
return demangle(typeid(T).name());
}

template <class T>
std::string default_value(T def)
{
return detail::lexical_cast<std::string>(def);
}

template <>
inline std::string readable_typename<std::string>()
{
return "string";
}

} // detail

//-----

class cmdline_error : public std::exception {
public:
cmdline_error(const std::string &msg): msg(msg){}
~cmdline_error() throw() {}
const char *what() const throw() { return msg.c_str(); }
private:
std::string msg;
};

template <class T>
struct default_reader{
T operator()(const std::string &str){
return detail::lexical_cast<T>(str);
}
};

template <class T>
struct range_reader{
range_reader(const T &low, const T &high): low(low), high(high) {}
T operator()(const std::string &s) const {
T ret=default_reader<T>()(s);
if (!(ret>=low && ret<=high)) throw cmdline::cmdline_error("range_error");
return ret;
}
private:
T low, high;
};

template <class T>
range_reader<T> range(const T &low, const T &high)
{
return range_reader<T>(low, high);
}

template <class T>
struct oneof_reader{
T operator()(const std::string &s){
T ret=default_reader<T>()(s);
if (std::find(alt.begin(), alt.end(), ret)==alt.end())
throw cmdline_error("");
return ret;
}
void add(const T &v){ alt.push_back(v); }
private:
std::vector<T> alt;
};

template <class T>
oneof_reader<T> oneof(T a1)
{
oneof_reader<T> ret;
ret.add(a1);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
ret.add(a6);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
ret.add(a6);
ret.add(a7);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
ret.add(a6);
ret.add(a7);
ret.add(a8);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
ret.add(a6);
ret.add(a7);
ret.add(a8);
ret.add(a9);
return ret;
}

template <class T>
oneof_reader<T> oneof(T a1, T a2, T a3, T a4, T a5, T a6, T a7, T a8, T a9, T a10)
{
oneof_reader<T> ret;
ret.add(a1);
ret.add(a2);
ret.add(a3);
ret.add(a4);
ret.add(a5);
ret.add(a6);
ret.add(a7);
ret.add(a8);
ret.add(a9);
ret.add(a10);
return ret;
}

//-----

class parser{
public:
parser(){
}
~parser(){
for (std::map<std::string, option_base*>::iterator p=options.begin();
p!=options.end(); p++)
delete p->second;
}

void add(const std::string &name,
char short_name=0,
const std::string &desc=""){
if (options.count(name)) throw cmdline_error("multiple definition: "+name);
options[name]=new option_without_value(name, short_name, desc);
ordered.push_back(options[name]);
}

template <class T>
void add(const std::string &name,
char short_name=0,
const std::string &desc="",
bool need=true,
const T def=T()){
add(name, short_name, desc, need, def, default_reader<T>());
}

template <class T, class F>
void add(const std::string &name,
char short_name=0,
const std::string &desc="",
bool need=true,
const T def=T(),
F reader=F()){
if (options.count(name)) throw cmdline_error("multiple definition: "+name);
options[name]=new option_with_value_with_reader<T, F>(name, short_name, need, def, desc, reader);
ordered.push_back(options[name]);
}

void footer(const std::string &f){
ftr=f;
}

void set_program_name(const std::string &name){
prog_name=name;
}

bool exist(const std::string &name) const {
if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name);
return options.find(name)->second->has_set();
}

template <class T>
const T &get(const std::string &name) const {
if (options.count(name)==0) throw cmdline_error("there is no flag: --"+name);
const option_with_value<T> *p=dynamic_cast<const option_with_value<T>*>(options.find(name)->second);
if (p==NULL) throw cmdline_error("type mismatch flag '"+name+"'");
return p->get();
}

const std::vector<std::string> &rest() const {
return others;
}

bool parse(const std::string &arg){
std::vector<std::string> args;

std::string buf;
bool in_quote=false;
for (std::string::size_type i=0; i<arg.length(); i++){
if (arg[i]=='\"'){
in_quote=!in_quote;
continue;
}

if (arg[i]==' ' && !in_quote){
args.push_back(buf);
buf="";
continue;
}

if (arg[i]=='\\'){
i++;
if (i>=arg.length()){
errors.push_back("unexpected occurrence of '\\' at end of string");
return false;
}
}

buf+=arg[i];
}

if (in_quote){
errors.push_back("quote is not closed");
return false;
}

if (buf.length()>0)
args.push_back(buf);

for (size_t i=0; i<args.size(); i++)
std::cout<<"\""<<args[i]<<"\""<<std::endl;

return parse(args);
}

bool parse(const std::vector<std::string> &args){
int argc=static_cast<int>(args.size());
std::vector<const char*> argv(argc);

for (int i=0; i<argc; i++)
argv[i]=args[i].c_str();

return parse(argc, &argv[0]);
}

bool parse(int argc, const char * const argv[]){
errors.clear();
others.clear();

if (argc<1){
errors.push_back("argument number must be longer than 0");
return false;
}
if (prog_name=="")
prog_name=argv[0];

std::map<char, std::string> lookup;
for (std::map<std::string, option_base*>::iterator p=options.begin();
p!=options.end(); p++){
if (p->first.length()==0) continue;
char initial=p->second->short_name();
if (initial){
if (lookup.count(initial)>0){
lookup[initial]="";
errors.push_back(std::string("short option '")+initial+"' is ambiguous");
return false;
}
else lookup[initial]=p->first;
}
}

for (int i=1; i<argc; i++){
if (strncmp(argv[i], "--", 2)==0){
const char *p=strchr(argv[i]+2, '=');
if (p){
std::string name(argv[i]+2, p);
std::string val(p+1);
set_option(name, val);
}
else{
std::string name(argv[i]+2);
if (options.count(name)==0){
errors.push_back("undefined option: --"+name);
continue;
}
if (options[name]->has_value()){
if (i+1>=argc){
errors.push_back("option needs value: --"+name);
continue;
}
else{
i++;
set_option(name, argv[i]);
}
}
else{
set_option(name);
}
}
}
else if (strncmp(argv[i], "-", 1)==0){
if (!argv[i][1]) continue;
char last=argv[i][1];
for (int j=2; argv[i][j]; j++){
last=argv[i][j];
if (lookup.count(argv[i][j-1])==0){
errors.push_back(std::string("undefined short option: -")+argv[i][j-1]);
continue;
}
if (lookup[argv[i][j-1]]==""){
errors.push_back(std::string("ambiguous short option: -")+argv[i][j-1]);
continue;
}
set_option(lookup[argv[i][j-1]]);
}

if (lookup.count(last)==0){
errors.push_back(std::string("undefined short option: -")+last);
continue;
}
if (lookup[last]==""){
errors.push_back(std::string("ambiguous short option: -")+last);
continue;
}

if (i+1<argc && options[lookup[last]]->has_value()){
set_option(lookup[last], argv[i+1]);
i++;
}
else{
set_option(lookup[last]);
}
}
else{
others.push_back(argv[i]);
}
}

for (std::map<std::string, option_base*>::iterator p=options.begin();
p!=options.end(); p++)
if (!p->second->valid())
errors.push_back("need option: --"+std::string(p->first));

return errors.size()==0;
}

void parse_check(const std::string &arg){
if (!options.count("help"))
add("help", '?', "print this message");
check(0, parse(arg));
}

void parse_check(const std::vector<std::string> &args){
if (!options.count("help"))
add("help", '?', "print this message");
check(args.size(), parse(args));
}

void parse_check(int argc, char *argv[]){
if (!options.count("help"))
add("help", '?', "print this message");
check(argc, parse(argc, argv));
}

std::string error() const{
return errors.size()>0?errors[0]:"";
}

std::string error_full() const{
std::ostringstream oss;
for (size_t i=0; i<errors.size(); i++)
oss<<errors[i]<<std::endl;
return oss.str();
}

std::string usage() const {
std::ostringstream oss;
oss<<"usage: "<<prog_name<<" ";
for (size_t i=0; i<ordered.size(); i++){
if (ordered[i]->must())
oss<<ordered[i]->short_description()<<" ";
}

oss<<"[options] ... "<<ftr<<std::endl;
oss<<"options:"<<std::endl;

size_t max_width=0;
for (size_t i=0; i<ordered.size(); i++){
max_width=std::max(max_width, ordered[i]->name().length());
}
for (size_t i=0; i<ordered.size(); i++){
if (ordered[i]->short_name()){
oss<<" -"<<ordered[i]->short_name()<<", ";
}
else{
oss<<" ";
}

oss<<"--"<<ordered[i]->name();
for (size_t j=ordered[i]->name().length(); j<max_width+4; j++)
oss<<' ';
oss<<ordered[i]->description()<<std::endl;
}
return oss.str();
}

private:

void check(int argc, bool ok){
if ((argc==1 && !ok) || exist("help")){
std::cerr<<usage();
exit(0);
}

if (!ok){
std::cerr<<error()<<std::endl<<usage();
exit(1);
}
}

void set_option(const std::string &name){
if (options.count(name)==0){
errors.push_back("undefined option: --"+name);
return;
}
if (!options[name]->set()){
errors.push_back("option needs value: --"+name);
return;
}
}

void set_option(const std::string &name, const std::string &value){
if (options.count(name)==0){
errors.push_back("undefined option: --"+name);
return;
}
if (!options[name]->set(value)){
errors.push_back("option value is invalid: --"+name+"="+value);
return;
}
}

class option_base{
public:
virtual ~option_base(){}

virtual bool has_value() const=0;
virtual bool set()=0;
virtual bool set(const std::string &value)=0;
virtual bool has_set() const=0;
virtual bool valid() const=0;
virtual bool must() const=0;

virtual const std::string &name() const=0;
virtual char short_name() const=0;
virtual const std::string &description() const=0;
virtual std::string short_description() const=0;
};

class option_without_value : public option_base {
public:
option_without_value(const std::string &name,
char short_name,
const std::string &desc)
:nam(name), snam(short_name), desc(desc), has(false){
}
~option_without_value(){}

bool has_value() const { return false; }

bool set(){
has=true;
return true;
}

bool set(const std::string &){
return false;
}

bool has_set() const {
return has;
}

bool valid() const{
return true;
}

bool must() const{
return false;
}

const std::string &name() const{
return nam;
}

char short_name() const{
return snam;
}

const std::string &description() const {
return desc;
}

std::string short_description() const{
return "--"+nam;
}

private:
std::string nam;
char snam;
std::string desc;
bool has;
};

template <class T>
class option_with_value : public option_base {
public:
option_with_value(const std::string &name,
char short_name,
bool need,
const T &def,
const std::string &desc)
: nam(name), snam(short_name), need(need), has(false)
, def(def), actual(def) {
this->desc=full_description(desc);
}
~option_with_value(){}

const T &get() const {
return actual;
}

bool has_value() const { return true; }

bool set(){
return false;
}

bool set(const std::string &value){
try{
actual=read(value);
has=true;
}
catch(const std::exception &){
return false;
}
return true;
}

bool has_set() const{
return has;
}

bool valid() const{
if (need && !has) return false;
return true;
}

bool must() const{
return need;
}

const std::string &name() const{
return nam;
}

char short_name() const{
return snam;
}

const std::string &description() const {
return desc;
}

std::string short_description() const{
return "--"+nam+"="+detail::readable_typename<T>();
}

protected:
std::string full_description(const std::string &desc){
return
desc+" ("+detail::readable_typename<T>()+
(need?"":" [="+detail::default_value<T>(def)+"]")
+")";
}

virtual T read(const std::string &s)=0;

std::string nam;
char snam;
bool need;
std::string desc;

bool has;
T def;
T actual;
};

template <class T, class F>
class option_with_value_with_reader : public option_with_value<T> {
public:
option_with_value_with_reader(const std::string &name,
char short_name,
bool need,
const T def,
const std::string &desc,
F reader)
: option_with_value<T>(name, short_name, need, def, desc), reader(reader){
}

private:
T read(const std::string &s){
return reader(s);
}

F reader;
};

std::map<std::string, option_base*> options;
std::vector<option_base*> ordered;
std::string ftr;

std::string prog_name;
std::vector<std::string> others;

std::vector<std::string> errors;
};

} // cmdline

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com