mac中加载C++动态库文件

前言

需要再mac系统下运行C++开发的程序,通过摸索,初步实现了一版,大致记录下

1. 前提准备

  1. 安装OpenCV
  • 使用Homebrew安装OpenCV:
bash 复制代码
brew install opencv
  • 确认安装路径:
bash 复制代码
brew --prefix opencv
复制代码
默认路径为/opt/homebrew/opt/opencv。
  • 确保头文件和动态库路径正确,以便在后续编译中正确链接。

2. 将C++程序编译成动态库

2.1 编写C++代码

  • 编写基于OpenCV的C++代码,通过C++类实现图像处理过程,通过
    链接函数。
  • 示例代码:头文件
cpp 复制代码
#ifndef IMAGE_PROCESS_H
#define IMAGE_PROCESS_H

#include <opencv2/opencv.hpp>

class ImageProcess
{
public:
    cv::Mat getImageProcess(const cv::Mat &image);
};

#ifdef __cplusplus
extern "C"
{
#endif

    // 声明一个 C 链接的函数,返回原始指针
    extern "C" unsigned char *processImage(unsigned char *input, int width, int height, int bytesPerRow);

#ifdef __cplusplus
}
#endif

#endif
cpp 复制代码
#include "image.h"

using namespace cv;
cv::Mat ImageProcess::getImageProcess(const cv::Mat &image)
{
    // 获取图像的宽度和高度
    int width = image.cols;
    int height = image.rows;

    // 计算矩形的左上角和右下角坐标
    int rectWidth = 150;  // 矩形的宽度
    int rectHeight = 50;  // 矩形的高度
    int startX = (width - rectWidth) / 2;  // 矩形的左上角 x 坐标
    int startY = (height - rectHeight) / 2;  // 矩形的左上角 y 坐标
    int endX = startX + rectWidth;  // 矩形的右下角 x 坐标
    int endY = startY + rectHeight;  // 矩形的右下角 y 坐标

    // 绘制矩形
    rectangle(image, Point(startX, startY), Point(endX, endY), Scalar(0, 255, 0), 4);  // 使用绿色绘制矩形,线条宽度为 2

    return image;
}

extern "C" unsigned char *processImage(unsigned char *input, int width, int height, int bytesPerRow)
{
    cv::Mat inputMat(height, width, CV_8UC4, input, bytesPerRow);
    ImageProcess imageProcess;
    cv::Mat outputMat = imageProcess.getImageProcess(inputMat);

    // 将输出 Mat 的数据复制到一个新的缓冲区
    unsigned char *outputData = new unsigned char[outputMat.total() * outputMat.elemSize()];
    std::memcpy(outputData, outputMat.data, outputMat.total() * outputMat.elemSize());

    return outputData;
}

extern "C" int add(int a, int b)
{
    return a + b;
}
  • 注意:使用extern "C"声明函数,避免C++的名称修饰问题,确保从Swift或其他语言调用时能够正确链接。

2.2 编译动态库

  • 编写CMakeLists.txt文件(推荐)或直接使用g++命令编译。
  • 示例CMakeLists.txt:
cpp 复制代码
cmake_minimum_required(VERSION 3.10)
project(ImageLib)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)

# 添加动态库
add_library(Image SHARED src/image.cpp)

# 找到 OpenCV 库
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

# 链接 OpenCV 库
target_link_libraries(Image ${OpenCV_LIBS})
  • 编译命令:通过执行 sh build.sh编译
bash 复制代码
#!/bin/bash

# 设置项目根目录
PROJECT_DIR=$(pwd)
echo "Project directory: $PROJECT_DIR"

# 创建构建目录
BUILD_DIR="${PROJECT_DIR}/build"
echo "Creating build directory: $BUILD_DIR"
mkdir -p "${BUILD_DIR}"

# 清空构建目录
if [ -d "${BUILD_DIR}" ]; then
    echo "Cleaning previous build directory..."
    rm -rf "${BUILD_DIR}"/*
else
    echo "Build directory does not exist. Creating it..."
    mkdir -p "${BUILD_DIR}"
fi

# 检查构建目录是否创建成功
if [ ! -d "${BUILD_DIR}" ]; then
    echo "Failed to create build directory: $BUILD_DIR"
    exit 1
fi

# 进入构建目录
echo "Changing directory to: $BUILD_DIR"
cd "${BUILD_DIR}"

# 运行 CMake
echo "Running CMake..."
cmake ..

# 检查 CMake 是否成功
if [ $? -ne 0 ]; then
    echo "CMake failed. Exiting..."
    exit 1
fi

# 编译项目
echo "Compiling project..."
make -j$(sysctl -n hw.ncpu)

# 检查编译是否成功
if [ $? -ne 0 ]; then
    echo "Compilation failed. Exiting..."
    exit 1
fi

# 检查动态库是否生成
if [ ! -f "${BUILD_DIR}/libImage.dylib" ]; then
    echo "Dynamic library not found: ${BUILD_DIR}/libImage.dylib"
    exit 1
fi

# 输出构建结果
echo "Build completed. Dynamic library is in ${BUILD_DIR}/libImage.dylib"
  • 生成的动态库文件为libImage.dylib(在macOS上)。

3. 在Swift中调用动态库

  1. 加载动态库
  • 使用dlopen加载动态库,并通过dlsym获取函数地址。
  • 示例代码:
swift 复制代码
image_processimport Foundation
import AppKit


func main() {
    // 1. 动态库加载模块
    typealias ProcessImageFunction = @convention(c) (UnsafeMutablePointer<UInt8>?, Int32, Int32, Int32) -> UnsafeMutablePointer<UInt8>?


    // 加载动态库
    let handle = dlopen("/Users/gongyong/Desktop/Keyi/test_ws/image_process/build/libImage.dylib", RTLD_LAZY)
    if handle == nil {
        print("Failed to load library: \(String(cString: dlerror()))")
        exit(1)
    } else {
        print("Library loaded successfully")
    }


    // 获取函数地址
    let processImageFunctionPointer = dlsym(handle, "processImage")
    if processImageFunctionPointer == nil {
        print("Failed to find function: \(String(cString: dlerror()))")
        dlclose(handle)
        exit(1)
    } else {
        print("Function 'processImage' found successfully")
    }


    // 将指针转换为函数指针类型
    let processImageFunction = unsafeBitCast(processImageFunctionPointer, to: ProcessImageFunction.self)


    // 2. 加载图像,并且调用动态库处理
    let filePath = "/Users/gongyong/Desktop/Keyi/test_ws/demo/Source/demo/face.png"
    let outputPath = "/Users/gongyong/Desktop/Keyi/test_ws/demo/Source/demo/output.png"


    if let (pixelData, width, height, bytesPerRow) = loadPNGImage(from: filePath) {
        print("Image loaded successfully with dimensions: \(width) x \(height), bytesPerRow: \(bytesPerRow)")


        // 调用 C+ 图像处理函数
        let processedPixelData = processImageFunction(pixelData, Int32(width), Int32(height), Int32(bytesPerRow))
        if processedPixelData == nil {
            print("Image processing failed: processImage returned nil")
        } else {
            print("Image processed successfully")


            // 保存处理后的图像
            savePNGImage(to: outputPath, pixelData: processedPixelData, width: width, height: height, bytesPerRow: bytesPerRow)


            // 打印输出图像的长宽
            print("Output image dimensions: \(width) x \(height)")
        }


        // 释放动态分配的内存
        free(processedPixelData)
    } else {
        print("Failed to load image from \(filePath)")
    }


    // 关闭动态库
    dlclose(handle)
    print("Library closed successfully")
}


// 加载 PNG 图像的辅助函数
func loadPNGImage(from path: String) -> (UnsafeMutablePointer<UInt8>?, Int, Int, Int)? {
    print("Loading image from \(path)")
    guard let image = NSImage(contentsOfFile: path) else {
        print("Failed to load image from \(path)")
        return nil
    }


    guard let tiffData = image.tiffRepresentation,
          let bitmapRep = NSBitmapImageRep(data: tiffData) else {
        print("Failed to create bitmap representation from image")
        return nil
    }


    let width = Int(bitmapRep.size.width)
    let height = Int(bitmapRep.size.height)
    let bytesPerRow = bitmapRep.bytesPerRow


    guard let pixelData = malloc(bytesPerRow * height)?.assumingMemoryBound(to: UInt8.self) else {
        print("Failed to allocate memory for pixel data")
        return nil
    }


    // Copy the bitmap data to the allocated memory
    memcpy(pixelData, bitmapRep.bitmapData, bytesPerRow * height)
    print("Image data copied to memory")


    // 打印输入图像的长宽
    print("Input image dimensions: \(width) x \(height)")


    return (pixelData, width, height, bytesPerRow)
}


// 保存 PNG 图像的辅助函数
func savePNGImage(to path: String, pixelData: UnsafeMutablePointer<UInt8>?, width: Int, height: Int, bytesPerRow: Int) {
    guard let pixelData = pixelData else {
        print("Failed to save image: pixelData is nil")
        return
    }


    var pixelDataPointer: UnsafeMutablePointer<UInt8>? = pixelData


    // For RGBA image
    let bitmapRep = NSBitmapImageRep(bitmapDataPlanes: &pixelDataPointer,
                                     pixelsWide: width,
                                     pixelsHigh: height,
                                     bitsPerSample: 8,
                                     samplesPerPixel: 4,
                                     hasAlpha: true,
                                     isPlanar: false,
                                     colorSpaceName: .calibratedRGB,
                                     bitmapFormat: .alphaFirst,
                                     bytesPerRow: bytesPerRow,
                                     bitsPerPixel: 32)


    guard let bitmapRep = bitmapRep else {
        print("Failed to create NSBitmapImageRep")
        return
    }


    guard let pngData = bitmapRep.representation(using: .png, properties: [:]) else {
        print("Failed to generate PNG data")
        return
    }


    do {
        try pngData.write(to: URL(fileURLWithPath: path))
        print("Image saved successfully to \(path)")
    } catch {
        print("Failed to save image: \(error)")
    }
}


main()
  • 注意:确保动态库路径正确,且动态库中的函数签名与Swift中声明的类型一致。
  1. 加载和处理图像
  • 使用Swift的NSImage和NSBitmapImageRep加载和保存图像。
  • 将图像数据传递给C++动态库进行处理,并接收处理后的数据。

4. 注意事项

  1. 动态库路径
  • 确保动态库路径正确,可通过dlopen加载指定路径的动态库。
  • 如果动态库路径不正确,会报错"Failed to load library"。
  1. 函数签名一致性
  • C++动态库中的函数签名必须与Swift中声明的函数指针类型一致,否则会报错"Failed to find function"或运行时崩溃。
  1. 内存管理
  • 动态分配的内存需要手动释放,避免内存泄漏。
  • 在Swift中使用malloc分配内存,并在处理完成后使用free释放。
  1. 图像格式
  • 确保输入和输出图像格式一致,例如RGBA或灰度图像。
  • 如果格式不一致,可能导致图像处理失败或输出图像异常。
  1. 调试
  • 使用dlerror获取动态库加载和函数查找的错误信息,便于调试。
  • 在开发过程中,建议逐步测试每个模块(如动态库加载、图像加载、图像处理、图像保存等),确保每个部分正常工作。

5. 效果演示

加载图像

调用C++处理后的图像

附件

先编译完image_process下的C++文件

然后xcode打开demo解压文件,更新动态库路径、加载图片和输出图片的绝对路径。

文件下载地址:

bash 复制代码
git clone [email protected]:xjturmy/xcode_c-.git
相关推荐
芯眼14 分钟前
STM32启动文件详解(重点)
java·开发语言·c++·stm32·单片机·mybatis
愚润求学43 分钟前
【Linux】动静态库链接原理
linux·运维·服务器·开发语言·笔记
呦呦彬1 小时前
【问题排查】easyexcel日志打印Empty row!
java·开发语言·log4j
Tummer83631 小时前
C#+WPF+prism+materialdesign创建工具主界面框架
开发语言·c#·wpf
九章云极AladdinEdu1 小时前
GPU与NPU异构计算任务划分算法研究:基于强化学习的Transformer负载均衡实践
java·开发语言·人工智能·深度学习·测试工具·负载均衡·transformer
好吃的肘子2 小时前
MongoDB 应用实战
大数据·开发语言·数据库·算法·mongodb·全文检索
ghost1432 小时前
C#学习第23天:面向对象设计模式
开发语言·学习·设计模式·c#
小白学大数据2 小时前
Scrapy框架下地图爬虫的进度监控与优化策略
开发语言·爬虫·python·scrapy·数据分析
立秋67892 小时前
用Python绘制梦幻星空
开发语言·python·pygame
汉克老师2 小时前
GESP2025年3月认证C++二级( 第三部分编程题(1)等差矩阵)
c++·算法·矩阵·gesp二级·gesp2级