linux so 中结构体改变对已有可执行程序运行的影响

本文探讨了动态库升级对可执行程序的影响,特别是在不同版本动态库间切换时的兼容性问题。通过实验验证,分析了结构体字段变更对运行时的影响,并提出了预防措施。

目录

 

研究的问题

问题分类

测试程序介绍

工程目录

动态库程序

dym.c

dym.h

so-makefile

主程序源码

main.c

main-makefile

测试思路

只升级so,可执行程序不升级

正常情况:main-1版本依赖so-1版本编译和运行

异常情况:main-1版本依赖so-2版本编译和运行

分析

可执行程序依赖新版本so编译,但是运行时依赖老版本so

异常情况:main-2版本依赖so-1版本运行

分析

结论


研究的问题

问题分类

可执行程序及其依赖动态库升级问题,有以下几种可能:

  1. 可执行程序是依赖老版本动态库编译的,未升级,动态库升级

  2. 可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。

  3. 可执行程序是依赖新版本动态库编译的,已升级,运行环境动态库也已经升级

第3中情况制定是没有问题,但是1,2种情况是否有问题呢?而且看一些开源库经常能看到结构体定义中会预留几个字段,很是迷惑,如果之后需要添加结构体字段,直接在后续版本中添加不就可以了吗?为什么需要还没有使用就预留呢。

测试程序介绍

工程目录

src 源码目录

     so 是动态库编译目录

install 是安装目录

动态库程序

 

 

 

 

dym.c

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

#ifdef __DYM_V_1__
int dym_dump(dym_t* d)
{
    if(!d) {
        return 0;
    }

    printf("dym = {id:%d;name:%s;att1:%d}\n", d->id, d->name, d->attr1);
    return 0;
}
#elif defined __DYM_V_2__
int dym_dump(dym_t* d)
{
    if(!d) {
        return 0;
    }

    printf("dym2 = {id:%d;name:%s;att1:%d;name2=%s;}\n", d->id, d->name, d->attr1, d->name2);
    return 0;
}
#endif

dym.h

#ifndef __DYM_H__
#define __DYM_H__

typedef struct dym_s
{
    int id;
    char name[256];
#ifdef __DYM_V_2__
    char name2[4];
#endif
    int attr1;
}dym_t;

int dym_dump(dym_t* d);
#endif

so-makefile

VER = 1
DYM_NAME = dym
OBJS = dym.o
LIB = lib$(DYM_NAME).so.$(VER)
LIB_LNS = lib$(DYM_NAME).so
DYM_V = __DYM_V_$(VER)__
CFLAG = -D$(DYM_V) -O0 -g3

all:$(LIB)
	@rm -f $(LIB_LNS)
	@ln -s $(LIB) $(LIB_LNS)
$(LIB):$(OBJS)
	gcc -shared -Wl,-soname,libdym.so -o $@ $^ 
%.o:%.c
	gcc $(CFLAG) -c -fPIC -o $@ $^
clean:
	@rm -f $(OBJS)
	@rm -f lib$(DYM_NAME).so*

install:
	@cp -d lib* ../../install/

主程序源码

main.c

#include <stdio.h>
#include <string.h>
#include "so/dym.h"

int main(void)
{
    dym_t d1 = {123, "d1", 
#ifdef __DYM_V_2__
    "d1",
#endif
    1};
    dym_t d2;

    d2.id = 124;
    strcpy(d2.name, "d2");
#ifdef __DYM_V_2__
    strcpy(d2.name2, "d2");
#endif
    d2.attr1 = 2;

#ifdef __DYM_V_1__
    printf("main-d1 = {id:%d;name:%s;att1:%d}\n", d1.id, d1.name, d1.attr1);
    printf("main-d2 = {id:%d;name:%s;att1:%d}\n", d2.id, d2.name, d2.attr1);
#elif defined __DYM_V_2__
    printf("main-d1 = {id:%d;name:%s;att1:%d;name2=%s}\n", d1.id, d1.name, d1.attr1, d1.name2);
    printf("main-d2 = {id:%d;name:%s;att1:%d;name2=%s}\n", d2.id, d2.name, d2.attr1, d2.name2);
#endif

    dym_dump(&d1);
    dym_dump(&d2);
}

main-makefile

VER = 2
ELF_NAME = main
LINK = -L./so -ldym
OBJS = main.o
DYM_V = __DYM_V_$(VER)__
CFLAG = -D$(DYM_V) -O0 -g3

