【GO】Golang/C++混合編程 - 進(jìn)階二

文章系列
【GO】Golang/C++混合編程 - SWIG
【GO】Golang/C++混合編程 - 初識(shí)
【GO】Golang/C++混合編程 - 入門
【GO】Golang/C++混合編程 - 基礎(chǔ)
【GO】Golang/C++混合編程 - 進(jìn)階一
【GO】Golang/C++混合編程 - 進(jìn)階二
【GO】Golang/C++混合編程 - 實(shí)戰(zhàn)

Golang/C++混合編程

C++類包裝

CGO 是 C 語(yǔ)言和 GO 語(yǔ)言之間的橋梁膀息,原則上無(wú)法直接支持 C++ 的類尼斧。CGO 不支持 C++ 語(yǔ)法的根本原因是 C++ 至今為止還沒(méi)有一個(gè)二進(jìn)制接口規(guī)范(ABI)皿哨。但是 C++ 是兼容 C 語(yǔ)言雄可,所以我們可以通過(guò)增加一組 C 語(yǔ)言函數(shù)接口作為 C++ 類和 CGO 之間的橋梁,這樣就可以間接地實(shí)現(xiàn) C++ 和 GO 之間的互聯(lián)澎埠。當(dāng)然瞬铸,因?yàn)?CGO 只支持 C 語(yǔ)言中值類型的數(shù)據(jù)類型台妆,所以我們是無(wú)法直接使用 C++ 的引用參數(shù)等特性的。

C++ 類到 Go 語(yǔ)言對(duì)象

實(shí)現(xiàn) C++ 類到 GO 語(yǔ)言對(duì)象的包裝需要經(jīng)過(guò)以下幾個(gè)步驟:首先是用純 C 函數(shù)接口包裝該 C++ 類拘荡;其次是通過(guò) CGO 將純 C 函數(shù)接口映射到 GO 函數(shù)臼节;最后是做一個(gè) GO 包裝對(duì)象,將 C++ 類到方法用 GO 對(duì)象的方法實(shí)現(xiàn)珊皿。

C++ 類

// my_buffer.h
#include <string>

// MyBuffer類實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的字符串緩沖區(qū)网缝,它有一個(gè)指定大小的構(gòu)造函數(shù),一個(gè)析構(gòu)函數(shù)蟋定,一個(gè)獲取緩沖區(qū)大小的函數(shù)和一個(gè)獲取緩沖區(qū)數(shù)據(jù)的函數(shù)粉臊。
struct MyBuffer {
    std::string* s_;

    MyBuffer(int size) {
        this->s_ = new std::string(size, char('\0'));
    }

    ~MyBuffer() {
        delete this->s_;
    }

    int Size() const {
        return this->s_->size();
    }

    char* Data() {
        return (char*)this->s_->data();
    }
};
// my_buffer.cpp
#include "my_buffer.h"

// use in c++
int main() {
    auto pBuf = new MyBuffer(1024);
    auto data = pBuf->Data();
    auto size = pBuf->Size();
    delete pBuf;
}

C 接口封裝

// my_buffer.c
#include "my_buffer.h"

// use in c
int main() {
    MyBuffer* pBuf = NewMyBuffer(1024);
    char* data = MyBuffer_Data(pBuf);
    auto size = MyBuffer_Size(pBuf);
    DeleteMyBuffer(pBuf);
}
// my_buffer_capi.h
typedef struct MyBuffer_T MyBuffer_T;
MyBuffer_T* NewMyBuffer(int size);
void DeleteMyBuffer(MyBuffer_T* p);
char* MyBuffer_Data(MyBuffer_T* p);
int MyBuffer_Size(MyBuffer_T* p);

// my_buffer_capi.cc
#include "./my_buffer.h"
extern "C" {
    #include "./my_buffer_capi.h"
}
struct MyBuffer_T: MyBuffer {
    MyBuffer_T(int size): MyBuffer(size) {}
    ~MyBuffer_T() {}
};
MyBuffer_T* NewMyBuffer(int size) {
    auto p = new MyBuffer_T(size);
    return p;
}
void DeleteMyBuffer(MyBuffer_T* p) {
    delete p;
}
char* MyBuffer_Data(MyBuffer_T* p) {
    return p->Data();
}
int MyBuffer_Size(MyBuffer_T* p) {
    return p->Size();
}

