Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   WMI Speicherfresser wie beseitiegen (https://www.delphipraxis.net/143836-wmi-speicherfresser-wie-beseitiegen.html)

Jackie1983 24. Nov 2009 13:01


WMI Speicherfresser wie beseitiegen
 
Servus,

habe mir aus dem Web ne Methode gesucht um per WMI auf Daten zu zugreifen.
Delphi-Quellcode:
var
  Server: string;
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
begin
  Server := '127.0.0.1';
  try
    log.Lines.BeginUpdate;
    Enum := CoSWbemLocator.Create.ConnectServer(Server, 'root\cimv2', '',
      '', '', '', 0, nil).ExecQuery('Select * from Win32_LogicalDisk', 'WQL',
      wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
(*    while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements))) and (lNumElements > 0) do
    begin
      DumpWMI_Process(IUnknown(varArr) as SWBemObject);
    end;*)
    Enum := CoSWbemLocator.free;
  finally
//    FreeAndNil(Enum);
    log.Lines.EndUpdate;
  end;
Nur oben dieser Code, wenn ich den per Timer immer wieder aufrufe, wird der Speicher größer und größer.
Bei FastMM4 ist alles ok.
Habe schon versucht Variablen freizugeben, aber nichts nur fehler.

Wie muss ich die sachen wieder freigeben?

Mfg

Luckie 24. Nov 2009 13:21

Re: WMI Speicherfresser wie beseitiegen
 
Zitat:

Zitat von Jackie1983
Nur oben dieser Code, wenn ich den per Timer immer wieder aufrufe, wird der Speicher größer und größer.

Sei doch froh, andere geben viel Geld für mehr Speicher aus.

Mit der Bezeichnung Speicher lässt sich nicht viel anfangen. Was für ein Speicherverbrauch wird immer höher und wie stellst du das fest?

himitsu 24. Nov 2009 13:33

Re: WMI Speicherfresser wie beseitiegen
 
Delphi-Quellcode:
Enum := CoSWbemLocator.free;
Neuen CoSWbemLocator erstellen und Methode Free aufrufen?
Gibt es überhaupt eine Methode "Free"?

Warum FastMM nix anzeigt ist klar, denn diese Objekte/Befehle nutzen den Speichermanager vom OLE
und nicht den von Delphi (FastMM).


Ja, erstmal Luchie's Fragen beantworten
und dann ... hast di denn schonmal in der DP umgesehn?
Hier sind auch einige andere Dinge vorhanden, mit welchen Zugriff auf WMI bekommt.

Jackie1983 24. Nov 2009 13:34

Re: WMI Speicherfresser wie beseitiegen
 
Zitat:

Zitat von Luckie
Zitat:

Zitat von Jackie1983
Nur oben dieser Code, wenn ich den per Timer immer wieder aufrufe, wird der Speicher größer und größer.

Sei doch froh, andere geben viel Geld für mehr Speicher aus.

:lol:

Speicherauslastung im Taskmanager meinte ich.
Delphi-Quellcode:
function ADsEnumerateNext(pEnumVariant: IEnumVARIANT; cElements: ULONG;
  var pvar: OleVARIANT; var pcElementsFetched: ULONG): HRESULT; safecall; external 'activeds.dll';

//..

while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements))) and (lNumElements > 0) do
Wenn ich die While Schleife raus nehme habe ich keinen Fresser mehr.

Bernhard Geyer 24. Nov 2009 13:34

Re: WMI Speicherfresser wie beseitiegen
 
Zitat:

Zitat von Jackie1983
Enum := CoSWbemLocator.free;

Etwas falsche verwendung?

Delphi-Quellcode:
Enum := nil
sollte bei Interfaces reichen!

Jackie1983 24. Nov 2009 13:36

Re: WMI Speicherfresser wie beseitiegen
 
Zitat:

Zitat von himitsu
Delphi-Quellcode:
Enum := CoSWbemLocator.free;
Neuen CoSWbemLocator erstellen und Methode Free aufrufen?
Gibt es überhaupt eine Methode "Free"?

Nein gibt es nicht, hatte ich nur zum Test drin :)

Jackie1983 25. Nov 2009 18:29

Re: WMI Speicherfresser wie beseitiegen
 
keiner ne idee?

himitsu 25. Nov 2009 20:36

Re: WMI Speicherfresser wie beseitiegen
 
