Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Problem mit ShellMessageBox [gelöst] (https://www.delphipraxis.net/18653-problem-mit-shellmessagebox-%5Bgeloest%5D.html)

MathiasSimmack 22. Mär 2004 20:46


Problem mit ShellMessageBox [gelöst]
 
Ich habe mal wieder eine kleine Spielerei inkl. Frage für die Interessierten unter euch. Ein Beitrag im DF hat mich wieder daran erinnert, dass ich auf der Platte noch eine HTML-Seite mit ein paar so genannten undokumentierten Funktionen von Windows habe. Darunter auch die "ShellMessageBox", s. hier:
Delphi-Quellcode:
type
  TShellMessageBoxA       = function(Module: THandle; Owner: HWND;
    Text: pchar; Caption: pchar; Style: UINT;
    Parameters: array of pointer): integer; cdecl;
  TShellMessageBoxW       = function(Module: THandle; Owner: HWND;
    Text: PWideChar; Caption: PWideChar; Style: UINT;
    Parameters: array of pointer): integer; cdecl;

// Die "cdecl"-Deklaration ist kein Fehler.


var
  ShellMessageBoxA        : TShellMessageBoxA;
  ShellMessageBoxW        : TShellMessageBoxW;

  dll                     : dword = 0;
begin
  dll := LoadLibrary('shell32.dll');

  if(dll <> 0) then
  begin
    @ShellMessageBoxA := GetProcAddress(dll,pointer(183));
    @ShellMessageBoxW := GetProcAddress(dll,pointer(182));

    if(@ShellMessageBoxA = nil) or
      (@ShellMessageBoxW = nil) then
    begin
      MessageBox(0,'Funktionen wohl nicht vorhanden',nil,0);
      FreeLibrary(dll);
      exit;
    end;


    { ... }

    FreeLibrary(dll);
  end;
end.
Das Interessante an der Box ist nun, das man gleich Stringressourcen angeben kann, ohne diese vorher laden zu müssen, etwa
Delphi-Quellcode:
ShellMessageBoxA(hInstance,self.Handle,
  MAKEINTRESOURCE(1000), // <-- Verweis auf Stringressource
  nil,MB_OK,[nil]);
(Klappt übrigens beim Titel auch!) Oder man kann mit der Escape-Sequenz %n (s. auch PSDK -> MSDN-Library durchsuchenFormatMessage @Daniel: ;)) einen Zeilenumbruch erzeugen:
Delphi-Quellcode:
ShellMessageBoxA(hInstance,self.Handle,
  'Hallo, Echo!%nHallo, Otto! :o)',
  nil,0,[nil]);
Aber eigentlich interessiert mich der letzte Parameter (das Pointer-Array). Wie bei "FormatMessage" soll man nämlich (ähnlich wie bei "wvsprintf" oder "Format") Strings ersetzen können. Dazu soll man eigentlich eine Sequenz wie #n nutzen, wobei das N eine Zahl von 1 bis 99 sein kann. Nur irgendwie bekomme ich keinen Fuß in die Tür (mal so gesprochen). Es wird alles angezeigt, nur nicht das was ich will:
Delphi-Quellcode:
szString := 'Hallo, Welt!';

ShellMessageBoxA(hInstance,self.Handle,
  '%1',nil,0,[pointer(szString)]);
Ich mache irgendwas falsch.
Aber was?

:gruebel:

NicoDE 22. Mär 2004 23:40

Re: Problem mit ShellMessageBox
 
Zitat:

Zitat von MathiasSimmack
Ich mache irgendwas falsch. Aber was?

Liegt nur daran, dass es nicht gerade einfach ist, mit Delphi Language variante Parameter (...) umzusetzen.

Delphi-Quellcode:
program ShMsgBox;

uses
  Windows, ShellAPI;

type
{$IFDEF UNICODE}
  LPCTSTR = PWideChar;
{$ELSE}
  LPCTSTR = PAnsiChar;
{$ENDIF}

////////////////////////////////////////////////////////////////////////////////
//
//  ShellMessageBox
//
//  ( [url]http://search.microsoft.com/?View=msdn&qu=ShellMessageBox[/url] )
//
//  Notes:
//    Maximum used ressource string length is 80 chars
//    ShellMessageBox adds MB_SETFOREGROUND to fuStyle
//

function ShellMessageBox(Inst: HINST; Wnd: HWND; Msg, Title: LPCTSTR;
  Style: UINT; const Arguments: array of LPCTSTR): Integer; stdcall;
