Programmieren mit dem Borland C++ Builder 5.5

Gesucht: Ein freier C-Compiler, mit dem man ohne aufwändige und speicherplatzfressende Installation Windows-Anwendungen erstellen kann.

Gefunden:  Borland C++ Builder 5.5 (genau genommen Version 5.5.1).

Inhalt:

Download und Installation
Erstellung einer Konsolenanwendung
Umlautproblematik bei Konsolenanwendungen
Erstellung einer "richtigen" Windows-Anwendung
Unicode-Anwendungen erstellen
Modulo und negative Zahlen
Vorsicht! Absturzgefahr!
Kreiszahl PI auf ungewöhnliche Weise ermitteln
Assemblercode einbauen
DLLs erstellen und anwenden
Nette Kleinigkeiten mit #define
Zugriff auf das Internet
Warnungen bei deklarierten aber nicht verwendeten Variablen
Windowsanwendungen ohne WinMain erstellen
Noch mehr Assembler verwenden
Kalenderfunktionen in Assembler encodiert

Kurzübersicht

Dieser läuft sowohl unter Windows 9x (95/98/ME) als auch auf der NT-Linie (NT 4/2000/XP/Vista/Windows Seven; auch auf 64Bit-Version von XP/Vista/Seven lauffähig). Er erstellt 32Bit Windows-Anwendungen (Konsole und Fenster), welche meistens auch unter Linux mit dem Emulator "wine" funktionieren. 16Bit-Anwendungen für DOS und Windows 3.x können nicht erstellt werden; dafür muss man Borland C++ 5.02 verwenden.

Schnell ist er auch noch dazu: 45 kB Ressourcen in 5 Sekunden compiliert; ca. 1 MB C-Source-Code in 5 Dateien in 25 Sekunden compiliert und mit den compilierten Ressourcen zu einem 670 kB großen EXE-File gelinkt - das aber nicht auf einem aktuellen Rechner, sondern getestet unter Windows 95B auf einem Pentium (Eins) mit 133 MHz.

Das Programm hat keine IDE; man muss einen externen Editor verwenden (z.B. Notepad von Windows - reicht für die ersten Versuche) und das Compilieren erfolgt mittels eines Befehls auf der Eingabeaufforderung, wobei für eine Konsolen-Anwendung ein Befehl reicht; bei einer "richtigen" Windows-Anwendung je einer für die Ressourcen und das Programm. Zur Eingabeaufforderung kommt man am schnellsten mit mit Start - Ausführen - Command (Windows 9x-Linie) bzw. CMD (Windows NT-Linie).


Download und Installation

Das Programm kann hier heruntergeladen werden und ist ca. 8,5 MB groß. Die Installation ist keine "richtige" Installation, sondern eigentlich nur ein Entpacken in ein Verzeichnis. Das Programm schlägt "C:\Borland\BCC55" vor; ich selbst habe "I:\Borland\BCC55" bevorzugt. Nach dem Entpacken belegt es ca. 50 MB.


Bevor wir loslegen können, müssen noch ein paar Kleinigkeiten angepasst werden:

Damit der Compiler und der Linker auch die Include bzw. Libriary-Files finden, müssen im Unterverzeichnis "Bin" noch zwei Konfigurationsdateien hinterlegt werden:

BCC32.CFG:

-I"I:\Borland\Bcc55\include"
-L"I:\Borland\Bcc55\lib"

ILINK32.CFG:

-L"I:\Borland\Bcc55\lib"

Info: Sofern kein Leerzeichen im Pfad drinnen ist, würde auch ausreichen:

-II:\Borland\Bcc55\include
-LI:\Borland\Bcc55\lib

Das Unterverzeichnis "Bin" muss noch in den Pfad mit aufgenommen werden:

SET PATH=I:\Borland\Bcc55\bin;%PATH%

(Pfade natürlich der Installation anpassen, Groß-/Kleinschreibung wie unter Windows gewohnt egal)

Da man den Pfad jedesmal nach dem Öffnen des Eingabefensters anpassen muss, empfiehlt sich hier die Erstellung eines Batch-Files, welches man z.B. PATHBC.BAT nennen kann. Das Hinzufügen des Bin-Verzeichnis am Beginn des Pfades beschleunigt den Compiliervorgang, weil nicht zuerst das Windows\System32-Verzeichnis mit hunderten Dateien durchsucht werden muss.

Jetzt kann man das Programm auch auf einen USB-Stick kopieren und damit auf einem "unbeteiligtem" Rechner arbeiten, ohne Spuren zu hinterlassen. Man braucht nur die oben genannten 3 Dateien anpassen.

Erstellung einer Konsolen-Anwendung

So, nun ist der Compiler einsatzbereit und wir können testweise eine Konsolen-Anwendung erstellen. Eine Konsolen-Anwendung ist eine Windows-Anwendung ohne Fenster, welche in der Eingabeauffordung läuft und optisch wie eine "normale" MS-DOS-Anwendung aussieht. Bevor hier wieder zum x-ten Mal das "Hello World!"-Beispiel wiederholt wird, möchten wir lieber was anderes machen. Der Compiler beherrscht auch (nicht standardisierte) __int64 und double long-Variablen. Der Wertebereich von __int64 reicht von -2^63 bis +2^63-1.

Anbei ein Beispiel: Wir berechnen wieviele Meter ein Lichtstrahl in ca. 1000 Jahren zurücklegt und stoßen dabei an die Grenze von 2^63:

INT64.C:

#include <stdio.h>
#include <math.h>

#define LIGHTSPEED 299792458  // Lichtgeschwindigkeit in Meter pro Sekunde

int main(void)

{
  __int64      iEntf,iLj;
  long double  dEntf,dLj;
  int          i,j;

  i=sizeof(dEntf);
  j=sizeof(iEntf);
  printf("\n Speicherbedarf: long double %d Bytes - __int64 %d Bytes",i,j);

  iEntf=1;
  for (i=0; i<62; i++)
    iEntf=iEntf*2;
  iEntf--;
  iEntf=2*iEntf+1;  // 2 hoch 63 - 1
#ifdef __BORLANDC__
  printf("\n\n Maximalwert __int64: %Ld ",iEntf);
#else
  printf("\n\n Maximalwert __int64: %lld ",iEntf);
#endif

  printf("\n\n Entfernung, die ein Lichtstrahl zuruecklegt: ");
  printf("\n\n                    __int64                 long double ");
  iLj=LIGHTSPEED;
  iLj=iLj*60*60*24;        //  "Lichttag"
  dLj=(long double) LIGHTSPEED *60.0*60.0*24.0;
  iLj=iLj*365 + iLj/4;     //  "Lichtjahr"
  dLj=dLj*365.0 + dLj/4.0;

  for (i=970; i<980; i++)
  {
    iEntf=iLj*i;
    dEntf=dLj*i;
    printf("\n %4d Lichtjahre = ",i);
#ifdef __BORLANDC__
    printf("%20Ld",iEntf);
    printf(" = %23.1Lf Meter ", dEntf);
#else
    printf("%20lld",iEntf);
    printf(" = %23.1llf Meter ", dEntf);
#endif
  }
  printf("\n");
  return(0);
}

Wichtig ist der doppelte Unterstrich vor und nach __BORLANDC__ ; so kann man das Programm zum Vergleich auch unter Microsoft Visual Studio ab Version 2005 compilieren. Wie man sieht, sind die Formatierungsparameter unter printf bei beiden Compileren unterschiedlich.

__BORLANDC__ enthält übrigens den Wert 1361 (dezimal), was 551 hexadezimal entspricht -> der Versionsnummer des Compilers.

Compiliert wird mit "BCC32 INT64.C" (auf der Eingabeaufforderung eingeben).
Das Ergebnis ist eine Datei namens "INT64.EXE" mit einer Dateigröße von 52.736 Bytes.
Startet man das Programm, dann ergibt sich folgende Ausgabe:

 Speicherbedarf: long double 10 Bytes - __int64 8 Bytes

 Maximalwert __int64: 9223372036854775807

 Entfernung, die ein Lichtstrahl zuruecklegt:

                    __int64                 long double
  970 Lichtjahre =  9176908558403376000 =   9176908558403376000.0 Meter
  971 Lichtjahre =  9186369288875956800 =   9186369288875956800.0 Meter
  972 Lichtjahre =  9195830019348537600 =   9195830019348537600.0 Meter
  973 Lichtjahre =  9205290749821118400 =   9205290749821118400.0 Meter
  974 Lichtjahre =  9214751480293699200 =   9214751480293699200.0 Meter
  975 Lichtjahre = -9222531862943271616 =   9224212210766280000.0 Meter
  976 Lichtjahre = -9213071132470690816 =   9233672941238860800.0 Meter
  977 Lichtjahre = -9203610401998110016 =   9243133671711441600.0 Meter
  978 Lichtjahre = -9194149671525529216 =   9252594402184022400.0 Meter
  979 Lichtjahre = -9184688941052948416 =   9262055132656603200.0 Meter

Man sieht deutlich den Überlauf der __int64-Variable, während die long double-Variable noch funktioniert. Übrigens: Unter Microsoft Visual Studio belegt long double auch nur 8 Bytes und entspricht somit einer "normalen" double-Variable und liefert daher bei dieser Größenordnung nicht so genaue Werte.

