如題:我用delphi10.2.3撰寫了一個dll,這個dll里面,帶了一個表單,且匯出了兩個涵數供外部呼叫。
其中一個是初始化的。
一個是呼叫功能的。
現在發現,在別的語言開發的程式中,如果是簡單的demo,比如:程式就只有一個簡單的視窗,在表單上呼叫這兩個涵數時,90%是沒問題的,但呼叫次數多了,會偶爾出現程式崩潰的情況。
但如果是在一個功能完善的應用程式中呼叫這個dll時,則100%,第二次呼叫時,dll就會造成主程式崩潰。
可能我表達的有點亂,下面我直接貼上代碼,懇請高手指點下:
我dll中,用到了表單,也用到了多執行緒,因為dll上面需要有界面顯示效果等。
dll代碼如下:
library leshua;
uses
System.SysUtils,
System.Classes,
Unit1 in 'Unit1.pas',
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
exports
initLeshua,
LeshuaPay;
begin
end.
單元1代碼如下:
unit Unit1;
interface
uses
Winapi.Windows, System.SysUtils, System.Classes, System.Hash, Vcl.Controls,
Vcl.Forms, Vcl.ExtCtrls, IdHTTP, IdSSLOpenSSL, IdGlobal, XMLDoc, XMLIntf,
StrUtils, messages, unit2, IniFiles;
type
str_thread_date = record
ls_djno: string;
ls_amount: string;
ls_paycode: string;
end;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override; {執行}
procedure Run; {宣告多一個程序,把功能代碼寫在這里再給Execute呼叫}
end;
function initLeshua(as_shh: PAnsiChar; as_t0: PAnsiChar; handle: THandle): Integer; stdcall;
function LeshuaPay(as_djno: PAnsiChar; as_amount: PAnsiChar; as_paycode: PAnsiChar; as_error: PAnsiChar): Integer; stdcall;
function GetDllPath: string;
procedure writeLog(logstr: string);
var
Form2: TForm2;
gs_error: string; //錯誤內容
gi_return: Integer; //回傳結果 < 0為失敗, >= 0為成功
lstr_thread_data: str_thread_date;
appHandle: THandle;
oldHandle: THandle;
gs_url: string; //請求的地址
gs_key: string; //KEY
gs_shh: string; //商戶號
implementation
var
MyThread: TMyThread; {宣告一個執行緒類物件}
procedure TMyThread.Execute;
begin
{ Place thread code here }
writeLog('執行緒開始執行');
FreeOnTerminate := True; {加上這句執行緒用完了會自動注釋}
writeLog('準備運行執行緒涵數run');
Run;
end;
procedure TMyThread.Run;
var
i: integer;
ls_showtext: string;
ls_shh: string;
myinifile: TIniFile; //組態檔
begin
writeLog('執行緒涵數run開始');
myinifile := Tinifile.Create(ExtractFilePath(GetDllPath()) + 'leshuapay.ini');
ls_shh := myinifile.Readstring('system', 'business_no', '');
myinifile.Destroy;
writeLog('執行緒涵數run獲取shh=' + ls_shh);
for i := 1 to 10 do
begin
writeLog('執行緒涵數run:第' + IntToStr(i) + '次回圈');
Form2.Canvas.Lock;
Form2.Panel1.Caption := '第' + IntToStr(i) + '次回圈';
Form2.Canvas.Unlock;
writeLog('執行緒涵數run:進行延時1秒');
Sleep(1000);
end;
writeLog('執行緒涵數run:準備關閉Form2');
Form2.Close;
writeLog('執行緒涵數run:結束');
end;
function initLeshua(as_shh: PAnsiChar; as_t0: PAnsiChar; handle: THandle): Integer; stdcall;
var
ls_shh: PAnsiChar;
myinifile: TIniFile; //組態檔
begin
if (appHandle = 0) then
appHandle := handle;
myinifile := Tinifile.Create(ExtractFilePath(GetDllPath()) + 'leshuapay.ini');
myinifile.writestring('system', 'business_no', as_shh);
myinifile.Destroy;
gs_key := 'ADB8145F4D8A820D30B20B1930';
gs_url := 'https://paygate.shuazf.com/cgi-bin/pay_gateway.cgi';
writeLog('初始化:商戶號=[' + as_shh + '],請求地址=[' + gs_url + ']');
Result := 1;
end;
function LeshuaPay(as_djno: PAnsiChar; as_amount: PAnsiChar; as_paycode: PAnsiChar; as_error: PAnsiChar): Integer; stdcall;
var
ls_error: string;
begin
writeLog('開始支付涵數');
MyThread := TMyThread.Create(False); {實體化執行緒類,為False時立即運行,為True時可加MyThread.Resume用來啟動}
writeLog('打開執行緒完畢');
oldHandle := Application.Handle;
Application.Handle := appHandle;
writeLog('實體華表單');
Application.CreateForm(TForm2, Form2);
try
writeLog('顯示表單');
Form2.ShowModal;
finally
writeLog('準備釋放資源');
Form2.Free;
writeLog('表單釋放完畢');
Application.Handle := oldHandle;
writeLog('句柄還原');
end;
ls_error := 'WXZF|0000290005619263';
writeLog('復制要回傳的資訊');
StrPCopy(as_error, AnsiString(ls_error));
writeLog('支付涵數回傳');
Result := 1;
end;
function GetDllPath: string;
var
ModuleName: string;
begin
SetLength(ModuleName, 255);
//取得Dll自身路徑
GetModuleFileName(HInstance, PChar(ModuleName), Length(ModuleName));
Result := PChar(ModuleName);
end;
procedure writeLog(logstr: string);
var
filev: TextFile;
path, name, logfile: string;
begin
//寫日志
path := ExtractFilePath(GetDllPath()) + 'log\';
if not FileExists((path)) then
ForceDirectories(path);
name := 'LESHUA_' + FormatDateTime('yyyymmdd', now); //取得日期
logfile := path + name + '.txt'; //日志存取路徑
name := logfile;
if FileExists(logfile) then
begin
AssignFile(filev, logfile);
append(filev);
writeln(filev, FormatDateTime('d', now) + '/' + FormatDateTime('hh:nn:ss.zzz', now) + ' ' + logstr);
end
else
begin
AssignFile(filev, logfile);
ReWrite(filev);
writeln(filev, FormatDateTime('d', now) + '/' + FormatDateTime('hh:nn:ss.zzz', now) + ' ' + logstr);
end;
CloseFile(filev);
end;
end.
單元二代碼如下:單元二其實就只有一個空白表單。
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;
type
TForm2 = class(TForm)
Panel1: TPanel;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
end.
我呼叫dll的主程式是PowerBuilder9.0的:
PB9宣告
FUNCTION long initLeshua(string appid,string payments,uLong handle) library "leshua.dll" Alias for "initLeshua"
FUNCTION long LeshuaPay(string paydjno,string payamount,string paycode,ref string error) library "leshua.dll" Alias for "LeshuaPay"
涵數呼叫如下:
初始化:
initLeshua("5208056","0",HANDle(this))
功能呼叫:
string ls_error
string ls_paycode
string ls_djno
string ls_amount
integer li_return
char ls_err[]
ls_error = space(255)
ls_djno = "LSZFCY20173" + string(today(),"YYMMDD") + string(now(),"HHMMSSFFF")
ls_paycode = trim(sle_1.text)
ls_amount = "1"
li_return = LeshuaPay(ls_djno,ls_amount,ls_paycode,ls_error)
IF li_return < 0 THEN
messagebox("錯誤","呼叫失敗:" + ls_error)
ELSE
messagebox("恭喜","呼叫成功:" + ls_error)
END IF
PS:PowerBuilder部份的,我貼的是簡單的demo程式的代碼,功能完善的程式的里面呼叫跟這里也是一樣的。
現在我通過寫的txt日志,顯示在完善的程式中呼叫dll涵數時,第一次都會成功,但第二次會失敗,日志記錄最后一條,
是for回圈里面寫完第一條日志后,主程式就崩潰了,即我標的那一條后面,日志就沒有了。
日志如下:

求大師能指點下我這dll中是哪里有問題?或是什么東西使用方法不對?
uj5u.com熱心網友回復:
我感覺是我記憶體或什么東西沒釋放?或是執行緒使用的不恰當造成的,但我翻了兩天的百度,也沒翻出個所以然來。
uj5u.com熱心網友回復:
我估計是你執行緒里面掉表單變數, 你先創建的執行緒后創建的表單,執行緒里面使用表單的時候,表單還沒有創建好,你把創建執行緒和表單的順序換一下uj5u.com熱心網友回復:
MyThread := TMyThread.Create(False); {實體化執行緒類,為False時立即運行,為True時可加MyThread.Resume用來啟動}writeLog('打開執行緒完畢');
oldHandle := Application.Handle;
Application.Handle := appHandle;
writeLog('實體華表單');
Application.CreateForm(TForm2, Form2);
try
writeLog('顯示表單');
Form2.ShowModal;
finally
writeLog('準備釋放資源');
Form2.Free;
//...
你建立執行緒之后表單還沒有建立,執行緒就可能參考Form2了,另外,Form2釋放之后,執行緒可能還沒有結束,此時再參考Form2也會例外
uj5u.com熱心網友回復:

我剛剛弄成在打開執行緒前,去實創建表單了。
但我測驗后,發現,還是一樣的有問題,日志如上圖。
看日志,我有兩點沒搞明白的:
1、我明明是執行緒里面執行的回圈,為何回圈沒執行完,也沒有去close表單時,確會退出了執行緒,而執行后后續的代碼?
2、第二次執行時,崩潰,基本都是在create表單時,出錯了,但我上次呼叫dll涵數時,在執行緒里面有去close表單,也有free表單,照理來說,再次create不會例外啊。
綜上,
1、我要怎么控制必須是執行緒跑完,才允許跑后面的代碼?
2、我如何確保我本次呼叫涵數創建和打開的表單完全關閉并釋放,而不影響下次涵數再創建表單?
uj5u.com熱心網友回復:
function LeshuaPay(as_djno: PAnsiChar; as_amount: PAnsiChar; as_paycode: PAnsiChar; as_error: PAnsiChar): Integer; stdcall;
var
ls_error: string;
begin
writeLog('開始支付涵數');
writeLog('處理視窗句柄Application.Handle=' + IntToStr(Application.Handle) + ' appHandle=' + IntToStr(appHandle));
oldHandle := Application.Handle;
Application.Handle := appHandle;
writeLog('開始實體化表單createform');
Application.CreateForm(TForm2, Form2);
writeLog('準備打開執行緒');
MyThread := TMyThread.Create(False); {實體化執行緒類,為False時立即運行,為True時可加MyThread.Resume用來啟動}
writeLog('打開執行緒完畢');
try
writeLog('顯示表單');
Form2.ShowModal;
finally
writeLog('準備釋放資源');
Form2.Free;
writeLog('表單釋放完畢');
Application.Handle := oldHandle;
writeLog('句柄還原');
end;
ls_error := 'WXZF|0000290005619263';
writeLog('復制要回傳的資訊');
StrPCopy(as_error, AnsiString(ls_error));
writeLog('支付涵數回傳====================================================');
Result := 1;
end;
附上我改后的部份的代碼。
uj5u.com熱心網友回復:
看來,這是個很難的問題?
難道delphi版高手都流失了?