'n Versuch
Delphi-Quellcode:
var
  Server: string;
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
begin
  Server := '127.0.0.1';
  try
    log.Lines.BeginUpdate;
    Enum := CoSWbemLocator.Create.ConnectServer(Server, 'root\cimv2', '',
      '', '', '', 0, nil).ExecQuery('Select * from Win32_LogicalDisk', 'WQL',
      wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
    VarClear(varArr); // kann zumindestens nicht schaden
    while Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements)) and (lNumElements > 0) do
    begin
      DumpWMI_Process(IUnknown(varArr) as SWBemObject);
      ADsFreeEnumerator(varArr);
    end;
  finally
    log.Lines.EndUpdate;
  end;
end;
http://www.delphipraxis.net/internal...050512#1050512

nahpets 26. Nov 2009 07:53

Re: WMI Speicherfresser wie beseitiegen
 
Hallo,

die Speicherlöscher kenne ich, habe sie aber bisher nicht wegbekommen, sie liegen (vermutlich) nicht in dem Bereich, auf den Du von Delphi aus Einfluss nehmen kannst. Also quasi jenseits der Schnittstelle.

Bei dem Programm, dass mir die meisten Probleme bereitet hatte, nahm die Zahl der benutzen Handles kontinuierlich zu, bis irgendwann das System keine Resourcen mehr frei hatte. Das Programm wird jetzt per Taskplaner je halbe Stunde gestartet und beendet sich dann, statt selbständig im Halbstundenrhythmus einen Job als "Dauerläufer" bzw. Dienst, zu erledigen.

Die Speicherlöscher habe ich auch schon bei anderen Programmen, die die WMI-Schnittstelle benutzten, beobachten können, die nicht mit Delphi geschrieben wurden. Vermute hier ein generelles Problem.

Schau bitte mal hier: speicher laeuft voll bei wmi.html, bietet leider keine Lösung, aber einige Lösungsansätze, damit Du nicht alles, was bisher nicht funktioniert hat, nochmal durchprobieren muss ;-)

siehe auch hier: http://www.delphipraxis.net/internal...050512#1050512, hilft eventuell.

Bernhard Geyer 26. Nov 2009 07:59

Re: WMI Speicherfresser wie beseitiegen
 
Wenns wirklich Speicherkritisch ist:

Start einen externen Prozess und lass dem die WMI-Aktionen durchführen und dir die Infos liefern. Wenn der Ressourcen-Verlust zu groß ist Start diese Exe neu.

himitsu 26. Nov 2009 10:09

Re: WMI Speicherfresser wie beseitiegen
 
Nja, bei deim einem Speicherleck, in meinem Code, lag es daran, daß die Schnittstelle nur als Out-Parameter definiert ist, weswegen der entsprechende "alte" Inhalt des Variants nie freigegeben wurde, da der Variant selber einfach nur überschrieben wurde.
Leider wird dieses Problem in vielen Codes nicht beachtet, wodurch diese vielen Lecks überall entstehen.

Tja, und darum erstmal der der Versuch von oben (VarClear), wobei das mit dem ADsFreeEnumerator(varArr) sogar im MSDN drinstand und es dennoch in seinem Code fehlte. :angel:

Jetzt hat er erstmal was zum Probieren und ich bin gespannt, ob sich was ändert. :-D

Jackie1983 30. Nov 2009 08:00

Re: WMI Speicherfresser wie beseitiegen
 
Servus,

VarClear(varArr) hat weitergeholfen.
Habe zwei VarClear(varArr) in den while schleifen eingebaut und nun past es.
Wenn die Funktion aufgerufen wird steigt der Speicher nur einmalig um +2MB,
denke wegen der activeds.dll einbindung.

thx

Gruber_Hans_12345 21. Okt 2016 17:52

AW: WMI Speicherfresser wie beseitiegen
 
Hier auch noch mal dir Frage :D

Hat wer ein funktionierendes Beispiel das eine einfache WMI Abfrage macht, die kein Memory Leak hat?

t.roller 21. Okt 2016 21:01

AW: WMI Speicherfresser wie beseitiegen
 
Delphi-Quellcode:
//     This code was generated by the Wmi Delphi Code Creator http://theroadtodelphi.wordpress.com
//     Version: 1.8.3.0
//     LIABILITY DISCLAIMER
//     THIS GENERATED CODE IS DISTRIBUTED "AS IS". NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED.
//     YOU USE IT AT YOUR OWN RISK. THE AUTHOR NOT WILL BE LIABLE FOR DATA LOSS,
//     DAMAGES AND LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING OR MISUSING THIS CODE.

