1

我正在为一块硬件开发一些东西,并且我有一个 C 库来与硬件通信。我有向硬件发送信号的方法(比如打开灯泡),并且使用 JNA 在 C# 和 Java 上都可以正常工作。

该机器还有一个可按下的按钮,当按下该按钮时,它将记录一个信号,该信号可以通过称为 A 的方法检索。

预期的工作方式是创建一个新线程,该线程一直调用此方法,直到它返回 1 在这种情况下,它将获得有关按钮按下的信息。

我已经使用以下代码在 C# 中工作:

    while (true)
    {
        byte[] ccbdata = new byte[255];
        short TagCommand = -1, nMsg_type = -1;
        int ccblen = 0, nGwId = 0, nNode = 0;
        int ret = CWrapper.Wrapper.A(ref nGwId, ref nNode, ref TagCommand, ref nMsg_type, ref ccbdata[0], ref ccblen);
        if (ret > 0)
        {
           // do stuff
        }
        Thread.Sleep(100);
    }

在 C# 中导入该方法的位置,例如:

[DllImport("Clibrary.dll")]
public static extern int C(ref int Gateway_ID, ref int Node_Addr, ref short Subcmd, ref short msg_type, ref byte data, ref int data_cnt);

我希望这段代码也能在 Java 上工作。不幸的是,当我使用 java 运行它时,它永远不会返回 1,这与 C# 实现不同。

我想知道我是否做错了什么,因为我没有使用 JNA 的经验。在Java中,我导入这样的方法:

int C(LongByReference gatewayID, LongByReference tag_addr, LongByReference Subcmd, LongByReference msg_type,
                  ByteByReference data, LongByReference data_cnt);

我尝试像这样运行代码:

    while(run) {
        gatewayID.setValue(0);
        tag_addr.setValue(0);
        subcmd.setValue(-1);
        msg_type.setValue(-1);
        byte b = 0;
        data.setValue(b);
        data_cnt.setValue(0);

        int ref = CWrapper.INSTANCE.C(gatewayID, tag_addr, subcmd, msg_type, data, data_cnt);
        if (ref > 0) {
            System.out.println("hit");
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

在我拥有的有关此方法的文档中,它在 C 中定义,例如:

C(ByRef gatewayID As Integer, ByRef tag_addr As Integer, ByRef subcmd As Integer, ByRef msg_type As Integer, ByRef data As Byte, ByRef data_cnt As Integer)

有谁知道为什么 C# 示例有效但 Java 示例无效?

--编辑 25-08-2021 12:04

文档说使用了提供给该方法的参数。所以我想如果它们以某种方式为空,该方法将不会返回任何东西。也许使用 ByReference 对象的初始化是错误的?

--编辑 26-08-2021

我得到了 C++ 签名,它是:

typedef int (__stdcall *pC)(int& Gateway_ID, int& Node_Addr, short& Subcmd, 
                  short& Msg_Type, unsigned char* Data, short& Data_Cnt);
4

2 回答 2

2

在java中,long是8个字节,你的c#代码使用ref int的是4个字节。
你试过使用IntByReference吗?

于 2021-08-24T09:33:41.243 回答
0

使用给定的 C 签名:

typedef int (__stdcall *pC)(int& Gateway_ID, int& Node_Addr, short& Subcmd, short& Msg_Type, unsigned char* Data, short& Data_Cnt);

我会这样绑定它:

interface CLibrary extends StdCallLibrary {
    int C(
        IntByReference Gateway_ID,
        IntByReference Node_Addr,
        ShortByReference Subcmd,
        ShortByReference Msg_Type,
        byte[] Data,
        ShortByReference Data_Cnt);
}

C 上的int可以预期是 32 位,这很重要,因此这可以干净地映射到 java int并且所有参数都通过引用传递IntByReference是你的朋友。short和ShortByReference也是如此(只是位数不同)

这就留下了DataData_Cnt会发生什么的问题。我猜,那个 Data 是函数放置数据的缓冲区(调用 C 是一个拉操作)。该缓冲区长 255 个字节(我假设,这是作为库的要求记录在案的地方)。现在,当拉取操作成功时,我希望数据被写入支持Data的缓冲区,并且接收到的数据量被写入Data_Cnt。现在调用方 (Java) 可以读取传输的数据量 ( Data_Cnt.getValue() ) 并从 byte[] 中读取那么多。Java 数组通过引用传递 - JNI 创建数组的副本,将其传递给函数,函数运行,数据被复制回数组。

这就是为什么您应该始终创建一个最小的可运行示例的原因:

您跳过了绑定的重要部分。C 签名有一个 __stdcall 标记。这改变了调用约定。从 StdCallLibrary 派生应该可以解决这个问题。

于 2021-08-26T17:26:04.987 回答