java通過c++讀取 c# 動態鏈接庫(dll)內容

一、背景

最近要對接省集採平台,把醫院的藥品計劃數據上傳到省集採平台,收到給的dll動態鏈接庫和tlb靜態鏈接庫如下

二、探索之路

對於一個java開發人員,我完全不知道這倆文件是幹啥的,一臉懵逼,開始百度,中間爬過不少坑,網上大部分是通過jna或jni對dll進行解析獲取方法,但是對於c#編輯的dll文件,java不能直接讀取裏面內容,需要通過c++讀取裏面內容,然後java通過jna讀取c++裏面方法,調用方向見下圖

三、上代碼

1. c#代碼查看

c#編譯後的dll文件是一個64位的動態鏈接庫,所以你需要轉變64位jdk環境

第三方c#代碼給的是一個service類implements另一個接口類,通過ILSPY(最新下載地址:https://github.com/icsharpcode/ILSpy)軟件打開dll鏈接庫,如下圖,有兩個類,類裏有具體方法,本文采用下面第一個方法測試

2. 創建c++空項目

因為c#編譯的文件已經給我,本文就不再闡述用創建c#項目過程,直接創建c++項目

通過visutal studio 2019 preview創建c++項目,依次按照下圖創建項目

 

創建好項目後,軟件右側會刷新出項目結構,因為項目是空的,所以文件夾下內容全部無文件

2. 創建讀取c#類入口文件

創建文件入口,按如下圖操作

編寫代碼,引入第三方dll鏈接庫

​
#ifdef MYLIBAPI 
#else 
#define  MYLIBAPI  extern "C" __declspec(dllimport)     
#endif 
​
### 聲明構造方法
MYLIBAPI char* GetSignature(char* userID, char* password, char* sha1);
​
### 通過絕對路徑引入dll
#using "D:\qlyl\SaleSec.dll"
​
### 引入命名空間,SaleSec為dll鏈接庫名稱
using namespace SaleSec;
using namespace System;
​
### 定義方法,這裏和c#編譯的dll鏈接庫方法名稱一致
char* GetSignature(char* userID, char* password, char* sha1)
{
    ### 聲明一個對象,通過對象調用方法
    ### SaleService方法對應c#中dll鏈接庫的方法名稱
    SaleService^ saleservice = gcnew SaleService();
    String^ userID1 = gcnew System::String(userID);
    String^ password1 = gcnew System::String(password);
    String^ sha11 = gcnew System::String(sha1);
    String^ result = saleservice->GetSignature(userID1, password1, sha11);
    char* results = (char*)(void*)System::Runtime::InteropServices::Marshal::StringToCoTaskMemAnsi(result);
    return results;
}
​
​

3. 創建文件時,錯誤信息處理

  1. 第一個錯誤,如下圖,需要對公共語言進行時進行設置,右鍵項目→屬性→高級→公共語言進行時,設置為公共語言運行時(/clr),應用

 

c/c++ → 公共語言進行時,應用→確定,頁面則不報錯了

因為我dll類庫需要64位版本,所以還需要進行如下設置

設置後頁面則不報錯了

2.  第二個錯誤,如下,這個錯誤一般是輸入參數類型或輸出參數類型不匹配導致的,我這裏是因為c#是string類型的,傳遞了char* 類型導致錯誤

E1767   無法使用給定參數列表調用 函數 "SaleSec::SaleService::GetSignature"    SaleSec D:\visio-workspace\SaleSec\dllmain.cpp  23  

3. 第三個錯誤, LNK1104,找不到某個dll文件

我的這個錯誤是因為c++環境沒有安裝全導致的,重新安裝下環境就好了,安裝環境過程

我是把所有包含c++內容的都給添加上了

右鍵項目→屬性→c/c++→語言→符合模式,選擇否

 

官網其他錯誤處理,https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/linker-tools-error-lnk1104?view=vs-2017

4. 啓動項目生成c++,dll動態鏈接庫

點擊上方本地windows調試器,生成dll文件,中間出了一個錯誤,不影響dll生成,這裏不做描述

 

5. 第四個錯誤,錯誤信息如下,修改如下圖

