一个超级奇怪的问题,与 IntToStr() 有关(100分)

  • 主题发起人 主题发起人 kin.ming
  • 开始时间 开始时间
K

kin.ming

Unregistered / Unconfirmed
GUEST, unregistred user!
主程序中的代码:

代码:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Menus, ToolWin, ComCtrls, StdCtrls, ImgList, ExtCtrls;

const
  TB_ADDBUTTONS = WM_USER+20;
  
type
  itbbutton = record
    iBitmap: Cardinal;
    idCommand: Integer;
    fsState: Byte;
    fsStyle: Byte;
    dwData: DWORD ;
    iString: Integer ;
  end;
  TForm1 = class(TForm)
    clbr1: TCoolBar;
    tlb1: TToolBar;
    mm1: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    btn2: TButton;
    il1: TImageList;
    btn3: TButton;
    img1: TImage;
    procedure Exit1Click(Sender: TObject);
    procedure btn2Click(Sender: TObject);
    procedure btn3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  c_toolbarbutton: function: itbbutton
cdecl;
  ibtn: itbbutton;
  hInst: HMODULE;
  ibmp: TBitmap;
  idx: Integer;

implementation

{$R *.dfm}

procedure TForm1.Exit1Click(Sender: TObject);
begin
close;
end;

//从DLL中接收一个TBBUTTON,然后在 Toolbar 上添加相应的按钮
procedure TForm1.btn2Click(Sender: TObject);
begin
  hInst := LoadLibrary(PChar('wxdll.dll'));
  if (hInst<>0) then
  begin
    c_toolbarbutton := GetProcAddress(hInst,'ToolbarButton');
    ibtn:= c_toolbarbutton();
    IntToStr(40);
    
//    ShowMessage(IntToStr(ibtn.iBitmap)+#13+inttostr(ibtn.idcommand));
//     ShowMessage('hey')
            //** This is not OK either.
    ibmp := TBitmap.Create;
    ibmp.LoadFromResourceID(hInst,ibtn.iBitmap);
    idx:=il1.Add(ibmp,nil);
    ibtn.iBitmap :=idx;
    SendMessage(tlb1.Handle, TB_ADDBUTTONS, 1, LongInt(@ibtn));
  end;

end;

procedure TForm1.btn3Click(Sender: TObject);
begin
if(Assigned(ibmp)) then
  ibmp.Free ;
if(hinst<>0) then
    FreeLibrary(hinst);
close;
end;

end.

DLL 中的相应函数的代码:

代码:
 extern "C" DLLFUNCTIONS_API TBBUTTON ToolbarButton()
    { 
        TBBUTTON tbb;
        ZeroMemory(&amp;tbb, sizeof(tbb))
   
        tbb.iBitmap = IDTOOL_BMP ;
        tbb.fsState = TBSTATE_ENABLED;
        tbb.fsStyle = TBSTYLE_BUTTON;
        tbb.idCommand = ID_DO_ASTYLE;
        return tbb;
     }

问题:   
  如果没有 TForm1.btn2Click() 中的 IntToStr() 一行,程序就会报错(非法访问),没有正确的结果。为什么?
 
.....................
 
补充:
1、这个问题在 Delphi 7 企业版, Delphi 6 企业版和个人版中均存在。其他的版本没有测试过。
2、关于DLL中的那段代码,肯定是没有问题的。这里 TBBUTTON 结构中的 iBitmap 被设置成了图片的资源ID,传到主程序中后经过处理才变成图片的索引号。
 
