天天看點

Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

一,項目前提:

        我們目前是想實作一個人臉識别考勤的項目,而廠商給我們所提供的是c++封裝好的jdk 。為了友善跟我們的Java平台對接,是以需要一些手段将C++項目融入到我們的Java 平台當中。我們最終選用JNA來對c++ sdk 來進行封裝。項目使用jdk(1.8.0_201)、 idea(2018.3.3)、jna版本(3.4.0)

ps:小編在jna版本上曾經踩過坑,發現使用的jna(4.2.2), 運作代碼始終不成功,類繼承 Structure 類的時候 。重寫

getFieldOrder() 方法寫法也很繁瑣, 後來發現很多人都用的是3.4.0版本的JNA 。并且這個版本的jna可以不用非要去重寫getFieldOrder()這個方法。後面我們會提到為什麼可以不用重寫這個方法了。
           

二,項目方案選擇:

JNI:是Java Native Interface的縮寫,它提供了若幹的API實作了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI标準成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行互動。

1,JNI

1,jni的簡單應用

Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

2,jni在java中的應用(jdk1.1 之後 才加入了JNI的概念:在 IO 、net 、lang包下面偏多)。下面我們去JDK中IO中去看下這些本地方法吧。

/**
     * Reads a subarray as a sequence of bytes.
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @exception IOException If an I/O error has occurred.
     */
    private native int readBytes(byte b[], int off, int len) throws IOException;
           

這個方法是出現在 FileInputStream 這個類中。 

/**
     * Reads up to <code>b.length</code> bytes of data from this input
     * stream into an array of bytes. This method blocks until some input
     * is available.
     *
     * @param      b   the buffer into which the data is read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the file has been reached.
     * @exception  IOException  if an I/O error occurs.
     */
    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }

    /**
     * Reads up to <code>len</code> bytes of data from this input stream
     * into an array of bytes. If <code>len</code> is not zero, the method
     * blocks until some input is available; otherwise, no
     * bytes are read and <code>0</code> is returned.
     *
     * @param      b     the buffer into which the data is read.
     * @param      off   the start offset in the destination array <code>b</code>
     * @param      len   the maximum number of bytes read.
     * @return     the total number of bytes read into the buffer, or
     *             <code>-1</code> if there is no more data because the end of
     *             the file has been reached.
     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
     * <code>len</code> is negative, or <code>len</code> is greater than
     * <code>b.length - off</code>
     * @exception  IOException  if an I/O error occurs.
     */
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }
           

然後這個 readBytes(b, off, len) 方法就會調用上面的這個本地Native 方法。至于這些方法的作用,自己看一看應該也能看得懂,但至于Native内部怎麼實作,也不必太過追究。

2,JNA的使用

1,JNA相比JNI有什麼優勢呢?

下面來看一看JNA的維基百科解釋

Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

PS:關鍵點在于:JNA的設計旨在以最少的努力以自然的方式提供原生通路。它能支援macOS,Microsoft Windows,FreeBSD / OpenBSD,Solaris,Linux,AIX,Windows Mobile和Android 這麼多的平台。so, 我們選型JNA(3.4.0)

三、JNA爬坑曆路 

1,結構體和我們的Java類的資料類型的轉換問題。

這裡給老司機們推薦幾篇文章,https://blog.csdn.net/ctwy291314/article/details/82895604 , https://blog.csdn.net/ctwy291314/article/details/84626829 等等文章。這裡其實有一些我沒那樣用,但是我的程式運作沒有問題的。 例如 c中 int 對應 java 中的 int 。還有附上JNA在GitHub(全球最大的同性交友網站)上的源碼位址:https://github.com/java-native-access/jna 。

Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

這裡是在網上找的對資料類型轉換的一張圖表。我在使用過程中大體沒有出現什麼問題。

下面是我在程式中用到的一些資料類型轉換的圖表,還有就是我的demo。

( 查詢人臉識别人員組資訊,pstInParam與pstOutParam記憶體由使用者申請釋放)