ALL:$(ELF_NAME)

$(ELF_NAME):$(OBJS)
	gcc  -o $@ $^ $(LINK)

%.o:%.c
	gcc $(CFLAG) -c $^ -o $@
clean:
	@rm $(OBJS) $(ELF_NAME)

install:
	@cp main ../install/

测试思路

1、so-makefile 和 main-makefile 通过设置ver参数改变编译的版本

2、通过main 和 so 版本不同,例如main-1版本依赖so-2版本运行,然后安装到install目录观察结果。

只升级so,可执行程序不升级

正常情况:main-1版本依赖so-1版本编译和运行

  1. main-makefile 和 so-makefile 的VER变量 都设置为1
  2. 在src/so 目录执行make clean;make;make install
  3. 在src 目录执行make clean;make;make install
  4. 在install 目录执行./main 结果如下:
 ./main
main-d1 = {id:123;name:d1;att1:1}
main-d2 = {id:124;name:d2;att1:2}
dym = {id:123;name:d1;att1:1}
dym = {id:124;name:d2;att1:2}

异常情况:main-1版本依赖so-2版本编译和运行

  1. “正常情况”基础上,把so-makefile VER修改为2
  2. 在src/so 目录执行make clean;make;make install
  3. 在install 目录执行./main 结果如下:
./main 
main-d1 = {id:123;name:d1;att1:1}
main-d2 = {id:124;name:d2;att1:2}
dym2 = {id:123;name:d1;att1:4195760;name2=;}
dym2 = {id:124;name:d2;att1:-1411195112;name2=;}

分析

  1. 由于main还是中是依赖dym_t-1版本编译的,所以dym_t结构体成员的访问,还是用dym_t-1版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
  2. 当main中调用dym_dump函数,传入的还是dym_t-1版本的地址结构(此处用gdb打印了一下,但main函数中dym_t的sizeof是264,dym_dump函数中打印是268),所以在dym_dump 中按dym_t-2版本访问dym_t-1版本长度的内存会出现问题。
  3. 其实在dym_dump中访问attr1的时候已经越界来,所以attr1是传入dym_dump函数dym-t-1版本268字节后的四个字节。
  4. 而name2现在使其访问的是main函数中attr1的地址,结果如下:
 > x/4xb d->name2
0x7fffffffead4: 0x01    0x00    0x00    0x00

可执行程序依赖新版本so编译,但是运行时依赖老版本so

异常情况:main-2版本依赖so-1版本运行

  1. “正常情况”基础上,把main-makefile和dym-makefile的VER变量修改为2
  2. 在src/so,执行make clean;make(不要执行make install)
  3. 在src目录执行main clean;make;make install;
  4. 在install目录 运行./main
> ./main 
main-d1 = {id:123;name:d1;att1:1;name2=d1}
main-d2 = {id:124;name:d2;att1:2;name2=d2}
dym = {id:123;name:d1;att1:12644}
dym = {id:124;name:d2;att1:12900}

分析

  1. 由于main还是中是依赖dym_t-2版本编译的,所以dym_t结构体成员的访问,还是用dym_t-2版本成员地址访问的,所以main-d1和main-d2的访问还是正常的。
  2. 当调用dym_dump函数时,实际是把dym_t-2版本的内存结构(268字节)传送给dym_dump函数,犹豫dym_dump 是按so-1版本编译的,所以会按dym_t-1版本成员地址访问dym_t-2版本内存结果。
  3. 所以dym_t-1版本attr1,实际访问的dym_t-2版本的name2地址,结果如下:实际值为d2
> x/4xb &d->attr1
0x7fffffffe9c4: 0x64    0x32    0x00    0x00

结论

1、可执行程序是依赖老版本动态库编译的,未升级,动态库升级

此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。

2、可执行程序是依赖新版本动态库编译的,已升级,但是运行环境动态库还是老版本的。

此种情况如果只是动态库内部逻辑有变动,对已有可执行文件,不会有运行影响;如果改变一些结构体成员变量大小,或者新添加了成员,可能会影响已有可执行文件运行。

综上所述:

改变动态库结构体可能带来向下的可执行程序不兼容。所以如果一些结构体未来可能改变,可以把接头体声明为对外结构不可见的,通过提供全面函数操作结构体。

源码在0001so-update.tar.gz中

 

