为什么从 JNI 调用时 SocketCAN bind() 总是调用 return 0?

Why does SocketCAN bind() call always return 0 when called from JNI?

我正在尝试创建一个 Java 封装的 CAN 总线的 C 实现,它将在 Android 环境中使用。我在 Linux 内核中使用了 SocketCAN 来创建套接字并将其绑定到 CAN 接口。

开发环境没有物理 CAN 总线,因此我通过 sudo ip link add dev vcan0 type vcansudo ip link set up vcan0.

创建虚拟总线

运行 此环境中的本机 C 代码按预期工作,接口存在时套接字绑定,return 接口不存在时出错。但是,当通过 JNI 运行 相同的本机 C 代码时,bind(...) 调用总是 returns 0 而不管接口的状态如何,尽管任何后续的 write(...) 调用都会按预期失败.

是否有什么我忽略的地方表明是这种情况?

JNI 代码如下(直接从我的 C 实现中提取,必要时附加类型转换):

JNIEXPORT jboolean JNICALL Java_SocketCAN_nativeOpen
  (JNIEnv * env, jobject jobj, jint bus_id)
{
  if ((int) bus_id < MAX_NUMBER_OF_CAN_BUSES)
  {
    int s;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) 
    {
      printf("Error while opening socket\n");
      return JNI_FALSE;
    }

    struct sockaddr_can addr;
    struct ifreq ifr;

    strcpy(ifr.ifr_name, "vcan0");
    ioctl(s, SIOCGIFINDEX, &ifr);
    
    addr.can_family  = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    {
      printf("Error in socket bind\n");
      return JNI_FALSE;
    }

    // Set the socketId in the Java class.
    jclass   jcls        = (*env)->FindClass(env, "SocketCAN");
    jfieldID socket_id   = (*env)->GetFieldID(env, jcls, "socket", "I");
    jint     j_socket_id = (*env)->GetIntField(env, jobj, socket_id);
    j_socket_id = s;
    (*env)->SetIntField(env, jobj, socket_id, j_socket_id);

    return JNI_TRUE;
  }
  
  return JNI_FALSE;
}

非常感谢任何帮助,谢谢!

编辑: 如果有人似乎遇到了这个奇怪的问题并想要解决方法(尽管这可能是正确的方法,但我忽略了它),请检查 ioctl(...) 函数调用中的 return 值。当 运行 C 和 JNI 都未设置“vcan0”时,returns -1。

我根据@12431234123412341234123和@AndrewHenle的建议修改后的代码如下:

JNIEXPORT jint JNICALL Java_SocketCAN_nativeOpen
  (JNIEnv * env, jobject jobj, jint bus_id)
{
  if ((int) bus_id < MAX_NUMBER_OF_CAN_BUSES)
  {
    int s;

    if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) 
    {
      printf("Error while opening socket\n");
      return -1;
    }

    struct sockaddr_can addr;
    struct ifreq ifr;

    strcpy(ifr.ifr_name, "vcan0");
    if (ioctl(s, SIOCGIFINDEX, &ifr) == -1)
    {
      printf("Error in ioctl\n");
      return -1;
    }
    
    addr.can_family  = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;

    if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    {
      printf("Error in socket bind\n");
      return -1;
    }

    return (jint) s;
  }
  
  return -1;
}

bind() 调用需要 can_ifindex 中的正确接口索引。要获得此值,您可以像往常一样使用 ioctl() 调用和 SIOCGIFINDEX。但是,当 ioctl() 调用失败时,ifreq 结构不一定具有正确的索引,它可能仍然具有之前占用相同内存区域的最后一个对象的“随机”值。因为您忽略了 ioctl() 中的 return 值,所以您使用“随机”接口索引调用了 bind()。这也意味着绑定可能会或可能不会失败,具体取决于值,因为在大多数情况下使用未初始化的值是 UB。为避免此错误,请检查 ioctl() 中的 return 值并相应地处理错误。

似乎这个“随机”值对于普通 C 版本和 JNI 版本是不同的。避免这种随机差异的一种可能性是将每个新的自动对象直接设置为一个值。在您的情况下,您可以将所有内容设置为 0:struct ifreq ifr={0};,与 addr 相同。这个额外的步骤可以获得更一致的行为。