高效阉割良田高拍仪JavaSDK所依赖的Eclipse SWT OLE Win32自动化组件

2023年 7月 14日 30.6k 0

良田高拍仪的SDK如官方所说,eloam windows sdk 其实是通用的,只与平台有关,不是针对语言的,语言不同只是调用方式不同罢了,标题所说的JavaSDK的其实也就是官方的一个小例子EloamViewJavaDemo,也就是用Eclipse SWTOLE组件去调用驱动的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参数,然后发送给swtOleAutomationinvoke()具体执行,那么我们就可以把这个过程抽离出来优化一下,不再依赖整个org.eclipse.swt

所以接下来就是处理一下这个OLE,在这个过程中,我去除了大部分OLE协议,但是我保留了一部分,我觉得保留一部分OLE协议,你才知道你用的是OLE协议,是在阉割的部分中我留下的一部分(是故意的)

检查具体依赖的部分

ole部分其实很小一块:

image.png

一看就这么几个文件,就可以更加的放心了,只可惜eclipse不提供这种比较细致引入,所以只能直接把这些类复制到我们的项目里,但请先不要着急直接复制过来,因为这些类还是有相当多引用的,我们应该找找具体用了哪些东西,然后再贴过来,所以先找一找eloamViewOCX类具体importole那些部分:

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,其实只有两个unusedaddEventListener()removeEventListener()方法在用,也是没有用处的,也是可以去掉的。最后是Variant,前文提到了这个是传输参数的,还是要保留的。

综上所述我们需要的就是两个类OleAutomationVariant,把这两个类就可以直接复制到新建的项目中来(但包结构最好还是保留),只需要找到他们的依赖就行。

解决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用到的参数只涉及COMOS的部分,所以可以其他引用统统删光,也就是像什么IDispatch dispatchDataIUnknown unknownData,包括那个Variant NULL,连同静态块一起删掉,然后就是把COMOS再复制过来了,但是他们也有很多引用,这个没有关系我已经整理好了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下面的DISPPARAMSEXCEPINFO复制过来,这两个类型没有引用而且用的地方太多可以完全保留,然后就是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类

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 OleAutomation {
  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

其中OSCOMGUID都是Variant的依赖,其余都是OleAutomation的依赖,当然其实OleAutomation也直接引用了前面Variant的3个依赖类,然后就可以用OleAutomation进行非常简单的调用了。

测试用例与总结

经过的“轻微”的改动,用起来其实就很简单了,就是看官方文档eloam windows sdk然后跟着下面的方式调用就行了:

static {
    System.loadLibrary("swt-win32-4430");
}

public static void main(String[] args) throws Exception {
    //这个guid就是良田高拍仪的设备号
    Automation auto = new Automation("{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的稀土掘金社区与阿里云开发者社区。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论