其中my_buffer_capi.h是用于 CGO 的橋接文件,必須是采用 C 語(yǔ)言規(guī)范的名字修飾規(guī)則驶兜。在 C++ 源文件包含時(shí)需要用extern "C"語(yǔ)句說(shuō)明扼仲。另外MyBuffer_T的實(shí)現(xiàn)只是從MyBuffer繼承的類,這樣可以簡(jiǎn)化包裝代碼的實(shí)現(xiàn)抄淑。同時(shí)和 CGO 通信時(shí)必須通過(guò)MyBuffer_T指針屠凶,我們無(wú)法將具體的實(shí)現(xiàn)暴露給 CGO,因?yàn)閷?shí)現(xiàn)中包含了 C++ 特有的語(yǔ)法肆资,CGO 無(wú)法識(shí)別 C++ 特性矗愧。

C 接口函數(shù)轉(zhuǎn)為 GO 函數(shù)

// my_buffer_capi.go
package main
/*
#cgo CXXFLAGS: -std=c++11
#include "my_buffer_capi.h"
*/
import "C"
type cgo_MyBuffer_T C.MyBuffer_T
func cgo_NewMyBuffer(size int) *cgo_MyBuffer_T {
    p := C.NewMyBuffer(C.int(size))
    return (*cgo_MyBuffer_T)(p)
}
func cgo_DeleteMyBuffer(p *cgo_MyBuffer_T) {
    C.DeleteMyBuffer((*C.MyBuffer_T)(p))
}
func cgo_MyBuffer_Data(p *cgo_MyBuffer_T) *C.char {
    return C.MyBuffer_Data((*C.MyBuffer_T)(p))
}
func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int {
    return C.MyBuffer_Size((*C.MyBuffer_T)(p))
}

其中-std=c++11是告訴編譯器使用 C++11 標(biāo)準(zhǔn),因?yàn)?C++11 才支持extern "C"郑原,否則編譯器會(huì)認(rèn)為extern "C"是無(wú)效的唉韭。

為了區(qū)分夜涕,我們?cè)?GO 中的每個(gè)類型和函數(shù)名稱前面增加了cgo_前綴,比如cgo_MyBuffer_T是對(duì)應(yīng) C 中的MyBuffer_T類型属愤。

為了處理簡(jiǎn)單女器,在包裝純 C 函數(shù)到 GO 函數(shù)時(shí),除了cgo_MyBuffer_T類型外春塌,對(duì)輸入?yún)?shù)和返回值的基礎(chǔ)類型晓避,我們依然是用的 C 語(yǔ)言的類型。

包裝為Go對(duì)象

在將純 C 接口包裝為 GO 函數(shù)之后只壳,我們就可以很容易地基于包裝的 GO 函數(shù)構(gòu)造出 GO 對(duì)象來(lái)俏拱。因?yàn)?code>cgo_MyBuffer_T是從 C 語(yǔ)言空間導(dǎo)入的類型,它無(wú)法定義自己的方法吼句,因此我們構(gòu)造了一個(gè)新的MyBuffer類型锅必,里面的成員持有cgo_MyBuffer_T指向的 C 語(yǔ)言緩存對(duì)象。

// my_buffer.go
package main

import "unsafe"
type MyBuffer struct {
    cptr *cgo_MyBuffer_T
}
func NewMyBuffer(size int) *MyBuffer {
    return &MyBuffer{
        cptr: cgo_NewMyBuffer(size),
    }
}
func (p *MyBuffer) Delete() {
    cgo_DeleteMyBuffer(p.cptr)
}
func (p *MyBuffer) Data() []byte {
    data := cgo_MyBuffer_Data(p.cptr)
    size := cgo_MyBuffer_Size(p.cptr)
    return ((*[1 << 31]byte)(unsafe.Pointer(data)))[0:int(size):int(size)]
}

同時(shí)惕艳,因?yàn)?GO 語(yǔ)言的切片本身含有長(zhǎng)度信息搞隐,我們將cgo_MyBuffer_Datacgo_MyBuffer_Size兩個(gè)函數(shù)合并為MyBuffer.Data方法,它返回一個(gè)對(duì)應(yīng)底層C語(yǔ)言緩存空間的切片远搪。

