最近有一个基恩士LKG5000测距仪要用,LKG5000可以用RS232和以太网通信,无奈电器用了网线接口,因此只能用以太网通信了。
基恩士提供的以太网通信例程是基于C++的,因此需要用到C#调用C++ DLL的技术。
C#调用C++函数需要按如下格式进行声明:
[DllImport("LKIF2.dll")]
public extern static RC LKIF2_CloseDevice();
其中,在调用基恩士C++函数时我碰到了以下几种情况:
1,传入参数和返回值均为基本数据类型,这类函数按照上面的格式直接写,写完调用就可以了,非常简单
[DllImport("LKIF2.dll")]
public extern static bool LKIF_ClearFigureData();
2,传入参数和返回值中存在枚举类型,如这里的RC就是基恩士自定义的枚举类型,这时只需要在C#中按照C#的格式将这个枚举类写一遍就可以了
[DllImport("LKIF2.dll")]
public extern static RC LKIF2_CloseDevice();
public enum RC
{
RC_OK = 0x0000, // The operation is completed successfully.
///
// Communication error from controller notification
//
RC_NAK_COMMAND = 0x1001, // Command error
RC_NAK_COMMAND_LENGTH, // Command length error
RC_NAK_TIMEOUT, // Timeout
RC_NAK_CHECKSUM, // Check sum error
RC_NAK_INVALID_STATE, // Status error
RC_NAK_OTHER, // Other error
RC_NAK_PARAMETER, // Parameter error
RC_NAK_OUT_STAGE, // OUT calculation count limitation error
RC_NAK_OUT_HEAD_NUM, // No. of used head/OUT over error
RC_NAK_OUT_INVALID_CALC, // OUT which cannot be used for calculation was specified for calculation.
RC_NAK_OUT_VOID, // OUT which specified for calculation is not found.
RC_NAK_INVALID_CYCLE, // Unavailable sampling cycle
RC_NAK_CTRL_ERROR, // Main unit error
RC_NAK_SRAM_ERROR, // Setting value error
///
// Communication DLL error notification
//
RC_ERR_OPEN_DEVICE = 0x2000,// Opening the device failed.
RC_ERR_NO_DEVICE, // The device is not open.
RC_ERR_SEND, // Command sending error
RC_ERR_RECEIVE, // Response receiving error
RC_ERR_TIMEOUT, // Timeout
RC_ERR_NODATA, // No data
RC_ERR_NOMEMORY, // No free memory
RC_ERR_DISCONNECT, // Cable disconnection suspected
RC_ERR_UNKNOWN, // Undefined error
}
3,传入参数中带有结构体的,碰到这种实际上我感觉挺麻烦,因为结构体里面一般还会有结构体,并且还有各种关键字如union等。这里以连接函数为例:
EXP RC WINAPI LKIF2_OpenDeviceETHER(LKIF_OPENPARAM_ETHERNET * OpenParam);
这里出现了一个结构体
typedef struct {
IN_ADDR IPAddress;
} LKIF_OPENPARAM_ETHERNET;
结构体里面还有一个结构体
//
// IPv4 Internet address
// This is an 'on-wire' format structure.
//
typedef struct in_addr {
union {
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
#define s_host S_un.S_un_b.s_b2 // host on imp
#define s_net S_un.S_un_b.s_b1 // network
#define s_imp S_un.S_un_w.s_w2 // imp
#define s_impno S_un.S_un_b.s_b4 // imp #
#define s_lh S_un.S_un_b.s_b3 // logical host
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
可以看到in_addr结构体里面还使用了关键字union,意思是union里面的变量共用了一个地址(重点——),看到这里实际上我也不知道该怎么写了,这么复杂,下面还有一堆define关键字。
看回基恩士给的例程,噢,发现他虽然传入了LKIF_OPENPARAM_ETHERNET,但他只对S_addr进行了赋值,而S_un_b、S_un_w和S_addr又是公用一段地址的,那么我是否只需要在C#的结构体里声明一个ULONG 类型的变量就可以了呢?
_sprintf_p(strIp,20,"%d.%d.%d.%d\0",_ttoi(strAddr[0]),_ttoi(strAddr[1]),_ttoi(strAddr[2]),_ttoi(strAddr[3]));
LKIF_OPENPARAM_ETHERNET paramEther;
paramEther.IPAddress.S_un.S_addr = inet_addr(strIp);
if(paramEther.IPAddress.S_un.S_addr == INADDR_NONE)
{
SHOW_ERROR_MSG;
return;
}
RC rc = LKIF2_OpenDeviceETHER(¶mEther);
((CLkIF2TestVCDlg*)GetParent())->DisplayCommandResult(_T("LKIF2_OpenDeviceETHER"),rc);
再进一步,既然只有一个变量,那么我是否可以直接将传入的参数更改为long,即如下面所示,因为只写一个long和写一堆结构体和union他们的地址是一样的
[DllImport("LKIF2.dll")]
public extern static RC LKIF2_OpenDeviceETHER(ref long IPAddress);
结果表明,这样写确实是可以的。虽然我没有研究过C#调用C++库的具体机制,但我猜想他是按照内存地址来读取和写入变量的,只要地址对的上,那最终结果就没有问题。