I have a strage effekt in Delphi XE8 and would like to know if anyone can reproduce this and has an explanation for it!
I'm calling the windows api function GetClipboardFormatName with a local variable as a buffer to receive the clipboard format names.
When this is done from a TButton Click Handler it works as expected, when it's done from a TToolButton Click Handler then it does not work and getlasterror returns 998 / ERROR_NOACCESS / Invalid access to memory location.
This did not happen under Delphi 7!
I'm not looking for a workaround, i just would like to know what is going on here. Am I doing something wrong? Is there a problem with our IDE installation (2 Developers)? Is it a BUG in XE8?
Here is a demo unit:
DFM File
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 File
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.
Your code is broken. The error is here:
GetClipboardFormatName(Format, fmtname, SizeOf(fmtname))
The documentation of GetClipboardFormatName
describes the cchMaxCount
parameter like this:
The maximum length, in characters, of the string to be copied to the buffer. If the name exceeds this limit, it is truncated.
You are passing the length in bytes rather than the length in characters. In Delphi 7 Char
is an alias for AnsiChar
, an 8 bit type, but in Unicode Delphi, 2009 and later, Char
is an alias for WideChar
, a 16 bit type.
As a consequence, under XE8 which is a Unicode Delphi, you are claiming that the buffer is twice as long as it actually is.
You must replace SizeOf(fmtname)
with Length(fmtname)
.
I should also mention that the change from 8 bit ANSI to 16 bit UTF-16 Unicode in Delphi 2009 should always be your first suspect when you find behaviour difference between ANSI Delphi and Unicode Delphi. In your question you wondered whether this was a Delphi bug, or an installation issue, but the first thought into your head should have been an issue with text encoding. With the reported symptoms that is going to be the culprit almost every time.
As an aside, it makes no real sense for GetRegisteredClipBoardFormatName
to be an instance method of a GUI form. It doesn't refer to Self
and it has nothing at all to do with your form class. This should be a low-level helper method that is not part of a GUI form type.