良田高拍仪的SDK如官方所说,eloam windows sdk 其实是通用的,只与平台有关,不是针对语言的,语言不同只是调用方式不同罢了,标题所说的JavaSDK的其实也就是官方的一个小例子EloamViewJavaDemo
,也就是用Eclipse SWT
的OLE
组件去调用驱动的API,如果你不清楚EloamViewJavaDemo
,可以看一下前文在Windows系统对接良田高拍仪驱动SDK,不过如果你手上没有那个demo,也没有关系,通过本文的学习你就不需要官方的demo(官方的SDK包也不提供这个demo了)
起步
无论你有没有EloamViewJavaDemo
这个demo,都应该新建一个项目进行开发测试,不要修改原本的demo,新建了我们当然就可以使用现代化的工具了,起步自然是用gradle配置,但实际也没什么可配置,因为我们除了一个swt-win32-4430.dll
之外将不需要任何的依赖!
简单配置一下gradle就可以将swt-win32-4430.dll
放到项目根目录中了,不要放错放到resources
目录里了,然后就可以找到它的依赖,org.eclipse.swt,接下来就是先分析分析EloamViewJavaDemo
用了这个依赖的哪些东西了,其实就是UI控件和ole
部分,一般来说java去调用它怎么都轮不到去用窗口UI界面的,所有关于UI控件的部分可以无视,下面就都是说这个ole的部分,也就是标题说的Eclipse SWT OLE Win32自动化组件:
那么ole
到底是什么呢?简单的说就是Windows应用之间相互通讯的一种机制,对于高拍仪来说就是这个SDK与驱动程序的通讯,浏览EloamViewJavaDemo
的几个操作按钮绑定的事件就可以发现,都是去调用eloamViewOCX
类的方法去完成的,而eloamViewOCX
类所有的方法操作都是先制作成Variant
参数,然后发送给swt
的OleAutomation
类invoke()
具体执行,那么我们就可以把这个过程抽离出来优化一下,不再依赖整个org.eclipse.swt
。
所以接下来就是处理一下这个OLE,在这个过程中,我去除了大部分OLE协议,但是我保留了一部分,我觉得保留一部分OLE协议,你才知道你用的是OLE协议,是在阉割的部分中我留下的一部分(是故意的)
检查具体依赖的部分
ole
部分其实很小一块:
一看就这么几个文件,就可以更加的放心了,只可惜eclipse不提供这种比较细致引入,所以只能直接把这些类复制到我们的项目里,但请先不要着急直接复制过来,因为这些类还是有相当多引用的,我们应该找找具体用了哪些东西,然后再贴过来,所以先找一找eloamViewOCX
类具体import
了ole
那些部分:
import org.eclipse.swt.ole.win32.OLE;
import org.eclipse.swt.ole.win32.OleAutomation;
import org.eclipse.swt.ole.win32.OleControlSite;
import org.eclipse.swt.ole.win32.OleFrame;
import org.eclipse.swt.ole.win32.OleListener;
import org.eclipse.swt.ole.win32.Variant;
首先是org.eclipse.swt.ole.win32.OLE
这个类,只是用到了OLE.OLEIVERB_SHOW
这个常量用于显示,所以可以去掉这个类,然后是OleAutomation
,这个是核心。后面的OleControlSite
完全是为了初始化OleAutomation
用的,但仔细看OleAutomation
初始化的过程,可以发现其实并不复杂,只需要一个ProgID(GUID)
,所以OleControlSite
意义也不大,可以改造一下OleAutomation
的初始化的部分,去掉其对OleControlSite
的依赖。然后是OleFrame
,只是用于显示,我们网络调用不需要UI自然也不需要这种东西,可以去掉,然后是OleListener
,其实只有两个unused
的addEventListener()
、removeEventListener()
方法在用,也是没有用处的,也是可以去掉的。最后是Variant
,前文提到了这个是传输参数的,还是要保留的。
综上所述我们需要的就是两个类OleAutomation
和Variant
,把这两个类就可以直接复制到新建的项目中来(但包结构最好还是保留),只需要找到他们的依赖就行。
解决Variant的依赖
首先解决Variant
,它只是为解决传输参数类型而设计的类,看看它自己又有哪些依赖:
import org.eclipse.swt.internal.ole.win32.COM;
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.internal.ole.win32.IUnknown;
import org.eclipse.swt.internal.ole.win32.VARIANT;
import org.eclipse.swt.internal.win32.OS;
但仔细分析一下,这个EloamViewJavaDemo
用到的参数只涉及COM
和OS
的部分,所以可以其他引用统统删光,也就是像什么IDispatch dispatchData
、IUnknown unknownData
,包括那个Variant NULL
,连同静态块一起删掉,然后就是把COM
和OS
再复制过来了,但是他们也有很多引用,这个没有关系我已经整理好了EloamViewJavaDemo
所需的部分,可以copy我整理后的这两个类使用:
整理OS类
package org.eclipse.swt.internal.win32;
public class OS {
public static final boolean IsDBLocale;
public static final int SM_IMMENABLED = 0x52;
public static final int GMEM_FIXED = 0;
public static final int GMEM_ZEROINIT = 64;
static {
OS.SetProcessDPIAware();
IsDBLocale = OS.GetSystemMetrics(SM_IMMENABLED) != 0;
}
public static final int PTR_SIZEOF = 4;
public static final native boolean SetProcessDPIAware();
public static final native int GetSystemMetrics(int nIndex);
public static final native int OleInitialize(int pvReserved);
public static final native void OleUninitialize();
public static final native int GlobalAlloc(int uFlags, int dwBytes);
public static final native void MoveMemory(int Destination, int[] Source, int Length);
public static final native void MoveMemory(int /*long*/ DestinationPtr, long[] Source, int Length);
public static final native void MoveMemory(long[] Destination, int /*long*/ SourcePtr, int Length);
public static final native void MoveMemory(int Destination, char[] Source, int Length);
public static final native void MoveMemory(int DestinationPtr, short[] Source, int Length);
public static final native void MoveMemory(int DestinationPtr, float[] Source, int Length);
public static final native void MoveMemory(int DestinationPtr, double[] Source, int Length);
public static final native void MoveMemory(int Destination, byte[] Source, int Length);
public static final native void MoveMemory(int[] Destination, int SourcePtr, int Length);
public static final native void MoveMemory(float[] Destination, int SourcePtr, int Length);
public static final native void MoveMemory(double[] Destination, int SourcePtr, int Length);
public static final native void MoveMemory(char[] Destination, int SourcePtr, int Length);
public static final native void MoveMemory(short[] Destination, int SourcePtr, int Length);
public static final native void MoveMemory(byte[] Destination, int Source, int Length);
public static final native int GlobalFree(int hMem);
public static final native int HeapFree(int hHeap, int dwFlags, int lpMem);
public static final native int HeapAlloc(int hHeap, int dwFlags, int dwBytes);
public static final native int GetProcessHeap();
}
整理COM类
package org.eclipse.swt.internal.ole.win32;
import org.eclipse.swt.internal.win32.OS;
public class COM extends OS {
public static final native int GUID_sizeof();
public static final short VT_EMPTY = 0;
public static final short VT_BYREF = 16384;
public static final short VT_I1 = 16;
public static final short VT_I2 = 2;
public static final short VT_I4 = 3;
public static final short VT_I8 = 20;
public static final short VT_NULL = 1;
public static final short VT_R4 = 4;
public static final short VT_R8 = 5;
public static final int VT_BOOL = 11;
public static final short VT_BSTR = 8;
public static final short VT_UI2 = 18;
public static final short VARIANT_TRUE = -1;
public static final short VARIANT_FALSE = 0;
public static final int S_OK = 0;
public static final int GMEM_FIXED = 0;
public static final int GMEM_ZEROINIT = 64;
public static final short DISPATCH_METHOD = 0x1;
public static final int LOCALE_USER_DEFAULT = 2048;
public static final int CLSCTX_INPROC_SERVER = 1;
public static final int CLSCTX_LOCAL_SERVER = 4;
public static final native int SysAllocString(char[] sz);
public static final native int SysStringByteLen(int bstr);
public static final native int CLSIDFromProgID(char[] lpszProgID, GUID pclsid);
public static final native int CLSIDFromString(char[] lpsz, GUID pclsid);
public static final native int IIDFromString(char[] var0, GUID var1);
public static final native int DISPPARAMS_sizeof();
public static final native int VARIANT_sizeof();
public static final native int EXCEPINFO_sizeof();
public static final native void VariantInit(int pvarg);
public static final native int VariantClear(int pvarg);
public static final native int CoCreateInstance(GUID var0, int var1, int var2, GUID var3, int[] var4);
public static final native int VtblCall(int fnNumber, int /*long*/ ppVtbl, GUID arg0, int[] arg1);
public static final native int VtblCall(int var0, int var1, GUID var2, int var3, int var4, int var5, int[] var6);
public static final native int VtblCall(int fnNumber, int /*long*/ ppVtbl, int arg0, int arg1, int[] arg2);
public static final native int VtblCall(int fnNumber, int /*long*/ ppVtbl, int arg0, GUID arg1, int arg2, int arg3, DISPPARAMS arg4, int arg5, EXCEPINFO arg6, int[] arg7);
public static final GUID IIDIUnknown = IIDFromString("{00000000-0000-0000-C000-000000000046}");
public static final GUID IIDIDispatch = IIDFromString("{00020400-0000-0000-C000-000000000046}"); //$NON-NLS-1$
private static GUID IIDFromString(String lpsz) {
int length = lpsz.length();
char[] buffer = new char[length + 1];
lpsz.getChars(0, length, buffer, 0);
GUID lpiid = new GUID();
return IIDFromString(buffer, lpiid) == 0 ? lpiid : null;
}
}
这两个类贴完就可以可以看到OS
是没有任何的引用的,但是后面的COM
有用到一个GUID
,但这个GUID
非常简单啦,没有任何其他的引用,直接把这个类复制来就可以,然后关于Variant
的部分就收官了。
解决OleAutomation的依赖
下面只要解决OleAutomation
的部分就完成了,但它引用非常之多,也略有些复杂,我也是很整理了一会,其实也就是各种阉割了,首先是把org.eclipse.swt.internal.ole.win32
下面的DISPPARAMS
和EXCEPINFO
复制过来,这两个类型没有引用而且用的地方太多可以完全保留,然后就是IDispatch
这个关键类,它是OLE协议的核心接口,不过我们前面的都阉割了大部分协议了,也不用太在意了:
整理IDispatch类
package org.eclipse.swt.internal.ole.win32;
import org.eclipse.swt.internal.win32.OS;
public class IDispatch {
private int address;
public EXCEPINFO excepInfo = new EXCEPINFO();
public IDispatch(int address) {
this.address = address;
}
public int GetIDsOfNames(String rgszNames, int[] rgDispId) {
int hHeap = OS.GetProcessHeap();
int ppNames = OS.HeapAlloc(hHeap, 8, OS.PTR_SIZEOF);
int memTracker = 0;
try {
int nameSize = rgszNames.length();
char[] buffer = new char[nameSize + 1];
rgszNames.getChars(0, nameSize, buffer, 0);
int pName = OS.HeapAlloc(hHeap, 8, buffer.length * 2);
OS.MoveMemory(pName, buffer, buffer.length * 2);
COM.MoveMemory(ppNames, new int[]{
pName}, OS.PTR_SIZEOF);
memTracker = pName;
return COM.VtblCall(5, address, new GUID(), ppNames, 1, COM.LOCALE_USER_DEFAULT, rgDispId);
} finally {
OS.HeapFree(hHeap, 0, memTracker);
OS.HeapFree(hHeap, 0, ppNames);
}
}
public int GetTypeInfo(int iTInfo, int lcid, int /*long*/[] ppTInfo) {
return COM.VtblCall(4, address, iTInfo, lcid, ppTInfo);
}
public int Invoke(int dispIdMember, DISPPARAMS pDispParams, int pVarResult, int[] pArgErr) {
return COM.VtblCall(6, address, dispIdMember, new GUID(), COM.LOCALE_USER_DEFAULT, COM.DISPATCH_METHOD, pDispParams, pVarResult, excepInfo, pArgErr);
}
}
其实也没有很大的改动,就是eloamViewOCX
类那边固定的参数去掉方便下面的OleAutomation
进行调用,然后就是最关键的部分OleAutomation
了,由它来加载设备、调用接口:
整理OleAutomation类
由于这个改动较大,又是操作流程的入口,所以我将这个类重新更名为SimpleOleAutomation
,也可以消除误解:
import org.eclipse.swt.internal.ole.win32.COM;
import org.eclipse.swt.internal.ole.win32.DISPPARAMS;
import org.eclipse.swt.internal.ole.win32.GUID;
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.internal.win32.OS;
public class SimpleOleAutomation {
private IDispatch objIDispatch;
private Variant invokeResult = new Variant();
private String disName;
public OleAutomation(String pId) {
OS.OleInitialize(0);
GUID appId = getClassID(pId);
if (appId == null) {
OS.OleUninitialize();
throw new RuntimeException("GUID not find:" + pId);
}
int flags = COM.CLSCTX_INPROC_SERVER;
if (pId.startsWith("Excel")) flags |= COM.CLSCTX_LOCAL_SERVER; //$NON-NLS-1$
int[] ppvObject = new int[1];
int result = COM.CoCreateInstance(appId, 0, flags, COM.IIDIUnknown, ppvObject);
if (result != COM.S_OK) {
OS.OleUninitialize();
throw new RuntimeException("CoCreateInstance failure");
}
int p = ppvObject[0];
ppvObject[0] = 0;
COM.VtblCall(0, p, COM.IIDIDispatch, ppvObject);
objIDispatch = new IDispatch(ppvObject[0]);
ppvObject[0] = 0;
objIDispatch.GetTypeInfo(0, COM.LOCALE_USER_DEFAULT, ppvObject);
}
private GUID getClassID(String clientName) {
GUID guid = new GUID();
char[] buffer = null;
if (clientName != null) {
int count = clientName.length();
buffer = new char[count + 1];
clientName.getChars(0, count, buffer, 0);
}
if (COM.CLSIDFromProgID(buffer, guid) != COM.S_OK) {
int result = COM.CLSIDFromString(buffer, guid);
if (result != COM.S_OK) return null;
}
return guid;
}
public OleAutomation invoke(String name, Variant[] rgvArg) {
DISPPARAMS pDispParams = new DISPPARAMS();
if (rgvArg != null) {
pDispParams.cArgs = rgvArg.length;
pDispParams.rgvarg = OS.GlobalAlloc(COM.GMEM_FIXED | COM.GMEM_ZEROINIT, COM.VARIANT_sizeof() * rgvArg.length);
int offset = 0;
for (int i = rgvArg.length - 1; i >= 0; i--) {
rgvArg[i].getData(pDispParams.rgvarg + offset);
offset += COM.VARIANT_sizeof();
}
}
int[] pArgErr = new int[1];
int pVarResultAddress = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, COM.VARIANT_sizeof());
int result = objIDispatch.Invoke(getIDsOfNames(disName = name), pDispParams, pVarResultAddress, pArgErr);
if (pVarResultAddress != 0) {
invokeResult.setData(pVarResultAddress);
COM.VariantClear(pVarResultAddress);
OS.GlobalFree(pVarResultAddress);
}
// free the Dispparams resources
if (pDispParams.rgdispidNamedArgs != 0) {
OS.GlobalFree(pDispParams.rgdispidNamedArgs);
}
if (pDispParams.rgvarg != 0) {
int offset = 0;
for (int i = 0, length = rgvArg.length; i < length; i++) {
COM.VariantClear(pDispParams.rgvarg + offset);
offset += COM.VARIANT_sizeof();
}
OS.GlobalFree(pDispParams.rgvarg);
}
if (result != 0) {
System.out.println("[invoke] [" + name + "] result is " + result);
}
return this;
}
public int getIDsOfNames(String name) {
int[] disId = new int[1];
int result = objIDispatch.GetIDsOfNames(name, disId);
if (result != COM.S_OK) return 0;
return disId[0];
}
public void printResult() {
System.out.println("Invoke [" + disName + "]:" + invokeResult);
}
public int resValue() {
return Integer.parseInt(invokeResult.str());
}
}
到此就算是结束了,他们的依赖一共就是下面几个包与类:
- org.eclipse.swt.internal
- ole.win32
- COM
- DISPPARAMS
- EXCEPINFO
- GUID
- IDispatch
- win32
- OS
- ole.win32
其中OS
和COM
、GUID
都是Variant
的依赖,其余都是OleAutomation
的依赖,当然其实OleAutomation
也直接引用了前面Variant
的3个依赖类,然后就可以用OleAutomation
改造后的SimpleOleAutomation
,对良田高拍仪进行调用了。
测试用例与总结
经过的“轻微”的改动,用起来其实就很简单了,就是看官方文档eloam windows sdk然后跟着下面的方式调用就行了:
public static void main(String[] args) throws Exception {
//这个guid就是良田高拍仪的设备号
SimpleOleAutomation auto = new SimpleOleAutomation("{CA2184F0-5A78-4D81-80F2-B0A7BFC74FBF}");
auto.invoke("InitDev", null).printResult();
//打开摄像头
auto.invoke("OpenVideoEx", new Variant[]{
Variant.ZERO, new Variant(1), Variant.ZERO}).printResult();
//SetVideoProcAmp设置调节视频数据
//0x1表示亮度,0x2表示对比度,0x3表示饱和度,0x4表示色调, 0x5表示清晰度,0x6表示伽马,0x7表示白平衡,0x8表示逆光对比,0x9表示启用颜色, 0xA表示增益
//此处设亮度为180
auto.invoke("SetVideoProcAmp", new Variant[]{
Variant.ZERO, new Variant(1), new Variant(180), new Variant(true)}).printResult();
//Deskew自动裁减
auto.invoke("Deskew", new Variant[]{
Variant.ZERO, new Variant(true)}).printResult();
//此处如果没有延时有时会拍不出来,可能是我的设备有问题
Thread.sleep(1000);
//摄像并将文件保存到temp.jpg
auto.invoke("Scan", new Variant[]{
Variant.ZERO, new Variant("temp.jpg"), new Variant(0x0100)}).printResult();
auto.invoke("DeInitDev", null).printResult();
}
用例就这么几行代码就可以了,非常简明呀!将这个的程序打包之后只有140kb
,其中120kb
还是因为swt-win32-4430.dll
。如果不是这样做,直接用官方给的EloamViewJavaDemo
所依赖的org.eclipse.swt.win32
那个jar包,它本身大小就有2.57mb
,还有很多依赖的dll
也有832kb
,还没开始写呢,依赖就3mb
起步了。而经过本文对ole的抽离阉割,加上用例总共也就140kb
,可以说能节省相当多资源了。
本文依照作者在2021年的一些开发经验,于2023年7月14日同时写作并发布在lyrieek的稀土掘金社区与阿里云开发者社区。