Delphi XE8 / 从 TToolbutton Click windows api 函数 GetClipboardFormatName 调用时对内存位置的访问无效
Delphi XE8 / Invalid access to memory location when calling windows api function GetClipboardFormatName from TToolbutton Click
我在 Delphi XE8 中有一个 strage effekt,想知道是否有人可以重现它并给出解释!
我正在调用 windows api 函数 GetClipboardFormatName 并使用局部变量作为接收剪贴板格式名称的缓冲区。
当这是从 TButton Click Handler 完成时,它按预期工作,当它从 TToolButton Click Handler 完成时,它不起作用并且 getlasterror returns 998 / ERROR_NOACCESS / 无效访问内存位置。
这在 Delphi 7!
下没有发生
我不是在寻找解决方法,我只是想知道这里发生了什么。难道我做错了什么?我们的 IDE 安装(2 个开发人员)有问题吗?是XE8的BUG吗?
这是一个演示单元:
DFM 文件
object Form3: TForm3
Left = 0
Top = 0
Caption = 'Form3'
ClientHeight = 311
ClientWidth = 643
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 643
Height = 41
Align = alTop
Caption = 'Panel1'
TabOrder = 0
object Button1: TButton
Left = 16
Top = 10
Width = 148
Height = 25
Caption = 'Standard TButton ==> OK'
TabOrder = 0
OnClick = Button1Click
end
end
object Memo1: TMemo
Left = 0
Top = 70
Width = 643
Height = 241
Align = alClient
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object ToolBar1: TToolBar
Left = 0
Top = 41
Width = 643
Height = 29
ButtonHeight = 21
ButtonWidth = 289
Caption = 'ToolBar1'
ShowCaptions = True
TabOrder = 2
object ToolButton1: TToolButton
Left = 0
Top = 0
Caption = 'Standard TToolBar / TToolButton ==> ERROR_NOACCESS'
ImageIndex = 0
OnClick = ToolButton1Click
end
end
end
PAS 文件
unit Unit3;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.StdCtrls,
Vcl.ExtCtrls,
Vcl.ComCtrls,
Vcl.ToolWin;
type
TForm3 = class(TForm)
Panel1: TPanel;
Memo1: TMemo;
Button1: TButton;
ToolBar1: TToolBar;
ToolButton1: TToolButton;
procedure Button1Click(Sender: TObject);
procedure ToolButton1Click(Sender: TObject);
private
procedure say(s: string);
procedure ListFormats;
function GetRegisteredClipBoardFormatName(Format: word): string;
function IsPredefinedFormat(format: word): boolean;
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
uses
clipbrd;
const arPredefinedFormats: array[0..27] of word = (
CF_TEXT,
CF_BITMAP,
CF_METAFILEPICT,
CF_SYLK,
CF_DIF,
CF_TIFF,
CF_OEMTEXT,
CF_DIB,
CF_PALETTE,
CF_PENDATA,
CF_RIFF,
CF_WAVE,
CF_UNICODETEXT,
CF_ENHMETAFILE,
CF_HDROP,
CF_LOCALE,
CF_MAX,
CF_DIBV5,
CF_MAX_XP,
CF_OWNERDISPLAY,
CF_DSPTEXT,
CF_DSPBITMAP,
CF_DSPMETAFILEPICT,
CF_DSPENHMETAFILE,
CF_PRIVATEFIRST,
CF_PRIVATELAST,
CF_GDIOBJFIRST,
CF_GDIOBJLAST);
{$R *.dfm}
procedure TForm3.ToolButton1Click(Sender: TObject);
begin
ListFormats;
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
ListFormats;
end;
procedure TForm3.ListFormats;
var
index: integer;
begin
for index := 0 to clipboard.formatcount - 1 do
begin
if not IsPredefinedFormat(clipboard.formats[index]) then
begin
say('Format: ' + inttostr(clipboard.formats[index]));
say('Name: ' + GetRegisteredClipBoardFormatName(clipboard.formats[index]));
end;
end;
end;
procedure TForm3.say(s: string);
begin
memo1.lines.add(s);
end;
function TForm3.IsPredefinedFormat(format: word): boolean;
var
index: integer;
begin
for index := low(arPredefinedFormats) to high(arPredefinedFormats) do
begin
if arPredefinedFormats[index] = format then
begin
result := true;
exit;
end;
end;
result := false;
end;
//------------------------------------------------------------------------------------------
(*
Strange effekt in function GetClipboardFormatName
when compiled with Delphi XE8 und Win 7.
If this function is called from tbutton click, then everything ist ok!
If this function is called from ttoolbutton click (and perhaps other controls...?)
then the call to GetClipboardFormatName fails with getlasterror = 998
which means
ERROR_NOACCESS
998 (0x3E6)
Invalid access to memory location.
which indicates that there is a problem with the local variable fmtname.
Some Facts...
* effekt happens under delphi xe8
* effekt did not happen under delphi 7
* it doesn't matter if I zero the memory of fmtname before using it.
* it doesn't matter if I call OpenClipboard / CloseClipboard
* if I use a local variable, then it does not work with ttoolbutton. The memorylocation of the local variable is
slightly different from the case when it's called from tbutton.
* if I use a global variable instead of a local variable, then it works with tbutton and ttoolbutton
since it's the same memorylocation for both calls
I'm NOT LOOKING FOR A WORKAROUND, I just would like to know if anybody can
reproduce the behaviour and has an explanation as to why this is happening.
Is there something wrong with using local variables for windows api calls in general?
*)
//------------------------------------------------------------------------------------------
function TForm3.GetRegisteredClipBoardFormatName(Format: word): string;
var
fmtname: array[0..1024] of Char;
begin
if OpenClipboard(self.handle) then //<--- does not make a difference if called or not
begin
if GetClipboardFormatName(Format, fmtname, SizeOf(fmtname)) <> 0 then
begin
result := fmtname;
end else
begin
result := 'Unknown Clipboard Format / GetLastError= ' + inttostr(getlasterror);
end;
CloseClipboard;
end else say('OpenClipboard failed');
end;
//------------------------------------------------------------------------------------------
end.
您的代码已损坏。错误在这里:
GetClipboardFormatName(Format, fmtname, SizeOf(fmtname))
GetClipboardFormatName
的文档这样描述 cchMaxCount
参数:
The maximum length, in characters, of the string to be copied to the buffer. If the name exceeds this limit, it is truncated.
您传递的是字节长度而不是字符长度。在 Delphi 7 Char
是 AnsiChar
的别名,一个 8 位类型,但在 Unicode Delphi, 2009 及之后, Char
是 WideChar
,一个16位的类型。
因此,在作为 Unicode Delphi 的 XE8 下,您声称缓冲区的长度是实际长度的两倍。
您必须将 SizeOf(fmtname)
替换为 Length(fmtname)
。
我还应该提到,当您发现 ANSI Delphi 和 Unicode 之间的行为差异时,Delphi 2009 中从 8 位 ANSI 到 16 位 UTF-16 Unicode 的变化应该始终是您的第一个怀疑对象Delphi。在您的问题中,您想知道这是 Delphi 错误还是安装问题,但您首先想到的应该是文本编码问题。几乎每一次报告的症状都是罪魁祸首。
顺便说一句,GetRegisteredClipBoardFormatName
成为 GUI 窗体的实例方法没有任何实际意义。它不涉及 Self
,并且与您的表单 class 完全无关。这应该是一个 low-level 辅助方法,它不是 GUI 表单类型的一部分。
我在 Delphi XE8 中有一个 strage effekt,想知道是否有人可以重现它并给出解释!
我正在调用 windows api 函数 GetClipboardFormatName 并使用局部变量作为接收剪贴板格式名称的缓冲区。
当这是从 TButton Click Handler 完成时,它按预期工作,当它从 TToolButton Click Handler 完成时,它不起作用并且 getlasterror returns 998 / ERROR_NOACCESS / 无效访问内存位置。
这在 Delphi 7!
下没有发生我不是在寻找解决方法,我只是想知道这里发生了什么。难道我做错了什么?我们的 IDE 安装(2 个开发人员)有问题吗?是XE8的BUG吗?
这是一个演示单元:
DFM 文件
object Form3: TForm3
Left = 0
Top = 0
Caption = 'Form3'
ClientHeight = 311
ClientWidth = 643
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 643
Height = 41
Align = alTop
Caption = 'Panel1'
TabOrder = 0
object Button1: TButton
Left = 16
Top = 10
Width = 148
Height = 25
Caption = 'Standard TButton ==> OK'
TabOrder = 0
OnClick = Button1Click
end
end
object Memo1: TMemo
Left = 0
Top = 70
Width = 643
Height = 241
Align = alClient
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object ToolBar1: TToolBar
Left = 0
Top = 41
Width = 643
Height = 29
ButtonHeight = 21
ButtonWidth = 289
Caption = 'ToolBar1'
ShowCaptions = True
TabOrder = 2
object ToolButton1: TToolButton
Left = 0
Top = 0
Caption = 'Standard TToolBar / TToolButton ==> ERROR_NOACCESS'
ImageIndex = 0
OnClick = ToolButton1Click
end
end
end
PAS 文件
unit Unit3;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.StdCtrls,
Vcl.ExtCtrls,
Vcl.ComCtrls,
Vcl.ToolWin;
type
TForm3 = class(TForm)
Panel1: TPanel;
Memo1: TMemo;
Button1: TButton;
ToolBar1: TToolBar;
ToolButton1: TToolButton;
procedure Button1Click(Sender: TObject);
procedure ToolButton1Click(Sender: TObject);
private
procedure say(s: string);
procedure ListFormats;
function GetRegisteredClipBoardFormatName(Format: word): string;
function IsPredefinedFormat(format: word): boolean;
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
uses
clipbrd;
const arPredefinedFormats: array[0..27] of word = (
CF_TEXT,
CF_BITMAP,
CF_METAFILEPICT,
CF_SYLK,
CF_DIF,
CF_TIFF,
CF_OEMTEXT,
CF_DIB,
CF_PALETTE,
CF_PENDATA,
CF_RIFF,
CF_WAVE,
CF_UNICODETEXT,
CF_ENHMETAFILE,
CF_HDROP,
CF_LOCALE,
CF_MAX,
CF_DIBV5,
CF_MAX_XP,
CF_OWNERDISPLAY,
CF_DSPTEXT,
CF_DSPBITMAP,
CF_DSPMETAFILEPICT,
CF_DSPENHMETAFILE,
CF_PRIVATEFIRST,
CF_PRIVATELAST,
CF_GDIOBJFIRST,
CF_GDIOBJLAST);
{$R *.dfm}
procedure TForm3.ToolButton1Click(Sender: TObject);
begin
ListFormats;
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
ListFormats;
end;
procedure TForm3.ListFormats;
var
index: integer;
begin
for index := 0 to clipboard.formatcount - 1 do
begin
if not IsPredefinedFormat(clipboard.formats[index]) then
begin
say('Format: ' + inttostr(clipboard.formats[index]));
say('Name: ' + GetRegisteredClipBoardFormatName(clipboard.formats[index]));
end;
end;
end;
procedure TForm3.say(s: string);
begin
memo1.lines.add(s);
end;
function TForm3.IsPredefinedFormat(format: word): boolean;
var
index: integer;
begin
for index := low(arPredefinedFormats) to high(arPredefinedFormats) do
begin
if arPredefinedFormats[index] = format then
begin
result := true;
exit;
end;
end;
result := false;
end;
//------------------------------------------------------------------------------------------
(*
Strange effekt in function GetClipboardFormatName
when compiled with Delphi XE8 und Win 7.
If this function is called from tbutton click, then everything ist ok!
If this function is called from ttoolbutton click (and perhaps other controls...?)
then the call to GetClipboardFormatName fails with getlasterror = 998
which means
ERROR_NOACCESS
998 (0x3E6)
Invalid access to memory location.
which indicates that there is a problem with the local variable fmtname.
Some Facts...
* effekt happens under delphi xe8
* effekt did not happen under delphi 7
* it doesn't matter if I zero the memory of fmtname before using it.
* it doesn't matter if I call OpenClipboard / CloseClipboard
* if I use a local variable, then it does not work with ttoolbutton. The memorylocation of the local variable is
slightly different from the case when it's called from tbutton.
* if I use a global variable instead of a local variable, then it works with tbutton and ttoolbutton
since it's the same memorylocation for both calls
I'm NOT LOOKING FOR A WORKAROUND, I just would like to know if anybody can
reproduce the behaviour and has an explanation as to why this is happening.
Is there something wrong with using local variables for windows api calls in general?
*)
//------------------------------------------------------------------------------------------
function TForm3.GetRegisteredClipBoardFormatName(Format: word): string;
var
fmtname: array[0..1024] of Char;
begin
if OpenClipboard(self.handle) then //<--- does not make a difference if called or not
begin
if GetClipboardFormatName(Format, fmtname, SizeOf(fmtname)) <> 0 then
begin
result := fmtname;
end else
begin
result := 'Unknown Clipboard Format / GetLastError= ' + inttostr(getlasterror);
end;
CloseClipboard;
end else say('OpenClipboard failed');
end;
//------------------------------------------------------------------------------------------
end.
您的代码已损坏。错误在这里:
GetClipboardFormatName(Format, fmtname, SizeOf(fmtname))
GetClipboardFormatName
的文档这样描述 cchMaxCount
参数:
The maximum length, in characters, of the string to be copied to the buffer. If the name exceeds this limit, it is truncated.
您传递的是字节长度而不是字符长度。在 Delphi 7 Char
是 AnsiChar
的别名,一个 8 位类型,但在 Unicode Delphi, 2009 及之后, Char
是 WideChar
,一个16位的类型。
因此,在作为 Unicode Delphi 的 XE8 下,您声称缓冲区的长度是实际长度的两倍。
您必须将 SizeOf(fmtname)
替换为 Length(fmtname)
。
我还应该提到,当您发现 ANSI Delphi 和 Unicode 之间的行为差异时,Delphi 2009 中从 8 位 ANSI 到 16 位 UTF-16 Unicode 的变化应该始终是您的第一个怀疑对象Delphi。在您的问题中,您想知道这是 Delphi 错误还是安装问题,但您首先想到的应该是文本编码问题。几乎每一次报告的症状都是罪魁祸首。
顺便说一句,GetRegisteredClipBoardFormatName
成为 GUI 窗体的实例方法没有任何实际意义。它不涉及 Self
,并且与您的表单 class 完全无关。这应该是一个 low-level 辅助方法,它不是 GUI 表单类型的一部分。