Info: Für __int64 kann man auch LONGLONG verwenden (#include <windows.h> erforderlich). Man kann die Ausgabe von __int64 (bzw. LONGLONG) auch mit %lld anstatt %Ld formatieren. Long double benötigt aber unbedingt %Lf, %llf würde unsinnige Werte ausgeben.

Der Compiler beherrscht in der Konsole auch die von der DOS-Version gewohnten Befehle für bunte und am Bildschirm positionierte Texte wie clrscr, textcolor, textbackground, cprintf, gotoxy, wherex, wherey ohne Verrenkungen (#include <conio.h> erforderlich). Dann läuft allerdings das Programm nicht mehr mit wine unter Linux.

Bei dieser Gelegenheit noch eine Warnung zur Vorsicht bei Integer-Divisionen:

#include <stdio.h>

int main(void)
{
  int    i;
  double c,f,f1,f2,f3;
  for (i=0; i<=100; i+=10)
  {
    c=i;
    f=9.0/5.0 * c + 32.0;  // perfekt, alle ganzzahligen Double-Werte richtig "markiert"
    f1=9/5 * c + 32;       // das ist problematisch weil int 9 / int 5 = int 1!
    f2=9.0/5 * c + 32;     // bei einem Double-Wert und einem Integer-Wert stimmt auch das Ergebnis
    f3=9/5.0 * c + 32;
    printf("\n %5.1f Celsius = %6.2f Fahrenheit %6.2f %6.2f %6.2f ",c,f,f1,f2,f3);
  }
  printf("\n");
  return(0);
}

Ergebnis:

   0.0 Celsius =  32.00 Fahrenheit  32.00  32.00  32.00
  10.0 Celsius =  50.00 Fahrenheit  42.00  50.00  50.00
  20.0 Celsius =  68.00 Fahrenheit  52.00  68.00  68.00
  30.0 Celsius =  86.00 Fahrenheit  62.00  86.00  86.00
  40.0 Celsius = 104.00 Fahrenheit  72.00 104.00 104.00
  50.0 Celsius = 122.00 Fahrenheit  82.00 122.00 122.00
  60.0 Celsius = 140.00 Fahrenheit  92.00 140.00 140.00
  70.0 Celsius = 158.00 Fahrenheit 102.00 158.00 158.00
  80.0 Celsius = 176.00 Fahrenheit 112.00 176.00 176.00
  90.0 Celsius = 194.00 Fahrenheit 122.00 194.00 194.00
 100.0 Celsius = 212.00 Fahrenheit 132.00 212.00 212.00

Wie man sieht, bei einer Division von zwei Integer-Werten kommt wieder ein Integerwert raus und da werden wie im Fall 9 / 5 beinhart alle Dezimalstellen abgeschnitten! (Falsches Ergebnis 1!) Daher sicherheitshalber alle ganzzahligen Doublewerte mit abschließendem .0 schreiben, also 9.0 / 5.0 (ergibt 1.8).

Und zum Abschluß noch ein Hexdump-Utility:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

void adresse(long);
long powlong(long, long);

int main(int argc, char **argv)
{
  char ch[260];            /* MAX_PATH = 260 */
  unsigned char c,c1,c2;
  int  i,j,s,m;
  int  max=16;             /* Vorsicht: max mind. 2 kleiner als Stringlaenge von ch[]! */
  int  sw=0;               /* sw=0 ANSI-Zeichensatz (MS-DOS); sw!=0 OEM-Zeichensatz (Windows) */
  long l=0;
  FILE *dz;
  if (argc<2)
  {
    printf("\n Hexdump Utility");
    printf("\n\n Usage: %s Filename columns [/oem]",argv[0]);
    printf("\n\n colums = number of columns in a row; from 1 to 128");
    printf("\n\n colums argument not necessary; in this case 16 columns are used");
    printf("\n\n /oem or /win = use OEM char set [Windows]");
    printf("\n (standard is ANSI char set [DOS or Console])");
    printf("\n");
    exit(1);
  }
  if ((dz=fopen(argv[1],"rb"))==NULL)
  {
    printf("\n Cannot open file \"%s\" - possibly file not found!",argv[1]);
    printf("\n");
    exit(2);
  }
  if (argc>2)
  {
    for (i=2; i<argc; i++)
    {
      m=atoi(argv[i]);
      if (m)
      {
        if (m<1)   m=1;
        if (m>128) m=128;
        max=m;             /* Vorsicht: max mind. 2 kleiner als Stringlaenge von ch[]! */
      }
      else
      {
        strcpy(ch, argv[i]);
        for (j=0; ch[j]; j++)
          ch[j]=(char) tolower(ch[j]);
        if (strstr(ch, "/oem") || strstr(ch, "\\oem") ||
            strstr(ch, "/win") || strstr(ch, "\\win")) sw=1;
      }
    }
  }
  s=0;
  for (i=0; i<max+2; i++)
    ch[i]='\0';
  while (fread(&c,1,1,dz)>0)
  {
    if (c>31 && c<128)
      ch[s]=c;
    else
    {
      ch[s]='.';
      if (c==0x84 || c==0xE4) ch[s] = (!sw) ? 0x84 : 0xE4;  /* ä */
      if (c==0x94 || c==0xF6) ch[s] = (!sw) ? 0x94 : 0xF6;  /* ö */
      if (c==0x81 || c==0xFC) ch[s] = (!sw) ? 0x81 : 0xFC;  /* ü */
      if (c==0x99 || c==0xD6) ch[s] = (!sw) ? 0x99 : 0xD6;  /* Ä */
      if (c==0x8E || c==0xC4) ch[s] = (!sw) ? 0x8E : 0xC4;  /* Ö */
      if (c==0x9A || c==0xDC) ch[s] = (!sw) ? 0x9A : 0xDC;  /* Ü */
      if (c==0xE1 || c==0xDF) ch[s] = (!sw) ? 0xE1 : 0XDF;  /* ß */
    }
    c1 = c/16 + 48;
    if (c1>57) c1+=7;
    c2 = c%16 + 48;
    if (c2>57) c2+=7;
    if (s==0) adresse(l);  /* oder einfach nur if (!s) */
    printf("%c%c ",c1,c2);
    if (++s==max) {s=0; printf("  *%s*\n",ch);}
    if (++l>=16777216) l=0;     /* 256*256*256 */
  }
  if (s!=0)  /* oder einfach nur if (s)
  {
     j=max-s;
     for (i=0; i<j; i++)
     {
       printf("   ");
       ch[max-i-1]='*';
     }
     printf("  *%s*\n",ch);
  }
  fclose(dz);
  return(0);
}

/* nur für positiven Exponenten */
/* erg = bas hoch exp           */

long powlong(long bas, long exp)
{
  long i,erg=bas;
  if (exp==0) return(1);
  if (exp<0 || bas==0) return(0);
  for (i=1; i<exp; i++)
    erg*=bas;
  return(erg);
}

void adresse(long a)
{
  long p;
  int  i,j=0;
  char he[7];
  unsigned char c;
  for (i=5; i>=0; i--)
  {
    p = powlong(16,i);
    c = a/p + 48;
    a = a%p;
    if (c>57) c+=7;
    he[j++]=(char) c;
  }
  he[j]='\0';
  printf("%s   ",he);
}

Ausgabe-Beispiel: (hier die compilierte EXE-Datei des Dump-Programmes)

000000   4D 5A 50 00 02 00 00 00 04 00 0F 00 FF FF 00 00   *MZP.............*
000010   B8 00 00 00 00 00 00 00 40 00 1A 00 00 00 00 00   *........@.......*
000020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   *................*
000030   00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00   *................*
000040   BA 10 00 0E 1F B4 09 CD 21 B8 01 4C CD 21 90 90   *........!..L.!..*
000050   54 68 69 73 20 70 72 6F 67 72 61 6D 20 6D 75 73   *This program mus*
000060   74 20 62 65 20 72 75 6E 20 75 6E 64 65 72 20 57   *t be run under W*
000070   69 6E 33 32 0D 0A 24 37 00 00 00 00 00 00 00 00   *in32..$7........*

Man könnte anstatt der long-Variablen auch int-Variablen verwenden; beide sind unter Win32 4 Bytes groß. Aber mit long anstatt int kann das Programm auch unter MS-DOS compiliert werden.

Noch ein Tipp: Wenn man schon Lust verspürt, so eine Windows-Konsolen-Anwendung auch unter "echtem" DOS laufen zu lassen, dann empfiehlt sich der HX-DOS-Extender (siehe Link in Wikipedia).



Umlautproblematik bei Konsolenanwendungen

Leider ist es so, dass in der Konsole (und auch bei MS-DOS-Anwendungen) ein anderer Zeichensatz für die Umlaute verwendet wird, als unter Windows. Schreibt man daher printf("äöüÄÖÜß") mit einem Windows-Editor und compiliert das Ganze dann als Konsolenanwendung, dann erscheinen ganz andere Zeichen auf dem Bildschirm. Bei der Erstellung von "richtigen" Windows-Anwendungen tritt dieser Effekt nicht mehr auf, da dann sowohl der Editor als auch die fertige Anwendung den gleichen Zeichensatz verwenden.

Das Problem lässt sich aber lösen:

- den Source-Code mit einem MS-DOS-basierten Editor (z.B. EDIT.COM, welcher unter Windows XP immer noch dabei ist) öffnen und die Umlaute manuell umbessern. Unter 64Bit-Betriebssystemen laufen aber keine 16Bit-DOS-Anwendungen mehr. Hier hilft der Emulator "DOSBox" weiter (siehe Link in Wikipedia).

- das Zeichen durch den entsprechenden HEX-Code ersetzen:
   z.B. Ä durch \x8E ersetzen. Also printf("\x8Epfel und Birnen") -> Ausgabe "Äpfel und Birnen"

Zeichen
Konsole
Windows
ä
84
E4
ö
94
F6
ü
81
FC
Ä
8E
C4
Ö
99
D6
Ü
9A
DC
ß
E1
DF

Alle Zeichencodes in Hex!


Erstellung einer "richtigen" Windows-Anwendung

So nun wird es Zeit, eine richtige Windows-Anwendung mit einem Fenster zu erstellen (auch GUI-Anwendung genannt; GUI = graphical user interface; auf Deutsch Grafische Benutzeroberfläche). Hier soll gezeigt werden, dass der Compiler auch Multithread-Anwendungen erstellen kann; sowie Anwendungen mit Visual Styles, ohne dass man die "comctl32.lib" manuell einbinden muss. (Visual Styles werden ab Windows XP sichtbar, sofern das Betriebssystem diesen Style verwenden soll; unter den Vorgängern sieht man dann halt nur den "normalen" Style wie von Win9x oder WinNT/Win2000 gewohnt.)

Wir benötigen folgende 3 Dateien:
(auf ein Icon wurde verzichtet)

TESTMF.RC - die Ressourcen

/////////////////////////////////////////////////////////////////////////////
//
// 24
//

1                       24      MOVEABLE PURE   "TESTMF.exe.manifest"

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

DlgBox DIALOG 20, 50, 90, 64
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "LOOP Dialogbox Eins"
BEGIN
  DEFPUSHBUTTON "CANCEL" IDCANCEL, 29, 44, 32, 14, WS_GROUP
  CTEXT "Iterating 1 - sqrt" -1, 0, 8, 90, 8
  CTEXT "0" 1000, 0, 23, 90, 8
END

DlgBox2 DIALOG 130, 50, 90, 64
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "LOOP Dialogbox Zwei"
BEGIN
  DEFPUSHBUTTON "CANCEL" IDCANCEL, 29, 44, 32, 14, WS_GROUP
  CTEXT "Iterating 2 - log" -1, 0, 8, 90, 8
  CTEXT "0" 1000, 0, 23, 90, 8
END

/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,0
 PRODUCTVERSION 1,0,0,0
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x4L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904b0"
        BEGIN
            VALUE "Comments", "\0"
            VALUE "CompanyName", "\0"
            VALUE "FileDescription", "Multithreading Test\0"            VALUE "FileVersion", "1.0\0"
            VALUE "InternalName", "TESTMF\0"
            VALUE "LegalCopyright", "FraBa 2009\0"
            VALUE "LegalTrademarks", "\0"
            VALUE "OriginalFilename", "TESTMF.exe\0"
            VALUE "PrivateBuild", "\0"
            VALUE "ProductName", "TESTMF\0"
            VALUE "ProductVersion", "1.0\0"
            VALUE "SpecialBuild", "\0"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END

TESTMF.exe.manifest - benötigt für Visual Styles und DpiAware (ab Windows Vista)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="X86"
    name="Fraba.TESTMF"
    type="win32"
/>

<description>TESTMF</description>

<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>

<asmv3:application>
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
    <dpiAware>true</dpiAware>
  </asmv3:windowsSettings>
</asmv3:application>

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
       </security>
  </trustInfo>

</assembly>

TESTMF.C - das eigentliche Programm:

#ifdef  __BORLANDC__
  #pragma resource "testmf.res"
#endif

#include <math.h>
#include <windows.h>
#include <commctrl.h>

HINSTANCE     hInstance;
volatile BOOL bDoAbort,bDoAbort2;
HWND          hwnd,hButton1,hButton2,hButton3;
INT           iWert=0,iWert2=0;

BOOL CALLBACK DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)

{
  if (uMsg == WM_COMMAND && LOWORD(wParam) == IDCANCEL)
  {
    EnableWindow(hwnd, TRUE);
    DestroyWindow(hwndDlg);
    return (bDoAbort = TRUE);
  }
  return FALSE;
}

BOOL CALLBACK DlgProc2(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)

{
  if (uMsg == WM_COMMAND && LOWORD(wParam) == IDCANCEL)
  {
    EnableWindow(hwnd, TRUE);
    DestroyWindow(hwndDlg);
    return (bDoAbort2 = TRUE);
  }
  return FALSE;
}

DWORD WINAPI DoIterate(LPVOID hwndDlg)

{
  int i;
  double d;
  char buf[18];
  i = 0;
  while (!bDoAbort)
  {
    SetWindowText(GetDlgItem((HWND)hwndDlg, 1000),
    itoa(i++, buf, 10));
    iWert=i;
    d=sqrt(i);
  }
  InvalidateRect(hwnd,NULL,TRUE);
  return 0;
}

DWORD WINAPI DoIterate2(LPVOID hwndDlg)

{
  int i;
  double d;
  char buf[18];
  i = 0;
  while (!bDoAbort2)
  {
    SetWindowText(GetDlgItem((HWND)hwndDlg, 1000),
    itoa(i++, buf, 10));
    iWert2=i;
    d=log10(i);
  }
  InvalidateRect(hwnd,NULL,TRUE);
  return 0;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{
  HWND            hwndDlg,hwndDlg2;
  DWORD           dwThreadId,dwThreadId2;
  HDC             hdc;
  PAINTSTRUCT     ps;
  HFONT           hFont;     
  SIZE            size;
  TCHAR           szPuffer[128];

  switch(uMsg)
  {
    case WM_CREATE:
      hButton1=CreateWindow("BUTTON", "Close",
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
               170, 15, 100, 25, hwnd, (HMENU) 1, hInstance, NULL);

      hButton2=CreateWindow("BUTTON", "Update",
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
               290, 15, 100, 25, hwnd, (HMENU) 2, hInstance, NULL);

      hButton3=CreateWindow("BUTTON", "Start",
               WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
               410, 15, 100, 25, hwnd, (HMENU) 3, hInstance, NULL);

      break;

    case WM_COMMAND:
      if (HIWORD(wParam)==BN_CLICKED)
      {
        if (LOWORD(wParam)==1)
          PostMessage(hwnd, WM_CLOSE, 0, 0);

        if (LOWORD(wParam)==2)
          InvalidateRect(hwnd,NULL,TRUE);

        if (LOWORD(wParam)==3)
        {
          if (!iWert && !iWert2)
          {
            DestroyWindow(hButton3);

            hwndDlg = CreateDialog(hInstance, "DlgBox", hwnd, DlgProc);
            ShowWindow(hwndDlg, SW_NORMAL);
            UpdateWindow(hwndDlg);
            EnableWindow(hwnd, FALSE);
            bDoAbort = FALSE;
            CreateThread(NULL, 0, DoIterate, (LPDWORD) hwndDlg, 0, &dwThreadId);

            hwndDlg2 = CreateDialog(hInstance, "DlgBox2", hwnd, DlgProc2);
            ShowWindow(hwndDlg2, SW_NORMAL);
            UpdateWindow(hwndDlg2);
            EnableWindow(hwnd, FALSE);
            bDoAbort2 = FALSE;
            CreateThread(NULL, 0, DoIterate2, (LPDWORD) hwndDlg2, 0, &dwThreadId2);

            EnableWindow(hwnd, TRUE);
          }
        }
      }

    case WM_PAINT:
      hdc=BeginPaint(hwnd, &ps);
      hFont=(HFONT) GetStockObject(SYSTEM_FIXED_FONT);
      SelectObject(hdc, hFont);
      wsprintf(szPuffer, "Wert 1: %d ", iWert);
      SetTextColor(hdc, RGB(80,80,80));
      TextOut(hdc, 10, 10, szPuffer, strlen(szPuffer));
      GetTextExtentPoint32(hdc, szPuffer, strlen(szPuffer), &size);
      wsprintf(szPuffer, "Wert 2: %d ", iWert2);
      TextOut(hdc, 10, 10+size.cy*4/3, szPuffer, strlen(szPuffer));
      EndPaint(hwnd, &ps);
      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      break;

    default:
      return DefWindowProc(hwnd, uMsg, wParam, lParam);
  }
  return 0;
}

int WINAPI WinMain(HINSTANCE hThisInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)

{
  MSG msg;
  WNDCLASS wndClass;
  hInstance = hThisInstance;
  InitCommonControls(); // required for UpDown-control
  if (!hPrevInstance)
  {
    memset(&wndClass, 0, sizeof(wndClass));
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.hInstance = hInstance;
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndClass.lpszClassName = "LOOP";
    if (!RegisterClass(&wndClass)) return FALSE;
  }
  hwnd = CreateWindow("LOOP", "Loop - Multithreading Test",
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                       NULL, NULL, hInstance, NULL);
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);
  while (GetMessage(&msg, NULL, 0, 0))
    DispatchMessage(&msg);
  return msg.wParam;
}

Zwei Schritte sind es bis zur fertigen Anwendung:

Compilieren der Ressorcen mit "BRCC32 TESTMF.RC".
Das Ergebnis ist eine Datei namens "TESTMF.RES" mit 2.512 Bytes Dateigröße.
Compilieren und Linken der Anwendung mit "BCC32 -W -tWM TESTMF.C".
Fertig ist die Anwendung "TESTMF.EXE" mit 55.296 Bytes Dateigröße.

Hinweise:

Mit -W oder -tW erstellt man Windowsanwendungen.
Der Parameter -tWM oder -WM ist für Anwendungen mit Multithreading.
#pragma resource "testmf.res" ist notwendig, um die Ressorcen einzubinden.
Ohne diese würde man eine Anwendung erhalten, die nicht startet.

Beim Compilieren erhält man einige Warnungen:

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
TESTMF.C:
Warning W8057 TESTMF.C 24: Parameter 'lParam' is never used in function DlgProc
Warning W8057 TESTMF.C 36: Parameter 'lParam' is never used in function DlgProc2
Warning W8004 TESTMF.C 54: 'd' is assigned a value that is never used in function DoIterate
Warning W8004 TESTMF.C 72: 'd' is assigned a value that is never used in function DoIterate2
Warning W8057 TESTMF.C 193: Parameter 'lpCmdLine' is never used in function WinMain
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

Die Zahl nach dem Dateinamen (TESTMF.C) ist jeweils die Zeilennummer.
Einige Parameter werden bei diesem Programm nicht verwendet, müssen aber bei den Funktionen "WinMain" und "WndProc" definitionsgemäß vorhanden sein.

Zur Info: Vermeiden kann man diese Warnungen wenn man

#pragma argsused

vor der betreffenden Funktion schreibt. Dieses #pragma ist allerdings Borland spezifisch.

Das Programm berechnet Wurzeln und Logarithmen zum "Zeitvertreib", die nicht mehr weiter verwendet werden, daher die beiden anderen Warnungen.

So, nun kann das Programm gestartet werden:



Man sieht das Hauptfenster mit 3 Buttons - und tatsächlich mit Visual Styles; in diesem Fall z.B. an den Kanten gerundete Buttons.
Drückt man auf den "Start"-Button, dann erscheinen zwei Dialogboxen. Jede Dialogbox ist ein eigener Thread. Einer berechnet Quadartwurzeln, der andere Logarithmen der Zahl, die gerade angezeigt wird. (Das Ergebnis wird aber nicht ausgegeben.)
Drückt man auf "Update", dann wird die aktuelle Zahl ins Hauptfenster übernommen:




Unicode-Anwendungen erstellen

Unicode-Anwendungen lassen sich sowohl für die Console als auch für die GUI erstellen; diese laufen aber nicht mehr unter Win9x (95,98,ME) sondern nurmehr unter der NT-Linie (NT, 2000, XP, Vista, Seven) sowie (meistens) mit dem Emulator wine. Es genügt beim Compilieren den Parameter -WU anzugeben. Also "BCC32 -WU name.c" für die Console und "BCC32 -W -WU name.c" für die GUI.

Bei der Console erwartet der Compiler für die Hauptfunktion wmain() statt main(); bei der GUI wWinMain() anstatt WinMain(). Damit man eine Anwendung sowohl für Unicode als auch für den "normalen" Zeichensatz compilieren kann, empfiehlt sich folgende "Konstruktion":

ifdef _UNICODE
  int wmain(void)
#else
  int main(void)
#endif
{
// Consolenprogramm-Hauptfunktion
}

#ifdef UNICODE
int CALLBACK            wWinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPWSTR              lpCmdLine,
    int                 nShowCmd
    )
#else
int CALLBACK            WinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPSTR               lpCmdLine,
    int                 nShowCmd
    )