ShowMessage(IntToStr(ibtn.iBitmap)+#13+inttostr(ibtn.idcommand));
是这句吗?
iBitmap不是整型?
 
你把c_toolbarbutton改成静态引用会不会出错?
 
To myhby:
我指的是 IntToStr(40) 这一句。不过,用你说的那句也没有问题,因为也用了 IntToStr().

To hs-kill:
在这里,静态引用和动态引用似乎区别不大。
 
再次补充:
DLL 用 C++ 编写,编译器为 MingW-3.4.5 + WinAPI-3.11。不过,我想问题应该不在DLL上。
 
在找不到原因的时候.....把所有的可能都试试......也许是因为很白痴的原因 ,自己习惯了没注意到.....我总干这事...-_-
 
c_toolbarbutton: function: itbbutton
cdecl;
修改下这个声明, c_toolbarbutton: function: itbbutton
stdcall;
这个是pascal调用dll的正确声明方式,inttostr没有问题,是因为dll出错了,所以delphi没有办法定位到正确的错误位置
 
没有使用过MingW-3.4.5 + WinAPI-3.11编译器,不知这个编译器对函数局部变量的返回值是如何处理的。不过,如果是VC生成的Dll,局部变量的TBBUTTON变量在函数返回后,应该会变成无效的变量了(被释放了)。也就是你的程序应该是无法正确获取这个Dll返回的TBBUTTON变量的数值的。而你的程序也就可能会出现不可预知的错误。所以,你最好检查检查你的Dll函数有没有返回正确的值引起的。
 
可能的原因:hanpengshan_00 说的正确,应该是你是用 cdecl 导致的。
原因:因为 cdecl 需要调用者平衡堆栈,如果你的代码没有 inttostr 正好“平衡一把”执行下面的语句会导致堆栈错误程序崩溃。
解决方法是声明为 stdcall;
还有,你 c 代码中 TBBUTTON tbb
是在堆栈中开辟的?(不是很了解啊)
 
To xiaopei:
  正确来说,是返回后才变成无效。值已经返回了,变量无效也没所谓。而且经过检查,返回值是正确的。

To LSUPER:
  tbb 是像平常的变量声名那样做的,不知道算不算是用了堆栈?

  应该是调用方式的原因了。那么请问该如何操作才可以按 cdecl 正确调用这个函数?实在不想改成 stdcall,因为在 MingW 下比较难实现。在
extern "C" __declspec(dllexport) 的方式下,使用 __stdcall 总是会出现 ToolbarButton@0 这样的符号,无法消除 @0。

  请高人指点,如何正确实现 cdecl 方式调用? 或者如何正确使用 MingW (不使用 DEF 文件)编译出 stdcall 的 DLL ?
 
调用方式应该是编译器给你做的,如果确实会有问题还是先检查检查代码好
cdecl是C使用的调用方式
stdcall是WINDOWS的调用方式
pascal才是PASCAL的调用约定
而DELPHI的默认调用约定是register
也就是说,并不是DELPHI一定是用stdcall来声明DLL函数的调用约定的,用stdcall只是为了可以兼容其他开发语言而已
 
  我跟踪过调用过程,发现如果没有 IntToStr(40) 的话,调用前后 ESP 不变,即是说,不需要进行堆栈调整(可以理解,因为我没有传入参数。在 VC 中对 cdecl 的堆栈调整是 add ESP,4*n 或 ret 4*n,n为参数个数)。只是标志位 PF(奇偶位) 和 ZF(零标志位) 从 0 变成了 1。而使用了 IntToStr(40) 之后,PF 和 ZF 又变成了 0。难道说那个异常是因为 PF 和 ZF 而引起的?
 
呵,看起来有点意思,能将你的程序和Dll发给我看看吗?
dawnadmire@21cn.com
 
收到你的邮件了,调试了一下,已经知道你的代码哪里有问题了。
var
Form1: TForm1;
[blue]//问题主要出在你的Dll使用的是stdcall调用约定,而你的主程序却使用了cdecl调用约定,因Delphi对指令的优化作用,所以出现了添加IntToStr和不添加IntToStr而优化出不同的指令导致操作Dll返回值不同。你用cdecl调用约定时不添加IntToStr时返回值的指针会被Delphi修改+4,而你添加了IntToStr之后Delphi又正确取得返回值指针。[/blue]
[red] c_toolbarbutton: function: itbbutton
cdecl;[/red][blue]//这一句的cdecl改成stdcall调用约定就没问题了。[/blue]
ibtn: itbbutton;
hInst: HMODULE;
ibmp: TBitmap;
idx: Integer;
 
代码:
#ifndef _DLL_H_
#define _DLL_H_

#include <windows.h>
#include <cstring>
#include <string>
#include <commctrl.h>


using namespace std;

static HINSTANCE hInst;
HINSTANCE hInst2;
const int NUMBUTTONS = 7;

#define DLLFUNCTIONS_EXPORTS

#define ID_TOOLBAR1 10001
#define ID_TOOL1 10002
#define ID_DO_ASTYLE 10003
#define ID_FILE_NEW 2
#define ID_FILE_OPEN 3
#define ID_FILE_SAVEAS 4

#ifdef DLLFUNCTIONS_EXPORTS
#define DLLFUNCTIONS_API __declspec(dllexport)
#else
#define DLLFUNCTIONS_API __declspec(dllimport)
#endif

extern "C" DLLFUNCTIONS_API TBBUTTON ToolbarButton();

extern "C" DLLFUNCTIONS_API void DLLFunction(HWND);
extern "C" DLLFUNCTIONS_API void DLLHandle(HINSTANCE handle);
extern "C" DLLFUNCTIONS_API void TestReport(HWND handle);
extern "C" DLLFUNCTIONS_API void InitialiseReport(HWND handle);
extern "C" DLLFUNCTIONS_API bool ManagesUnit(void);
extern "C" DLLFUNCTIONS_API HWND Retrieve_Toolbars(HWND hWnd);
extern "C" DLLFUNCTIONS_API void CutExecute();
extern "C" DLLFUNCTIONS_API void CopyExecute();
extern "C" DLLFUNCTIONS_API void PasteExecute();
extern "C" DLLFUNCTIONS_API void Terminate();
extern "C" DLLFUNCTIONS_API void DisableDesignerControls();
extern "C" DLLFUNCTIONS_API void OnToolbarEvent(WORD wParam)
    
extern "C" DLLFUNCTIONS_API void SetPnlBrowsersVisible(bool b);
extern "C" DLLFUNCTIONS_API void SetBoolInspectorDataClear(bool b);
extern "C" DLLFUNCTIONS_API void SetDisablePropertyBuilding(bool b);
extern "C" DLLFUNCTIONS_API bool IsCurrentPageDesigner();
extern "C" DLLFUNCTIONS_API bool SaveFileAndCloseEditor(char * s, bool b);
extern "C" DLLFUNCTIONS_API void MyOpenFile(char * s);
extern "C" DLLFUNCTIONS_API void OpenUnit(char * s);
extern "C" DLLFUNCTIONS_API bool IsForm(char * s);
extern "C" DLLFUNCTIONS_API bool SaveFile(char * s);
extern "C" DLLFUNCTIONS_API void Activate(char * FileName);
extern "C" DLLFUNCTIONS_API bool IsSource(char * FileName);
extern "C" DLLFUNCTIONS_API char *  GetDefaultText(char * FileName);
extern "C" DLLFUNCTIONS_API char * GetFilter(char * editorName);
extern "C" DLLFUNCTIONS_API char * Get_EXT(char * editorName);
extern "C" DLLFUNCTIONS_API void GenerateXPM(char * s);
extern "C" DLLFUNCTIONS_API void NewProject(char * s);
extern "C" DLLFUNCTIONS_API void MainPageChanged(char * editorName);
extern "C" DLLFUNCTIONS_API void Reload(char * FileName);
extern "C" DLLFUNCTIONS_API void ReloadFromFile(char * FileName, char * fileToReloadFrom);
extern "C" DLLFUNCTIONS_API void TerminateEditor(char * FileName);
extern "C" DLLFUNCTIONS_API HWND * Retrieve_Form_Items();
extern "C" DLLFUNCTIONS_API void Init(char * FileName);
extern "C" DLLFUNCTIONS_API void MyShowMsg();

#endif /* _DLL_H_ */

请问,以上的 C++ 声明是 cdecl 还是 stdcall ? MingW 编译器里加上了 "--add-stdcall-alias" 选项。
 
DLLFUNCTIONS_API看这个调用约定声明可以90%确定是stdcall调用约定,因为90%的Windows API函数使用的都是stdcall调用约定。
 
[:O]...原来你的 C 语言水平...
那个 DLLFUNCTIONS_API 是我自己定义的,实际上每个函数展开后都是
extern "C" __declspec(dllexport) TBBUTTON ToolbarButton();
的形式,一般都认为这样的声明是 C/C++ 的默认调用方式,即 cdecl。虽然在编译器里有"--add-stdcall-alias"选项,但应该不会改变调用方式。
对于 cdecl 方式的函数调用,似乎 Delphi 会自动给我们清理堆栈。因为之前我使用的 cdecl 方式调用一直都没有问题。不过,关于指令优化的说法,倒很有意思。
 
#ifdef DLLFUNCTIONS_EXPORTS
#define DLLFUNCTIONS_API __declspec(dllexport)
#else
#define DLLFUNCTIONS_API __declspec(dllimport)
#endif
没看到你的这一句,我以为你的DLLFUNCTIONS_API是你的MingW 编译器定义的。[:I]
 

Similar threads

后退
顶部