您的位置  > 互联网

(每日一题)调用C/C++的方法

这种语言近年来迅速成为越来越多的人的首选,因为它极其容易学习、使用、可读性和丰富的扩展使其学习友好和项目友好。 然而,一旦与传统编程语言(C/C++)相比,人们往往会想到效率问题。 本文无意讨论语言之间的比较。 但在实际使用中,确实存在使用较低级别的C/C++更好的情况。 因此,本系列旨在介绍几种在比较常见的环境下调用C/C++的方法。 (挖坑:,SWIG,BOOST.,)

阅读这篇文章需要什么?

语言:简单的基础知识和简单的 C/C++ 基础知识。

C/C++环境和环境。

一步步跟进

搜索引擎/参考书可以随时检查不懂的地方。

PS:本文中会有一些扩展的知识点,而我的中文水平很差,导致文笔很吓人,所以如果您在阅读时感到不舒服,请务必跳过扩展部分(大括号内的部分)。

目录

3. 指针和引用数组总结 4. 参考资料 1. 环境配置

由于这是本系列的第一篇文章,所以简单介绍一下环境(vim + 命令行专家可以跳过...):

:可以从【官网】下载()(不翻墙好像有点难打开……?)。 现在关于选择2还是3的问题……大家还是有自己的看法。 不过,您可以同时安装两者,所以没关系。 IDE 是默认的。 PS:一定要和C上的位数一致! 如果GCC是32位的,使用64位会报错。 这是一个惨痛的教训……

C/C++:我使用的IDE是[](代号::)——一个跨平台的开源C/C++集成开发环境。 推荐下载mingw-setup版本,自带GCC/G++和GDB。 另外,如果需要VC编译,需要下载VC并调整其中的路径。

现在作为高手和C高手的李狗蛋从官网下载了.5 32位和mingw-setup。 他自信,因为他了解自己的命运。 他是宇宙排名int(-1)的程序员。 他是最强的人(不是)。 他很高兴能够编写一个可以征服世界的程序。

2.C/C++端

使用是通过调用C/C++动态链接库(DLL)来实现的,所以在进入正题之前,我们首先要讲一下动态链接库的构造方法。 这个领域会涉及各种编译器、系统和语言相关的问题。 本文只讨论目前我们需要了解的部分。

图书馆

库的本质是打包好的代码包,一般分为静态(.lib.a)和动态(.dll.so)。 静态库会在主程序编译时编译成最终的可执行文件。 但是,没有编译过程。 主要使用动态库,即运行时在库中查找内容。

对于使用IDE的用户来说,要构建库,只需在创建新项目时将项目属性选择为库即可。

对于命令行用户,需要在编译时添加一些命令(以C++为例,只需将C中的g++改为gcc即可):

     g++ -fPIC -shared -o libsource.so Source.cpp

其中-表示这是一个动态库,-fPIC使得位置无关。 如果程序本来是独立的,会有警告,忽略即可) -o 指定输出文件,改成dll后缀就可以使用。

如果想用C/C++来调用这个库,还是需要很多繁琐的过程。 幸运的是,我们使用的是调用,所以我们可以到此为止。

读完之后,李狗蛋用自己极其高深的编程功力稳稳地控制着鼠标,依次点击code:block菜单栏上的文件-新建-链接,一路按下一步,动作如雷。 就像一个无私的舞者。 突然,他发现对话框卡在一处。 原来他是想给程序命名。 他沉吟了一会儿,毅然打出了一行英文:SSR似乎是为了填补人生的空白。

代码

弹出的窗口中充满了许多未知的符号。 李狗蛋一惊,心中道:哈! 这真有趣。 确实让我愣了1秒。 一个好人不会立即遭受任何损失。 我先学一个吧

如果你像我说的那样在 IDE 中创建一个新的动态库,你或多或少会被弹出的样板文件淹没(本文假设读者和我一样,只写过非常简单的 C/C++ 文档)。 这并不奇怪。 为了各方面的兼容,如果要按照项目标准来写一个文件,那么在单独写小文件时,会有一些命令是个人无法使用的。 例如:

“C”

由于C++具有函数重载的功能,因此不能仅通过名称来识别函数。

结果就是编译后,函数名(比如`git`)往往会变成``(不同的编译器有不同的做法)。 直接原因是调用困难。 这就是“C”命令发挥作用的时候。 该命令告诉编译器在C中编译这句话,而C的函数名不会改变,所以编译后可以直接用函数名调用。 对于我们来说,几乎每个C++函数都要加上“C”。

/*函数重载:比如函数int fun(int a)和int fun(a)在C眼中是同一个函数(同名),但在C眼中是不同函数(同名、同姓、同性别) C++,字体大小不同)。 理论上C++更科学,但是为了区分,它会对函数进行重命名。 比如对李狗蛋:LJ_李狗蛋_男单身,结果是当我说李狗蛋时,它不知道是谁,只知道有一个名字很长的函数。 所以我们需要“C”告诉它不要随意更改别人的名字,否则它会被殴打。 */

看到标题,很多人会想:这和上面有什么关系吗? ……

答案是肯定和否定。 为了解释,我们需要简单了解一下这两个关键词。

对于局部变量来说,意味着静态变量,即它们不会随着函数的生死而被创建或销毁。 当然这是题外话,我们要谈的是另一个方面。

对于全局变量/函数定义, 和 ​​是一对反义词。

表示内容不限于本文件,可以在其他文件中定义/调用,但表示该元素只允许在本文件中使用。 上述命令(“C”)的本质是“是的,它也是C”。

不过,由于“C”其实就只有这个用法,而且只有当函数需要外部调用的时候我们才关心它是否被重命名,所以实际上就形成了类似于英语中的固定搭配的用法,也无所谓。 。

在我们的使用中,它可以用来构成类的一部分效果。

/* 也就是说,如果我们写了一个叫()的函数,但是我们不想被外部调用,那么我们可以声明我想对别人偷偷隐藏这个东西。 但如果其他人调用了一个(),这个函数就可以调用该函数(因为它是内部的)。 变量也是如此。 我们可以声明我们不希望被访问的变量以避免直接访问。 */

(使用VC编译必读)

对于GCC编译来说,所有函数都默认导出(不添加),即可以被外部调用。 但对于VC来说就不一样了。 VC默认是不导出的,所以我们需要这一段:

 #ifdef _MSC_VER
        #define DLL_EXPORT __declspec(dllexport) 
    #else
        #define DLL_EXPORT

这是VC的一个宏定义,用来检测编译器是否是VC并决定是否使用()

然后这样写函数

extern "C" DLL_EXPORT int function()

这是两种不同的调用方式,涉及到编译和底层汇编的一些细节,这里不再讨论。 默认使用C/C++。 你可以通过在函数前面添加关键字来定义函数的调用方法,而我们不需要关心它。

#ifdef

很多类似的语句都是宏,一般存在于模板中以避免重复定义。 意思是“如果……被定义了……那么……”,还有一些类似的大家可以自己理解。

对于我们来说,(如果使用 GCC)只需在写入的文件中添加“C”(可能更多)即可。

读完之后,李狗蛋精神抖擞。 他打算在这个项目中用自己最强的一段代码来感谢文章的作者。 于是他写下了这样一段话:

#include
extern "C" void saikyo()
{
     printf("Hello world!");
}

狗蛋满意地笑了,手上举起了刀。

3. 加载

首先,毫无疑问我们想要这样做。

有两种调用方式,CDLL 和 . 对应上面提到的总和,我们一般使用CDLL。

.dll 后缀会自动添加,而 Linux 需要包含要调用的扩展名的全名。

要加载dll,我们有很多方法:

cdll.filename
cdll.LoadLibrary("filename")
CDLL("filename")

这三个都会调用.dll(后缀是下面自动添加的)并返回类似句柄的东西,我们可以使用句柄来调用库中的函数。 喜欢:

cdll.study.math()