program GetWMI_Info;

{$APPTYPE CONSOLE}

uses
(*  SysUtils, ActiveX, ComObj, Variants; *)
  Winapi.Windows,
  System.SysUtils,
  Winapi.ActiveX,
  System.Win.ComObj,
  System.Variants;

function VarToInt(const AVariant: Variant): INT64;
begin Result := StrToIntDef(Trim(VarToStr(AVariant)), 0); end;

// "CIM_MemoryCapacity" beschreibt den Speichertyp, der auf einem physikalischen
// Element installiert werden kann, und seine minimale/maximale Konfiguration.
// Instanzen der Klasse "CIM_PhysicalMemory" enthalten Informationen über die
// Speicherposition und die minimalen/maximalen Anforderungen.

procedure GetCIM_MemoryCapacityInfo;
const
  WbemUser           ='';
  WbemPassword       ='';
  WbemComputer       ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService  : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject  : OLEVariant;
  oEnum        : IEnumvariant;
  iValue       : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService  := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM CIM_MemoryCapacity','WQL',wbemFlagForwardOnly);
  oEnum        := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('Caption                 %s',[VarToStr(FWbemObject.Caption)]));// String
    Writeln(Format('Description             %s',[VarToStr(FWbemObject.Description)]));// String
    Writeln(Format('MaximumMemoryCapacity   %d',[VarToInt(FWbemObject.MaximumMemoryCapacity)]));// Uint64
    Writeln(Format('MemoryType              %d',[VarToInt(FWbemObject.MemoryType)]));// Uint16
    Writeln(Format('MinimumMemoryCapacity   %d',[VarToInt(FWbemObject.MinimumMemoryCapacity)]));// Uint64
    Writeln(Format('Name                    %s',[VarToStr(FWbemObject.Name)]));// String
      
    Writeln('');
    FWbemObject:=Unassigned;
  end;
end;

procedure GetCIM_PhysicalMemoryInfo;
const
  WbemUser           ='';
  WbemPassword       ='';
  WbemComputer       ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService  : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject  : OLEVariant;
  oEnum        : IEnumvariant;
  iValue       : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService  := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM CIM_PhysicalMemory','WQL',wbemFlagForwardOnly);
  oEnum        := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('BankLabel              %s',[VarToStr(FWbemObject.BankLabel)]));// String
    Writeln(Format('Capacity               %d',[VarToInt(FWbemObject.Capacity)]));// Uint64
    Writeln(Format('Caption                %s',[VarToStr(FWbemObject.Caption)]));// String
    Writeln(Format('CreationClassName      %s',[VarToStr(FWbemObject.CreationClassName)]));// String
    Writeln(Format('DataWidth              %d',[VarToInt(FWbemObject.DataWidth)]));// Uint16
    Writeln(Format('Description            %s',[VarToStr(FWbemObject.Description)]));// String
    Writeln(Format('FormFactor             %d',[VarToInt(FWbemObject.FormFactor)]));// Uint16
    Writeln(Format('HotSwappable           %s',[VarToStr(FWbemObject.HotSwappable)]));// Boolean
    Writeln(Format('InstallDate            %s',[VarToStr(FWbemObject.InstallDate)]));// Datetime
    Writeln(Format('InterleavePosition     %d',[VarToInt(FWbemObject.InterleavePosition)]));// Uint32
    Writeln(Format('Manufacturer           %s',[VarToStr(FWbemObject.Manufacturer)]));// String
    Writeln(Format('MemoryType             %d',[VarToInt(FWbemObject.MemoryType)]));// Uint16
    Writeln(Format('Model                  %s',[VarToStr(FWbemObject.Model)]));// String
    Writeln(Format('Name                   %s',[VarToStr(FWbemObject.Name)]));// String
    Writeln(Format('OtherIdentifyingInfo   %s',[VarToStr(FWbemObject.OtherIdentifyingInfo)]));// String
    Writeln(Format('PartNumber             %s',[VarToStr(FWbemObject.PartNumber)]));// String
    Writeln(Format('PositionInRow          %d',[VarToInt(FWbemObject.PositionInRow)]));// Uint32
    Writeln(Format('PoweredOn              %s',[VarToStr(FWbemObject.PoweredOn)]));// Boolean
    Writeln(Format('Removable              %s',[VarToStr(FWbemObject.Removable)]));// Boolean
    Writeln(Format('Replaceable            %s',[VarToStr(FWbemObject.Replaceable)]));// Boolean
    Writeln(Format('SerialNumber           %s',[VarToStr(FWbemObject.SerialNumber)]));// String
    Writeln(Format('SKU                    %s',[VarToStr(FWbemObject.SKU)]));// String
    Writeln(Format('Speed                  %d',[VarToInt(FWbemObject.Speed)]));// Uint32
    Writeln(Format('Status                 %s',[VarToStr(FWbemObject.Status)]));// String
    Writeln(Format('Tag                    %s',[VarToStr(FWbemObject.Tag)]));// String
    Writeln(Format('TotalWidth             %d',[VarToInt(FWbemObject.TotalWidth)]));// Uint16
    Writeln(Format('Version                %s',[VarToStr(FWbemObject.Version)]));// String

    Writeln('');
    FWbemObject:=Unassigned;
  end;
