C&C++——C函数与C++函数相互调用问题

【转载】C&C++——C函数与C++函数相互调用问题

C C++相互调用

在项目中融合C和C++有时是不可避免的,在调用对方的功能函数的时候,或许会出现这样那样的问题,但只要我的C代码和我的C++代码分别都能成功编译,那其他就不是问题。近来在主程序是C语言,而调用C++功能函数的时候,C++的*.h头文件都能找到,功能函数也都定义了,最重要的是,单独编译C++的时候,完全没有问题,但当用主程序的C调用C++的功能函数时,总是提示该函数未定义(undefined),这里分析问题的出处便是混合调用出现的问题了。

关键点在这里:我们就靠在C++的*.h和*.cpp的头尾加入下面代码才得以解决问题。

#ifdef __cplusplus

extern "C" {

#endif

//一段代码

#ifdef __cplusplus

}

#endif

这样的代码到底是什么意思呢?首先,__cplusplus是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{和}处理其中的代码。

要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生的函数名字的处理是不一样的. 目的就是主要实现C与C++的相互调用问题。

c.h的实现

#ifndef _c_h_

#define _c_h_

#ifdef __cplusplus

extern "C" {

#endif

void C_fun();

#ifdef __cplusplus

}

#endif

#endif

-----------------------------------

c.c的实现

#include "c.h"

void C_fun()

{

}

------------------------------------

在cpp.cpp中调用c.c中的C_fun()

cpp.cpp的实现

#include "c.h"

int main()

{

C_fun()

}

其中__cplusplus是C++编译器的保留宏定义.就是说C++编译器认为这个宏已经定义了.

所以关键是extern "C" {}    //这里告诉你的是__cplusplus不是最重要的,重要的是extern "C" {}

extern "C"是告诉C++编译器件括号里的东东是按照C的obj文件格式编译的,要连接的话按照C的命名规则去找.

==========================

那么C中是如何调用C++中的函数cpp_fun()呢?这就是最上面我提到的问题,即用C写主程序,然后调用C++功能函数。

因为先有C后有C++, 所以只能从C++的代码中考虑了.

加入C++中的函数或变量有可能被C中的文件调用,则应该这样写,也是用extern "C"{}

不过是代码中要加,头文件也要加,因为可能是C++中也调用

--------------------------------------

cpp.h的实现

#ifndef _c_h_

#define _c_h_

#ifdef __cplusplus

extern "C" {

#endif

void CPP_fun();

#ifdef __cplusplus

}

#endif

#endif

.-------------------------------------------

Cpp.cpp的实现

extern "C" {    //告诉C+++编译器,扩号里按照C的命名规则编译

void CPP_fun()

{

.....

}

}

C和C++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

下面摘录一篇文章关于extern  "C"的说明;

在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数

extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

//模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );

#endif

在模块B中引用该函数:

//模块B实现文件  moduleB.cpp

#i nclude "moduleA.h"

foo(2,3);

实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

加extern "C"声明后的编译和连接方式

加extern "C"声明后,模块A的头文件变为:

//模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern "C" int foo( int x, int y );

#endif

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。

明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:

extern "C"的惯用法

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"

{

#i nclude "cExample.h"

}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(int x,int y);

#endif

/* c语言实现文件:cExample.c */

#i nclude "cExample.h"

int add( int x, int y )

{

return x + y;

}

// c++实现文件,调用add:cppFile.cpp

extern "C"

{

#i nclude "cExample.h"

}

int main(int argc, char* argv[])

{

add(2,3);

return 0;

}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" {  }。