#endif
{
// GUI-Programm Hauptfunktion
}

Es gibt allerdings einen Bug, der auftritt, wenn man ein C++-Programm unter Unicode compilieren will. Dann meldet der Linker, dass er wWinMain nicht auflösen kann. Unter Borland C++ 5.02 tritt dieser Bug nicht auf. Abhilfe schafft folgender zusätzlicher Code:

#ifdef UNICODE

 #if defined (__cplusplus) && defined (__BORLANDC__) && (__BORLANDC__ > 0x530) /* Borland C++ 5.5.1 */
  extern "C" int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPWSTR lpCmdLine, int nShowCmd);
 #endif

int CALLBACK            wWinMain

... (wie oben)

Hier für einige Funktionen ihr Unicode-Pendant:

  strcpy    lstrcpy
  strcat     lstrcat
  strcmp   wcscmp
  strstr      wcsstr
  atoi       _wtoi
  atof       _wtof

lstrcpy + lstrcat können auch für nicht Unicode-Anwendungen verwendet werden, es muss aber "windows.h" eingebunden werden. Das ist aber bei Windows-GUI-Anwendungen sowieso der Fall.

Um eine Anwendung sowohl normal als auch für Unicode compilieren zu können, setzt man vor jedem Text das Macro TEXT("..");
also aus printf("Dies ist ein Test"); wird printf(TEXT("Dies ist ein Test"));

Bei bestehenden längeren Programmen ist das händische Ersetzen ziemlich mühsam. Daher habe ich ein kleines Utility erstellt, welches diese Arbeit abnehmen kann. Es ersetzt in einer Zeile das erste (dritte, fünfte, also ungerade Anführungszeichen) durch TEXT(" und jedes zweite(vierte, sechste, also gerade Anführungszeichen) durch "). Zur Sicherheit wird, falls in einer Zeile bereits TEXT( oder #pragma oder #include vorkommt, keine Ersetzung durchgeführt. Das gleiche gilt bei fopen, damit Dateinamen nicht verändert werden. (Kann natürlich leicht individuell angepasst werden.)

KONVUNI.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
  FILE *dz,*lst;
  char c,szText[10];
  int  s=0,m=0,n=0,o=0,p=0;

  printf("\n Zur Info: char ist %d Zeichen gro\xE1 \n",sizeof(c));

  if ((lst=fopen("OUT.C","rb"))!=NULL)
  {
    printf("\n Zieldatei existiert - Programmabbruch! \n");
    exit(3);
  }
  if ((lst=fopen("OUT.C","wb"))==NULL)
  {
    printf("\n Zieldatei kann nicht nicht erstellt werden - Programmabbruch! \n");
    exit(2);
  }

  if ((dz=fopen("IN.C","rb"))==NULL)
  {
    printf("\n Quelldatei nicht gefunden - Programmabbruch! \n");
    fclose(lst);
    exit(1);
  }

  while (fread(&c,sizeof(c),1,dz)>0)
  {
    if (c==0x0D || c==0x0A)
      {s=0; m=0; n=0; o=0; p=0;}
    // prüfen auf Zeichenfolge TEXT(
    if (c=='T' && m==0) m++;
    if (c=='E' && m==1) m++;
    if (c=='X' && m==2) m++;
    if (c=='T' && m==3) m++;
    if (c=='(' && m==4) m++;
    if (c=='"' && m==5) m++;
    // prüfen auf Zeichenfolge #pragma
    if (c=='#' && n==0) n++;
    if (c=='p' && n==1) n++;
    if (c=='r' && n==2) n++;
    if (c=='a' && n==3) n++;
    if (c=='g' && n==4) n++;
    if (c=='m' && n==5) n++;
    if (c=='a' && n==6) n++;
    // prüfen auf Zeichenfolge fopen
    if (c=='f' && o==0) o++;
    if (c=='o' && o==1) o++;
    if (c=='p' && o==2) o++;
    if (c=='e' && o==3) o++;
    if (c=='n' && o==4) o++;
    // prüfen auf Zeichenfolge #include
    if (c=='#' && p==0) p++;
    if (c=='i' && p==1) p++;
    if (c=='n' && p==2) p++;
    if (c=='c' && p==3) p++;
    if (c=='l' && p==4) p++;
    if (c=='u' && p==5) p++;
    if (c=='d' && p==6) p++;
    if (c=='e' && p==7) p++;

    if (c=='"' && m<5 && n<7 && o<5 && p<8)
    {
      s=1-s;
      if (s)   // "beginnendes" Anführungszeichen " -> TEXT("
        strcpy(szText,"TEXT(\"");
      else     // "beendendes"  Anführungszeichen " -> ")
        strcpy(szText,"\")");
      if (!fwrite(&szText,(int) strlen(szText),1,lst))
      {
        printf("\n Problem beim Schreiben in die Zieldatei - Programmabbruch! \n");
        exit(4);
      }
    }
    else
    {
      if (!fwrite(&c,sizeof(c),1,lst))
      {
        printf("\n Problem beim Schreiben in die Zieldatei - Programmabbruch! \n");
        exit(4);
      }
    }
  }

  printf("\n Konvertierung durchgef\x81hrt! \n");
  fclose(dz);
  fclose(lst);
  return(0);
}


Die Dateinamen müssen noch angepasst werden; IN.C ist die Ausgangsdatei; OUT.C die Zieldatei. Der Konvertierungsvorgang dauert nur Sekundenbruchteile. Falls die Zieldatei bereits vorhanden sein sollte, dann bricht das Programm ab, damit diese nicht irrtümlich überschrieben wird. Natürlich ist dieses Konvertierungsprogramm nicht perfekt, aber immer noch besser, als jede einzelne Zeile manuell umzubessern.


Modulo und negative Zahlen

Machen wir mal folgenden Test:

#include <stdio.h>

int main(void)
{
  int i,m,n;
  for (i=10; i>=-10; i--)
  {
    m = i %  3;
    n = i % -3;
    printf("\n Zahl: %3d  Modulo 3: %2d  Modulo -3: %2d  ",i,m,n);
  }
  printf("\n");
  return(0);
}