type
  TFNShellMessageBoxA = function(hInst: HINST; hWnd: HWND; pszMsg: LPCSTR;
    pszTitle: LPCSTR; fuStyle: UINT {...}): Integer; cdecl;
  TFNShellMessageBoxW = function(hInst: HINST; hWnd: HWND; pszMsg: LPCWSTR;
    pszTitle: LPCWSTR; fuStyle: UINT {...}): Integer; cdecl;
  TFNShellMessageBox = function(hInst: HINST; hWnd: HWND; pszMsg: LPCTSTR;
    pszTitle: LPCTSTR; fuStyle: UINT {...}): Integer; cdecl;
const
  ShellMessageBoxProcNameA = 'ShellMessageBoxA';
  ShellMessageBoxProcNameW = 'ShellMessageBoxW';
  ShellMessageBoxProcOrdA = PAnsiChar(183);
  ShellMessageBoxProcOrdW = PAnsiChar(182);
{$IFDEF UNICODE}
  ShellMessageBoxProcName = ShellMessageBoxProcNameW;
  ShellMessageBoxProcOrd = ShellMessageBoxProcOrdW;
{$ELSE}
  ShellMessageBoxProcName = ShellMessageBoxProcNameA;
  ShellMessageBoxProcOrd = ShellMessageBoxProcOrdA;
{$ENDIF}
var
  ShellModule: HMODULE;
  FNShellMessageBox: TFNShellMessageBox;
begin
  Result := 0;
  ShellModule := LoadLibrary(shell32);
  if (ShellModule <> 0) then
  try
    FNShellMessageBox := TFNShellMessageBox(
      GetProcAddress(ShellModule, ShellMessageBoxProcName));
    if not Assigned(FNShellMessageBox) then
      FNShellMessageBox := TFNShellMessageBox(
        GetProcAddress(ShellModule, ShellMessageBoxProcOrd));
    if Assigned(FNShellMessageBox) then
    begin
      asm
              push   ebx                 // save EBX
              mov    ecx,[Arguments-$04] // High(Arguments)
              mov    ebx,ecx             // offset to last argument
              shl    ebx,$02              // (High * SizeOf)
              inc    ecx                 // Length(Arguments)
              jz     @@call              // no arguments?
              mov    edx,[Arguments]     // first argument
              add    edx,ebx             // last argument
      @@loop: push   dword ptr [edx]     // push argument
              sub    edx,$04              // next argument
              loop   @@loop              // until ecx = 0
      @@call: push   dword ptr [Style]   // push params
              push   dword ptr [Title]   // ...
              push   dword ptr [Msg]
              push   dword ptr [Wnd]
              push   dword ptr [Inst]
              call   FNShellMessageBox   // call function
              add    esp,$18              // cleanup stack (params)
              add    esp,ebx             // cleanup stack (arguments)
              mov    [Result],eax        // return value into Result
              pop    ebx                 // restore EBX
      end;
    end;
  finally
    FreeLibrary(ShellModule);
  end;
end;

////////////////////////////////////////////////////////////////////////////////
// Sample

begin
  ShellMessageBox(0, 0,
    'function %2(%4: %3; %6: %5; %8, %10: %7; %12: %11 {...}): %1;',
    'ShellMessageBox', MB_OK or MB_DEFBUTTON1 or MB_ICONINFORMATION,
    ['Integer', 'ShellMessageBox', 'HINST', 'hInst', 'HWND', 'hWnd',
    'LPCTSTR', 'pszMsg', 'LPCTSTR', 'pszTitle', 'UINT', 'fuStyle']);
end.
Gruss Nico

[edit] BASM Optimierungen [/edit]
[edit] Anpassungen D2-D6 [/edit]
[edit] Unicode-Anpassung [/edit]

MathiasSimmack 23. Mär 2004 08:49

Re: Problem mit ShellMessageBox
 
Hallo Nico.
Dankeschön kann ich da nur sagen.

Zitat:

Zitat von NicoDE
Delphi-Quellcode:
////////////////////////////////////////////////////////////////////////////////
//
//  ShellMessageBox
//
//  ( [url]http://search.microsoft.com/?View=msdn&qu=ShellMessageBox[/url] )

Ich wusste gar nicht, dass man mittlerweile überhaupt was dazu findet. Aber das ist bei einigen anderen Funktionen auch so. Offiziell unterstützt werden sie, laut PSDK, erst ab Windows 2000 (der "PickIconDlg" ist auch so ein Fall), aber tatsächlich vorhanden und nutzbar sind sie mit Ordinalwert auch schon vorher unter 98 meinetwegen. Na ja, Microsoft eben ... ;)

