dll動態庫生成與調用(1):生成dll動態庫、C程序調用動態庫

dll動態庫生成與調用(1):生成dll動態庫、C程序調用動態庫
dll動態庫生成與調用(2):Java程序利用JNI、JNA調用動態庫

文章簡介

本文介紹了使用C語言去生成一個動態庫,並使用C程序去調用這個動態庫。

當然,你可能想用C++來創建自己的調用庫,按照文中的步驟直接調用肯定是會出現問題的,這是因爲在C++中,爲了面向對象中的重載等特性,會在編譯中將函數的名字做相應修改,你可以將要調用的內容採用extern "C"進行修飾。

extern "C"有兩層含義:
第一,被它限定的函數或變量是extern類型的。
第二,被它修飾的變量和函數是按照C語言方式編譯和連接的。

開發環境

IDE: Visual Studio 2015、IDEA 2019.1

JNA(Java庫): 3.5.2

JDK: 1.8

文件目錄結構

C:

JNI_test項目中的dll.h、dll.c用於生成dll動態庫文件,JNI_test_call項目中的call.c用於調用dll庫。

在這裏插入圖片描述

注: 如果是VS新手,建議這兩個項目不要放在同一個解決方案中,爲這兩個項目各創建一個解決方案,能避開一些VS使用上的問題。

一、動態庫的生成

注:項目JNI_test中進行操作。

1.新建項目,用於生成dll庫

在VS中新建項目:Win32控制檯應用程序->控制檯應用程序/DLL->空項目

我一般是在控制檯應用程序中進行函數調試,再調VS選項進行DLL生成,這樣比較方便,如果你直接選擇DLL,就不需要調設置了。

設置在 項目屬性頁 的 常規->配置類型 中,將配置類型從 “.exe” 改爲 “.dll” 即可,如下所示:

在這裏插入圖片描述

不一定需要空項目,如果選擇創建的不是空項目,會默認帶幾個文件,沒影響。

2.編寫動態庫的.h頭文件、.c源文件

dll.h

// 宏定義
#define C_API _declspec(dllexport)

// 在DLL中,將被調用的函數
C_API int start(char *);

第2行定義一個宏,用C_API替代_declspec(dllexport)。當然,可以不定義宏,直接在函數前面寫_declspec(dllexport)也是沒問題的。

第4行聲明一個函數,將在dll.c中進行實現。

dll.c

#include<stdio.h>
#include"dll.h"

C_API int start(char *string) {

	printf("\n\n");
	printf("=========DLL out start=========\n");

	printf("call print: %s\n",string);// 打印傳過來的字符傳

	printf("=========DLL out end=========\n\n");

	return 0;
}

第2行,別忘了包含頭文件。

3.生成動態庫文件

在VS的“生成”菜單中進行生成,會產生和項目名同名的.dll.lib文件,庫的生成就完成了。

二、在C語言程序中調用dll動態庫

項目JNI_test_call中進行操作。本文寫了2種調用dll的方法。
推薦第二種調用方法。

1.第一種調用方法:僅需要.dll文件(“顯式鏈接”)

注意: 本調用方法採用的是動態庫的顯式鏈接。(可以看文章末尾 補充閱讀第5項)主要需要2個Windows的API:LoadLibraryGetProcAddress

1.C語言庫代碼:

call.c

#include<stdio.h>
#include<windows.h> // 用於庫調用函數
#include<tchar.h> // 用於_T等編碼轉換宏

int main() {

	HINSTANCE hDll;
	int(*startCall)(char *);// 函數指針,用於存放dll中要調用的函數
	LPCWSTR szString = _T("JNI_test.dll");// 將char字符串轉換爲LPCWSTR格式,不然無法找到dll文件
	
	// 加載庫(有2種加載方式)
	//hDll = LoadLibraryEx("E:/JNI_test/JNI_test.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);// 庫的絕對路徑
	hDll = LoadLibrary(szString);// 將dll移到當前程序目錄下,可以直接用dll名替代

	if (hDll == NULL) {	// 庫加載失敗
		printf("%s", "failed to load dll!\n");
	}
	else { // 庫加載成功

		// 獲取start函數的內存地址
		startCall = GetProcAddress(hDll,"start");
		// 利用函數指針調用函數
		startCall("hello, I'm JNI_test_call!");
	}

	FreeLibrary(hDll);

	return 0;
}

_T宏 用於將char字符串轉換成LPCWSTR,詳細說明可以看 本文末尾的 錯誤記錄第1.2和1.3 或者 參考文章第8項

2.dll文件的調用路徑:
在調用程序生成目錄下(產生.exe的目錄),放入我們要調用的.dll文件,並不需要.lib文件