Das Ergebnis:

  Zahl:  10  Modulo 3:  1  Modulo -3:  1 
 Zahl:   9  Modulo 3:  0  Modulo -3:  0 
 Zahl:   8  Modulo 3:  2  Modulo -3:  2 
 Zahl:   7  Modulo 3:  1  Modulo -3:  1 
 Zahl:   6  Modulo 3:  0  Modulo -3:  0 
 Zahl:   5  Modulo 3:  2  Modulo -3:  2 
 Zahl:   4  Modulo 3:  1  Modulo -3:  1 
 Zahl:   3  Modulo 3:  0  Modulo -3:  0 
 Zahl:   2  Modulo 3:  2  Modulo -3:  2 
 Zahl:   1  Modulo 3:  1  Modulo -3:  1 
 Zahl:   0  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -1  Modulo 3: -1  Modulo -3: -1 
 Zahl:  -2  Modulo 3: -2  Modulo -3: -2 
 Zahl:  -3  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -4  Modulo 3: -1  Modulo -3: -1 
 Zahl:  -5  Modulo 3: -2  Modulo -3: -2 
 Zahl:  -6  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -7  Modulo 3: -1  Modulo -3: -1 
 Zahl:  -8  Modulo 3: -2  Modulo -3: -2 
 Zahl:  -9  Modulo 3:  0  Modulo -3:  0 
 Zahl: -10  Modulo 3: -1  Modulo -3: -1 

Man sieht: Solange die Zahl größer als 0 ist, kommt eine positive Zahl als Ergebnis heraus; ansonsten eine negative Zahl. Dieses Verhalten ist aber meistens unerwünscht. Abhilfe schafft eine zusätzliche Zeile:

  for (i=10; i>=-10; i--)
  {
    m = i %  3;
    n = i % -3;
    if (m<0) m+=3;
    if (n<0) n+=3;
    printf("\n Zahl: %3d  Modulo 3: %2d  Modulo -3: %2d  ",i,m,n);
  }

Ergebnis:

 Zahl:  10  Modulo 3:  1  Modulo -3:  1 
 Zahl:   9  Modulo 3:  0  Modulo -3:  0 
 Zahl:   8  Modulo 3:  2  Modulo -3:  2 
 Zahl:   7  Modulo 3:  1  Modulo -3:  1 
 Zahl:   6  Modulo 3:  0  Modulo -3:  0 
 Zahl:   5  Modulo 3:  2  Modulo -3:  2 
 Zahl:   4  Modulo 3:  1  Modulo -3:  1 
 Zahl:   3  Modulo 3:  0  Modulo -3:  0 
 Zahl:   2  Modulo 3:  2  Modulo -3:  2 
 Zahl:   1  Modulo 3:  1  Modulo -3:  1 
 Zahl:   0  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -1  Modulo 3:  2  Modulo -3:  2 
 Zahl:  -2  Modulo 3:  1  Modulo -3:  1 
 Zahl:  -3  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -4  Modulo 3:  2  Modulo -3:  2 
 Zahl:  -5  Modulo 3:  1  Modulo -3:  1 
 Zahl:  -6  Modulo 3:  0  Modulo -3:  0 
 Zahl:  -7  Modulo 3:  2  Modulo -3:  2 
 Zahl:  -8  Modulo 3:  1  Modulo -3:  1 
 Zahl:  -9  Modulo 3:  0  Modulo -3:  0 
 Zahl: -10  Modulo 3:  2  Modulo -3:  2 

Beispiel wo man so eine Korrektur vornehmen muss:
Ermittlung des Wochentages aus dem Modifiziertem Julianischem Datum, weil sonst kann man keinen Wochentag für Tage vor dem 17. November 1858 bestimmen:

Dem heutigen Tag (9. Februar 2010) ist das Modifizierte Juliansiche Datum 55236 zugeordnet. Modulo 7 ergibt 6; also ein Dienstag.
Der 9. November 1858 hat das Modifizierte Julianische Datum -8. Modulo 7 ergibt -1. Wenn man 7 dazu zählt erhält man wieder 6; auch dieser Tag war ein Dienstag.


Vorsicht! Absturzgefahr!

Schnell ist es passiert: Die Wurzel aus einer negativen Zahl gezogen. Eigentlich sollte hier als Ergebnis "NAN" (für "Not a Number") sein, allerdings schreibt er bei einer Konsolenanwendung zusätzlich noch folgendes raus: "sqrt: DOMAIN error". (Das heißt, dass der Wertebereich übertreten worden ist.)
Bei Windows-Anwendungen erscheint allerdings folgende Messagebox:



Wenn man jetzt auf OK drückt, dann kommt noch ein- oder zweimal diese Messagebox, aber dann kann es passieren, dass das Betriebssystem mit einem "Blue Screen of Death" abstürzt. Falls so eine Messagebox erscheinen sollte, dann SOFORT den Taskmanager mit dem "Affengriff" Strg-Alt-Entf (auch Ctrl-Alt-Entf) aufrufen und die Anwendung, die den Fehler verursacht hat, beenden.

Um gleich solche Probleme von Haus aus auszuschließen, verwendet man am besten adaptierte Funktionen, die den Wertebereich der Eingabe überprüfen:

// verhindert Abstürze wenn irrtümlich sqrt von negativer Zahl ermittelt wird
double sqrt_sec(double s)
{
  if (s<1e-35) s=0.0;
  return(sqrt(s));
}

// verhindert Abstürze wenn irrtümlich asin und acos von Zahl
// außerhalb des Bereiches -1 .. +1 ermittelt wird
double asin_sec(double s)
{
  if (s<-1.0) s=-1.0;
  if (s>1.0)  s=1.0;
  return(asin(s));
}

double acos_sec(double s)
{
  if (s<-1.0) s=-1.0;
  if (s>1.0)  s=1.0;
  return(acos(s));
}

// verhindert Probleme mit atan2 wenn beide Zahlen 0 sind
double atan2_sec(double x, double y)
{
  if (x==0.0 && y==0.0)
    return(PIE);
  else
    return(atan2(x,y));
}

Ebenso Vorsicht, dass man nicht eine Division durch 0 ausführt und das Ergebnis dann z. B. mit printf ausgeben will.
Dies führt zu einer Schutzverletzung und in weiterer Folge zum Programmabbruch!


Kreiszahl PI auf ungewöhnliche Weise ermitteln

Wie errechnet man die Zahl PI mit einer möglichst großen Stellenanzahl?
Bitte sehr, das kann doch ein "netter" Dreizeiler:

int f[9814],b,c=9814,g,i; long a=1e4,d,e,h;
main(){for(;b=c,c-=14;i=printf("%04d",e+d/a),e=d%a)
while(g=--b*2)d=h*b+a*(i?f[ b ]:a/5),h=d/--g,f[ b ]=d%g;}