// main.go
package main

//#include <stdio.h>
import "C"
import "unsafe"
func main() {
    buf := NewMyBuffer(1024)
    defer buf.Delete()
    copy(buf.Data(), []byte("hello\x00"))
    C.puts((*C.char)(unsafe.Pointer(&(buf.Data()[0]))))
}

例子中劣纲,我們創(chuàng)建了一個(gè)1024字節(jié)大小的緩存,然后通過(guò)copy函數(shù)向緩存填充了一個(gè)字符串谁鳍。為了方便 C 語(yǔ)言字符串函數(shù)處理癞季,我們?cè)谔畛渥址哪J(rèn)用\0表示字符串結(jié)束。最后我們直接獲取緩存的底層數(shù)據(jù)指針倘潜,用 C 語(yǔ)言的puts函數(shù)打印緩存的內(nèi)容绷柒。

GO 語(yǔ)言對(duì)象到 C++ 類

要實(shí)現(xiàn) GO 語(yǔ)言對(duì)象到 C++ 類的包裝需要經(jīng)過(guò)以下幾個(gè)步驟:首先是將 GO 對(duì)象映射為一個(gè) id;然后基于 id 導(dǎo)出對(duì)應(yīng)的 C 接口函數(shù)涮因;最后是基于 C 接口函數(shù)包裝為 C++ 對(duì)象废睦。

一個(gè) GO 對(duì)象示例

package main

type Person struct {
    name string
    age  int
}
func NewPerson(name string, age int) *Person {
    return &Person{
        name: name,
        age:  age,
    }
}
func (p *Person) Set(name string, age int) {
    p.name = name
    p.age = age
}
func (p *Person) Get() (name string, age int) {
    return p.name, p.age
}

映射為 C 接口

// person_capi.h

#include <stdint.h>

typedef uintptr_t person_handle_t;
person_handle_t person_new(char* name, int age);
void person_delete(person_handle_t p);
void person_set(person_handle_t p, char* name, int age);
char* person_get_name(person_handle_t p, char* buf, int size);
int person_get_age(person_handle_t p);

通過(guò) CGO 導(dǎo)出 C 函數(shù),輸入?yún)?shù)和返回值類型都不支持const修飾养泡,同時(shí)也不支持可變參數(shù)的函數(shù)類型嗜湃。

// person_capi.go

package main

//#include "./person_capi.h"
import "C"

import "unsafe"

//export person_new
func person_new(name *C.char, age C.int) C.person_handle_t {
    id := NewObjectId(NewPerson(C.GoString(name), int(age)))
    return C.person_handle_t(id)
}
//export person_delete
func person_delete(h C.person_handle_t) {
    ObjectId(h).Free()
}
//export person_set
func person_set(h C.person_handle_t, name *C.char, age C.int) {
    p := ObjectId(h).Get().(*Person)
    p.Set(C.GoString(name), int(age))
}
//export person_get_name
func person_get_name(h C.person_handle_t, buf *C.char, size C.int) *C.char {
    p := ObjectId(h).Get().(*Person)
    name, _ := p.Get()
    n := int(size) - 1
    bufSlice := ((*[1 << 31]byte)(unsafe.Pointer(buf)))[0:n:n]
    n = copy(bufSlice, []byte(name))
    bufSlice[n] = 0
    return buf
}
//export person_get_age
func person_get_age(h C.person_handle_t) C.int {
    p := ObjectId(h).Get().(*Person)
    _, age := p.Get()
    return C.int(age)
}

在創(chuàng)建 GO 對(duì)象后,我們通過(guò)NewObjectId將 GO 對(duì)應(yīng)映射為 id澜掩。然后將 id 強(qiáng)制轉(zhuǎn)義為person_handle_t類型返回净蚤。其它的接口函數(shù)則是根據(jù)person_handle_t所表示的 id,讓根據(jù) id 解析出對(duì)應(yīng)的 GO 對(duì)象输硝。

封裝C++對(duì)象

有了 C 接口之后封裝 C++ 對(duì)象就比較簡(jiǎn)單了今瀑。常見(jiàn)的做法是新建一個(gè)Person類,里面包含一個(gè)person_handle_t類型的成員對(duì)應(yīng)真實(shí)的 GO 對(duì)象,然后在Person類的構(gòu)造函數(shù)中通過(guò) C 接口創(chuàng)建 GO 對(duì)象橘荠,在析構(gòu)函數(shù)中通過(guò) C 接口釋放 GO 對(duì)象屿附。

// person.h

extern "C" {
    #include "./person_capi.h"
}

struct Person {
    person_handle_t goobj_;
    Person(const char* name, int age) {
        this->goobj_ = person_new((char*)name, age);
    }
    ~Person() {
        person_delete(this->goobj_);
    }
    void Set(char* name, int age) {
        person_set(this->goobj_, name, age);
    }
    char* GetName(char* buf, int size) {
        return person_get_name(this->goobj_ buf, size);
    }
    int GetAge() {
        return person_get_age(this->goobj_);
    }
}
// person.cpp

#include "person.h"
#include <stdio.h>

int main() {
    auto p = new Person("gopher", 10);
    char buf[64];
    char* name = p->GetName(buf, sizeof(buf)-1);
    int age = p->GetAge();
    printf("%s, %d years old.\n", name, age);
    delete p;
    return 0;
}

封裝C++對(duì)象改進(jìn)

在前面的封裝 C++ 對(duì)象的實(shí)現(xiàn)中,每次通過(guò)new創(chuàng)建一個(gè)Person實(shí)例需要進(jìn)行兩次內(nèi)存分配:一次是針對(duì) C++ 版本的Person哥童,再一次是針對(duì) GO 語(yǔ)言版本的Person挺份。其實(shí) C++ 版本的Person內(nèi)部只有一個(gè)person_handle_t類型的 id,用于映射 GO 對(duì)象贮懈。我們完全可以將person_handle_t直接當(dāng)中 C++ 對(duì)象來(lái)使用匀泊。

// person.h

extern "C" {
    #include "./person_capi.h"
}
struct Person {
    static Person* New(const char* name, int age) {
        return (Person*)person_new((char*)name, age);
    }
    void Delete() {
        person_delete(person_handle_t(this));
    }
    void Set(char* name, int age) {
        person_set(person_handle_t(this), name, age);
    }
    char* GetName(char* buf, int size) {
        return person_get_name(person_handle_t(this), buf, size);
    }
    int GetAge() {
        return person_get_age(person_handle_t(this));
    }
};

我們?cè)?code>Person類中增加了一個(gè)叫New靜態(tài)成員函數(shù),用于創(chuàng)建新的Person實(shí)例朵你。在New函數(shù)中通過(guò)調(diào)用person_new來(lái)創(chuàng)建Person實(shí)例各聘,返回的是person_handle_t類型的 id,我們將其強(qiáng)制轉(zhuǎn)型作為Person*類型指針?lè)祷芈找健T谄渌某蓡T函數(shù)中躲因,我們通過(guò)將this指針再反向轉(zhuǎn)型為person_handle_t類型,然后通過(guò) C 接口調(diào)用對(duì)應(yīng)的函數(shù)忌傻。

靜態(tài)庫(kù)/動(dòng)態(tài)庫(kù)

CGO 在使用 C/C++ 資源的時(shí)候一般有三種形式:直接使用源碼大脉;鏈接靜態(tài)庫(kù);鏈接動(dòng)態(tài)庫(kù)水孩。直接使用源碼就是在import "C"之前的注釋部分包含 C 代碼镰矿,或者在當(dāng)前包中包含 C/C++ 源文件。鏈接靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的方式比較類似俘种,都是通過(guò)在LDFLAGS選項(xiàng)指定要鏈接的庫(kù)方式鏈接衡怀。

使用 C 靜態(tài)庫(kù)

構(gòu)造一個(gè) C 靜態(tài)庫(kù)

// number/number.h
int number_add_mod(int a, int b, int mod);

// number/number.c
#include "number.h"
int number_add_mod(int a, int b, int mod) {
    return (a+b)%mod;
}
cd ./number
gcc -c -o number.o number.c
ar rcs libnumber.a number.o
// main.go