冒似連查看人數,都沒多少。。。。
uj5u.com熱心網友回復:
你為啥不用執行緒創建表單?uj5u.com熱心網友回復:
初始化的時候創建一次即可uj5u.com熱心網友回復:
動態創建?我沒明白。
我現在是在執行緒里面create表單的呢。
uj5u.com熱心網友回復:
你是說在初始化的那個initLeshua涵數里面創建執行緒?還是表單?
但這樣不是一樣的么?因為我程式中一樣會多次呼叫這個initLeshua初始化涵數的。并不是只呼叫一次。
uj5u.com熱心網友回復:
那你不能用Form2.ShowModal;應該用Form2.Show吧uj5u.com熱心網友回復:
不對啊,你是在:
function LeshuaPay(as_djno: PAnsiChar; as_amount: PAnsiChar; as_paycode: PAnsiChar; as_error: PAnsiChar): Integer; stdcall;
var
ls_error: string;
begin
writeLog('開始支付涵數');
MyThread := TMyThread.Create(False); {實體化執行緒類,為False時立即運行,為True時可加MyThread.Resume用來啟動}
writeLog('打開執行緒完畢');
oldHandle := Application.Handle;
Application.Handle := appHandle;
writeLog('實體華表單');
Application.CreateForm(TForm2, Form2);
里創建的啊
應該再Run()里創建啊
uj5u.com熱心網友回復:
那你不能用Form2.ShowModal;應該用Form2.Show吧
用form2.show的話,達不到效果,因為要用模態表單,也就是彈出這個表單時,別的地方不能操作的,必須等這個涵數操作完,并關閉這個表單后,才能繼續后面的操作,且表單上面,也要根據涵數處理的進度,顯示相應的提示資訊。
uj5u.com熱心網友回復:
你為啥不用執行緒創建表單?
動態創建?我沒明白。
我現在是在執行緒里面create表單的呢。
不對啊,你是在:
function LeshuaPay(as_djno: PAnsiChar; as_amount: PAnsiChar; as_paycode: PAnsiChar; as_error: PAnsiChar): Integer; stdcall;
var
ls_error: string;
begin
writeLog('開始支付涵數');
MyThread := TMyThread.Create(False); {實體化執行緒類,為False時立即運行,為True時可加MyThread.Resume用來啟動}
writeLog('打開執行緒完畢');
oldHandle := Application.Handle;
Application.Handle := appHandle;
writeLog('實體華表單');
Application.CreateForm(TForm2, Form2);
里創建的啊
應該再Run()里創建啊
哦,你是說在run里面的開始位置去create表單,在run的結束位置去close并free表單?我再試試,但我記得我好像試過這樣的,也會崩潰。。。。
uj5u.com熱心網友回復:
DLL_PROCESS_ATTACH中創建視窗DLL_PROCESS_DETACH中釋放視窗
uj5u.com熱心網友回復:
DLL_PROCESS_ATTACH中創建視窗
DLL_PROCESS_DETACH中釋放視窗
我百度了下,沒搞明白這個在dll初始化和釋放時去弄這個表單,能請問一下,有demo或詳細的說時的鏈接么?求個。
uj5u.com熱心網友回復:
library xxx;uses WinApi.Windows;
// ...
procedure DllMain(reason: integer);
begin
case reason of
DLL_PROCESS_ATTACH:
begin
//
end;
DLL_PROCESS_DETACH:
begin
//
end;
end;
end;
begin
DllProc := DllMain;
DLLMain(DLL_PROCESS_ATTACH);
end.
uj5u.com熱心網友回復:
記憶體里沒有真的釋放,你可以在結尾加一個計數器測驗一下,沒有問題在把他刪了。uj5u.com熱心網友回復:
盡量不要在執行緒里面操作資源先創建好表單,再去操作執行緒
然后資料的更新用訊息,這樣就不會出問題了
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/22593.html