Das gibt beim Compilieren zwar drei Warnungen, aber auch ein Ergebnis:
(zwei davon kann man durch #include <stdio.h> vermeiden)

314159265358979323846264338327950288419716939

(nur der Anfang der Ausgabe - zusätzlich fehlt das Komma nach der ersten Ziffer 3)
(Erklärung im Wikipedia )

Nun das ganze etwas übersichtlicher:

#include <stdio.h>

#define SIZE 9814   // muss durch 14 ohne Rest teilbar sein

 int main(void)
{
  int  m=14,f[SIZE],z=0;
  long a=10000,b,c,d,e=0,g,h=0,l;

  for (g=0; g<SIZE; g++)
    f[g]=0;

  for (c = SIZE-m;  c != 0; c -= m)
  {
    for (b = c+m-1; b != 0; --b)
    {
      d = h*b+a*(e ? f[b] : a/5);
      g = b*2-1;
      h = d/g;
      f[b] = d%g;
    }
    // printf("%04ld",e+d/a);
    l=e+d/a;
    if (!z) printf("\n %ld.%03ld", l/1000, l%1000);
      else  printf("%04ld", l);
    e = d%a;
    if (z++ % 14 == 13) printf("\n ");    // alle 14*4 Stellen eine neue Zeile
  }
  printf("\n Anzahl der Dezimalstellen: %d \n", z*4-1);
  return(0);
}

Diesmal erscheinen beim Compilieren (bcc32 pi.c) keine Warnungen und das EXE-File ist 52736 Bytes groß.
Die Ausgabe ist jetzt besser formatiert (50 Zeilen zu 56 Ziffern) und sogar das Komma wurde "dazugebastelt":

 3.1415926535897932384626433832795028841971693993751058209
 74944592307816406286208998628034825342117067982148086513
 ....
 18159813629774771309960518707211349999998372978049951059
 ....
 27166102135969536231442952484937187110145765403590279934
 
 Anzahl der Dezimalstellen: 2799

Interessant ist auch, dass in der 14. Zeile sechsmal hintereinander die Zahl '9' folgt (Dezimalstelle 762 - 767).

Um noch mehr Stellen zu erhalten, kann man SIZE 9814 durch eine andere größere Zahl ersetzen, die ohne Rest durch 14 teilbar ist.
(Wenn bei der Division durch 14 ein Rest bleiben sollte, dann crasht das Programm!)
Aber Vorsicht: Die Dauer der Berechnung wächst quadratisch mit der Größe von SIZE!
Bei 140000 war mein Computer schon fast eine halbe Minute beschäftigt, dafür waren dann 39995 Dezimalstellen berechnet.
Die Anzahl der berechneten Dezimalstellen ergibt sich wie folgt: (SIZE/14-1) * 4 - 1

Die maximal mögliche Größe von SIZE beträgt bei meinem System 258496; darüber hinaus funktioniert das Programm nicht mehr, weil dann der Stack, in dem die Variablen gespeichert werden, voll ist - und dieser beträgt standardmäßig ca. 1 MB. Abhilfe schafft die Verwendung von dynamisch reserviertem Speicher, welcher auf dem Heap abgelegt wird:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 1400000  // muss durch 14 ohne Rest teilbar sein

int main(void)
{
  int  m=14,*f,z=0;
  long a=10000,b,c,d,e=0,g,h=0,l;

  g=SIZE;
  if (g % 14)
  {
    printf("\n SIZE muss durch 14 ohne Rest teilbar sein!!! \n");
    return(1);
  }

  f = (int *) calloc(SIZE, sizeof(int));
  if (f==NULL)
  {
    printf("\n Nicht genug freier Speicher vorhanden!!! \n");
    return(2);
  }

  /*
  // nicht mehr notwendig weil calloc das bereits erledigt
  for (g=0; g<SIZE; g++)
    f[g]=0;
  */

  for (c = SIZE-m;  c != 0; c -= m)
  {
    for (b = c+m-1; b != 0; --b)
    {
      d = h*b+a*(e ? f[b] : a/5);
      g = b*2-1;
      h = d/g;
      f[b] = d%g;
    }
    // printf("%04ld",e+d/a);
    l=e+d/a;
    if (!z) printf("\n %ld.%03ld", l/1000, l%1000);
      else  printf("%04ld", l);
    e = d%a;
    if (z++ % 14 == 13) printf("\n ");    // alle 14*4 Stellen eine neue Zeile
  }
  if (z % 14) printf("\n");  // Bei Bedarf Leerzeile nach letzter Ziffer
  printf("\n Anzahl der Dezimalstellen: %d at SIZE %d \n", z*4-1, SIZE);
  printf("\n = (SIZE/14-1) * 4 - 1 = %d Anzahl der Dezimalstellen \n", (SIZE/14-1)*4-1);
  free(f);
  return(0);
}

Aber Vorsicht: Das Programm braucht je nach Prozessorleistung mehrere Minuten oder gar Stunden, bis es alle Stellen berechnet hat!

Ich hab mal versucht, den maximal möglichen Speicher zu reservieren:

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

#define BUFFERSIZE 900000000

int main(void)
{
  char     x;
  char     *szDataA,*szDataB,*szDataC,*szDataD;
  char     *szDataE,*szDataF,*szDataG,*szDataH;
  char     *szDataI,*szDataJ,*szDataK,*szDataL;
  char     *szDataM,*szDataN,*szDataO,*szDataP;
  unsigned int iBufferSize;
  __int64 iSumme=0;

  iBufferSize=BUFFERSIZE;
  do
  {
    iBufferSize/=10;
    iBufferSize*=9;
    szDataA = (char *) calloc(iBufferSize, sizeof(char));
  }
  while(!szDataA && iBufferSize);
  iSumme+=iBufferSize;
  printf("\n %10u Bytes = %6u kB = %3u MB fuer Block A reserviert ",iBufferSize,iBufferSize/1024,iBufferSize/1048576);

... wiederholen mit szDataB, szDataC, .. , szDataP;

#ifdef BORLANDC
  printf("\n\n %10Ld Bytes gesamt reserviert ",iSumme);
  printf("\n = %8Ld Megabytes ",iSumme/(1024*1024));
 #else
  printf("\n\n %10lld Bytes gesamt reserviert ",iSumme);
  printf("\n = %8lld Megabytes ",iSumme/(1024*1024));
 #endif

  printf("\n\n Bitte ESC-Taste zum Beenden druecken ");
  do
  {
    x=getch();
  } while (x!=27);
  printf("\n");

   printf("\n\n Der RAM wurde wieder freigegeben! ");
  printf("\n ");
  return(0);   // Das Beenden des Programmes gibt den Speicher auch wieder frei
}

Ergebnis: Auf meinem System konnten 2 Blöcke zu je 772 MB, 1 Block zu 269 MB, 1 Block zu 68 MB sowie 7 kleinere Blöcke von 11 bis 23 MB reserviert werden.
Gesamt ca. 2015 MB; das entspricht fast den 2 GB, die jedem Task zugeordnet werden können.
(natürlich kann das System zum Auslagern von Speicher auf die Festplatte anfangen, wenn nicht genug freier RAM vorhanden ist und das ganze Programm langsam ablaufen.)

Die Zahl PI mit tausenden Dezimalstellen ist zwar schön zum Ansehen, aber man kann mit ihr nicht rechnen.
Daher kann man die Zahl PI sinnvollerweise so ermitteln:

#include <stdio.h>
#include <math.h>

void main(void)
{
  long double pi;
  pi=atan(1.0)*4.0;
  printf("\n %.18Lf", pi);
  pi=M_PI;  // vordefiniert in math.h
  printf("\n %.18Lf", pi);
}

Das Programm beruht darauf, dass bei 45 Grad Sinus und Cosinus gleich groß sind und somit der Tangens genau 1.0 ist. Daher liefert die Umkehrfunktion Arkustangens von 1.0 genau 45 Grad; das ist im Bogenmaß ein Viertel von PI.

Ergebnis:

 3.141592653589793238
 3.141592653589793116

Das erste Resultat ist bei Verwendung von long double genauer, obwohl als die Definition in "math.h" lautet:

#define M_PI        3.14159265358979323846

Weil wir schon bei Kreisfunktionen sind:
Schon gewußt, dass bei kleinen Winkeln der Sinus ziemlich genau dem Winkel (im Bogenmaß) entspricht?

#include <stdio.h>
#include <math.h>

int main(void)
{
  int i;
  double r,s,d,p;
  for (i=0; i<=45; i+=3)
  {
    r = i * M_PI / 180.0;
    s = sin(r);
    d = r - s;
    if (r>0.0) p = d * 100.0 / r; else p = 0.0;  // Vorsicht Division durch Null vermeiden
    printf("\n %3d Grad = %.8f Rad Sinus = %.8f Diff. = %.8f (%5.4f %%) ",i,r,s,d,p);
  }
  printf("\n");
  return(0);
}

Hier muss man wieder aufpassen; denn eine Division durch Null ergibt eine Schutzverletzung und einen Programmabbruch als Folge!

Ergebnis:

   0 Grad = 0.00000000 Rad Sinus = 0.00000000 Diff. = 0.00000000 (0.0000 %)
   3 Grad = 0.05235988 Rad Sinus = 0.05233596 Diff. = 0.00002392 (0.0457 %)
   6 Grad = 0.10471976 Rad Sinus = 0.10452846 Diff. = 0.00019129 (0.1827 %)
   9 Grad = 0.15707963 Rad Sinus = 0.15643447 Diff. = 0.00064517 (0.4107 %)
  12 Grad = 0.20943951 Rad Sinus = 0.20791169 Diff. = 0.00152782 (0.7295 %)
  15 Grad = 0.26179939 Rad Sinus = 0.25881905 Diff. = 0.00298034 (1.1384 %)
  18 Grad = 0.31415927 Rad Sinus = 0.30901699 Diff. = 0.00514227 (1.6368 %)
  21 Grad = 0.36651914 Rad Sinus = 0.35836795 Diff. = 0.00815119 (2.2239 %)
  24 Grad = 0.41887902 Rad Sinus = 0.40673664 Diff. = 0.01214238 (2.8988 %)
  27 Grad = 0.47123890 Rad Sinus = 0.45399050 Diff. = 0.01724840 (3.6602 %)
  30 Grad = 0.52359878 Rad Sinus = 0.50000000 Diff. = 0.02359878 (4.5070 %)
  33 Grad = 0.57595865 Rad Sinus = 0.54463904 Diff. = 0.03131962 (5.4378 %)
  36 Grad = 0.62831853 Rad Sinus = 0.58778525 Diff. = 0.04053328 (6.4511 %)
  39 Grad = 0.68067841 Rad Sinus = 0.62932039 Diff. = 0.05135802 (7.5451 %)
  42 Grad = 0.73303829 Rad Sinus = 0.66913061 Diff. = 0.06390768 (8.7182 %)
  45 Grad = 0.78539816 Rad Sinus = 0.70710678 Diff. = 0.07829138 (9.9684 %) 

Bis 10 Grad beträgt der Fehler max. ca. 0,5 Prozent.



Assemblercode einbauen

Dieses Windows-Programm ermittelt die (momentan ungefähre) Taktfrequenz der CPU:

#include <windows.h>
#include <tchar.h>
#include <lmaccess.h>    // wegen NTSTATUS
#include <stdio.h>

#ifdef __BORLANDC__
typedef enum {
    SystemPowerPolicyAc,
    SystemPowerPolicyDc,
    VerifySystemPolicyAc,
    VerifySystemPolicyDc,
    SystemPowerCapabilities,
    SystemBatteryState,
    SystemPowerStateHandler,
    ProcessorStateHandler,
    SystemPowerPolicyCurrent,
    AdministratorPowerPolicy,
    SystemReserveHiberFile,
    ProcessorInformation,
    SystemPowerInformation,
    ProcessorStateHandler2,
    LastWakeTime,                                   // Compare with KeQueryInterruptTime()
    LastSleepTime,                                  // Compare with KeQueryInterruptTime()
    SystemExecutionState,
    SystemPowerStateNotifyHandler,
    ProcessorPowerPolicyAc,
    ProcessorPowerPolicyDc,
    VerifyProcessorPowerPolicyAc,
    VerifyProcessorPowerPolicyDc,
    ProcessorPowerPolicyCurrent,
    SystemPowerStateLogging,
    SystemPowerLoggingEntry
} POWER_INFORMATION_LEVEL;
#endif

typedef LRESULT (WINAPI * CALLNTPOWERINFORMATION)(POWER_INFORMATION_LEVEL, PVOID,
                 ULONG, PVOID, ULONG);

typedef struct _PROCESSOR_POWER_INFORMATION {
    ULONG                   Number;
    ULONG                   MaxMhz;
    ULONG                   CurrentMhz;
    ULONG                   MhzLimit;
    ULONG                   MaxIdleState;
    ULONG                   CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;

#if defined(__cplusplus) && !defined(INLINE)
  #define INLINE inline
#else
  #define INLINE
#endif

#ifdef __BORLANDC__
    void getRdtsc( UINT64* pResult )
    {
        _asm
        {
            db  0x0F
            db  0x31
            mov ebx, pResult
            mov [ebx], eax
            mov [ebx+4], edx
        }
    }

    UINT64 rdtsc(void)
    {
        UINT64 result;
        getRdtsc( &result );
        return result;
    }
#else
    inline UINT64 rdtsc(void)
    {
        _asm __emit 0x0F
        _asm __emit 0x31
    }
#endif

UINT_PTR GetCpuSpeed(VOID)
{
  PROCESSOR_POWER_INFORMATION ppi;
  CALLNTPOWERINFORMATION      pfn;
  LARGE_INTEGER               liFreq,li1,li2;
  OSVERSIONINFO               osvi;
  HINSTANCE                   hDll;
  NTSTATUS                    stat;
  UINT_PTR                    uSpeed;
  UINT64                      timestamp;
  HANDLE                      hThread;
  int                         nPriority;

  ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osvi);

  if (osvi.dwMajorVersion>=5)
  {
    ZeroMemory(&ppi, sizeof(ppi));
    stat = ERROR_CALL_NOT_IMPLEMENTED;

    if (NULL != (hDll = LoadLibrary(TEXT("powrprof.dll"))))
    {
        if (NULL != (pfn = (CALLNTPOWERINFORMATION)GetProcAddress(hDll,
                           "CallNtPowerInformation")))
        {
            stat = pfn(ProcessorInformation, NULL, 0, &ppi, sizeof(ppi));
        }
        FreeLibrary(hDll);
    }

    if(ERROR_SUCCESS == stat)
        return(ppi.CurrentMhz);
  }

  hThread   = GetCurrentThread();
  nPriority = GetThreadPriority(hThread);

  QueryPerformanceFrequency(&liFreq);
  liFreq.QuadPart /= 10;
  SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);

  timestamp = rdtsc();
  QueryPerformanceCounter(&li1);

  do
  {
    QueryPerformanceCounter(&li2);
  } while((li2.QuadPart - li1.QuadPart) < liFreq.QuadPart);

  uSpeed = (UINT_PTR) ((rdtsc() - timestamp) / 100000);
  SetThreadPriority(hThread, nPriority);
  return(uSpeed);
}

#ifdef __BORLANDC__
  #pragma argsused
#endif

#if defined(__cplusplus)
  int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
#else

#ifdef UNICODE
int CALLBACK            wWinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPWSTR              lpCmdLine,
    int                 nShowCmd
    )
#else
int CALLBACK            WinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPSTR               lpCmdLine,
    int                 nShowCmd
    )
#endif
#endif
{
  TCHAR szSpeed[128];
  wsprintf(szSpeed, TEXT("CPU-Speed: ~%u MHz \n"), GetCpuSpeed());
  MessageBox(NULL, szSpeed, TEXT(" Info "), MB_OK);
  return(0);
}

Ab Windows 2000 wird versucht, auf die "powrprof.dll" zuzugreifen und die momentane Taktfrequenz zu ermitteln.
Sollte dies nicht möglich sein, dann wird die Taktfrequenz mittels einer Assembler-Routine ermittelt.
Compiliert wird das Programm mit "BCC32 -W CPUSPEED.C".
Doch es funktioniert nicht, sondern es erscheint folgende Fehlermeldung:

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
CPUSPEED.C:
Warning W8002 CPUSPEED.C 57: Restarting compile using assembly in function getRdtsc
Error E2133: Unable to execute command 'tasm32.exe'

Aha, die Fehlermeldung deutet darauf hin, dass man den Turboassemlber "tasm32.exe" benötigt, damit man Assemblercode compilieren kann. Diesen kopiert man am Besten in das BIN-Verzeichnis des Borland-Compilers oder macht ihn durch den Pfad "PATH" erreichbar. Siehe da jetzt funktioniert es:

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
CPUSPEED.C:
Warning W8002 CPUSPEED.C 57: Restarting compile using assembly in function getRdtsc
Turbo Assembler  Version 5.2  Copyright (c) 1988, 1999 Inprise Corporation

Assembling file:   CPUSPEED.ASM
Error messages:    None
Warning messages:  None
Passes:            1

Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

Das Ergebnis ist eine EXE-Datei  namens "CPUSPEED.EXE" mit 48.128 Bytes Dateigröße.
Nun starten wir das Programm:



Noch ein Tipp: Man kann zur Not auch die Datei "TASM.EXE" von Borland C++ 3.1 nehmen und diese in "TASM32.EXE" umbenennen.
Diese 16Bit-Version funktioniert aber nicht mit einem 64Bit-Betriebssystem.
(Getestet mit Version mit Dateidatum vom 11.06.1992 und Dateigröße von 129266 Bytes.)


DLLs erstellen und anwenden

Wir erstellen jetzt aus dem vorigen Assembler Programm eine DLL:

#include <windows.h>
#include <tchar.h>
#include <lmaccess.h>    // needs NTSTATUS
#include <stdio.h>

#ifdef __BORLANDC__
typedef enum {
    SystemPowerPolicyAc,
    SystemPowerPolicyDc,
    VerifySystemPolicyAc,
    VerifySystemPolicyDc,
    SystemPowerCapabilities,
    SystemBatteryState,
    SystemPowerStateHandler,
    ProcessorStateHandler,
    SystemPowerPolicyCurrent,
    AdministratorPowerPolicy,
    SystemReserveHiberFile,
    ProcessorInformation,
    SystemPowerInformation,
    ProcessorStateHandler2,
    LastWakeTime,                                   // Compare with KeQueryInterruptTime()
    LastSleepTime,                                  // Compare with KeQueryInterruptTime()
    SystemExecutionState,
    SystemPowerStateNotifyHandler,
    ProcessorPowerPolicyAc,
    ProcessorPowerPolicyDc,
    VerifyProcessorPowerPolicyAc,
    VerifyProcessorPowerPolicyDc,
    ProcessorPowerPolicyCurrent,
    SystemPowerStateLogging,
    SystemPowerLoggingEntry
} POWER_INFORMATION_LEVEL;
#endif