package main
//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
    fmt.Println(C.number_add_mod(10, 5, 12))
}

其中有兩個(gè)#cgo命令,分別是編譯和鏈接參數(shù)安疗。CFLAGS通過(guò)-I./numbernumber庫(kù)對(duì)應(yīng)頭文件所在的目錄加入頭文件檢索路徑。LDFLAGS通過(guò)-L${SRCDIR}/number將編譯后number靜態(tài)庫(kù)所在目錄加為鏈接庫(kù)檢索路徑够委,-lnumber表示鏈接libnumber.a靜態(tài)庫(kù)荐类。需要注意的是,在鏈接部分的檢索路徑不能使用相對(duì)路徑(C/C++代碼的鏈接程序所限制)茁帽,我們必須通過(guò) CGO 特有的${SRCDIR}變量將源文件對(duì)應(yīng)的當(dāng)前目錄路徑展開為絕對(duì)路徑玉罐。

因?yàn)槲覀冇?code>number庫(kù)的全部代碼,所以我們可以用go generate工具來(lái)生成靜態(tài)庫(kù)潘拨,或者是通過(guò)Makefile來(lái)構(gòu)建靜態(tài)庫(kù)吊输。因此發(fā)布 CGO 源碼包時(shí),我們并不需要提前構(gòu)建 C 靜態(tài)庫(kù)铁追。

因?yàn)槎嗔艘粋€(gè)靜態(tài)庫(kù)的構(gòu)建步驟季蚂,這種使用了自定義靜態(tài)庫(kù)并已經(jīng)包含了靜態(tài)庫(kù)全部代碼的 GO 包無(wú)法直接用go get安裝。不過(guò)我們依然可以通過(guò)go get下載,然后用go generate觸發(fā)靜態(tài)庫(kù)構(gòu)建扭屁,最后才是go install來(lái)完成安裝算谈。

使用 C 動(dòng)態(tài)庫(kù)

cd number
gcc -shared -o libnumber.so number.c
package main

//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
    fmt.Println(C.number_add_mod(10, 5, 12))
}

編譯時(shí) GCC 會(huì)自動(dòng)找到libnumber.alibnumber.so進(jìn)行鏈接。

需要注意的是料滥,在運(yùn)行時(shí)需要將動(dòng)態(tài)庫(kù)放到系統(tǒng)能夠找到的位置然眼。

導(dǎo)出 C 靜態(tài)庫(kù)

// number.go

package main

import "C"

func main() {}

//export number_add_mod
func number_add_mod(a, b, mod C.int) C.int {
    return (a + b) % mod
}

根據(jù) CGO 文檔的要求,我們需要在main包中導(dǎo)出 C 函數(shù)葵腹。對(duì)于 C 靜態(tài)庫(kù)構(gòu)建方式來(lái)說(shuō)高每,會(huì)忽略main包中的main函數(shù),只是簡(jiǎn)單導(dǎo)出 C 函數(shù)践宴。

go build -buildmode=c-archive -o number.a

在生成number.a靜態(tài)庫(kù)的同時(shí)鲸匿,CGO 還會(huì)生成一個(gè)number.h文件。

// number.h

#ifdef __cplusplus
extern "C" {
#endif
extern int number_add_mod(int p0, int p1, int p2);
#ifdef __cplusplus
}
#endif
// main.c

#include "number.h"
#include <stdio.h>
int main() {
    int a = 10;
    int b = 5;
    int c = 12;
    int x = number_add_mod(a, b, c);
    printf("(%d+%d)%%%d = %d\n", a, b, c, x);
    return 0;
}
gcc -o a.out _test_main.c number.a
./a.out

導(dǎo)出 C 動(dòng)態(tài)庫(kù)

go build -buildmode=c-shared -o number.so

gcc -o a.out _test_main.c number.so
./a.out

GO 函數(shù)回調(diào)注冊(cè)

  • 1浴井、通過(guò)export將Go函數(shù)聲明導(dǎo)出函數(shù)晒骇,Go函數(shù)要與被C回調(diào)的函數(shù)原型保持一致;
  • 2磺浙、將回調(diào)函數(shù)轉(zhuǎn)換為C的函數(shù)指針洪囤,傳給C函數(shù)庫(kù),等待觸發(fā)調(diào)用撕氧;
  • 3瘤缩、回調(diào)函數(shù)被觸發(fā),能在Go訪問(wèn)到C的內(nèi)存伦泥;
