如题:我用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;interfaceuses
  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;  //商户号implementationvar
  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;interfaceuses
  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 IFPS:PowerBuilder部份的,我贴的是简单的demo程序的代码,功能完善的程序的里面调用跟这里也是一样的。现在我通过写的txt日志,显示在完善的程序中调用dll涵数时,第一次都会成功,但第二次会失败,日志记录最后一条,
是for循环里面写完第一条日志后,主程序就崩溃了,即我标的那一条后面,日志就没有了。
日志如下:求大师能指点下我这dll中是哪里有问题?或是什么东西使用方法不对?

解决方案 »

  1.   

    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也会异常
      

  2.   


    我刚刚弄成在打开线程前,去实创建窗体了。
    但我测试后,发现,还是一样的有问题,日志如上图。看日志,我有两点没搞明白的:
    1、我明明是线程里面执行的循环,为何循环没执行完,也没有去close窗体时,确会退出了线程,而执行后后续的代码?
    2、第二次执行时,崩溃,基本都是在create窗体时,出错了,但我上次调用dll涵数时,在线程里面有去close窗体,也有free窗体,照理来说,再次create不会异常啊。综上,
    1、我要怎么控制必须是线程跑完,才允许跑后面的代码?
    2、我如何确保我本次调用涵数创建和打开的窗体完全关闭并释放,而不影响下次涵数再创建窗体?
      

  3.   

    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;
    附上我改后的部份的代码。
      

  4.   

    看来,这是个很难的问题?
    难道delphi版高手都流失了?
    冒似连查看人数,都没多少。
      

  5.   

    动态创建?我没明白。
    我现在是在线程里面create窗体的呢。
      

  6.   


    你是说在初始化的那个initLeshua涵数里面创建线程?还是窗体?
    但这样不是一样的么?因为我程序中一样会多次调用这个initLeshua初始化涵数的。并不是只调用一次。
      

  7.   

    那你不能用Form2.ShowModal;应该用Form2.Show吧
      

  8.   

    动态创建?我没明白。
    我现在是在线程里面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()里创建啊
      

  9.   

    用form2.show的话,达不到效果,因为要用模态窗体,也就是弹出这个窗体时,别的地方不能操作的,必须等这个涵数操作完,并关闭这个窗体后,才能继续后面的操作,且窗体上面,也要根据涵数处理的进度,显示相应的提示信息。
      

  10.   

    动态创建?我没明白。
    我现在是在线程里面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窗体?我再试试,但我记得我好像试过这样的,也会崩溃。
      

  11.   

    DLL_PROCESS_ATTACH中创建窗口
    DLL_PROCESS_DETACH中释放窗口
      

  12.   


    我百度了下,没搞明白这个在dll初始化和释放时去弄这个窗体,能请问一下,有demo或详细的说时的链接么?求个。
      

  13.   

    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.