typedef LRESULT (WINAPI * CALLNTPOWERINFORMATION)(POWER_INFORMATION_LEVEL, PVOID,
                 ULONG, PVOID, ULONG);

typedef struct _PROCESSOR_POWER_INFORMATION {
    ULONG                   Number;
    ULONG                   MaxMhz;
    ULONG                   CurrentMhz;
    ULONG                   MhzLimit;
    ULONG                   MaxIdleState;
    ULONG                   CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;

#ifdef __BORLANDC__
    void getRdtsc( UINT64* pResult )
    {
        _asm
        {
            db  0x0F
            db  0x31
            mov ebx, pResult
            mov [ebx], eax
            mov [ebx+4], edx
        }
    }

    UINT64 rdtsc(void)
    {
        UINT64 result;
        getRdtsc( &result );
        return result;
    }
#else
    inline UINT64 rdtsc(void)
    {
        _asm __emit 0x0F
        _asm __emit 0x31
    }
#endif

UINT_PTR GetCpuSpeed(VOID)

{
  PROCESSOR_POWER_INFORMATION ppi;
  CALLNTPOWERINFORMATION      pfn;
  LARGE_INTEGER               liFreq,li1,li2;
  OSVERSIONINFO               osvi;
  HINSTANCE                   hDll;
  NTSTATUS                    stat;
  UINT_PTR                    uSpeed;
  UINT64                      timestamp;
  HANDLE                      hThread;
  int                         nPriority;

  ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osvi);

  if (osvi.dwMajorVersion>=5)
  {
    ZeroMemory(&ppi, sizeof(ppi));
    stat = ERROR_CALL_NOT_IMPLEMENTED;

    if (NULL != (hDll = LoadLibrary(TEXT("powrprof.dll"))))
    {
        if (NULL != (pfn = (CALLNTPOWERINFORMATION)GetProcAddress(hDll,
                           "CallNtPowerInformation")))
        {
            stat = pfn(ProcessorInformation, NULL, 0, &ppi, sizeof(ppi));
        }
        FreeLibrary(hDll);
    }

    if(ERROR_SUCCESS == stat)
        return(ppi.CurrentMhz);
  }

  hThread   = GetCurrentThread();
  nPriority = GetThreadPriority(hThread);

  QueryPerformanceFrequency(&liFreq);
  liFreq.QuadPart /= 10;
  SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);

  timestamp = rdtsc();
  QueryPerformanceCounter(&li1);

  do
  {
    QueryPerformanceCounter(&li2);
  } while((li2.QuadPart - li1.QuadPart) < liFreq.QuadPart);

  uSpeed = (UINT_PTR) ((rdtsc() - timestamp) / 100000);
  SetThreadPriority(hThread, nPriority);
  return(uSpeed);
}

__declspec(dllexport) int CpuSpeed(void)

{
  return((int) GetCpuSpeed());
}

#ifdef __BORLANDC__
  #pragma argsused
#endif

BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
  return TRUE;
}

Compiliert wird mit "BCC32 -WD CPUSPEED.C":

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
CPUSPEED.C:
Warning W8002 CPUSPDLL.C 51: Restarting compile using assembly in function getRdtsc
Turbo Assembler  Version 5.2  Copyright (c) 1988, 1999 Inprise Corporation

Assembling file:   CPUSPDLL.ASM
Error messages:    None
Warning messages:  None
Passes:            1

Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

Das Ergebnis ist eine Datei namens "CPUSPEED.DLL" mit einer Dateigröße von 47104 Bytes, welche wir gleich in einem Programm verwenden wollen:

#include <windows.h>

typedef int __stdcall TGetCpuSpeed(void);

#ifdef UNICODE
  #define strcpy lstrcpy

int CALLBACK            wWinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPWSTR              lpCmdLine,
    int                 nShowCmd
    )
#else
int CALLBACK            WinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPSTR               lpCmdLine,
    int                 nShowCmd
    )
#endif
{
  HINSTANCE             hDll;
  TCHAR                 szSpeed[128];

  hDll = LoadLibrary(TEXT("cpuspeed.dll"));

  if (!hDll)
    strcpy(szSpeed, TEXT("DLL Error - \npossibly DLL not found    "));
  else
  {
    TGetCpuSpeed* GetSpeed=(TGetCpuSpeed*) GetProcAddress(hDll, "_CpuSpeed");

    if (GetSpeed)
      wsprintf(szSpeed, TEXT(" CPU-Speed: ~%d MHz    \n"),(*GetSpeed) ());
    else
      strcpy(szSpeed, TEXT(" DLL Function Error "));
  }

  MessageBox(NULL, szSpeed, TEXT(" Info "), MB_OK);
  FreeLibrary(hDll);
  return(0);
}

Basierend auf diesem Programm erstellen wir nun eines, was noch mehr Informationen anzeigt:

#include <windows.h>

typedef int __stdcall TGetCpuSpeed(void);

#ifdef UNICODE
  #define strcpy lstrcpy
  #define strcat lstrcat
int CALLBACK            wWinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPWSTR              lpCmdLine,
    int                 nShowCmd
    )
#else
int CALLBACK            WinMain
    (
    HINSTANCE           hInstance,
    HINSTANCE           hPrevInst,
    LPSTR               lpCmdLine,
    int                 nShowCmd
    )
