使用dll的一些基礎見識
不是談理論, 也不是談規範, 更不談原則, 就只是見招拆招但又不想太費工夫...
引用dll中的api, 第一步就是要先知道函式名稱, 可以透過工具抓dll的函式列表出來觀察
Windows下最常見就是 WINAPI , 在C/C++裡面就是 __stdcall
Windows是MS Windows, MS的VC如無特別定義, 預設是 __cdecl
以VC來觀察, 3個一樣的函數在dll會有什麼不同
extern "C" __declspec(dllexport) __stdcall int DllApiTest1(int x)
{return x;}
extern "C" __declspec(dllexport) __cdecl int DllApiTest2(int x)
{return x;}
extern "C" __declspec(dllexport) int DllApiTest3(int x)
{return x;}
於dll裏所見的函數名稱分別為
_DllApiTest1@4
DllApiTest2
DllApiTest3
也就是只有 DllApiTest1 的樣子不一樣, 會被修飾成以底線開頭,
同是末尾銜接@加上傳遞參數的長度(每個參數需4bytes, 因為是32位元程式)
而於Borland C/C++加底線的規則剛好相反, 會是
DllApiTest1
_DllApiTest2
_DllApiTest3
如果改編譯成x64的版本, 則就不會有底線, VC/BC都一樣是
DllApiTest1
DllApiTest2
DllApiTest3
而既然談dll, 當然是Windows的dll, 如果 WINAPI 就是 __stdcall
那麼當然 __stdcall 最能廣為通用, 而要解決的問題就只有函式名稱不同而已~
於這篇文 使用HFOCX資訊示範AmiBroker上用ADK設計Realtime Quote
提到 ADK 使用 __cdecl , 官方都是用VC6和DevC作說明, 因為DevC命名的方式和VC相同,
而好處就是不論 x86 or x64 平台, 函式名稱都會一樣, 缺點就是其它語言(如VB)就無法使用了~
另一缺點則是, 選擇Borland C/C++來開發的話,
因為命名不一樣, 所以AmiBroker不能正確抓到進入點所以就不能用了,
這個並非真的不能用, 只要技巧編修dll內的函式名稱過後就還是能正確使用的
所以不同於AmiBroker的ADK作法, 平台產出的dll都是用 __stdcall
由於大致上還是以VC為主平台, 常可看到針對 Win32 和 x64 上的變通整合作法是這樣的,
以 DbfTCdll2.dll 應用在上面所提的ADK範例作說明
Win32平台(x86,x32)
#include "DbfTCdll2.h" // for 使用 DbfTCdll2 連接 DTS
//視環境配合調整 DbfTCdll2.dll 的位置
#define DbfTCdllLibSource "DbfTCdll2.dll" //x86的版
//#define DbfTCdllLibSource "DbfTCdll2_x64.dll" //x64的版
//啟用指定函數, 使用 WWXfunc_DllFuncAdd 的方式美化程式 (產生函式指標預設為NULL)
#define WWXfunc_DllFuncAdd(fnName) pWWXfunc_##fnName fnName=NULL;
//取得指定函數DLL進入點, 使用 WWXfunc_DllImport 的方式美化程式
#define WWXfunc_DllImport(hmDll,fnName) fnName=(pWWXfunc_##fnName)(GetProcAddress(hmDll, WWXfunc_##fnName));
//啟用指定函數
WWXfunc_DllFuncAdd(fnDbfTCdll_SelectDataSet)
WWXfunc_DllFuncAdd(fnDbfTCdll_InitUser)
WWXfunc_DllFuncAdd(fnDbfTCdll_Start)
WWXfunc_DllFuncAdd(fnDbfTCdll_Stop)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetConnectionStatus)
WWXfunc_DllFuncAdd(fnDbfTCdll_CallBack_Register)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetCurrTagValue)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetPrevTagValue)
WWXfunc_DllFuncAdd(fnDbfTCdll_CreateStringIdMapping)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetStringId)
WWXfunc_DllFuncAdd(fnDbfTCdll_CreateItemQuoteMemory)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetItemQuoteMemory)
WWXfunc_DllFuncAdd(fnDbfTCdll_GetQueueCount)
//啟用指定函數(Tick-Manager)
WWXfunc_DllFuncAdd(fnDbfTCdll_STManCreateGroup)
WWXfunc_DllFuncAdd(fnDbfTCdll_STManCreateProduct)
WWXfunc_DllFuncAdd(fnDbfTCdll_STManGetTickHandle)
WWXfunc_DllFuncAdd(fnDbfTCdll_STManGetTickMemory)
HMODULE hmDbfTCdll = NULL;
而 WWXfunc_DllImport 這個 macro 會於 x64 平台上作一些調整
#define WWXfunc_DllImport(hmDll,fnName) fnName=(pWWXfunc_##fnName)(GetProcAddress(hmDll, CheckApiFunctionNameForX64(WWXfunc_##fnName)));
char * CheckApiFunctionNameForX64(char *cpName)
{
if (*cpName == '_')
{
char *p = (char *)memccpy(cpName, cpName + 1, '@', strlen(cpName));
if (p)
*(--p) = 0;
}
return cpName;
}
如果想要切換平台不用更動程式, 可以這樣改寫
x86,x64平台共用程式碼
//取得指定函數DLL進入點, 使用 WWXfunc_DllImport 的方式美化程式
#ifndef WWXPx64
#define WWXfunc_DllImport(hmDll,fnName) fnName=(pWWXfunc_##fnName)(GetProcAddress(hmDll, WWXfunc_##fnName));
#else
//於VC的x64專案上,可把專案屬性裏的 預定義(Preprocessor Definitions) 加入 WWXPx64 就會改跑這段
#define WWXfunc_DllImport(hmDll,fnName) fnName=(pWWXfunc_##fnName)(GetProcAddress(hmDll, CheckApiFunctionNameForX64(WWXfunc_##fnName)));
char * CheckApiFunctionNameForX64(char *cpName)
{
if (*cpName == '_')
{
char *p = (char *)memccpy(cpName, cpName + 1, '@', strlen(cpName));
if (p)
*(--p) = 0;
}
return cpName;
}
#endif
其實 CheckApiFunctionNameForX64 就是把原本.h檔中刻好的x86版本函式名稱去掉前面的 _ 和刪除後面 @ 的延伸
也就是不管什麼平台都用同一個.h檔, 而且內容很單純, 由x86攜至x64直接使用, 不需作任何的因應或調整
所以在那ADK的示範程式中掛接dll函式是這樣寫的, 這樣就看明瞭了吧!
PLUGINAPI int Init(void)
{
if (!hmDbfTCdll)
hmDbfTCdll = LoadLibrary(DbfTCdllLibSource);
if (hmDbfTCdll)
{
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_SelectDataSet);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_InitUser);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_Start);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_Stop);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetConnectionStatus);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CallBack_Register);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetCurrTagValue);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetPrevTagValue);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CreateStringIdMapping);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetStringId);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CreateItemQuoteMemory);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetItemQuoteMemory);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetQueueCount);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManCreateGroup);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManCreateProduct);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManGetTickHandle);
WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManGetTickMemory);
if (fnDbfTCdll_CallBack_Register)
{
//CallBack 模式
fnDbfTCdll_CallBack_Register(TagDataProcessFunction, 1);// 1 - By Tag, 每個商品的每個Tag更新都會呼叫
fnDbfTCdll_CallBack_Register(ItemProcessFunction, 2);// 2 - By Item, 有更新的商品會呼叫 (傳入之cpVarName,cpNewValue,cpOldValue等參數為NULL)
if (fnDbfTCdll_STManCreateGroup)
{
unsigned long uGroupIndex = fnDbfTCdll_STManCreateGroup("RecentInfoDataItem", sizeof(RecentInfoDataItem), NULL, 64, 256);
unsigned long uProductIndex = fnDbfTCdll_STManCreateProduct(uGroupIndex);
vpQuoteIndexHandle = fnDbfTCdll_STManGetTickHandle(uGroupIndex, uProductIndex);
if (!vpQuoteIndexHandle)
MessageBox(0, "Dll file "DbfTCdllLibSource" STMan Err! Memory crash!!", PLUGIN_NAME" Init Error!", MB_OK);
}
else
MessageBox(0, "Dll file "DbfTCdllLibSource" not support STMan", PLUGIN_NAME" Init Error!", MB_OK);
}
else
{
FreeLibrary(hmDbfTCdll);
hmDbfTCdll = NULL;
MessageBox(0, "Dll file "DbfTCdllLibSource" load faild!!", PLUGIN_NAME" Init Error!", MB_OK);
}
}
else
{
MessageBox(0,
"Dll file "DbfTCdllLibSource" not found!!\n"
"請將檔案置於AmiBroker的工作環境資料夾內"
, PLUGIN_NAME" Init Error!"
, MB_OK
);
}
if (hmDbfTCdll)
{
fnDbfTCdll_InitUser(PLUGIN_NAME); // 直接用PLUGIN_NAME當User,同一資訊源相同User只能一個在線; 可改成用參數提供設定
fnDbfTCdll_SelectDataSet(caDB_list);
if (fnDbfTCdll_Start(caDTS_host))
g_nStatus = STATUS_WAIT;
}
return 1; // default implementation does nothing
};
於這篇使用HFOCX資訊示範AmiBroker上用ADK設計Realtime Quote文中所介紹的示範專案,
包含 VC6 , VC10(x32,x64) , DevC , DevC5(x32,x64) , BCB5 , BCB6 都是只需安裝好最基本的IDE,
甚至用免安裝的版本也可以, 就能直接成功編譯出dll, 如預料只有BCB的版本不能被AmiBroker正確使用,
其它版本包含 x32 , x64 全都能正常運行, 如此程式修改同一份就能重新使用各種IDE釋出新版本的dll,
在切換x32或x86的平台專案過程, 也是完全不用更動程式碼, 是個非常理想完美的範例,
雖然此範例專案有用到本平台上的 DbfTCdll2.dll , 但因為是動態掛接的模式,
即使手邊沒有這個dll仍能使用此專案作為雛型來開發AmiBroker的ADK應用,
往往很多情況是專案環境弄不好, 或是不知怎麼從零開始,
現在只要拿這份程式碼作基礎, 依樣畫葫蘆很容易就能上手了~
加上AmiBroker可以免費下載使用, 軟體也不大, 是個不錯的練習項目
沒有 DbfTCdll2.dll 的狀況, 也能知道ADK是否正確工作
關掉於ADK中所設計彈出的訊息窗後, 右下角的狀態也會反映在ADK裏設計好的狀態回應
可供收藏的壓縮檔內容架構如下
有來問說最新的VS版本是否也可以用, 答案當然是可以, 完全不會有任何奇奇怪怪的問題
於VS2008的VC9可以直接開VC6的專案轉換後就能直接使用(原本的.dsw 轉過以後則改用.sln),
而VS2015的VC14和VS2017的VC14.1都是直接開VC10的專案轉換後就直接可以用了(轉換其實只有更新VC版本號而已)
由於不管是什麼VC版本還是任何其他IDE產品, 要使用本程式碼都很簡單, 只需產生一個dll的空專案,
然後作一個動作把程式碼include就可以用了, 因此壓縮檔內不需要放太多具體的範例, 看看就懂了
真的想用BCB來開發ADK也是可以的~ 壓縮檔內有提供修正程式
如有興趣, 可以透過街口轉帳任意金額, 備註留信箱說明索取ADKdllSample, 待作業完成就會寄送至所留信箱