使用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, 待作業完成就會寄送至所留信箱