#endif
{
  int                   iMHz=0,maj,min;
  TCHAR                 szPuffer[512];
  TCHAR                 szSpeed[2048];
  OSVERSIONINFO         osvi;
  MEMORYSTATUS          memstat;
  SYSTEM_INFO           lpSystemInfo;
#ifndef _WIN64
  HINSTANCE             hDll;
#endif

  ZeroMemory(&memstat, sizeof(MEMORYSTATUS));
  GlobalMemoryStatus(&memstat);

  wsprintf(szPuffer, TEXT("\n\n Speicherbelegung: \t%i%% \t\t "), memstat.dwMemoryLoad);
  strcpy(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n\n Physikalischer Speicher: \t%i MB "), memstat.dwTotalPhys/(1024*1024));
  strcat(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n Freier physik. Speicher: \t%i MB "), memstat.dwAvailPhys/(1024*1024));
  strcat(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n Maximum Pagespeicher: \t%i MB "), memstat.dwTotalPageFile/(1024*1024));
  strcat(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n Freier Pagespeicher: \t%i MB "), memstat.dwAvailPageFile/(1024*1024));
  strcat(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n Virtueller Speicher: \t%i MB "), memstat.dwTotalVirtual/(1024*1024));
  strcat(szSpeed, szPuffer);

  wsprintf(szPuffer, TEXT("\n Freier virtueller Speicher: \t%i MB "), memstat.dwAvailVirtual/(1024*1024));
  strcat(szSpeed, szPuffer);

  // Anzahl der Prozessoren ermitteln

  ZeroMemory(&lpSystemInfo, sizeof(SYSTEM_INFO));
  GetSystemInfo(&lpSystemInfo);

  wsprintf(szPuffer, TEXT("\n\n Anzahl der Prozessoren: \t%d "),
                     lpSystemInfo.dwNumberOfProcessors);
  strcat(szSpeed, szPuffer);

  // CPU-Frequenz detektieren

  if (NULL != (hDll = LoadLibrary(TEXT("cpuspeed.dll"))))
  {
    TGetCpuSpeed* GetSpeed=(TGetCpuSpeed*) GetProcAddress(hDll, "_CpuSpeed");
    if (GetSpeed) iMHz=(*GetSpeed) ();
    FreeLibrary(hDll);
  }

  if (iMHz)
  {
    wsprintf(szPuffer, TEXT("\n CPU-Frequenz: \t~%d MHz"), iMHz);
    strcat(szSpeed, szPuffer);
  }

  // Betriebssystem Version ermitteln

  ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osvi);
  maj=osvi.dwMajorVersion;
  min=osvi.dwMinorVersion;

  strcat(szSpeed, TEXT("\n\n Betriebssystem Version: "));
  wsprintf(szPuffer, TEXT("\n\n Hauptversion: \t%d "), maj);
  strcat(szSpeed, szPuffer);
  wsprintf(szPuffer, TEXT("\n Unterversion: \t%d "), min);
  strcat(szSpeed, szPuffer);
  strcat(szSpeed, TEXT("\n\n"));

  MessageBox(NULL, szSpeed, TEXT(" Info "), MB_OK);
  FreeLibrary(hDll);
  return(0);
}

Compiliert wird wie gehabt mit "BCC32 -W CPUTOTAL.C".
Das Ergebnis ist ein Programm namens "CPUTOTAL.EXE" mit 48128 Bytes Dateigröße.
Nun starten wir das Programm:



Info: GlobalMemoryStatus zeigt max. 2 GB RAM an, auch wenn mehr installiert ist. Dafür gibt es GlobalMemoryStatusEx, aber dieses funktioniert erst ab Windows 2000. Wenn man ein Programm mit dieser Funktion unter Windows NT4 oder Windows 98 aufruft, dann verweigert es den Start. Da hilft auch keine Betriebssystemversionsabfrage. Eine Möglichkeit wäre, diese Funktion in eine DLL auszulagern, die Betriebssystemversion abzufragen und erst ab Windows 2000 (MajorVersion>=5) die DLL einzubinden. Wird die DLL nicht gefunden oder ist das Betriebssystem ungeeignet, dann kann man immer noch GlobalMemoryStatus abfragen.

Info: Ein 32Bit-Programm kann nur auf eine 32Bit-DLL zugreifen, aber nicht auf eine 16Bit-DLL oder 64Bit-DLL.
Folgende interessante Fehlermeldung gab es, als ich versucht habe, mit einem 64Bit-Programm auf eine 32Bit-DLL zuzugreifen:



Info:

Das Fenster poppt nicht von alleine auf; die Variable hDLL bekommt den Wert NULL; if (hDLL) wird nicht ausgeführt.
Dann kann man folgendermaßen den zu letzt aufgetretenen Fehler abfragen:

void ZeigeLetztenFehler(void)
{
  LPVOID lpMsgBuf;
  TCHAR  *szFehler=TEXT("Last Error Message");

  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                NULL,
                GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR) &lpMsgBuf,
                0,
                NULL);
 
  MessageBox(NULL, (LPCTSTR)lpMsgBuf, szFehler, MB_OK | MB_ICONERROR );
  LocalFree(lpMsgBuf);
}

Hier ein Beispiel wie man einer DLL mehrere Parameter (auch Zeiger) übergeben kann:

int moon(double jdb, double *lam, double *bet, double *rho)
{
  double        pobj[3], dt;
  HINSTANCE     hDll;
  typedef void (*MOONINFO) (double, double *);
  MOONINFO mooninfo;

  hDll = LoadLibrary(TEXT("moondll.dll"));

  if (hDll)
  {
    mooninfo=(MOONINFO) GetProcAddress(hDll, "_moon2000x");
    if (mooninfo)
    {
      mooninfo(jdb, pobj);
      *lam = pobj[0];
      *bet = pobj[1];
      *rho = pobj[2];
      FreeLibrary(hDll);
      return(0);   // erfolgreich
    }
    FreeLibrary(hDll);
    return(2);   // Fehler aufgetreten, Funktion in DLL nicht gefunden
  }
  return(1);   // Fehler aufgetreten, DLL nicht gefunden
}



Nette Kleinigkeiten mit #define

Gleitkommazahl in Integer runden:

#define roundtoint(v) (int)(((v*10.0)+5)/10)

ab x.5 wird auf x+1 aufgerundet, sonst auf x abgerundet.
Also aus 1.1 wird 1 und 1.5 wird 2.
Vorsicht: Funktioniert nicht bei negativen Zahlen!
(-1.5 ergibt -1; -2.1 ergibt -1; -2.5 ergibt -2; -3.0 ergibt -2)

Winkel von Grad (Degree) in Radiant umrechnen (und umgekehrt):

#define degrad(d) ((d) * (M_PI / 180.0))   // deg->rad
#define raddeg(d) ((d) * (180.0 / M_PI))   // rad->deg


Winkel auf den Wertebereich von 0 .. 360 Grad bzw. 0 .. 2x PI fixieren:

#define fixangle(a)    ((a) - 360.0 * (floor((a) / 360.0)))  // Fix angle degree
#define fixanglerad(a) ((a) -   M_PI*2.0* (floor((a) / (M_PI*2.0))))    // Fix angle radiant




Zugriff auf das Internet

Auch auf Dateien vom Internet zuzugreifen ist gar nicht schwer.
Wir versuchen jetzt vom Suse FTP-Server eine Datei einzulesen:

#include <windows.h>
#include <wininet.h>
#include <stdio.h>
#include <conio.h>

int main(void)
{
  HINTERNET hIS, hIC, hIF;
  DWORD     dwBytes,dwSize,lpdwFileSizeHigh;
  int       l=0,s=0,n=0;
  char      c,x;

  hIS = InternetOpen("FTPGET", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
  hIC = InternetConnect(hIS, "ftp.suse.com",
                        INTERNET_DEFAULT_FTP_PORT, NULL, NULL,
                        INTERNET_SERVICE_FTP, 0, 0);
  hIF = FtpOpenFile(hIC, "pub/README.mirror-policy", GENERIC_READ,
                    FTP_TRANSFER_TYPE_ASCII, 0);

  dwSize=FtpGetFileSize(hIF, &lpdwFileSizeHigh);

  printf("\n Text von ftp.suse.com/pub/README.mirror-policy: ");
  printf("\n =============================================== ");
  printf("\n (Dateigröße: %d Bytes) ",dwSize);  // Umlautproblematik beachten
  printf("\n\n ");

  while (InternetReadFile(hIF, &c, 1, &dwBytes) && !s)
  {
    if (dwBytes != 1) break; else n++;
    if (c==0xA) l++;
    putchar(c);
    if (l==15)
    {
      l=0;
      printf("\n <Bitte Taste druecken (ESC=Ende)> ");
      x=getch();
      if (x==0x1B) s=1;
      printf("\n ");
    }
  }

  if (s) printf("\n Textausgabe wurde vorzeitig abgebrochen! ");
  printf("\n Anzahl der gelesenen Zeichen: %d ",n);
  printf("\n ");

  InternetCloseHandle(hIF);
  InternetCloseHandle(hIC);
  InternetCloseHandle(hIS);
  return(0);
}

Compiliert wird mit "bcc32 -O2 ftptest.c -I wininet.lib" (Die wininet.lib wird nicht automatisch eingebunden). Wenn man das Programm jetzt startet und man ohne Proxy direkt mit dem Internet verbunden ist, dann sollte der Inhalt der Datei auf dem Bildschirm erscheinen. Es kann auch sein, dass sich die Firewall meldet und man dem Programm erst den Zugriff auf das Internet freigeben muss.

 Text von ftp.suse.com/pub/README.mirror-policy:
 ===================================
 (Dateigröße: 2277 Bytes)

 Dear Admin,

Servers with popular content are usually highly loaded both in terms
of the I/O subsystem of the hardware and the bandwidth consumtion of
the network. Due to strict limitations imposed on these factors, we
are grateful for offers to mirror our directories on publically
available servers for the non-commercial purpose of providing
download service.

....

 / _| |_ _ __   ___ _   _ ___  ___   ___ ___  _ __ ___ 
| |_| __| '_ \ / __| | | / __|/ _ \ / __/ _ \| '_ ` _ \
|  _| |_| |_) |\__ \ |_| \__ \  __/| (_| (_) | | | | | |
|_|  \__| .__(_)___/\__,_|___/\___(_)___\___/|_| |_| |_|
        |_|                                            

 Anzahl der gelesenen Zeichen: 2277
 

Hier ein Beispiel für das Einlesen einer Datei von einem http-Server:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wininet.h>

int    iBufferSize=600000;
char   *szDatPuffer;

void GetFileFromWeb(TCHAR *szStar)
{
  int           i,l,iSwProxy;
  DWORD         dwSize;
  HINTERNET     hIS, hIC, hIF;
  TCHAR         szProxy[128];
  TCHAR         szServer[128];
  TCHAR         szFile[512];

  l=(int) strlen(szStar);
  for (i=0; i<l; i++)
    if (szStar[i]==' ') szStar[i]='+';

  strcpy(szProxy, "proxy.aon.at");    // oder z.B. "192.168.0.1:8080" oder "" wenn kein Zugriff über Proxy
  strcpy(szServer, "simbad.u-strasbg.fr");
  strcpy(szFile, "simbad/sim-id?Ident=");
  strcat(szFile, szStar);
  strcat(szFile, "&NbIdent=1&Radius=2&Radius.unit=arcmin&submit=submit+id");

  ZeroMemory(szDatPuffer, iBufferSize);
  if (strlen(szProxy)) iSwProxy=1; else iSwProxy=0;

  if (!iSwProxy)   // http - Datei vom Server holen - direkt
    hIS = InternetOpen(TEXT("SIMBAD"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
  else             // http - Datei vom Server holen - via Proxy
     hIS = InternetOpen(TEXT("SIMBAD"), INTERNET_OPEN_TYPE_PROXY, szProxy, NULL, 0);

   hIC = InternetConnect(hIS, szServer, INTERNET_DEFAULT_HTTP_PORT,
         "ImageLoad", NULL, INTERNET_SERVICE_HTTP, 0, 1);
   hIF = HttpOpenRequest(hIC, "GET", szFile, "HTTP/1.1", NULL, NULL,
         INTERNET_FLAG_CACHE_IF_NET_FAIL, 0);

   /*  // so könnte man noch die Dateigröße ermitteln
  dwLength=0;
  dwLengthSize=sizeof(DWORD);
   fSuccess = HttpQueryInfo(hIF, HTTP_QUERY_CONTENT_LENGTH|
                    HTTP_QUERY_FLAG_NUMBER, &dwLength, &dwLengthSize, 0);
   */

  if (HttpSendRequest(hIF, NULL, 0, NULL, 0))
  {
    InternetReadFile(hIF, (LPVOID) szDatPuffer, iBufferSize-10, &dwSize);
    if (dwSize!=0) szDatPuffer[dwSize]='\0';
  }
  InternetCloseHandle(hIF);
  InternetCloseHandle(hIC);
  InternetCloseHandle(hIS);
}

int main(int argc, char *argv[])
{
  TCHAR         szStar[300];
  int           iStar;

  if (argc<2)
    strcpy(szStar, "sirius");
  else
    strcpy(szStar, argv[1]);

  do
  {
    iBufferSize=iBufferSize*9/10;
    szDatPuffer = (char *) calloc(iBufferSize, sizeof(char));
  }
  while(!szDatPuffer && iBufferSize);

  GetFileFromWeb(szStar);

  // szDatPuffer sollte jetzt mit dem Inhalt der Webseite gefüllt sein

  free(szDatPuffer);
  return(0);
}



Warnungen bei deklarierten aber nicht verwendeten Variablen

Des öfteren definiert man Variablen, die man dann doch nicht verwendet. So es ist praktisch, beim Compilieren darauf hingewiesen zu werden.
(Besonders beim Compilerlauf vor der Veröffentlichung eines Programmes.)

Ergänzen wir die Zeile mit der Deklaration der long-Variablen der übersichtlichen Berechnung von PI mit 2799 Dezimalstellen wie folgt:

  long a=10000,b,c,d,e=0,g,h=0,l,x;

Hier wurde die long-Variable x absichtlich deklariert und nicht verwendet. Mann muss die Compiler-Option "-wuse" angeben, damit man darauf hingewiesen wird:

bcc32 -wuse PI.C

(Info: bcc32 PI.C -wuse ist falsch. Es wird zwar das Programm compiliert, aber es gibt keine Warnung.)

Ergebnis:

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
PI.C:
Warning W8080 PI.C 32: 'x' is declared but never used in function main
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

Aha, in Zeile 32 wurde die Variable x deklariert aber sie wird in der Funktion main nie verwendet.


Windowsanwendungen ohne WinMain erstellen


Man kann auch Windowsanwendungen nur mit main() (bzw. wmain() bei Unicode) ganz ohne WinMain() bzw. wWinMain() erstellen.
Hier der Beispielcode:

#include <windows.h>

// bei Bedarf aktivieren
#if defined(UNICODE) || defined(_UNICODE)

 // #define strcpy lstrcpy
 // #define strcat lstrcat
 // #define strcmp wcscmp
 // #define strstr wcsstr
 #define strlen wcslen
#endif

void CommandLineCutter(TCHAR *szCommand, TCHAR *szParameter)

{
  int i,j,l,p=0,s=0;

  lstrcpy(szParameter, szCommand);

  // beim ersten Leerzeichen abbrechen
  // - aber nicht innerhalb von ".. .."

  l=(int) strlen(szCommand);
  for (i=0; i<l; i++)
  {
    if (szCommand[i]==TEXT('\"'))
      {s=1-s; continue;}
    if (!s && szCommand[i]==TEXT(' '))
      {szCommand[i]=TEXT('\0'); p=i; break;}
  }
  if (!p) p=l;

  // für den Parameterstring den erkannten
  // Commandstring durch Leerzeichen ersetzen

  for (i=0; i<p; i++)
    szParameter[i]=TEXT(' ');

  // überflüssige Leerstellen zu Beginn entfernen

  for (i=0; i<l; i++)
    if (szParameter[0]==' ')
      for (j=0; j<l; j++)
        szParameter[j]=szParameter[j+1];
}

#if defined(UNICODE) || defined(_UNICODE)
  int wmain(void)
#else
  int main(void)
#endif

{
  TCHAR szInfo[64];
  TCHAR szCommand[MAX_PATH];
  TCHAR szParameter[MAX_PATH];
  TCHAR szText[MAX_PATH*4];

#ifdef __cplusplus
  lstrcpy(szInfo, TEXT(" C++ "));
#else
  lstrcpy(szInfo, TEXT(" C "));
#endif

#ifdef UNICODE
  lstrcat(szInfo, TEXT("Info (Unicode) "));
#else
  lstrcat(szInfo, TEXT("Info (kein Unicode) "));
#endif

  lstrcpy(szText, TEXT("\n Commandline = \n \""));
  lstrcat(szText, GetCommandLine());
  lstrcat(szText, TEXT("\""));

  lstrcpy(szCommand, GetCommandLine());
  lstrcat(szText, TEXT("\n\n Commandline aufgeteilt in Programm und Parameter:             \n\n"));
  CommandLineCutter(szCommand, szParameter);
  lstrcat(szText, szCommand);
  lstrcat(szText, TEXT("\n"));
  lstrcat(szText, szParameter);

  // falls benötigt
  // hInstance = GetModuleHandle(NULL);
  MessageBox(NULL, szText, szInfo, MB_OK);

  return(0);
}

compilieren muss man mit:

bcc32 -O2 -laa progname.c bzw. für Unicode mit bcc32 -O2 -laa -WU progname.c

Vergißt man den -laa Paramter, dann erstellt der Compiler eine Consolenanwendung, aus welcher dann die Messagebox hervorpoppt.

Das Programm ist ein Beispiel wie man mit GetCommandLine() die Befehlszeile bekommt und diese in zwei Teile teilt.
Ruft man das Programm z.B. mit

progname 1234abc

auf, dann liefert GetCommandLine() folgenden String:

progname 1234abc

dieser wird dann aufgeteilt in die beiden Strings szCommand und szParameter:

progname
1234abc

Das funktioniert auch unter Unicode und sogar dann, wenn der Programmname Leerzeichen enthält:






Noch mehr Assembler verwenden

Ruft man BCC32 mit dem Parameter -S <Dateiname.c> , dann erstellt der Compiler eine Assembler(quellcode)-Datei mit der Endung .asm.

Wenn man folgnde Datei namens "TEST.C" mit
BCC32 -S test.c
"assembliert", dann erhält man folgendes Ergebnis:

int multest(int x, int y, int z)
{
  int d;
  d=x*y;
  d=d/z;
  return d;
}

(auszugsweise)

   ;    int multest(int x, int y, int z)
    push      ebp
    mov       ebp,esp
   ;    {
   ;      int d;
   ;      d=x*y;
@1:
    mov       ecx,dword ptr [ebp+8]
    imul      ecx,dword ptr [ebp+12]
   ;      d=d/z;
?live1@32: ; ECX = d
    mov       eax,ecx
    cdq
    idiv      dword ptr [ebp+16]
    mov       ecx,eax
   ;      return d;
    mov       eax,ecx
   ;    }

Gut und schön, aber wir können das mit weniger Befehlen erreichen:
(besonders interessant ist, dass das Ergebnis schon in eax steht, aber dennoch nach ecx kopiert wird, um dann doch wieder zurück zu eax kopiert zu werden)

#include <stdio.h>

#ifdef __BORLANDC__
  // disable warning: function should return a value (Default ON)
  #pragma warn -rvl -8070
  // disable warning: Restarting compile using assembly (Default ON)
  #pragma warn -asc -8002
#endif

int muldivtest(int x, int y, int z)

{
  _asm
  {
    mov  eax,x
    mov  ebx,y
    mov  ecx,z
    imul eax,ebx
    cdq
    idiv ecx
  }
  // EAX enthält das Ergebnis
}

#ifdef __BORLANDC__
  #pragma warn +rvl +8070
#endif

int main(void)

{
  printf("\n Ergebnis: %d \n",muldivtest(20, 30, 12));
  return(0);
}

Compiliert wird mit BCC32 test.c.(ev. noch mit den Parametern -O2 -wuse -wsig)
Wenn man dann das Programm startet, dann kommt als Ergebnis wie erwartet 50 heraus.
(20*30 = 600 / 12 = 50)

Neugierige können sich mit BCC32 -S test.c ansehen, wie die Funktion muldivtest() diesmal in Assemblercode umgewandelt wird.
Die #pragma warn verhindern unnötige Warnungen (testweise mal auskommentieren).


Kalenderfunktionen in Assembler encodiert

Weil wir schon bei Assembler sind, dann wollen wir doch gleich zwei Funktionen, die man zur Kalenderrechnung benötigt in Assembler encodieren.
Die erste berechnet den Wochentag aus einem Datum oder gibt 0 zurück, wenn das Datum ungültig ist:

/*
 * gibt Wochentag zurück                            
 * 1  = Sonntag, 2 = Montag, ... , 7 = Samstag      
 * 0  = Datum ungültig z.B. 29.2.2007, 32.12.2007   
 * neu: auch julianischer Kalender wird berücksichtigt
 * 1.1.0001 - 4.10.1582 jul., ab 15.10.1582 greg.
 * (date format dd/mm/yyyy)
 */

int dayofweek(int d, int m, int y)

{
  int s=m&1; if (m==2) s-=2; if (m>7) s=1-s;
  if (m==2 && !(y & 3)) {s++; if (y>1582 && !(y % 100) && y % 400) s--;}
  if (y<1 || y>9999 || m<1 || m>12 || d<1 || d>s+30 ||
     (y==1582 && m==10 && d>4 && d<15)) return(0);
  if (m++ < 3) {m += 12; y--;}
  s = d + 26*m/10 + y + y/4 + 4;  // julianisch
  if (y>1582 || (y==1582 && m>11) || (y==1582 && m==11 && d>14))
     s += y/400 - y/100 + 2;      // gregorianisch
  return(s % 7 + 1);
}

wird zu

#ifdef __BORLANDC__
  // disable warning: function should return a value (Default ON)
  #pragma warn -rvl -8070
  // disable warning: Restarting compile using assembly (Default ON)
  #pragma warn -asc -8002
#endif

int dayofweekasm(int d, int m, int y)

{
  _asm
  {
    mov  edi, d
    mov  eax, m
    mov  ecx, y

    // if (y<1583 || y>9999 || m<1 || m>12) return(0);

    cmp  eax, 1
    jb   LABELERROR
    cmp  eax, 12
    jg   LABELERROR

    cmp  ecx, 1
    jb   LABELERROR
    cmp  ecx, 9999
    jg   LABELERROR

    // m % 2 (m & 1) esi = s

    mov  esi, eax
    and  esi, 1

    // if (m>7) s=1-s; (invert bit 0)

    cmp  eax, 8
    jb   LABELMON
    not  esi
    and  esi, 1

  LABELMON:

    // if (m==2) {s=-2;
    // if (y % 4==0)   s++;}  // y & 3

    // if (y>1582) {
    // if (y % 100==0) s--;
    // if (y % 400==0) s++; }

    // esi = s  ecx = y   eax = m

    cmp  eax, 2
    jne  LABELNOFEB
    sub  esi, 2

    mov  eax, ecx
    and  eax, 3
    cmp  eax, 0
    jne  LABELFEB
    inc  esi

    cmp  ecx, 1583
    jb   LABELFEB

    mov  eax, ecx
    mov  ebx, 100
    cdq
    idiv ebx
    cmp  edx, 0
    jne  LABELFEB
    dec  esi

    and  eax, 3
    cmp  eax, 0
    jne  LABELFEB
    inc  esi

  LABELFEB:

    mov  eax, 2   // restore February

  LABELNOFEB:

    // esi = s  ecx = y   eax = m   edi = d

    // if (d<1 || d>s+30) return(0);

    cmp  edi, 1
    jb   LABELERROR
    add  esi, 30
    cmp  edi, esi
    jg   LABELERROR

    // if (m++<3) {m+=12; y--;}

    inc  eax
    cmp  eax, 3
    jg   LABELMONYEAR

    dec  ecx
    add  eax, 12

  LABELMONYEAR:

    // ecx = y   eax = m   edi = esi = d
    // edi is used for sum
    // + y + 4

    mov  esi, edi
    add  edi, ecx
    add  edi, 4

    // 26*m/10

    push eax
    imul eax, 26
    mov  ebx, 10
    cdq
    idiv ebx
    add  edi, eax
    pop  eax

    // Test if jul. or greg. or invalid

    cmp  ecx, 1582
    jb   LABELBOTH
    jg   LABELGREG
    cmp  eax, 11
    jb   LABELBOTH
    jg   LABELGREG
    cmp  esi, 5
    jb   LABELBOTH
    cmp  esi, 15
    jb   LABELERROR

  LABELGREG:

    // - y/100

    mov  eax, ecx
    mov  ebx, 100
    cdq
    idiv ebx
    sub  edi, eax

    // + y/400 + 2
    // eax = (already) y / 100

    sar  eax, 2
    add  edi, eax
    add  edi, 2

  LABELBOTH:

    // + y/4

    sar  ecx, 2
    add  edi, ecx

    // % 7 + 1

    mov  eax, edi
    mov  ebx, 7
    cdq
    idiv ebx
    inc  edx
    jmp  LABELGOOD

  LABELERROR:

    // edx = 0 if date is invalid
    xor  edx, edx

  LABELGOOD:

    mov  eax, edx
  }
  // EAX enthält das Ergebnis
}

#ifdef __BORLANDC__
  #pragma warn +rvl +8070
#endif

Bei der Assemblerversion kann man den Inhalt, welcher in einer C-Zeile steht im Code so verteilen, wie es am praktischen ist.
Es wird z.B. gleich zu Beginn auf ungültige Monate und Jahre geprüft und dann erst, wenn s feststeht, auf ungültige Tage.
(in C ist das in einer Zeile praktischer und übersichtlicher)

Die zweite berechnet das Datum des Ostersonntages (benötigt für die beweglichen Feiertage):

/*
 * Osterformel nach Meeus
 *
 * Rückgabewert:
 * bis 31: März
 * ab  32: April
 *
 * ab 1583    gregorianisch
 * bis 1582   julianisch
 */

int eastersundaymeeus(int y)

{
  int a,b,c,d,e,f,g,h,i,j,k,l,m=0;

  j = y % 19;

  if (y<1583)
  {
    // Julianischer Kalender
    a = y & 3;      // y % 4
    c = y % 7;
    h = (19*j + 15) % 30;
    l = (2*a + 4*c - h + 34) % 7;
  }
  else
  {
    // Gregorianischer Kalender
    b = y / 100;
    c = y % 100;
    d = b / 4;
    e = b & 3;      // b % 4
    i = c / 4;
    k = c & 3;      // c % 4
    f = (b + 8) / 25;
    g = (b - f + 1) / 3;
    h = (19*j + b - d - g + 15) % 30;
    l = (32 + 2*e + 2*i - h - k) % 7;
    m = (j + 11*h + 22*l) / 451;
  }

  a = h + l - 7*m + 114;
  b = a / 31;
  c = a % 31;
  return ((b - 3)*31 + c + 1);
}

Diese Formel ist gegenüber der Formel von Gauß leichter zu encodieren, da sie keine IF-Anweisungen besitzt und linear abgearbeitet wird.

#ifdef __BORLANDC__
  // disable warning: function should return a value (Default ON)
  #pragma warn -rvl -8070
  // disable warning: Restarting compile using assembly (Default ON)
  #pragma warn -asc -8002
#endif

int eastersundayasm(int y)

{
  _asm
  {
    mov  ecx, y

    // j = y % 19;

    mov  eax, ecx
    mov  ebx, 19
    cdq
    idiv ebx
    mov  esi, edx

    // if (y<1583)

    cmp  ecx, 1583
    jb   LABELJUL1

    // Gregorianischer Kalender

    // b = y / 100;
    // c = y % 100;

    mov  eax, ecx
    mov  ebx, 100
    cdq
    idiv ebx
    mov  edi, eax
    mov  ecx, edx

    // eax = b   ecx = c   edi = b   esi = j

    // d = b / 4;
    // e = b % 4;

    mov  ebx, 4
    cdq
    idiv ebx
    push edx
    push ecx
    mov  ecx, eax

    // ecx = d  edi = b   esi = j   Stapel e und c

    // f = (b + 8) / 25;

    mov  eax, edi
    add  eax, 8
    mov  ebx, 25
    cdq
    idiv ebx

    // g = (b - f + 1) / 3;

    // eax = f

    mov  ebx, eax
    mov  eax, edi
    sub  eax, ebx
    inc  eax
    mov  ebx, 3
    cdq
    idiv ebx

    // h = (19*j + b - d - g + 15) % 30;

    // eax = g

    mov  ebx, eax
    mov  eax, esi
    imul eax, 19
    add  eax, edi
    sub  eax, ecx
    sub  eax, ebx
    add  eax, 15
    mov  ebx, 30
    cdq
    idiv ebx
    mov  edi, edx

    // i = c / 4;
    // k = c % 4;

    pop  eax
    mov  ebx, 4
    cdq
    idiv ebx

    // l = (32 + 2*e + 2*i - h - k) % 7;

    // eax = i   edx = k   ecx = e   edi = h

    pop  ecx
    sal  eax, 1
    sal  ecx, 1
    add  eax, 32
    add  eax, ecx
    sub  eax, edi
    sub  eax, edx
    mov  ebx, 7
    cdq
    idiv ebx
    push edx

    // m = (j + 11*h + 22*l) / 451;

    // edx = l   edi = h   esi = j

    mov  eax, esi
    mov  ecx, edi
    imul ecx, 11
    add  eax, ecx
    imul edx, 22
    add  eax, edx
    mov  ebx, 451
    cdq
    idiv ebx

    // eax = h  ebx = l   ecx = m

    mov  ecx, eax
    pop  ebx
    mov  eax, edi

    jmp  LABELCOMMON

  LABELJUL1:

    // Julianischer Kalender

    // a = y % 4;

    mov  eax, ecx
    and  eax, 3
    push eax

    // c = y % 7;

    mov  eax, ecx
    mov  ebx, 7
    cdq
    idiv ebx
    push edx

    // h = (19*j + 15) % 30;

    mov  eax, esi
    imul eax, 19
    add  eax, 15
    mov  ebx, 30
    cdq
    idiv ebx

    // l = (2*a + 4*c - h + 34) % 7;

    // edx = h  ecx = c   eax = a

    pop  ecx
    pop  eax
    push edx
    sal  eax, 1
    sal  ecx, 2
    add  eax, ecx
    sub  eax, edx
    add  eax, 34
    mov  ebx, 7
    cdq
    idiv ebx
    mov  ebx, edx
    xor  ecx, ecx
    pop  eax

  LABELCOMMON:

    // a = (h + l - 7*m + 114);

    // eax = h  ebx = l   ecx = m
 
    add  eax, ebx
    imul ecx, 7
    sub  eax, ecx
    add  eax, 114

    // b = a / 31;
    // c = a % 31;

    mov  ebx, 31
    cdq
    idiv ebx

    // eax = b   edx = c

    // (b - 3)*31 + c + 1

    sub  eax, 3
    imul eax, 31
    add  eax, edx
    inc  eax
  }
  // EAX enthält das Ergebnis
}

#ifdef __BORLANDC__
  #pragma warn +rvl +8070
#endif

Franz Bachler (18.05.2009 / 28.08.2009 / 18.2.2010 / 16.09.2010 / 24.01.2011)

 zurück