使用OPC进行PLC与C#.NET通讯,安装了Kepware,有VC++、VB、VB.NET的Demo,唯独没有C#的,网上找了一些C#的源码,自己也照着Kepware的接口文档改了改C#的源码。
其中在调用一个方法时遇到了InvalidCastException,怎么不能上传图片呢?!只能自己描述一下了:
foreach (int item in (Array)(m_opcServer.QueryAvailableLocaleIDs()))
{
Console.WriteLine(item);
}
异常描述为:Unable to cast object of type 'System.Int32[*]' to type 'System.Int32[]'.
以下为Kepware提供的API文档描述:
4.1.5.6 QueryAvailableLocaleIDs
Description Return the available LocaleIDs for this server/client session. The LocaleIDs are returned as an array of longs.
Syntax QueryAvailableLocaleIDs () As Variant
Example Dim LocaleID As Variant
Dim AnOPCTextSting as String
AnOPCServerLocaleID = AnOPCServer.QueryAvailableLocaleIDs()
For i = LBound(LocaleID) To UBound(LocaleID)
AnOPCTextSting = LocaleIDToString(LocaleID(i))
listbox.AddItem AnOPCTextSting
Next i
VB的示例代码就是这样的,别说我粘错了。
使用的是OPCDAAuto.dll,文档说是C++开发的。
QueryAvailableLocaleIDs () As Variant
返回类型为 Variant,你能否把 C++ 中对此 API 的申明给贴出来。
没源码,示例代码中也没有调用这个方法。我只是测试这个方法的功能时,调一下来着。
@TigerSpringLiu: 不是源码,是API接口定义。估计说了你也不明白。这样吧,你是用的P/Invoke API,还是用的COM引用?你在代码中看到 m_opcServer.QueryAvailableLocaleIDs() 返回的类型是什么?
@Launcher: COM引用,函数声明为:dynamic QueryAvailableLocaleIDs();
我是不明白呢,因为您佬什么也没说,只是“估计”而已。博客园是个技术交流的平台,您愿意分享您的知识就分享,不愿意又何必主观臆断加人格贬低呢?
@TigerSpringLiu: 因为我想快速的解决你的问题,而不是给你讲解一大堆的DLL调用技术。因为如果我有你的项目和文档,我很快就能找到正确的方式来调用,所以希望你能把你的问题描述的尽可能详尽。所有的解决方案都是从已经掌握的知识树上通过决策选取出来的。
如果我遇到你这样的问题,首先我会先使用C++来调用此DLL(因为此DLL是C++编写的),如果我不会C++,我就会先去学习一下C++编写DLL和调用DLL的知识;由于它又是一个COM组件,那么我还会接着学习下COM的编程知识。直到我能通过C++成功调用后,我才会结合我已经掌握的C#知识来做跨平台调用的尝试。
很显然,你是不会这么做的,所以你才会直接提出问题,那么解答你的问题的人就会需要更详尽的描述来确认此问题域究竟是什么。
回到你这个问题,在你提供的代码中,VB 定义的接口为:QueryAvailableLocaleIDs () As Variant
C#中定义的接口为 dynmaic,这说明什么问题呢?我单从VB的语法能够推断出,C++定义的应该是 tagVARIANT 类型,此类型定义如下:
struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; _VARIANT_BOOL *pbool; SCODE *pscode; CY *pcyVal; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } ;
此类型为什么在C#中被解释为 dynmic 了呢?由于我没有这样实际的经验,那么我需要先编写一个C++的COM组件,实现一个接口,返回 tagVARIANT 类型数据。由于你的异常中提到了数组,那么我会以 SAFEARRAY *parray 来作为运行时类型,同时我还得考虑用 SAFEARRAY **pparray 来作为运行时类型来实现我的接口。
基于上面的分析,如果实际编程去测试并最后通过,那要花费我的时间,因此在你没法详细告诉我C++接口到底实现了什么类型之前,我只能自己推断出一些条件,然后帮你搜一下解决方案:
http://zfgis.blog.163.com/blog/static/825009452010323552272/
同时,基于C#的知识,我可以推荐你在运行时通过调试实际的查看一下 m_opcServer.QueryAvailableLocaleIDs() 的运行时类型到底是什么,不要直接就强制转为成 Array。事实上,这是遇到你这个问题之前,第一步要做的事。
@Launcher: 您赐教的很是,小生C++正在自学中。也希望大神推荐些好资料。
运行时调试返回的是:
Name Value Type
m_opcServer.QueryAvailableLocalIDs() {int[1..5} dynmic{int[]}
[1] 0 int
[2] 2048 int
[3] 1033 int
[4] 9 int
[5] 1024 int
不知道 博客园什么时候不让上传图片了,不然我提问的时候就会把这些截图贴上的。
@TigerSpringLiu: 你尝试下这样调用:
dynmic array = m_opcServer.QueryAvailableLocaleIDs();
int a = array[1];
@Launcher: 还是一样的异常呢,其实之前就这样试过的。
贴一下异常堆栈:
at CallSite.Target(Closure , CallSite , Object , Int32 )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at OPCDAAutoTest.Tester.EnumServerInfos() in D:\MineSpace\Works\Test\OPCDAAuto\OPCDAAuto\Tester.cs:line 300
at OPCDAAutoTest.Tester.work() in D:\MineSpace\Works\Test\OPCDAAuto\OPCDAAuto\Tester.cs:line 130
at OPCDAAutoTest.Tester.Main(String[] args) in D:\MineSpace\Works\Test\OPCDAAuto\OPCDAAuto\Tester.cs:line 17
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
----------------------------------------------------------------------------------------------
但改了一下,这样调用:
object array = m_opcServer.QueryAvailableLocaleIDs();
foreach (int item in (Array)array)
{
Console.WriteLine(item);
}
居然就成了。还望大神分析分析,解释解释。
实在折腾人呢!
@TigerSpringLiu: 你可以这样测试下:
1:
object array = m_opcServer.QueryAvailableLocaleIDs();
Array a = (Array)array;
2:
var array = m_opcServer.QueryAvailableLocaleIDs();
Array a = (Array)array;
3:
dynmic array = m_opcServer.QueryAvailableLocaleIDs();
Array a = (Array)array;
你需要把IL的代码找出来对比下才知道具体的区别。
@Launcher: 测试了,还就第一个行,其余两个都报一样的InvalidCaseExcption。
IL代码看不懂:
.method private hidebysig instance void EnumServerInfos() cil managed { // Code size 184 (0xb8) .maxstack 3 .locals init ([0] object 'array', [1] class [mscorlib]System.Array arr, [2] object array0, [3] class [mscorlib]System.Array arr0, [4] object array1, [5] class [mscorlib]System.Array arr1) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld class OPCAutomation.OPCServer OPCDAAutoTest.Tester::MyServer IL_0007: callvirt instance object OPCAutomation.IOPCAutoServer::QueryAvailableLocaleIDs() IL_000c: stloc.0 IL_000d: ldloc.0 IL_000e: castclass [mscorlib]System.Array IL_0013: stloc.1 IL_0014: ldarg.0 IL_0015: ldfld class OPCAutomation.OPCServer OPCDAAutoTest.Tester::MyServer IL_001a: callvirt instance object OPCAutomation.IOPCAutoServer::QueryAvailableLocaleIDs() IL_001f: stloc.2 IL_0020: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site1' IL_0025: brtrue.s IL_004e IL_0027: ldc.i4.s 16 IL_0029: ldtoken [mscorlib]System.Array IL_002e: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0033: ldtoken OPCDAAutoTest.Tester IL_0038: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_003d: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [mscorlib]System.Type, class [mscorlib]System.Type) IL_0042: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) IL_0047: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site1' IL_004c: br.s IL_004e IL_004e: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site1' IL_0053: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>>::Target IL_0058: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site1' IL_005d: ldloc.2 IL_005e: callvirt instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>::Invoke(!0, !1) IL_0063: stloc.3 IL_0064: ldarg.0 IL_0065: ldfld class OPCAutomation.OPCServer OPCDAAutoTest.Tester::MyServer IL_006a: callvirt instance object OPCAutomation.IOPCAutoServer::QueryAvailableLocaleIDs() IL_006f: stloc.s array1 IL_0071: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site2' IL_0076: brtrue.s IL_009f IL_0078: ldc.i4.s 16 IL_007a: ldtoken [mscorlib]System.Array IL_007f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_0084: ldtoken OPCDAAutoTest.Tester IL_0089: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_008e: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [mscorlib]System.Type, class [mscorlib]System.Type) IL_0093: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder) IL_0098: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site2' IL_009d: br.s IL_009f IL_009f: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site2' IL_00a4: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>>::Target IL_00a9: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>> OPCDAAutoTest.Tester/'<EnumServerInfos>o__SiteContainer0'::'<>p__Site2' IL_00ae: ldloc.s array1 IL_00b0: callvirt instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite,object,class [mscorlib]System.Array>::Invoke(!0, !1) IL_00b5: stloc.s arr1 IL_00b7: ret } // end of method Tester::EnumServerInfos
谢谢!
@TigerSpringLiu: 从IL看,dynmic 和 var 是一样的,都有运行时类型转换的过程。由于此转换类型错误,导致转换失败。在C#中,variant 用 object 来表示,如果你使用较低版本的 .Net Framework,接口应该返回 object 类型,variant 表示可变类型,其具体的类型由字段 VARTYPE vt 来决定,在C#中应该有个系统转换通过 vt 来决定返回具体的 C# 类型。
System.Int32[] 是从0开始的int数组。System.Int32[*] 是从非0开始的int数组。比如以下C#代码创建了一个长度为10,下标从-1开始的int数组。
Array.CreateInstance(typeof(int), new[] { 10 }, new[] { -1 })
我看你的文档说返回“an array of longs",你怎么遍历的时候item的类型是int?QueryAvailableLocaleIDs() 这个方法返回的是什么?按照文档应该是一个long[]
确实索引从1开始的,长见识了,多谢。
但文档说了是C++的源码库,C++的long类型不是对应C#的int类型吗?至少PInvoke的时候是这么对应的吧。而且,刚刚也改成了long试一下的,还是这样的异常。
@TigerSpringLiu: n你确定C++的long对应C#的int么。。。那C++里用什么来表示64位整数呢
@水牛刀刀: 这个其实也不确定,要根据不同平台及C++编译器确定呢。
我只是说了多数情况下,C#语言的int可以与long对应。
具体可看一下链接,对C/C++整型的描述:
http://www.bccn.net/Article/kfyy/cjj/jszl/200511/978.html
32位的的int类型的指针?