2012年1月20日 星期五

programming DLL using C/C++ for Powerbuilder external function

真的,用PowerBuuilder的人實在太少了,再加上又會寫C語言作為輔助的更是寥寥可數,只好自己用功一點『自學』了。

C語言有很強大的功能,但每種版本的C語言又有許多的不一樣(C、C++、C#、Object C...),真的學起來相當辛苦,如果電腦原理基礎觀念不好,操作C語言可能真的會成為夢饜,雖然C++與C#有許多強大的功能與函式,但是作為可以被『異種』程式語言呼叫的獨立函式庫,可能就非常不理想,因此需要以標準的C語言編譯,讓其他程式可以使用就是一個原則(這是我的想法,如果你有不同的意見歡迎提出)。

雖然C語言的原始碼可以跨平台,不過會由於編譯器的不同而有不太一樣的指令方式。
例如,我的開發環境在Visual Studio 2005底下,使用到某些C指令就會有所警告,像是:
strcpy => strcpy_s
strdup => _scrdup
諸如此類,雖然編譯會發生警告,但是還是可以使用。但是以 Visual Studio 的觀點來說,這些都是被列為不安全的指令。所以我撰寫的程式碼都是在符合 Visual Studio 環境底下設計的,若你需要使用到原本C語言原始指令,就可能要自行改寫或是參考一下MSDN的說明了。

好了,來說說如何製作一個標準的DLL供 PowerBuilder 使用吧(不一定限定PB,也可以是VB、Delphi之類):

首先,你必須建立一個新的專案(部分C開發環境沒有專案概念,例如:Dev-C,每個檔案都必須自行建立與整理)



然後 Project-type 選擇 Visual C++ ,點選以後,再選擇 Win32 Project,
輸入 name 專案名稱,以及選擇專案儲存的路徑 Location,按下 OK,
進入 精靈 畫面時,選擇 Application Setting,右側選擇 DLL,再按 Finish 即可。

有人說用 MFC 也可以建立 DLL,這也是可以,但是 MFC 的 DLL 必須仰賴 MFC 函式庫,若只是單純的簡易處理 PB 不足之處(例如二進位運算),說實話,做好的 DLL 還要包(Packged)上一堆 MFC 函式庫實在不單純啊,PB本身的 Runtime 已經很肥了,再加上這堆用不到的 MFC 程式會變更肥啊,請好好考慮一下吧。

建立專案以後就是撰寫程式的重點了,如果對於下列程式碼有疑問的地方可以先看看後面的參考網頁,可以了解一下為啥麼要這麼作了:

我用 test2 來建立專案名稱,這個專案會建立下面的檔案:
會看到一個 test2.cpp 的檔案,主要就是撰寫這個檔案內容,當開啟這個檔案後會看到這個檔案已經有一些內容,這些內容是標準的 C++ DLL 基本的語法結構。

這時在內容最下方加入
#ifdef  __cplusplus
extern "C" {
#endif

/* 函式寫在這裡*/

#ifedf __cplusplus
}
#endif


這個宣告的意義在於,由 extern "C" 所包起來的內容將在編譯時,編譯成標準 C 指令的函數。
而裡面函式的標準寫法如下:
__declspec(dllexport) 回傳型別 __stdcall 函式名稱(參數型別 參數名稱1,參數型別 參數名稱2,...){程式碼陳述;}
例如:

__declspec(dllexport) int __stdcall Add2U(int *i, int *j)
{
//數值傳址方式操作,可以改變原始內容
int f,x;
f = *i + *j;
x = *j;
*j = *i;
*i = x;
return f;
}

__declspec(dllexport) char* __stdcall stringToUpper(char* src)
{
//字串內容小寫轉大寫
char* dest = _strdup(src);
char* trg = dest;
for(; *dest != '\0';dest ++)
*dest = (char) toupper(*dest);
return trg;
}


有了以上內容可以當然直接編譯(Build Solution),的不過這樣PB還是會呼叫不到,因為__declspec(dllexport)之後函數名稱會被編譯器轉換,導致無法取得正確的名稱,所以必須自己建立定義檔(.DEF),所以需要在 Source Files 底下新增一個 def 檔:

新增了以後,打開 test2.def 檔案並加入下面的定義宣告,這是標準API的宣告方式,才能讓其他程式找到該 DLL 函式庫內的成員:

LIBRARY "test2"
EXPORTS
Add2U @1
stringToUpper @2


此時,就可以使用『Build』>『Build Solution』,建立好了以後可以在該專案路徑下找到『debug』資料夾,底下就會有一個 DLL 檔案,把這個檔案複製到需要呼叫它的PB程式路徑下就可以引用了。

PB的引用方式,先在 external function 定義:
function int Add(ref int x , ref int y) library "test2.dll" alias for "Add2U"
function string ToUpper(ref String  strss) library "test2.dll" alias for "stringToUpper"


然後就快樂使用囉!





參考:函數調用規則__stdcall__cdecel區別在C++底下建立DLL__declspec(dllexport)與.def文件

2 個回應:

Spencer Lee 提到...


請問:先進,
我有這個議題實際寫作,採用vs2013(vc11)開發出一個範例測試,但PB10 build5032 call external function C++ dll,Got Error Message "Bed Runtime function reference...".Why? What is wrong? Can you help me or give me a tip?

to see following my c++ Project(console application dll) dll_test.proj code
//------dll_test.cpp, Spencer Lee,2015/10/06---
//
#include "stdafx.h"
#include "dll_test.h"
DLLEXPORT int add(int x,int y)
{
return x+y;
}
DLLEXPORT char hello(char amsg)
{
char *p="hello ",*q="!";
return *p+amsg+*q;
}
// -------dll_test.h
// written by Spencer Lee 2015/10/06
//---------------------------------
#define DLLEXPORT __declspec(dllexport)
#define DLLIMPORT __declspec(dllimport)

DLLEXPORT int add(int x,int y);
DLLEXPORT char hello(char a_msg);

// PB - Powerscript Code-----
//...............Global External Function(C++ dll)..........
//FUNCTION integer cplusplus_add( int x, integer y) LIBRARY "dlltest.dll" alias for "add"
//FUNCTION string cplusplus_hello( string z) LIBRARY "dlltest.dll" alias for "hello"
//int p
//p=cplusplus_add(3,8)
//messagebox('1007add',string(cplusplus_add(4,9)))
messagebox('1007externalfunction...hello',cplusplus_hello('Chin-Poon'))
//

Best Regards,
Spencer Lee

WILDOX 提到...

Re: Spencer Lee <3678596261282919654>
Hi Spencer Lee
就我看到的部分,你的 DLL是否編譯成 dll_test.dll
而您用PB10呼叫的DLL是呼叫 dlltest.dll
如果是的話請修改程相同檔名的DLL