在這裏插入圖片描述

2.第二種調用方法:需要.h,.lib,.dll文件(推薦此方法)

1.C語言庫的調用程序代碼:

call.c

#include<stdio.h>
#include"dll.h"
//#pragma comment(lib, "JNI_test.lib")

int main() {

    // 當做普通行數調用即可
	start("hello,I'm JNI_test_call!");

	return 0;
}

2.配置.h頭文件:

在這裏插入圖片描述

再在call.c中添加對動態庫頭文件的引用:

#include"dll.h"

3.配置.lib.dll文件:

配置lib和dll文件所在目錄:

在這裏插入圖片描述

配置需要調用的lib文件:

在這裏插入圖片描述

注意: “附加依賴項“可以在call.c代碼中添加#pragma comment(lib, "JNI_test.lib")來進行代替。

4.運行項目:

最後在VS中運行本項目即可,結果如下:

在這裏插入圖片描述

三、附加內容

1. 錯誤記錄

1.1 LNK2019 LNK1120 無法解析的外部符號 “xxxx1”" ,該符號在函數 xxxx2 中被引用

問題的原因在於,找不到被我們引用的函數的聲明。

xxxx1是我們要引用的函數,函數的聲明和實現在其它的文件中。

xxxx2是使用xxxx1的函數。舉個例子:如果我們在main()中使用start(),如果出現此錯誤,那麼會報無法解析的外部符號 “start”" ,該符號在函數main中被引用

解決方法:

  1. 檢查函數名是否書寫正確,確認一下你包含的頭文件中是否有函數的聲明

  2. 檢查頭文件配置是否正確

1.2 warning C4133: “函數”: 從“char [xx]”到“LPCWSTR”的類型不兼容

第一種解決方法:(不推薦)

在項目的屬性頁中找到 常規->字符集:把 “使用 Unicode 字符集” 改爲 “使用多字節字符集”,如下所示:

在這裏插入圖片描述

第二種解決方法:

使用_T宏,對char字符串進行轉換,關鍵代碼如下:

需要包含頭文件:#include<tchar.h>

LPCWSTR szString = _T("JNI_test.dll");// 字符串轉換
	
hDll = LoadLibrary(szString);// 加載庫

用這種方法,就不需要改變項目所使用的字符集。

1.3 無法加載庫

主要的原因&解決方法:

  1. 庫的路徑錯誤,查看庫是否存在以及庫的路徑是否在我們配置的地方。

  2. 顯式加載庫中的字符串編碼格式不符合調用要求,可以參考本文中錯誤記錄的第1.2項,進行處理。

1.4 調整VS項目屬性頁,但卻沒有效果

這個問題在以前的文章中有提到過,VS設置如果沒有效果,那麼應該先檢查 VS項目配置頁 是否和 當前項目生成的配置 相符,如下所示:

在這裏插入圖片描述

如果並不是上面的設置的問題,那麼你可能是你改動的設置對你要解決的問題沒有效果。對此的建議是,將VS的錯誤提示再多在網上進行查詢,找到問題的,如果問題查詢不到,你可以將提示中的關鍵詞抽取出來,列在搜索框中進行查詢。另外,項目中的警告,也應當注意,如果有心有餘力的話,不妨將警告也解決掉吧。

2. 參考文章

  1. VS 編寫c++dll庫文件推薦閱讀,有講到本文章中的一些基礎知識)
  2. c語言調用dll文件
  3. c語言怎麼調用dll文件?
  4. LoadLibrary加載動態庫失敗的思考
  5. loadlibrary()失敗的問題
  6. warning C4133: “函數”: 從“char [5]”到“LPCWSTR”的類型不兼容
  7. C語言創建動態dll和調用dll(visual studio 2013環境下)
  8. 在vs中char類型的實參與LPCWSTR類型的形參類型不兼容怎麼解決?推薦閱讀,內容提及:UNICODE和ASCII碼之間的關係、LPCTSTR是什麼)

3. 補充閱讀

  1. #pragma_百度百科
  2. #ifndef的用法(可使C++中的.h頭文件只被包含一次)
  3. extern和extern “C”(如果使用C++,則需要了解)
  4. dllMain函數的作用(這個函數可以不寫,如果沒有,會自動調用缺省的dllMain)
  5. 使用LoadLibrary調用DLL(文中有介紹“顯式鏈接”和“隱式鏈接”)
  6. 關於“Error: “const char *” 類型的實參與 "LPCWSTR"類型的形參不兼容”錯誤的解決方案推薦閱讀,內容提及:字符串編碼格式的介紹)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章