// 查詢人臉識别人員組資訊,pstInParam與pstOutParam記憶體由使用者申請釋放
CLIENT_NET_API BOOL CALL_METHOD CLIENT_FindGroupInfo(LLONG lLoginID, const NET_IN_FIND_GROUP_INFO* pstInParam, NET_OUT_FIND_GROUP_INFO *pstOutParam, int nWaitTime = 1000);
           

PS:這裡的LLONG是我們自己自定義的一個類型。目的是考慮相容32bit的作業系統 和 64bit的作業系統。

下面是我對LLONG這種資料類型的封裝,代碼如下:

public static class LLong extends IntegerType {
		private static final long serialVersionUID = 1L;
		
		/** Size of a native long, in bytes. */
	    public static int size;
	    static {
	        size = Native.LONG_SIZE;
	        if (Utils.getOsPrefix().toLowerCase().equals("linux-amd64")
	        		|| Utils.getOsPrefix().toLowerCase().equals("win32-amd64")) {
	        	size = 8;
	        } else if (Utils.getOsPrefix().toLowerCase().equals("linux-i386")
	        		|| Utils.getOsPrefix().toLowerCase().equals("win32-x86")) {
	        	size = 4;
	        }
	    }
           
// 擷取操作平台資訊
	public static String getOsPrefix() {
		String arch = System.getProperty("os.arch").toLowerCase();
		final String name = System.getProperty("os.name");
		String osPrefix;
		switch(Platform.getOSType()) {
			case Platform.WINDOWS: {
				if ("i386".equals(arch))
	                arch = "x86";
	            osPrefix = "win32-" + arch;
			}
            break;
			case Platform.LINUX: {
				if ("x86".equals(arch)) {
	                arch = "i386";
	            }
	            else if ("x86_64".equals(arch)) {
	                arch = "amd64";
	            }
	            osPrefix = "linux-" + arch;
			}			       
	        break;
			default: {
	            osPrefix = name.toLowerCase();
	            if ("x86".equals(arch)) {
	                arch = "i386";
	            }
	            if ("x86_64".equals(arch)) {
	                arch = "amd64";
	            }
	            int space = osPrefix.indexOf(" ");
	            if (space != -1) {
	                osPrefix = osPrefix.substring(0, space);
	            }
	            osPrefix += "-" + arch;
			}
	        break;
	       
		}

		return osPrefix;
	}
           
public static String getOsName() {
    	String osName = "";
		String osPrefix = getOsPrefix();
		if(osPrefix.toLowerCase().startsWith("win32-x86")
				||osPrefix.toLowerCase().startsWith("win32-amd64") ) {
			osName = "win";
		} else if(osPrefix.toLowerCase().startsWith("linux-i386")
				|| osPrefix.toLowerCase().startsWith("linux-amd64")) {
			osName = "linux";
		}
		
		return osName;
    }
           

上面寫的一些Util來的這一些方法,就是對作業系統long類型的判斷,32位系統的 long 類型占4位元組 , 64位系統的long類型占 8位元組。

下面我們來看這個接口的C++中的封裝。

void CDispatchGroupDlg::RefreshDispatchInfo(const char *szGroupId, BOOL bRefreshShow)
{
	int i = 0;
	BOOL bRet = FALSE;
	NET_IN_FIND_GROUP_INFO stuInParam = {sizeof(stuInParam)};
	NET_OUT_FIND_GROUP_INFO stuOutParam = {sizeof(stuOutParam)};
	stuOutParam.nMaxGroupNum = 50;
	NET_FACERECONGNITION_GROUP_INFO *pGroupInfo = NULL;
	stuOutParam.pGroupInfos = new NET_FACERECONGNITION_GROUP_INFO[stuOutParam.nMaxGroupNum];
	if (NULL == stuOutParam.pGroupInfos)
	{
		MessageBox(ConvertString("Memory error"), "");
		bRet = FALSE;
		goto e_clear;
	}

	memset(stuOutParam.pGroupInfos, 0, sizeof(NET_FACERECONGNITION_GROUP_INFO)*stuOutParam.nMaxGroupNum);
	for (i = 0; i < stuOutParam.nMaxGroupNum; i++)
	{
		pGroupInfo = stuOutParam.pGroupInfos + i;
		pGroupInfo->dwSize = sizeof(*pGroupInfo);
	}
	bRet = CLIENT_FindGroupInfo(m_lLoginID, &stuInParam, &stuOutParam, DEFAULT_WAIT_TIME);
	if (!bRet)
	{
		MessageBox(ConvertString("Failed to find group infos!"), "");
		bRet = FALSE;
		goto e_clear;
	}
	for (i = 0; i < stuOutParam.nRetGroupNum; i++)
	{
		pGroupInfo = stuOutParam.pGroupInfos + i;
		if (0 == strcmp(szGroupId, pGroupInfo->szGroupId))
		{
			memcpy(&m_stuDispatchGroupInfo, pGroupInfo, sizeof(m_stuDispatchGroupInfo));

		}
	}

	if (bRefreshShow)
	{
		CleanDispatchList();
		m_DispatchInfoList.ResetContent();
		for (i = 0; i < m_stuDispatchGroupInfo.nRetChnCount; i++)
		{
			CString str;
			str.Format("%4d                   %4d                %s", m_stuDispatchGroupInfo.nChannel[i]+1, m_stuDispatchGroupInfo.nSimilarity[i], "已布控");
			m_DispatchInfoList.AddString(str);
			NET_DISPATCH_INFO *pstDispatchInfo = new NET_DISPATCH_INFO;
			if (pstDispatchInfo)
			{
				memset(pstDispatchInfo, 0, sizeof(*pstDispatchInfo));
				//pstDispatchInfo->nIndex = i;
				pstDispatchInfo->nChannel = m_stuDispatchGroupInfo.nChannel[i];
				pstDispatchInfo->nSimilarity = m_stuDispatchGroupInfo.nSimilarity[i];
				pstDispatchInfo->bDispatch = TRUE;
				m_lstDispatchChannelInfo.push_back(pstDispatchInfo);
			}
		}
	}
           

首先 NET_IN_FIND_GROUP_INFO stuInParam = {sizeof(stuInParam)}; sizeof() 庫函數在c++中傳回結構體對象傳回的位元組數。

// CLIENT_FindGroupInfo接口輸入參數
typedef struct tagNET_IN_FIND_GROUP_INFO   
{
    DWORD               dwSize;
    char                szGroupId[ST_COMMON_STRING_64];// 人員組ID,唯一辨別一組人員,為空表示查詢全部人員組資訊
}NET_IN_FIND_GROUP_INFO;

// CLIENT_FindGroupInfo接口輸出參數
typedef struct tagNET_OUT_FIND_GROUP_INFO   
{
    DWORD               dwSize;
    NET_FACERECONGNITION_GROUP_INFO *pGroupInfos;      // 人員組資訊,由使用者申請空間,大小為sizeof(NET_FACERECONGNITION_GROUP_INFO)*nMaxGroupNum
    int                 nMaxGroupNum;                  // 目前申請的數組大小
    int                 nRetGroupNum;                  // 裝置傳回的人員組個數
}NET_OUT_FIND_GROUP_INFO;
           

下面先來看看這兩個結構體:

本例中用到的資料類型轉換(跟上面不一樣的資料類型)

demo java c++
int DWORD
short WORD
LLONG LONG
public class NET_IN_FIND_GROUP_INFO extends Structure implements Common{

	public int dwSize;
	public byte[] szGroupId = new byte[NET_COMMON_STRING_64];//人員組ID,唯一辨別一組人員,為空表示查詢全部人員組資訊

	public NET_IN_FIND_GROUP_INFO()
	{
		this.dwSize = this.size();
	}

	@Override
	protected List getFieldOrder() {
		return Arrays.asList(new String[] { "dwSize", "szGroupId" ,
		});
	}
}
           
public class NET_FACERECONGNITION_GROUP_INFO extends Structure implements Common {

	public int 			dwSize;
	public int 			emFaceDBType;							   		  // 人員組類型,詳見EM_FACE_DB_TYPE, 取值為EM_FACE_DB_TYPE中的值
	public byte[] 		szGroupId = new byte[NET_COMMON_STRING_64];		  // 人員組ID,唯一辨別一組人員(不可修改,添加操作時無效)
	public byte[] 		szGroupName = new byte[NET_COMMON_STRING_128];	  // 人員組名稱
	public byte[] 		szGroupRemarks = new byte[NET_COMMON_STRING_256]; // 人員組備注資訊
	public int 			nGroupSize;									      // 目前組内人員數
	public int			nRetSimilarityCount;							  // 實際傳回的庫相似度門檻值個數
	public int[]		nSimilarity = new int[MAX_SIMILARITY_COUNT];	  // 庫相似度門檻值,人臉比對高于門檻值認為比對成功
	public int			nRetChnCount;									  // 實際傳回的通道号個數
	public int[] 		nChannel = new int[NET_MAX_CAMERA_CHANNEL_NUM];   // 目前組綁定到的視訊通道号清單
	public int[] 		nFeatureState = new int[MAX_FEATURESTATE_NUM];	  // 人臉組模組化狀态資訊:
	// [0]-準備模組化的人員數量,不保證一定模組化成功
	// [1]-模組化失敗的人員數量,圖檔不符合算法要求,需要更換圖檔
	// [2]-已模組化成功人員數量,資料可用于算法進行人臉識别
	// [3]-曾經模組化成功,但因算法更新變得不可用的數量,重建立模就可用

	public NET_FACERECONGNITION_GROUP_INFO()
	{
		this.dwSize = this.size();
	}

	@Override
	protected List getFieldOrder() {
		return Arrays.asList(new String[] { "dwSize", "emFaceDBType","szGroupId","szGroupName", "szGroupRemarks" ,
				"nGroupSize", "nRetSimilarityCount","nSimilarity","nRetChnCount", "nChannel" ,
				"nFeatureState",
		});
	}

}
           

我的Common 接口 :都是放一些常量的接口,讀者可不必在意。

this.dwSize = this.size(); 通過撸Structure 這個類的源碼才知道 這個類裡面的Size,是以我們用構造方法來初始化結構體的dwSize;

下面我們來看一下Structure這個類的calculateSize() 方法 的具體的實作方法,有興趣的可以去撸一撸這個地方的實作原理。這裡解決了 sizeof()  這個c++ 中的類庫函數了。

int calculateSize(boolean force, boolean avoidFFIType) {
        boolean needsInit = true;
        Structure.LayoutInfo info;
        synchronized(layoutInfo) {
            info = (Structure.LayoutInfo)layoutInfo.get(this.getClass());
        }

        if (info == null || this.alignType != info.alignType || this.typeMapper != info.typeMapper || !this.fieldOrderMatch(info.fieldOrder)) {
            info = this.deriveLayout(force, avoidFFIType);
            needsInit = false;
        }

        if (info != null) {
            this.structAlignment = info.alignment;
            this.structFields = info.fields;
            info.alignType = this.alignType;
            info.typeMapper = this.typeMapper;
            info.fieldOrder = this.fieldOrder;
            if (!info.variable) {
                synchronized(layoutInfo) {
                    layoutInfo.put(this.getClass(), info);
                }
            }

            if (needsInit) {
                this.initializeFields();
            }

            return info.size;
        } else {
            return -1;
        }
    }
           

PS:這個地方遇到的坑:1,剛開始我麼還沒有收到廠家的關于C++ 的代碼,剛開始都不知道怎麼入參合适,都不知道dwsize這個字段是個啥意思。後來他們給了廠家的c++的代碼,于是馬上裝了一個vs 2015的社群版,在大佬的幫助下才艱難的把這個C++ 的程式跑起來。(因為我也是一直做的Java開發,對C++也不是很熟悉,全靠大學的那一點兒基礎,還好大學學的C,C++ 還勉強算學的可以的類型,不然人都要炸掉。)

在調用這個接口的時候遇到了以下的問題:

1,NET_IN_FIND_GROUP_INFO stuIn = new NET_IN_FIND_GROUP_INFO();

stuIn = 1000;的這個時候完全不知道這裡該怎麼指派, 但是和廠家那邊溝通他們那邊感覺也不是開發人員,隻知道這裡是一定要指派的,然後我就在這裡指派1000。 發現調用這個CLIENT_FindGroupInfo()接口的時候接口始終傳回false; 

後來給 NET_OUT_FIND_GROUP_INFO stuOut = new NET_OUT_FIND_GROUP_INFO();

後來給 stuOut.dwSize = 1000; 發現調用這個CLIENT_FindGroupInfo()接口的時候接口始終傳回true 了; 

解決方案: 後來發現 Structure 這個類中有一個size 參數,就是對應的是dwSize 這個參數的值:是以在構造函數中就将這個數值指派進去。

2,我先把這個java部分代碼貼出來:

public NET_FACERECONGNITION_GROUP_INFO[] findGroupInfo(LLong loginHandle , String groupId) {

		NET_FACERECONGNITION_GROUP_INFO[] groupInfoRet = null;

		/*
		 * 入參
		 */
		NET_IN_FIND_GROUP_INFO stuIn = new NET_IN_FIND_GROUP_INFO();
		System.arraycopy(groupId.getBytes(), 0, stuIn.szGroupId, 0, groupId.getBytes().length);

		/*
		 * 出參
		 */
		int max = 20;
		NET_FACERECONGNITION_GROUP_INFO[] groupInfo  = new NET_FACERECONGNITION_GROUP_INFO[max];
		for(int i = 0; i < max; i++) {
			groupInfo[i] = new NET_FACERECONGNITION_GROUP_INFO();
		}

		NET_OUT_FIND_GROUP_INFO stuOut = new NET_OUT_FIND_GROUP_INFO();
		stuOut.pGroupInfos = new Memory(groupInfo[0].size() * groupInfo.length);     // Pointer初始化
		stuOut.pGroupInfos.clear(groupInfo[0].size() * groupInfo.length);
		stuOut.nMaxGroupNum = groupInfo.length;

		ToolKits.SetStructArrToPointerData(groupInfo, stuOut.pGroupInfos);  // 将數組記憶體拷貝給Pointer

		if(Clibrary.INSTANCE.CLIENT_FindGroupInfo(loginHandle , stuIn, stuOut, 4000)) {

			// 将Pointer的值輸出到 數組 NET_FACERECONGNITION_GROUP_INFO
			ToolKits.GetPointerDataToStructArr(stuOut.pGroupInfos, groupInfo);

			if(stuOut.nRetGroupNum > 0) {
				// 根據裝置傳回的,将有效的人臉庫資訊傳回
				groupInfoRet = new NET_FACERECONGNITION_GROUP_INFO[stuOut.nRetGroupNum];
				for(int i = 0; i < stuOut.nRetGroupNum; i++) {
					groupInfoRet[i] = groupInfo[i];
				}
			}
		} else {
			log.info("查詢人員資訊失敗");
			return null;
		}

		return groupInfoRet;
	}
           

如下是ToolKits 這個類的部分代碼:

/**
	 * 将結構體數組拷貝到記憶體
	 *
	 * @param pNativeData
	 * @param pJavaStuArr
	 */
	public static void SetStructArrToPointerData(Structure[] pJavaStuArr, Pointer pNativeData) {
		long offset = 0;
		for (int i = 0; i < pJavaStuArr.length; ++i) {
			SetStructDataToPointer(pJavaStuArr[i], pNativeData, offset);
			offset += pJavaStuArr[i].size();
		}
	}

public static void SetStructDataToPointer(Structure pJavaStu, Pointer pNativeData, long OffsetOfpNativeData) {
		pJavaStu.write();
		Pointer pJavaMem = pJavaStu.getPointer();
		pNativeData.write(OffsetOfpNativeData, pJavaMem.getByteArray(0, pJavaStu.size()), 0, pJavaStu.size());
	}
           

頭檔案的說明:

NET_FACERECONGNITION_GROUP_INFO *pGroupInfos;      // 人員組資訊,由使用者申請空間,大小為sizeof(NET_FACERECONGNITION_GROUP_INFO)*nMaxGroupNum。

C++中的代碼:

memset(stuOutParam.pGroupInfos, 0, sizeof(NET_FACERECONGNITION_GROUP_INFO)*stuOutParam.nMaxGroupNum);
           

Java中的代碼:

stuOut.pGroupInfos = new Memory(groupInfo[0].size() * groupInfo.length);     // Pointer初始化
        stuOut.pGroupInfos.clear(groupInfo[0].size() * groupInfo.length);
        stuOut.nMaxGroupNum = groupInfo.length;

        ToolKits.SetStructArrToPointerData(groupInfo, stuOut.pGroupInfos);  // 将數組記憶體拷貝給Pointer
           

對于這種在  NET_OUT_FIND_GROUP_INFO 類中 有一個屬性是 pGroupInfos 是 Pointer 類型的。

public Pointer pGroupInfos; // 人員組資訊,由使用者申請空間, 指向 NET_FACERECONGNITION_GROUP_INFO 的指針

說明:這個指針空間需要自己去開辟,指針指向NET_FACERECONGNITION_GROUP_INFO這個結構體。

1,首先用 Memory 開辟記憶體空間,然後調用java.sun.jna.Memory 類中的 clear()方法,目的是鎖定記憶體,

Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

 本質是調用了jni裡面的setMemory 方法。

2,結構體和Pointer之間的關系:

比如上面,我們會出現很多将Pointer 指向 結構體,

public static void SetStructDataToPointer(Structure pJavaStu, Pointer pNativeData, long OffsetOfpNativeData) {
		pJavaStu.write();
		Pointer pJavaMem = pJavaStu.getPointer();
		pNativeData.write(OffsetOfpNativeData, pJavaMem.getByteArray(0, pJavaStu.size()),     0, pJavaStu.size());
	}
           

pJavaStu.getPointer();便可以得到一個Pointer類型了

pJavaStu.write();  Writes the fields of the struct to native memory 

pJavaStu.read(); Reads the fields of the struct from native memory
           
Jna調用C++使用心得分享一,項目前提:二,項目方案選擇: 三、JNA爬坑曆路 四、總結

 https://java-native-access.github.io/jna/4.2.0/com/sun/jna/Structure.html 上面截圖的來源;

2,資料類型轉換問題:

下面是我自己整理的 java 和 C++ 平台資料類型轉換的一個表格

資料類型轉換

Java C++ 額外可以
LLONG(自己定義)64bit 就是 LONG 類型 LLONG
int DWORD
int BOOL
LONG LDWORD
Pointer void*
short WORD
String / Pointer char*
Structure struct*/struct
String const char * chDVRIP
IntByReference
           
int * error
Pointer stuUIDS Structure *stuUIDs
Pointer pBuffer char *  pBuffer

四、總結

至此我在JNA這一塊兒爬坑目前看似是告一段落,終于把接入商湯封裝的SDK攝像頭進行了封裝。但是對 Structure 這個類的很多屬性和 方法 仍然還不知道 是怎麼設計和 運作的。比如 ALIGN_DEFAULT 、 ALIGN_NONE 、 ALIGN_GNUC 、ALIGN_MSVC 這四個 屬性分别代表什麼意思呢?歡迎大家在部落格下多多讨論交流這方面的經驗。

JNA

繼續閱讀