//main.go
package main

/*
#cgo LDFLAGS: -L${SRCDIR}/lib -lcallback
#cgo CFLAGS: -I callback
#include "callback.h"

int goFuncForCallback(struct info *, char *);
*/
import "C"
import "fmt"

func main(){
    C.setcallback(C.callbackFuncProto(C.goFuncForCallback))
    C.caller()
    C.freeObject()
}

//導(dǎo)出為C函數(shù)
//export goFuncForCallback
func goFuncForCallback(info *C.struct_info, roomId *C.char) C.int{
    fmt.Println("goFunc", info.size, C.GoString(roomId))
    return 1
}
// callback/callback.h
#ifndef __TEST_H__
#define __TEST_H__
#ifdef __cplusplus
extern "C"{
#endif


typedef struct info{
    void* a;
    int  size;
}CInfo;

//C函數(shù)指針剥啤,函數(shù)原型一致
typedef int(*callbackFuncProto) (CInfo* n, char *roomId);

//接收C的函數(shù)指針,用于被C回調(diào)
int setcallback(callbackFuncProto s);

//回調(diào)函數(shù)觸發(fā)器
void caller();

void freeObject();

#ifdef __cplusplus
}
#endif
#endif
// callback/callback.c
#include "callback.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

callbackFuncProto callback;
CInfo info;

int setcallback(callbackFuncProto foo){
    callback = foo;
    info.a = malloc(3);
    info.size = 3;
    char t[3] = "c";
    memcpy(info.a, t, 3);
    return 1;
}

void caller(){
    int r = callback(&info, (char *)" call from C func");
    printf("---%d", r);
}

void freeObject(){
    free(info.a);
}

參考鏈接

  1. cgo鏈接C庫(kù)不脯,向C庫(kù)傳入Go函數(shù)作為回調(diào)
  2. 使用 cgo 將函數(shù)指針傳遞給 C 代碼
  3. cgo 將goLang函數(shù)作為回調(diào)函數(shù)傳遞給C的簡(jiǎn)單示例
  4. 如何在Golang裡面實(shí)作Callback function讓C來(lái)呼叫
  5. CGO中處理C中的回調(diào)函數(shù)
  6. GO語(yǔ)言高級(jí)編程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末府怯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子防楷,更是在濱河造成了極大的恐慌牺丙,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件复局,死亡現(xiàn)場(chǎng)離奇詭異冲簿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)亿昏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門峦剔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人角钩,你說(shuō)我怎么就攤上這事吝沫∩肜剑” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵野舶,是天一觀的道長(zhǎng)易迹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)平道,這世上最難降的妖魔是什么睹欲? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮一屋,結(jié)果婚禮上窘疮,老公的妹妹穿的比我還像新娘。我一直安慰自己冀墨,他們只是感情好闸衫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诽嘉,像睡著了一般蔚出。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虫腋,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天骄酗,我揣著相機(jī)與錄音,去河邊找鬼悦冀。 笑死趋翻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盒蟆。 我是一名探鬼主播踏烙,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼历等!你這毒婦竟也來(lái)了讨惩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寒屯,失蹤者是張志新(化名)和其女友劉穎荐捻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浩螺,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年仍侥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了要出。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡农渊,死狀恐怖患蹂,靈堂內(nèi)的尸體忽然破棺而出或颊,到底是詐尸還是另有隱情,我是刑警寧澤传于,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布囱挑,位于F島的核電站,受9級(jí)特大地震影響沼溜,放射性物質(zhì)發(fā)生泄漏平挑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一系草、第九天 我趴在偏房一處隱蔽的房頂上張望通熄。 院中可真熱鬧,春花似錦找都、人聲如沸唇辨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赏枚。三九已至,卻和暖如春晓猛,著一層夾襖步出監(jiān)牢的瞬間饿幅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工鞍帝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留诫睬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓帕涌,卻偏偏與公主長(zhǎng)得像摄凡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚓曼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容