<think>我们被要求将MATLAB代码整合到其他编程语言开发的程序中。根据引用[1],有两种主要方式:以MATLAB为主程序调用C++代码,或者以C++为主程序调用MATLAB代码。用户的问题是如何将MATLAB代码整合到其他语言(如C++)的程序中,因此我们重点讨论第二种方式:以其他语言为主程序调用MATLAB代码。 主要方法: 1. MATLAB引擎API:MATLAB提供了引擎API,允许C/C++或Fortran程序启动MATLAB进程并与之交互。这样可以在C++程序中调用MATLAB函数并传递数据。 2. 将MATLAB代码编译为可执行文件或库:使用MATLAB Compiler将MATLAB代码编译为独立的可执行文件(.exe)或共享库(如C++可调用的动态链接库),然后在主程序中调用这些库。 3. 使用MATLAB Coder:MATLAB Coder可以将MATLAB代码转换为C/C++代码,然后可以直接将生成的C/C++代码集成到项目中。 4. 使用MEX文件:虽然MEX文件主要用于在MATLAB中调用C/C++代码,但也可以通过一些技巧反向调用,过这是主要方式。 下面详细说明: 方法一:使用MATLAB引擎API - 适用于C/C++、Fortran等语言。 - 需要在系统中安装MATLAB。 - 步骤: a. 在C++程序中包含引擎头文件:`#include "engine.h"` b. 启动MATLAB引擎:`Engine *ep = engOpen(NULL);` c. 创建MATLAB数组(mxArray)并设置数据。 d. 将数组放入MATLAB工作空间:`engPutVariable(ep, "variable_name", array);` e. 执行MATLAB命令:`engEvalString(ep, "MATLAB command");` f. 获取结果:`mxArray *result = engGetVariable(ep, "variable_name");` g. 关闭引擎:`engClose(ep);` - 优点:可以交互式地使用MATLAB的全部功能。 - 缺点:需要安装MATLAB,运行时启动MATLAB进程,开销较大。 方法二:将MATLAB代码编译为共享库 - 使用MATLAB Compiler SDK(原MATLAB Builder)将MATLAB函数编译为C/C++共享库(如DLL、SO)或Java类、.NET程序集等。 - 步骤: a. 在MATLAB中使用`mcc`命令或MATLAB Compiler工具将MATLAB函数编译为共享库。 b. 在C++项目中包含生成的库文件和头文件。 c. 调用生成的库中的函数。 - 注意:运行时需要MATLAB Compiler Runtime (MCR),它可以在没有安装MATLAB的机器上运行。 - 优点:需要安装完整的MATLAB,只需MCR;可以保护MATLAB源代码。 - 缺点:MCR需要单独安装,且库文件较大。 方法三:使用MATLAB Coder生成C/C++代码 - MATLAB Coder可以将特定的MATLAB代码(通常为算法部分)转换为C/C++代码。 - 步骤: a. 使用MATLAB Coder识别需要转换的MATLAB函数。 b. 生成C/C++代码(可能需要进行一些调整,因为并非所有MATLAB函数都支持代码生成)。 c. 将生成的C/C++代码直接集成到C++项目中。 - 优点:生成的代码是独立的,需要MATLAB环境;执行效率高。 - 缺点:只支持MATLAB的一个子集,且需要调整代码以满足代码生成的要求。 选择哪种方法? - 如果需要完整的MATLAB功能(如调用工具箱函数)且介意安装MATLAB或MCR,可以选择方法一或二。 - 如果追求性能和独立性,且代码符合MATLAB Coder的要求,则方法三是最佳选择。 示例(方法一:C++调用MATLAB引擎): 以下是一个简单的C++程序,它调用MATLAB引擎来计算一个正弦函数并绘图。 ```cpp #include <iostream> #include "engine.h" #include <cmath> int main() { Engine *ep; if (!(ep = engOpen(""))) { std::cerr << "Can't start MATLAB engine" << std::endl; return 1; } // 创建数据 const int N = 100; double x[N], y[N]; for (int i=0; i<N; i++) { x[i] = i * 2 * M_PI / N; y[i] = std::sin(x[i]); } // 创建MATLAB数组 mxArray *mx_x = mxCreateDoubleMatrix(1, N, mxREAL); mxArray *mx_y = mxCreateDoubleMatrix(1, N, mxREAL); memcpy(mxGetPr(mx_x), x, sizeof(x)); memcpy(mxGetPr(mx_y), y, sizeof(y)); // 将数据放入MATLAB工作空间 engPutVariable(ep, "x", mx_x); engPutVariable(ep, "y", mx_y); // 执行MATLAB命令 engEvalString(ep, "plot(x, y); title('Sine wave'); xlabel('x'); ylabel('y');"); // 等待用户关闭图形 std::cout << "Press Enter to exit..."; std::cin.get(); // 清理 mxDestroyArray(mx_x); mxDestroyArray(mx_y); engClose(ep); return 0; } ``` 编译和运行此程序需要链接MATLAB引擎库。在Linux下,编译命令可能如下: ```bash g++ -o matlab_engine_example matlab_engine_example.cpp -I/usr/local/MATLAB/R2021a/extern/include -L/usr/local/MATLAB/R2021a/bin/glnxa64 -leng -lmx -lmat ``` 注意:路径需要根据实际MATLAB安装位置调整。 总结: 将MATLAB代码整合到其他语言开发的程序中,主要取决于具体需求和环境。MATLAB提供了多种方式来实现这一目标,包括使用引擎API、编译为共享库或使用代码生成技术。开发者可以根据实际情况选择最适合的方法。 §§相关问题§§ 1. MATLAB引擎API支持哪些编程语言? 2. 如何在没有安装MATLAB的计算机上运行由MATLAB Compiler生成的共享库? 3. MATLAB Coder支持哪些工具箱函数? 4. 在C++中调用MATLAB引擎时,如何传递复杂数据结构(如结构体或单元格数组)?</think>要将MATLAB代码集成到其他编程语言(如C++、Python、Java等)开发的程序中,可通过以下核心方法实现: --- ### **1. MATLAB引擎API调用** 在C++/Fortran等编译型语言中直接调用MATLAB引擎: ```cpp #include "engine.h" // MATLAB引擎头文件 Engine *ep = engOpen(NULL); // 启动MATLAB引擎 mxArray *result = NULL; engEvalString(ep, "y = sin(pi/4);"); // 执行MATLAB命令 result = engGetVariable(ep, "y"); // 获取结果 engClose(ep); // 关闭引擎 ``` **特点**: - 需安装MATLAB运行时环境 - 实时交互,适合复杂计算[^1] - 支持数据双向传输(`mxArray`数据类型) --- ### **2. 编译MATLAB为独立库** 使用**MATLAB Compiler SDK**将MATLAB函数编译为可调用库: ```matlab % 将函数编译为C++共享库 mcc -W cpplib:libCalc -T link:lib sinCalc.m ``` 在C++中调用生成的库: ```cpp #include "libCalc.hpp" mwArray y = sinCalc(mwArray(3.14)); // 调用MATLAB函数 ``` **特点**: - 生成标准共享库(DLL/SO) - 依赖**MATLAB Runtime (MCR)**,无需完整MATLAB[^1] - 支持C/C++、Java、.NET等语言 --- ### **3. MATLAB Coder生成原生代码** 将MATLAB算法转换为C/C++源代码: ```matlab % 配置代码生成参数 cfg = coder.config('lib'); codegen -config cfg myFunction -args {0} ``` **特点**: - 生成纯C/C++代码,无MATLAB依赖 - 适合性能敏感场景(如嵌入式系统) - 仅支持MATLAB子集(需代码适配) --- ### **4. 通过文件或网络通信** **间接集成方法**: - **文件交换**:MATLAB输出数据到文件(如CSV/HDF5),主程序读取 - **进程通信**:通过TCP/IP、gRPC等协议通信 - **REST API**:将MATLAB部署为Web服务(MATLAB Production Server) --- ### **选择策略** | 方法 | 适用场景 | 依赖项 | |---------------------|----------------------------------|---------------------| | MATLAB引擎API | 实时交互,快速原型 | 完整MATLAB安装 | | 编译为共享库 | 生产环境部署,跨语言调用 | MATLAB Runtime (MCR)| | MATLAB Coder | 高性能需求,无MATLAB环境 | 无 | | 文件/网络通信 | 松耦合系统,异步处理 | 无 | > ⚠️ **关键注意**: > - 共享库和MATLAB Coder需检查函数兼容性(如支持图形函数) > - 复杂数据结构(如结构体、表格)需特殊处理 > - 跨平台部署需统一编译环境(如gcc版本) --- ### **示例:Python调用MATLAB库** ```python import matlab.engine eng = matlab.engine.start_matlab() # 启动引擎 result = eng.sqrt(4.0) # 调用MATLAB的sqrt函数 print(result) # 输出: 2.0 eng.quit() ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值