您还可以保存句柄并使用它:

    h = cdll.study
    h.math()

这里还有另一种用法。 由于dll导出的时候会有一个表,记录了哪些函数是导出的函数。 同时这个表还有函数的序号,所以我们可以像这样访问表中的第一个函数。

cdll.study[1]

(请注意编号从1开始,而不是从0开始)返回的内容与函数的指针相同。 如果我们想调用它,只需在末尾添加()即可。

关于表格,好像GCC在编译的时候会自动生成def。 你可以在里面查一下。 如果你只有DLL的话,可以用VC或者来查看一下。

数据类型

李狗蛋在dll文件所在目录下创建了一个py文件,然后用IDLE打开并运行shell。 他反复运用上一节学到的技巧,输出了几十行Hello World。 旁边的张小花忍不住吐露了自己的心声。 心眼。 一旁的王小锤冷笑:“有什么好嚣张的?数学题都能做吗?” 李狗蛋听了这话,不禁有些愤怒。 他竟然敢挑战狗王的权威。 今天我就来告诉你,变老意味着什么。 司机。 于是他又加了一个函数,还不忘加上新学的“C”:

extern "C" int hardestproblem(int a,int b)
{
     return a+b;
}

/*None、 、 bytes 和 () 是在这些调用中唯一可以使用的。 None 是 C NULL ,字节和它们的数据(char * 或 *)的块。 作为C int类型,它们的值适合C类型。*/

以上摘自官方文档。 描述了C端可以直接传递的几种参数类型及其形式。

狗蛋在中输入了以下内容

from ctypes import *
god = cdll.god
god.hardestproblem(1,2)

得到输出3,完美解决了小锤子的问题。

对于我们来说,添加浮点值几乎足以满足一般用途。 然而,浮点值默认是不可传递的,所以我们需要进行类型转换:

    c_float(3.14)#单精度浮点类型
    c_double(3.14)#双精度浮点类型

定义变量后,使用 i.value 访问该值。

小翠似乎还想再说什么,但狗蛋已经看穿了:“你要我求圆的面积,哼。你以为我不会回答这么前沿的问题吗?你看.所以在C++源文件中添加A函数:

extern "C" double circle(double pi,double r)
{
      return r*r*pi;
}

他用刚刚学到的转换句子输入:

god.circle(c_double(3.14),c_double(1))

然而,命运似乎总是会捉弄主角。 屏幕上显示,狗蛋的脸色渐渐变得铁青……

我们先把狗蛋遇到的问题放在一边。 仅从参数类型来看,似乎这已经足够我们使用了。 但是,您需要注意字符串。 字符串由于编码等原因存在很多问题,比如:

str和bytes之间的编码问题。 默认字符串str经过编码,占用两个字节。 所以如果你只是使用双引号写一个字符串并将其传递给函数,并且如果你需要修改/读取函数中的指定位,那么就会发生奇怪的事情。 原因是Char部分的两个字节和一个字不匹配。具体请参考字符串和编码

为了解决这个问题,我们还需要添加 `b""` 来强制它生成 bytes 类型。

或者更安全的方法是使用()生成更类似于C字符数组的东西以进行安全操作。 喜欢:

create_string_buffer(b"abcdefg",10)

要打开一个长度为10的字符数组,第一部分末尾用NUL填充。

另外:输出在C中只会出现(即在命令行中),不会在IDLE中出现。

为了赢回小花的心,狗蛋回家后仔细阅读了这篇教程,写了一个非常浪漫的程序并发送给小花:

extern "C" int password(char *A,int n)
{
     printf("%c",A[n])
}

然后在让小花再输入以下内容

from ctypes import *
god = cdll.god
god.password(b"Live",0)
god.password(b"cool",1)
god.password(b"Have",2)
god.password(b"None",3)

结果我们都可以猜到:小花很感动………………………………然后拒绝了他。

类型名称详细表见文末附图。

如果需要使用自定义数据类型作为参数传递,则参数中需要有一个命名的变量,并且会找到具有该名称的变量作为参数传递。 涉及自定义类的内容就不展开了。