end;

procedure GetWin32_PerfRawData_PerfOS_MemoryInfo;
const
  WbemUser           ='';
  WbemPassword       ='';
  WbemComputer       ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService  : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject  : OLEVariant;
  oEnum        : IEnumvariant;
  iValue       : LongWord;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService  := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_PerfRawData_PerfOS_Memory','WQL',wbemFlagForwardOnly);
  oEnum        := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Writeln(Format('AvailableBytes                         %d',[VarToInt(FWbemObject.AvailableBytes)]));// Uint64
    Writeln(Format('AvailableKBytes                        %d',[VarToInt(FWbemObject.AvailableKBytes)]));// Uint64
    Writeln(Format('AvailableMBytes                        %d',[VarToInt(FWbemObject.AvailableMBytes)]));// Uint64
    Writeln(Format('CacheBytes                             %d',[VarToInt(FWbemObject.CacheBytes)]));// Uint64
    Writeln(Format('CacheBytesPeak                         %d',[VarToInt(FWbemObject.CacheBytesPeak)]));// Uint64
    Writeln(Format('CacheFaultsPersec                      %d',[VarToInt(FWbemObject.CacheFaultsPersec)]));// Uint32
    Writeln(Format('Caption                                %s',[VarToStr(FWbemObject.Caption)]));// String
    Writeln(Format('CommitLimit                            %d',[VarToInt(FWbemObject.CommitLimit)]));// Uint64
    Writeln(Format('CommittedBytes                         %d',[VarToInt(FWbemObject.CommittedBytes)]));// Uint64
    Writeln(Format('DemandZeroFaultsPersec                 %d',[VarToInt(FWbemObject.DemandZeroFaultsPersec)]));// Uint32
    Writeln(Format('Description                            %s',[VarToStr(FWbemObject.Description)]));// String
    Writeln(Format('FreeAndZeroPageListBytes               %d',[VarToInt(FWbemObject.FreeAndZeroPageListBytes)]));// Uint64
    Writeln(Format('FreeSystemPageTableEntries             %d',[VarToInt(FWbemObject.FreeSystemPageTableEntries)]));// Uint32
    Writeln(Format('Frequency_Object                       %d',[VarToInt(FWbemObject.Frequency_Object)]));// Uint64
    Writeln(Format('Frequency_PerfTime                     %d',[VarToInt(FWbemObject.Frequency_PerfTime)]));// Uint64
    Writeln(Format('Frequency_Sys100NS                     %d',[VarToInt(FWbemObject.Frequency_Sys100NS)]));// Uint64
    Writeln(Format('LongTermAverageStandbyCacheLifetimes   %d',[VarToInt(FWbemObject.LongTermAverageStandbyCacheLifetimes)]));// Uint32
    Writeln(Format('ModifiedPageListBytes                  %d',[VarToInt(FWbemObject.ModifiedPageListBytes)]));// Uint64
    Writeln(Format('Name                                   %s',[VarToStr(FWbemObject.Name)]));// String
    Writeln(Format('PageFaultsPersec                       %d',[VarToInt(FWbemObject.PageFaultsPersec)]));// Uint32
    Writeln(Format('PageReadsPersec                        %d',[VarToInt(FWbemObject.PageReadsPersec)]));// Uint32
    Writeln(Format('PagesInputPersec                       %d',[VarToInt(FWbemObject.PagesInputPersec)]));// Uint32
    Writeln(Format('PagesOutputPersec                      %d',[VarToInt(FWbemObject.PagesOutputPersec)]));// Uint32
    Writeln(Format('PagesPersec                            %d',[VarToInt(FWbemObject.PagesPersec)]));// Uint32
    Writeln(Format('PageWritesPersec                       %d',[VarToInt(FWbemObject.PageWritesPersec)]));// Uint32
    Writeln(Format('PercentCommittedBytesInUse             %d',[VarToInt(FWbemObject.PercentCommittedBytesInUse)]));// Uint32
    Writeln(Format('PercentCommittedBytesInUse_Base        %d',[VarToInt(FWbemObject.PercentCommittedBytesInUse_Base)]));// Uint32
    Writeln(Format('PoolNonpagedAllocs                     %d',[VarToInt(FWbemObject.PoolNonpagedAllocs)]));// Uint32
    Writeln(Format('PoolNonpagedBytes                      %d',[VarToInt(FWbemObject.PoolNonpagedBytes)]));// Uint64
    Writeln(Format('PoolPagedAllocs                        %d',[VarToInt(FWbemObject.PoolPagedAllocs)]));// Uint32
    Writeln(Format('PoolPagedBytes                         %d',[VarToInt(FWbemObject.PoolPagedBytes)]));// Uint64
    Writeln(Format('PoolPagedResidentBytes                 %d',[VarToInt(FWbemObject.PoolPagedResidentBytes)]));// Uint64
    Writeln(Format('StandbyCacheCoreBytes                  %d',[VarToInt(FWbemObject.StandbyCacheCoreBytes)]));// Uint64
    Writeln(Format('StandbyCacheNormalPriorityBytes        %d',[VarToInt(FWbemObject.StandbyCacheNormalPriorityBytes)]));// Uint64
    Writeln(Format('StandbyCacheReserveBytes               %d',[VarToInt(FWbemObject.StandbyCacheReserveBytes)]));// Uint64
    Writeln(Format('SystemCacheResidentBytes               %d',[VarToInt(FWbemObject.SystemCacheResidentBytes)]));// Uint64
    Writeln(Format('SystemCodeResidentBytes                %d',[VarToInt(FWbemObject.SystemCodeResidentBytes)]));// Uint64
    Writeln(Format('SystemCodeTotalBytes                   %d',[VarToInt(FWbemObject.SystemCodeTotalBytes)]));// Uint64
    Writeln(Format('SystemDriverResidentBytes              %d',[VarToInt(FWbemObject.SystemDriverResidentBytes)]));// Uint64
    Writeln(Format('SystemDriverTotalBytes                 %d',[VarToInt(FWbemObject.SystemDriverTotalBytes)]));// Uint64
    Writeln(Format('Timestamp_Object                       %d',[VarToInt(FWbemObject.Timestamp_Object)]));// Uint64
    Writeln(Format('Timestamp_PerfTime                     %d',[VarToInt(FWbemObject.Timestamp_PerfTime)]));// Uint64
    Writeln(Format('Timestamp_Sys100NS                     %d',[VarToInt(FWbemObject.Timestamp_Sys100NS)]));// Uint64
    Writeln(Format('TransitionFaultsPersec                 %d',[VarToInt(FWbemObject.TransitionFaultsPersec)]));// Uint32
    Writeln(Format('TransitionPagesRePurposedPersec        %d',[VarToInt(FWbemObject.TransitionPagesRePurposedPersec)]));// Uint32
    Writeln(Format('WriteCopiesPersec                      %d',[VarToInt(FWbemObject.WriteCopiesPersec)]));// Uint32

    Writeln('');
    FWbemObject:=Unassigned;
  end;
end;
//------------------------------------------------------------------------------
var
  memory: TMemoryStatusEx;
begin
ReportMemoryLeaksOnShutdown := True;
  memory.dwLength := SizeOf(memory);
  GlobalMemoryStatusEx(memory);
  WriteLn('Total memory:    ' + IntToStr(memory.ullTotalPhys) + ' Bytes');
  WriteLn('Available memory: ' + IntToStr(memory.ullAvailPhys) + ' Bytes');

try
    CoInitialize(nil);
    try
      GetCIM_MemoryCapacityInfo;
  WriteLn('---------------------------------------------------------------');
      GetCIM_PhysicalMemoryInfo;
  WriteLn('---------------------------------------------------------------');
      GetWin32_PerfRawData_PerfOS_MemoryInfo;
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit'); Readln;
end.


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:20 Uhr.

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