3. 使用结构体传递CAN消息数据

任务目标

本案例演示如何在CAPL与DLL之间通过结构体传递CAN消息数据。你将定义一个包含ID、DLC及数据字节的C结构体,在DLL中实现对该结构体的处理函数(修改消息ID与数据内容),并在CAPL侧声明相同布局的结构体类型,调用DLL函数完成CAN消息内容的修改。

环境与依赖

  • 开发工具:Visual Studio(C++环境,用于编译DLL)
  • 运行环境:Vector CANoe(支持CAPL与DLL调用)
  • 必要文件:can_struct_dll.dll(编译产物)、CanStruct.h(结构体定义头文件)

结构体定义(CanStruct.h)

// CanStruct.h
#pragma pack(push, 1)  // 按1字节对齐,确保CAPL与DLL内存布局一致
typedef struct {
    unsigned int   id;        // CAN消息ID
    unsigned char  dlc;       // 数据长度
    unsigned char  data[8];   // 数据字节
} CanMessage;
#pragma pack(pop)

DLL实现(完整代码)

// dllmain.cpp
#include <windows.h>
#include "CanStruct.h"

// 修改消息ID与数据内容
extern "C" __declspec(dllexport) void ModifyMessage(CanMessage* msg) {
    if (msg == NULL) return;
    
    msg->id = 0x123;                    // 修改ID
    msg->dlc = 4;                       // 修改DLC
    msg->data[0] = 0xAA;                // 修改数据
    msg->data[1] = 0xBB;
    msg->data[2] = 0xCC;
    msg->data[3] = 0xDD;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    return TRUE;
}

CAPL调用代码

// CAPL侧结构体声明(与DLL保持一致)
struct CanMessage {
    dword id;
    byte  dlc;
    byte  data[8];
};

// DLL函数原型声明
extern void ModifyMessage(struct CanMessage* msg);

// 全局消息变量
struct CanMessage g_msg;

on start {
    // 初始化消息
    g_msg.id = 0x100;
    g_msg.dlc = 2;
    g_msg.data[0] = 0x11;
    g_msg.data[1] = 0x22;
    
    write("修改前: ID=0x%X, DLC=%d, Data[0]=0x%X", g_msg.id, g_msg.dlc, g_msg.data[0]);
    
    // 调用DLL函数修改消息
    ModifyMessage(&g_msg);
    
    write("修改后: ID=0x%X, DLC=%d, Data[0]=0x%X, Data[1]=0x%X", 
          g_msg.id, g_msg.dlc, g_msg.data[0], g_msg.data[1]);
}

on message * {
    // 在接收消息时也可调用DLL处理
    struct CanMessage rx_msg;
    rx_msg.id = this.id;
    rx_msg.dlc = this.dlc;
    for (int i = 0; i < this.dlc; i++) {
        rx_msg.data[i] = this.byte(i);
    }
    
    ModifyMessage(&rx_msg);
    
    // 将修改后的数据写回消息
    this.id = rx_msg.id;
    this.dlc = rx_msg.dlc;
    for (int i = 0; i < rx_msg.dlc; i++) {
        this.setByte(i, rx_msg.data[i]);
    }
    output(this);
}

编译与配置步骤

  1. 编译DLL:在Visual Studio中创建Win32项目,将上述dllmain.cpp与CanStruct.h加入工程,配置为DLL输出,编译生成can_struct_dll.dll。
  2. 放置DLL:将生成的DLL文件复制到CANoe的Exec32目录(或工程目录)。
  3. CAPL配置:在CANoe的Simulation Setup中,为节点添加CAPL程序,并在程序中使用#pragma library("can_struct_dll")声明DLL依赖。

验证方法

  • 启动CANoe,在Write窗口观察on start事件中输出的修改前后对比信息。
  • 预期输出(由代码逻辑直接推出):
    修改前: ID=0x100, DLC=2, Data[0]=0x11
    修改后: ID=0x123, DLC=4, Data[0]=0xAA, Data[1]=0xBB
  • 通过条件:Write窗口显示ID从0x100变为0x123,DLC从2变为4,数据字节被修改为0xAA~0xDD,说明结构体在CAPL与DLL间正确传递且修改生效。

常见问题

  • 内存布局不一致:确保CAPL与DLL使用相同的字节对齐方式(本案例使用#pragma pack(1))。若CAPL侧结构体未对齐,可在CAPL中使用#pragma pack(1)指令。
  • DLL加载失败:确认DLL文件名与#pragma library声明一致,且DLL位于CANoe搜索路径中。
  • 指针传递:DLL函数必须使用指针参数,CAPL侧传递结构体地址(&g_msg),否则修改无法回传。