C2338   C++/CLI、C++/CX 或 OpenMP 不支持兩階段名稱查找;請使用 /Zc:twoPhase-    saleSec D:\visio-workspace\saleSec\c1xx 1   

6. 第五個錯誤,啓動報錯,如下圖,需要把c#編譯的dll鏈接庫也放在jdk/bin目錄下

7. 其他錯誤官網查詢解決,https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/linker-tools-error-lnk1104?view=vs-2017

四、java通過jna調用c++生成的動態鏈接庫

  1. pom文件引入依賴

    <!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
            <dependency>
                <groupId>net.java.dev.jna</groupId>
                <artifactId>jna</artifactId>
                <version>5.5.0</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform -->
            <dependency>
                <groupId>net.java.dev.jna</groupId>
                <artifactId>jna-platform</artifactId>
                <version>5.5.0</version>
            </dependency>
    

     

  2. 新建測試類,dll文件放在jdk安裝目錄 bin目錄下

    package oms;
    ​
    import com.sun.jna.Library;
    import com.sun.jna.Native;
    import com.sun.jna.Platform;
    import com.sun.jna.win32.StdCallLibrary;
    import org.junit.Test;
    ​
    import java.util.UUID;
    ​
    /**
     * @author insistOn
     * @Title:
     * @Package
     * @Description:
     * @date 2020/4/279:46
     */
    public class DllTest {
    ​
        private static final String USERID= "aaa";
        private static final String PASSWORD= "w23wer";
        private static final String DESKEY= "235424rwerw";
        private static final String SHA1= "12312";
        /**
         * 測試電腦上msvcrt.dll文件
         *
         * @Description:
         * @author: liufan
         * @date: 2020年5月1日 上午10:37:58
         */
        public interface msvcrt extends StdCallLibrary {
            // DLL文件默認路徑為項目根目錄,若DLL文件存放在項目外,請使用絕對路徑
            msvcrt INSTANCE = (msvcrt) Native.load((Platform.isWindows() ? "msvcrt" : "c"),
                    msvcrt.class);// 加載動態庫文件
            // 聲明將要調用的DLL中的方法(可以是多個方法)
            void printf(String format, Object... args);
            void printf_s(String format, Object... args);
        }
    ​
        /**
         * SaleSec
         *
         * @Description: 讀取調用StdCall方式導出的DLL動態庫方法
         * @author: LinWenLi
         * @date: 2018年7月18日 上午10:37:58
         */
        public interface StdCallDll extends StdCallLibrary {
            // DLL文件默認路徑為項目根目錄,若DLL文件存放在項目外,請使用絕對路徑
            StdCallDll INSTANCE = (StdCallDll) Native.load((Platform.isWindows() ? "saleSec" : "c"),
                    StdCallDll.class);// 加載動態庫文件
            // 聲明將要調用的DLL中的方法(可以是多個方法)
    ​
            String GetSignature(String userId, String password, String SHA1);
        }
    ​
        /**
         * DLL動態庫調用方法2
         *
         * @Description: 讀取調用Decl方式導出的DLL動態庫方法
         * @author: LinWenLi
         * @date: 2018年7月18日 上午10:49:02
         */
        public interface CLibrary extends Library {
            // DLL文件默認路徑為項目根目錄,若DLL文件存放在項目外,請使用絕對路徑
            CLibrary INSTANCE = (CLibrary) Native.load(Platform.isWindows() ? "saleSec" : "c",
                    CLibrary.class);
    ​
            // 聲明將要調用的DLL中的方法(可以是多個方法)
            String GetSignature(String format, String PASSWORD, String SHA1);
    ​
        }
    ​
        @Test
        public void test(){
            System.out.println(StdCallDll.INSTANCE.GetSignature(USERID, PASSWORD, SHA1));
    //        System.out.println(CLibrary.INSTANCE.GetSignature(USERID, PASSWORD, SHA1));
        }
    ​
    }
    ​
    

     

  3. 運行測試類,輸出如下參數

 

五、大功告成,項目正確引入

參考文章:

https://www.cnblogs.com/wyongbo/p/jnaTest.html#

https://www.cnblogs.com/zhijian233/p/10752062.html

https://blog.csdn.net/weixin_34401479/article/details/92559052