访问导出的变量

与函数一样,dll 中的导出变量也可以从外部访问。 格式如下:

c_int.in_dll(study,"score")

其中c_int代表数据类型,表示是在dll中,study是dll名,后面的字符串是变量名。 应该注意的是,变量与函数相同。 如果不添加“C”,该名称将由编译器重命名。

函数参数in和out的定义

dll的源文件不会被直接读取,那么如何得知函数需要哪些参数呢? 答案是使用。

fuction.argtypes = [c_char_p,c_int]

这样以后你使用参数的时候就会自动处理你的参数,就像调用参数一样。

就像上面的一样,不仅看不到函数想要什么,而且看不到函数返回什么。 默认情况下,C 中的函数被认为返回 int 类型。但是,如果我们的函数返回其他类型,则需要使用命令。

function.restype = c_char_p

该函数的返回值是指定的,以便可以按照我们想要的方式进行处理。

我们甚至可以将函数的返回值设置为一个对象(例如函数)。 目标函数执行后返回一个int,使用int直接调用对象。 这里就不展开展开了。

据说,狗蛋被拒绝后,伤心欲绝,失去了力量,于是去网吧修炼了10年。 读完这一段,犹如恍然大悟。 于是我和小吹约了半夜在情人谷见面。 到了时间,只见小吹骑着自行车荡着上山,后座是小花。

“小锤子!我终于解决了十年前让我失去一切的问题!我不怕麻烦,今天我就告诉你,美丽是强者的专利!”

话还没说完,狗蛋就拿出了自己已经写好的程序。 C部分没有变化,但C部分确实不同了。

circle = god.circle
circle.argtypes = [c_double,c_double]
circle.restype = c_double
circle(3.14,1)

只见屏幕上闪烁着金光(狗蛋视角),弹出了让狗蛋震惊的数字:3.14。

狗蛋愣了一下,随即不禁狂喜起来。 他追求了这么多年,终于有了结果。 据凌云的同学说,半夜听到有人哈哈哈哈哈哈哈哈哈哈哈哈,大家都准备去抄那家伙了。

小垂和小花对视了几秒,漫不经心地说道:那……没关系,我们先下去吧……点了鸡排。

于是,上来没几分钟,两人就又骑马下了山,紧紧地拥抱在一起。 但狗蛋的笑声不绝于耳,却多了几分悲伤……

指针和参考

指针和引用非常常用(尤其是在 C 语言中)。 这里不介绍它们,只介绍它们的用法。

    byref(i)
    pointer(i)

分别生成对 i 的引用和指向 i 的指针。 如果不需要使用指针,引用会更快。 通过输出指针可以观察到指针的属性。

大批

重载了*,因此我们可以使用类型*n来表示该类型的n个元素,它们一起构成一个整体。 例如,定义一个整数数组类型:

    int_10 = c_int *10
    myarr = int_10()
    myarr[2]=c_int(24)

传递给C的函数参数应该是(int a[ ]),对于多维数组也是如此。

概括

现在我们应该已经学会了如何创建一个dll,如何加载dll,以及如何访问里面的函数和变量。 传入一些基本参数并接收一些返回值。 还有union等一些用法,我就不一一细说了。 (其实我也不太了解……)现在所学的东西应该足以支持普通的C/C++调用了。

后来有一天,在机房,一位颓废的中年男子走进来,打开远程机器,问我:dll加载的写法有4种,你知道吗?

----------

(完整的类型对应表摘录如下:)(抱怨知乎不能用……)

----------

4.参考资料

1.“C”的使用分析-Holt-博客园:///2012/03/20/.html

2.C++静态库和动态库-吴勤-博客园://p/.html

3.、、和的区别 - 学无止境 - 博客频道 - CSDN.NET://///

4.聊天模块-蛇之魅-知乎专栏:/p/?=-dev

5. 3:

6. 字符串和编码/wiki//

另外,感谢您的提醒,标题图片已被删除。