(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。

C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h

#ifndef CPP_EXAMPLE_H

#define CPP_EXAMPLE_H

extern "C" int add( int x, int y );

#endif

//C++实现文件 cppExample.cpp

#i nclude "cppExample.h"

int add( int x, int y )

{

return x + y;

}

/* C实现文件 cFile.c

/* 这样会编译出错:#i nclude "cExample.h" */

extern int add( int x, int y );

int main( int argc, char* argv[] )

{

add( 2, 3 );

return 0;

}

C 函数中调用 C++函数

举例:

// cpp.h

#ifndef  __cpp_h__

#define  __cpp_h__

class  class1 {

class1();

~class1();

public:

int  I;

int  J;

int  getI(void);

};

#endif

// end file

// cpp.cpp

#i nclude "stdafx.h"

#i nclude 

#i nclude  "cpp.h"

#i nclude  "c.h"

using namespace  std;      // 打开标准库名字空间

class1::class1()

{}

class1::~class1()

{}

int  class1::getI(void)

{

return  I++;

}

// 按 C 调用方式编译下面函数

extern "C"

int  get_class1_I(struct1 * p)

{

class1 * pClass1 = (class1 *)p;

cout << "c++: " << pClass1->getI() << endl;

return  pClass1->getI();

}

// end file

// c.h

#ifndef  __c_h__

#define  __c_h__

#ifdef  __cplusplus

extern "C" {

#endif

typedef struct {

int  i;            // 与 class1 类中变量一致

int  j;

}struct1;

#ifdef  __cplusplus

}

#endif

#endif

// end file

// c.c

#i nclude 

#i nclude  "c.h"

extern  int  get_class1_I(void * p);

struct1  s;

int  main(void)

{

printf ("c: %d\n", get_class1_I(&s));

printf ("c: %d\n", get_class1_I(&s));

return 0;

}

// end file

参考了eCos中的混和编程实现方式(http://sources.redhat.com/ecos/)。

本例在ADS 1.2中编译通过,执行结果正确。

VC++中编译时,C.C文件编译选项中选择 Not using precompile headers。





C中如何调用C++函数、类内函数

在C中如何调用C++函数的问题,简单回答是将函数用extern "C"声明,当被问及如何将类内成员函数声明时,一时语塞,后来网上查了下,网上有一翻译C++之父的文章可以作为解答,遂拿来Mark一下。

该翻译的文档Bjarne Stroustrup的原文链接地址是  http://www.research.att.com/~bs/bs_faq2.html#callCpp

Linux C调用C++库

转自:http://blog.linuxgem.org/tzc/show/551.html

调用C++函数库,一般不能直接调用,需要将C++库转换成C接口输出,方可以使用C调用

将 C++ 函数声明为``extern "C"''(在你的 C++ 代码里做这个声明),然后调用它(在你的 C 或者 C++ 代码里调用)。例如:

// C++ code:

extern "C" void f(int);

void f(int i)

{

// ...

}

然后,你可以这样使用 f():

/* C code: */

void f(int);

void cc(int i)

{

f(i);

/* ... */

}

当然,这招只适用于非成员函数。如果你想要在 C 里调用成员函数(包括虚函数),则需要提供一个简单的包装(wrapper)。例如:

// C++ code:

class C

{

// ...

virtual double f(int);

};

extern "C" double call_C_f(C* p, int i) // wrapper function

{

return p->f(i);

}

然后,你就可以这样调用 C::f():

/* C code: */

double call_C_f(struct C* p, int i);

void ccc(struct C* p, int i)

{

double d = call_C_f(p,i);

/* ... */

}

如果你想在 C 里调用重载函数,则必须提供不同名字的包装,这样才能被 C 代码调用。例如:

// C++ code:

void f(int);

void f(double);

extern "C" void f_i(int i) { f(i); }

extern "C" void f_d(double d) { f(d); }

然后,你可以这样使用每个重载的 f():

/* C code: */

void f_i(int);

void f_d(double);

void cccc(int i,double d)

{

f_i(i);

f_d(d);

/* ... */

}

注意,这些技巧也适用于在 C 里调用 C++ 类库,即使你不能(或者不想)修改 C++ 头文件。

再看下面的例子:

•aa.cxx

#include "aa.h"

int sample::method()

{

cout<<"method is called!\n";

}

•aa.h

#include

using namespace std;

class sample

{

public:

int method();

};

将上面的两个文件生成动态库libaa.so放到 /usr/lib目录下,编译命令如下

sudo g++ -fpic -shared -g -o /usr/lib/libaa.so aa.cxx -I ./

由于在C中不能识别类,所以要将上面类的成员函数封装成C接口函数输出,下面进行封装,将输出接口转换成C接口。

•mylib.cxx

#include "add.h"

#ifndef _cplusplus

#define _cplusplus

#include "mylib.h"

#endif

int myfunc()

{

sample ss;

ss.method();

return 0;

}

•mylib.h

#ifdef _cplusplus

extern "C"

{

#endif

    int myfunc();

#ifdef _cplusplus

}

#endif

在 linux下,gcc编译器并没用变量_cplusplus来区分是C代码还是C++代码,如果使用gcc编译器,这里我们可以自己定义一个变量 _cplusplus用于区分C和C++代码,所以在mylib.cxx中定义了一个变量_cplusplus用于识别是否需要“extern "C"”将函数接口封装成C接口。但是如果使用g++编译器则不需要专门定义_cplusplus,编译命令如下:

g++ -fpic -shared -g -o mylib.so mylib.cxx -la -I ./

•main.c

#include

#include

#include "mylib.h"

int

main()

{

int (*dlfunc)();

void *handle;      //定义一个句柄

handle = dlopen("./mylib.so", RTLD_LAZY);//获得库句柄

dlfunc = dlsym(handle, "myfunc"); //获得函数入口

(*dlfunc)();

dlclose(handle);

return 0;

}

编译命令如下:

gcc -o main main.c ./mylib.so -ldl

下面就可以执行了。

需要说明的是,由于main.c 和 mylib.cxx都需要包含mylib.h,并且要将函数myfunc封装成C接口函数输出需要“extern "C"”,而C又不识别“extern "C"”,所以需要定义_cplusplus来区别处理mylib.h中的函数myfunc。

在main.c的main函数中直接调用myfunc()函数也能执行,这里介绍的是常规调用库函数的方法。





C++和C语言函数相互调用

1.引言

C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程 式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支 持函数的重载,C++对全局函数的处理方式与C有明显的不同。

2.从标准头文件说起

某企业曾经给出如下的一道面试题:

面试题为什么标准头文件都有类似以下的结构?

#ifndef __INCvxWorksh

#define __INCvxWorksh

#ifdef __cplusplus

extern "C"{

#endif

#ifdef __cplusplus

}                                                                                                  #endif                                                                                            #endif

分析

显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

那么

#ifdef __cplusplus

extern "C" {

#endif

#ifdef __cplusplus

}

#endif                                                                                            的作用又是什么呢?

3.深层揭密extern "C"

extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。

被extern "C"限定的函数或变量是extern类型的;

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:    extern int a;

仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函 数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A 编译生成的目标代码中找到此函数。

与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;

未加extern “C”声明时的编译方式

首先看看C++中对类似C的函数是怎样编译的。

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:      void foo( int x, int y );

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

·

同样 地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译 器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

// 模块A头文件 moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );        #endif

在模块B中引用该函数:

// 模块B实现文件 moduleB.cpp

#include "moduleA.h"                                                                                                                                      foo(2,3);

实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

加extern "C"声明后的编译和连接方式

加extern "C"声明后,模块A的头文件变为:

// 模块A头文件 moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern "C" int foo( int x, int y );

#endif

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。

如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):

实现C++与C及其它语言的混合编程。

明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。

4.extern "C"的惯用法

(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"

{

#include "cExample.h"

}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(int x,int y);

#endif

#include "cExample.h"

int add( int x, int y )

{

return x + y;

}

// c++实现文件,调用add:cppFile.cpp

extern "C"

{

#include "cExample.h"

}

int main(int argc, char* argv[])

{

add(2,3);

return 0;

}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }

(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅在C文件中将C++中定义的extern "C"函数声明为extern类型。

笔者编写的C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h

#ifndef CPP_EXAMPLE_H

#define CPP_EXAMPLE_H

extern "C" int add( int x, int y );

#endif

//C++实现文件 cppExample.cpp

#include "cppExample.h"

int add( int x, int y )

{

return x + y;

}

extern int add( int x, int y );

int main( int argc, char* argv[] )

{

add( 2, 3 );

return 0;

}

如果深入理解了第3节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。

如侵删。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容