C++调用Go方法的字符串传递问题及解决方案

摘要:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。

现象

在一个APP技术项目中,子进程按请求加载Go的ServiceModule,将需要拉起的ServiceModule信息传递给Go的Loader,存在C++调用Go方法,传递字符串的场景。

方案验证时,发现有奇怪的将std::string对象的内容传递给Go方法后,在Go方法协程中取到的值与预期不一致。

经过一段时间的分析和验证,终于理解问题产生的原因并给出解决方案,现分享如下。

背景知识

  1. Go有自己的内存回收GC机制,通过make等申请的内存不需要手动释放。
  2. C++中为std::string变量赋值新字符串后,.c_str()和.size()的结果会联动变化,尤其是.c_str()指向的地址也有可能变化。
  3. go build -buildmode=c-shared .生成的.h头文件中定义了C++中Go的变量类型的定义映射关系,比如GoString、GoInt等。其中GoString实际是一个结构体,包含一个字符指针和一个字符长度。

原理及解释

通过代码示例方式解释具体现象及原因,详见注释

C++侧代码:

//
  // Created by w00526151 on 2020/11/5.
  //

  #include <string>
  #include <iostream>
  #include <unistd.h>
  #include "libgoloader.h"

  /**
   * 构造GoString结构体对象
   * @param p
   * @param n
   * @return
   */
  GoString buildGoString(const char* p, size_t n){
    //typedef struct { const char *p; ptrdiff_t n; } _GoString_;
    //typedef _GoString_ GoString;
    return {p, static_cast<ptrdiff_t>(n)};
  }

  int main(){
    std::cout<<"test send string to go in C++"<<std::endl;

    std::string tmpStr = "/tmp/udsgateway-netconftemplateservice";
    printf("in C++ tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size());
    {
      //通过new新申请一段内存做字符串拷贝
      char *newStrPtr = NULL;
      int newStrSize = tmpStr.size();
      newStrPtr = new char[newStrSize];
      tmpStr.copy(newStrPtr, newStrSize, 0);

      //调用Go方法,第一个参数直接传std::string的c_str指针和大小,第二个参数传在C++中单独申请的内存并拷贝的字符串指针,第三个参数和第一个一样,但是在go代码中做内存拷贝保存。
      //调用Go方法后,通过赋值修改std::string的值内容,等待Go中新起的线程10s后再将三个参数值打印出来。
      LoadModule(buildGoString(tmpStr.c_str(), tmpStr.size()), buildGoString(newStrPtr, newStrSize), buildGoString(tmpStr.c_str(),tmpStr.size()));
      //修改tmpStr的值,tmpStr.c_str()得到的指针指向内容会变化,tmpStr.size()的值也会变化,Go中第一个参数也会受到影响,前几位会变成新字符串内容。
      //由于在Go中int是值拷贝,所以在Go中,第一个参数的长度没有变化,因此实际在Go中已经出现内存越界访问,可能产生Coredump。
      tmpStr = "new string";
      printf("in C++ change tmpStr and delete newStrPtr, new tmpStr: %p, tmpStr: %s, tmpStr.size:%lu \r\n", tmpStr.c_str(), tmpStr.c_str(), tmpStr.size());
      //释放新申请的newStrPtr指针,Go中对应第二个string变量内存也会受到影响,产生乱码。
      // 实际在Go中,已经在访问一段在C++中已经释放的内存,属于野指针访问,可能产生Coredump。
      delete newStrPtr;
    }
    pause();
  }

Go侧代码:

package main

  import "C"
  import (
    "fmt"
    "time"
  )

  func printInGo(p0 string, p1 string, p2 string){
    time.Sleep(10 * time.Second)
    fmt.Printf("in go function, p0:%s size %d, p1:%s size %d, p2:%s size %d", p0, len(p0), p1, len(p1), p2, len(p2))
  }

  //export LoadModule
  func LoadModule(name string, version string, location string) int {
    //通过make的方式,新构建一段内存来存放从C++处传入的字符串,深度拷贝防止C++中修改影响Go
    tmp3rdParam := make([]byte, len(location))
    copy(tmp3rdParam, location)
    new3rdParam := string(tmp3rdParam)
    fmt.Println("in go loadModule,first param is",name,"second param is",version, "third param is", new3rdParam)
    go printInGo(name, version, new3rdParam);
    return 0
  }

Go侧代码通过-buildmode=c-shared的方式生成libgoloader.so及libgoloader.h供C++编译运行使用

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

程序执行结果:

test send string to go in C++
    in C++ tmpStr: 0x7fffe1fb93f0, tmpStr: /tmp/udsgateway-netconftemplateservice, tmpStr.size:38
    # 将C++的指针传给Go,一开始打印都是OK的
    in go loadModule,first param is /tmp/udsgateway-netconftemplateservice second param is /tmp/udsgateway-netconftemplateservice third param is /tmp/udsgateway-netconftemplateservice
    # 在C++中,将指针指向的内容修改,或者删掉指针
    in C++ change tmpStr and delete newStrPtr, new tmpStr: 0x7fffe1fb93f0, tmpStr: new string, tmpStr.size:10
    # 在Go中,参数1、参数2对应的Go string变量都受到了影响,参数3由于做了深度拷贝,没有受到影响。
    in go function, p0:new string eway-netconftemplateservice size 38, p1:        p���  netconftemplateservice size 38, p2:/tmp/udsgateway-netconftemplateservice size 38

结论

  • 结论:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝。即参数三的处理方式
  • 原因:传入的字符串GoString,实际是一个结构体,第一个成员p是一个char*指针,第二个成员n是一个int长度。

在C++代码中,任何对成员p的char*指针的操作,都将直接影响到Go中的string对象的值。

只有通过单独的内存空间开辟,进行独立内存管理,才可以避免C++中的指针操作对Go的影响。

ps:不在C++中进行内存申请释放的原因是C++无法感知Go中何时才能真的已经没有对象引用,无法找到合适的时间点进行内存释放。

本文分享自华为云社区《C++调用Go方法的字符串传递问题及解决方案》,原文作者:王芾。

到此这篇关于C++调用Go方法的字符串传递问题及解决方案的文章就介绍到这了,更多相关C++调用Go字符串传递内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C/C++函数调用的几种方式总结

    调用函数时,计算机常用栈来存储传递给函数的参数. 栈是一种先进后出的数据结构,栈有一个存储区.一个栈顶指针.栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶).用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改.用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改.函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算.函数计算结束以

  • C++调用C#的DLL实现方法

    SwfDotNet是C#编写的,这是个特别好的读写Swf文件的库.本文讲述了在C++项目中,怎么让C++调用C#的DLL动态链接库文件. 具体的实现步骤如下: 一.创建C# DLL,需要指定应用类型为"类库",代码: namespace CSLib { public class Class1 { private string name; public string Name { get { return name; } set { name = "Your Name: &qu

  • Lua教程(四):在Lua中调用C语言、C++的函数

    本教程将介绍如何在Lua里面调用c/c++函数. 在Lua里面调用c/c++函数其实是比较简单,本文将通过两个示例演示具体的做法:一个是求平均数,另一个是打印lua函数的一些参数信息. 最后,本文会介绍如何把这两个函数定义成一个模块,这样lua代码里面就可以不再使用全局的名字空间了. 前言 当我们需要在Lua里面调用c/c++函数时,所有的函数都必须满足以下函数签名: 复制代码 代码如下: typedef int (*lua_CFunction) (lua_State *L); 换句话说,所有的

  • c++生成dll使用python调用dll的方法

    第一步,建立一个CPP的DLL工程,然后写如下代码,生成DLL 复制代码 代码如下: #include <stdio.h> #define DLLEXPORT extern "C" __declspec(dllexport) DLLEXPORT int __stdcall hello()     {         printf("Hello world!\n");         return 0;     } 第二步,编写一个 python 文件:

  • PHP中调用C/C++制作的动态链接库的教程

    一般而言,php速度已经比较快,但是,对于一些较高级开发者而言,如果想要追求更快的速度,那毫无疑问可以通过自己写c代码,并编译为动态链接库(常为.so文件),然后php通过创建一个新的扩展(extension),并在扩展里调用该.so文件,同时对外暴露出php函数接口. 在实际使用中,只要调用该函数接口,即可使用底层更快速的c函数服务. 一.动态链接库(shared) 动态链接库的文件名后缀通常是 ".so".在Windows系统中,其文件名后缀是".dll". 程

  • Python调用C/C++动态链接库的方法详解

    本文以实例讲解了Python调用C/C++ DLL动态链接库的方法,具体示例如下: 示例一: 首先,在创建一个DLL工程(本例创建环境为VS 2005),头文件: //hello.h #ifdef EXPORT_HELLO_DLL #define HELLO_API __declspec(dllexport) #else #define HELLO_API __declspec(dllimport) #endif extern "C" { HELLO_API int IntAdd(in

  • php调用c++的方法

    1.在/var/www中建个测试文件夹 cpp 在此文件夹中新建c++文件sort.cpp,如下 编译并测试执行通过进行以下步骤. 2.在cpp文件夹下新建文件cpp.html,如下 3.同样在cpp下建php文件cpp.php,如下 保存. 4.程序执行如下 提交后:

  • Python 调用VC++的动态链接库(DLL)

    1. 首先VC++的DLL的导出函数定义成标准C的导出函数: 复制代码 代码如下: #ifdef LRDLLTEST_EXPORTS #define LRDLLTEST_API __declspec(dllexport) #else #define LRDLLTEST_API __declspec(dllimport) #endif extern "C" LRDLLTEST_API int Sum(int a , int b); extern "C" LRDLLTE

  • C++生成dll和调用dll的方法实例

    本人根据网络多个相关博客帖子原创 1)生成dll 建立两个文件 xxx.h , xxx.cpp xxx.h内容如下: #ifdef BUILD_XXX_DLL#define EXPORT __declspec(dllexport)#else#define EXPORT __declspec(dllimport)#endif extern "C"{EXPORT void example(void);... ...} xxx.cpp内容如下: #define BUILD_XXX_DLL#i

  • C++调用Go方法的字符串传递问题及解决方案

    摘要:C++调用Go方法时,字符串参数的内存管理需要由Go侧进行深度值拷贝. 现象 在一个APP技术项目中,子进程按请求加载Go的ServiceModule,将需要拉起的ServiceModule信息传递给Go的Loader,存在C++调用Go方法,传递字符串的场景. 方案验证时,发现有奇怪的将std::string对象的内容传递给Go方法后,在Go方法协程中取到的值与预期不一致. 经过一段时间的分析和验证,终于理解问题产生的原因并给出解决方案,现分享如下. 背景知识 Go有自己的内存回收GC机

  • 在java中由类名和方法名字符串实现其调用方式

    js里通过eval()函数,在知道某个方法名是可以实现调用该方法,那么在java里边又怎么实现的呢? java里边是通过反射机制来实现,代码如下: import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws Exception { String className = "com.runqianapp.ngr.alias.example.FunClass&quo

  • Kotlin 语言中调用 JavaScript 方法实例详解

    Kotlin 语言中调用 JavaScript 方法实例详解 Kotlin 已被设计为能够与 Java 平台轻松互操作.它将 Java 类视为 Kotlin 类,并且 Java 也将 Kotlin 类视为 Java 类.但是,JavaScript 是一种动态类型语言,这意味着它不会在编译期检查类型.你可以通过动态类型在 Kotlin 中自由地与 JavaScript 交流,但是如果你想要 Kotlin 类型系统的全部威力 ,你可以为 JavaScript 库创建 Kotlin 头文件. 内联 J

  • jQuery中调用WebService方法小结

    个人感觉有两点是比较方便的:第一,在对WebService做ajax请求的时候,请求的url的写法是:服务地址/调用的方法名称,这样在请求的url中就确定了要调用的方法了,不必再在WebService代码中去判断ajax请求调用的是哪个方法了.第二,方法可以返回更多的数据类型,比如对象,泛型集合等:在ajax请求返回后,会自动将这些类型转换为json对象.而使用ashx的方式的话,需要先将这些类型转换为json格式的数据才能返回. 在使用jQuery调用WebService的方法的时候,只能发送

  • JavaScript 中调用 Kotlin 方法实例详解

    JavaScript 中调用 Kotlin 方法实例详解 Kotlin 编译器生成正常的 JavaScript 类,可以在 JavaScript 代码中自由地使用的函数和属性 .不过,你应该记住一些微妙的事情. 用独立的 JavaScript 隔离声明 为了防止损坏全局对象,Kotlin 创建一个包含当前模块中所有 Kotlin 声明的对象 .所以如果你把模块命名为 myModule,那么所有的声明都可以通过 myModule 对象在 JavaScript 中可用.例如: fun foo() =

  • 详解Android中通过Intent类实现组件间调用的方法

    Intent是Android中用来调用其它组件的类,通过Intent,我们可以非常方便的调用Activity,Broadcast Receiver和Service. Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent); 上面这段代码可以用来调用第三方的Activity(启动第三方浏览器来打开百度首页

  • 原生js调用json方法总结

    Ajax基础 ajax:无刷新数据读取,读取服务器上的信息 HTTP请求方法: GET:用于获取数据,如浏览帖子 ajax.judgeXmlHttpRequest('get', 'index.php', function(data){ alert(data); //这是服务器返回的数据 }) POST:用于上传数据,如用户注册 var dataJson = { name: 'ys',age: 123 } ajax.judgeXmlHttpRequest('post', 'index.php',

  • android webview中使用Java调用JavaScript方法并获取返回值

    在android平板上用webview打开一个网页,调用里面的javascript方法,同时相互传参. 网上例子很少啊,基本都不能获取返回值,贴一个自己最后调试完的代码如下: Java: 复制代码 代码如下: protected void onCreate(Bundle savedInstanceState) { ........ x = (WebView)this.findViewById(R.id.webView_viewTable); x.setScrollBarStyle(View.SC

  • java编程中实现调用js方法分析

    本文实例讲述了java编程中实现调用js方法.分享给大家供大家参考,具体如下: /* * 加载脚本引擎,并在java中调用js方法 */ public void test2() { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("javascript"); try { String str="2&1"

  • asp.net模板引擎Razor调用外部方法用法实例

    本文实例讲述了asp.net模板引擎Razor调用外部方法用法.分享给大家供大家参考.具体如下: 首先使用Razor的步骤:读取cshtml.解析cshtml同时指定cacheName. 而这个步骤是重复的,为了遵循DRY原则,将这段代码封装为一个RazorHelper()方法 public class RazorHelper { public static string ParseRazor(HttpContext context, string csHtmlVirtualPath, obje

随机推荐