Als kleine Frage, just for the sake of completeness, :oops:, was genau passiert im Assemberteil?
Zitat:

Delphi-Quellcode:
      asm
              push   ebx
              mov    ecx, [Arguments-04h] // High(Arguments)
              mov    ebx, ecx
              shl    ebx, 02h
              inc    ecx
              jz     @@call
              mov    edx, [Arguments]
              add    edx, ebx
      @@loop: push   dword ptr [edx]
              sub    edx, 04h
              loop   @@loop
      @@call: push   dword ptr [Style]
              push   dword ptr [Title]
              push   dword ptr [Msg]
              push   dword ptr [Wnd]
              push   dword ptr [Inst]
              call   FNShellMessageBox
              add    esp, 18h
              add    esp, ebx
              mov    [Result], eax
              pop    ebx
      end;

Also im Prinzip weiß ich es: du ordnest Titel, Caption, Style usw. zu (bzw. legst sie auf dem Stack? ab) und rufst dann die Dialogbox auf. Aber warum so? Gab´s nicht eine Lösung in ... äh ... normaler Delphi-Sprache, die auch ich verstehe? :stupid:

Gruß.


PS: Ich setze trotzdem mal den Erledigt-Haken, denn das Grundproblem ist ja gelöst. Alles, was jetzt noch an Fragen und Antworten kommt, wäre nur Bonus. ;)

NicoDE 23. Mär 2004 09:25

Re: Problem mit ShellMessageBox
 
Zitat:

Zitat von MathiasSimmack
Dankeschön kann ich da nur sagen.

Keine Ursache :)

Zitat:

Zitat von MathiasSimmack
was genau passiert im Assemberteil?

Code:
[color=blue]       { EBX muss gesichert werden (wird auf dem Stack abgelegt) }[/color]
[color=gray]       push   ebx[/color]
[color=blue]       { Bei 'array of' befindet sich vor dem ersten Element der Wert von High }[/color]
[color=gray]       mov    ecx,[Arguments-$04][/color]
[color=blue]       { Hier wird der Offset zum letzten Element zusammengebaut (High*4) }[/color]
[color=gray]       mov    ebx,ecx[/color]
[color=gray]       shl    ebx,$02[/color]
[color=blue]       { Aus dem High(Arguments) wird nun Length(Arguments) (+1) }[/color]
[color=gray]       inc    ecx[/color]
[color=blue]       { Prüfen ob es gar keine zusätzlichen Argumente gibt (ecx ist 0) }[/color]
[color=blue]       { Wenn dann geht es sofort zur Übergabe der benötigten Parameter }[/color]
[color=gray]       jz     @@call[/color]
[color=blue]       { Erste Element holen und dann zum letzten Element wechseln }[/color]
[color=blue]       { cdecl erwartet die Parameter in 'umgekehrter' Reihenfolge }[/color]
[color=gray]       mov    edx,[Arguments][/color]
[color=gray]       add    edx,ebx[/color]
[color=blue]       { Aktuelles Element auf dem Stack ablegen }[/color]
[color=gray]@@loop: push   dword ptr [edx][/color]
[color=blue]       { Zum nächsten Element wechseln (rückwärts) }[/color]
[color=gray]       sub    edx,$04[/color]
[color=blue]       { ECX um 1 verringern und auf 0 prüfen }[/color]
[color=blue]       { solange ECX <> 0 weiter mit Schleife }[/color]
[color=gray]       loop   @@loop[/color]
[color=blue]       { Nun die benötigten Parameter auf dem Stack ablegen }[/color]
[color=gray]@@call: push   dword ptr [Style][/color]
[color=gray]       push   dword ptr [Title][/color]
[color=gray]       push   dword ptr [Msg][/color]
[color=gray]       push   dword ptr [Wnd][/color]
[color=gray]       push   dword ptr [Inst][/color]
[color=blue]       { Funktion aufrufen (Ergebnis ist dann in EAX) }[/color]
[color=gray]       call   FNShellMessageBox[/color]
[color=blue]       { Stack mit benötigten Parametern abräumen }[/color]
[color=blue]       { ($14, aber ebx basiert nicht auf Length, sondern auf High) }[/color]
[color=gray]       add    esp,$18[/color]
[color=blue]       { Stack mit zusätzlichen Argumenten abräumen }[/color]
[color=gray]       add    esp,ebx[/color]
[color=blue]       { Ergebnis in Result speichern }[/color]
[color=gray]       mov    [Result],eax[/color]
[color=blue]       { EBX wiederhergestellen (vom Stack holen) }[/color]
[color=gray]       pop    ebx[/color]
Zitat:

Zitat von MathiasSimmack
Gab´s nicht eine Lösung in ... äh ... normaler Delphi-Sprache

Leider nicht, ...
Delphi-Quellcode:
asm
        push   ...
end;
ShellMessageBox(...)
asm
        add    esp,...
end;
...wäre viel zu unsicher (da es von der Annahme ausgeht, dass der Compiler keine Anweisung zischen die asm-Bläcke und den Aufruf der Funktion packt).


Gruss Nico

ps: kurz vor Deiner Antwort hab ich oben noch ne Kleinigkeit geändert :)
[edit] elende Tippfehler, nicht mein Tag heute ... [/edit]

MathiasSimmack 23. Mär 2004 13:20

Re: Problem mit ShellMessageBox
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von NicoDE
ps: kurz vor Deiner Antwort hab ich oben noch ne Kleinigkeit geändert :)

´ne Kleinigkeit, wie?
Zitat:

Zuletzt bearbeitet von NicoDE am 23.03.2004, 08:47, insgesamt 22-mal bearbeitet.
;) Na ja.


@all: Ich hänge mal das Ergebnis ran. Ein kleines Programm zum Rumspielen mit noch ein paar anderen Funktionen. Inkl. Hilfedatei (undok.chm) und gepatchter "base.chm" für die CHM-Version der Win32-API-Tutorials (@Luckie: :stupid:)), wodurch der Inhalt der "undok.chm" unter den Systemfunktionen erscheint. Die Erklärung für den Assemblerteil habe ich 1:1 von Nico übernommen. Das einzige, das ich dazu beigetragen habe, ist, dass man´s ein- und ausblenden kann ... :mrgreen:

Ach ja, das Programm verursacht absichtlich zwei Compilerfehler, damit ihr lest was an der Stelle im Quellcode steht.


Nochmals Danke, @Nico.
Grüße.

NicoDE 23. Mär 2004 13:28

Re: Problem mit ShellMessageBox
 
Zitat:

Zitat von MathiasSimmack
Na ja.

Alles wird gut :)
Edit 23 erweiterte es - dank Deines Hinweises - um Kommentare im BASM-Code...
(habe unbewußt versucht einen neuen Tippfehler/Edit-Record aufzugestellen *g*)

MathiasSimmack 23. Mär 2004 16:33

Re: Problem mit ShellMessageBox [gelöst]
 
Liste der Anhänge anzeigen (Anzahl: 1)
Auf Einwand bzw. Hinweis von Nico wollte ich noch was zum Anhang sagen:

Das Beispielprogramm ist ein Beispiel wie man lieber nicht programmieren sollte. Das liegt an dem Mischmasch von Ansi- und Unicode. Die einzige Sache, die richtig funktioniert, ist Nicos "ShellMessageBox". Aber das liegt daran, dass diese Funktion sowohl als Ansi- als auch als Unicode-Version exportiert wird.

Beim "Anderes Symbol"-Dialog (PickIconDlg) sieht das schon anders aus. Den Dialog gibt es auch unter 9x, dort allerdings undokumentiert und nur als Ansi-Version. Den PSDK-Deklarationen zufolge benutzt der Dialog ab und unter Windows 2000 aber Widestrings (also Unicode, salopp gesagt). Wenn ich unter XP die AnsiChar-Version nutze, dann erhalte ich die Fehlermeldung, weil der Dateiname nicht erkannt wird (s. Bild im Anhang). Das gleiche mit umgekehrten Vorzeichen passiert unter 98, wenn ich dort die Unicode-Version ausprobiere.


Wer die Funktionen also tatsächlich in einem Programm verwenden will, der sollte den Weg gehen, den Nico vorgemacht hat, bzw. der sollte beide Varianten laden und OS-abhängig nutzen. Betrachtet die Demo von mir bitte daher als das was sie ist: als Programm, das unter bestimmten Bedingungen funktioniert aber keinesfalls als produktives Beispiel dienen sollte. ;)

Nico wird´s mir sicher nicht übelnehmen, wenn ich aus seiner PM zitiere:
Zitat:

Es ist für den Standard-User eher besser, den UNICODE-Schalter zu vermeiden, da er sonst die ANSI-RTL mit seinem Unicode-Code mischt (und zudem fälschlicherweise der Meinung sein könnte, dass es noch auf Win9x läuft...).
Gruß.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:31 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz