1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947
|
<title>An Introduction To Embedded Tk</title>
<h1>Embedded Tk</h1>
<p>
If you've ever tried to build a large-scale, compute-intensive, or
commercial application using Tcl/Tk, you probably had a difficult
time of it.
A pure Tcl/Tk script is terrific for writing small programs
or for prototyping, but it is often
inadequate for really big problems.
This is due to several factors:
<ul>
<li> Execution speed is usually too slow for serious computation.
<li> Complex data structures are difficult to implement in Tcl.
<li> The lack of structure and typing in the Tcl language complicates the
development of large codes.
<li> Tcl/Tk source is easily read by the end user,
making it hard to protect proprietary algorithms.
<li> A script will run only on machines where the correct version
of Tcl/Tk has been installed. This makes scripts more difficult
to distribute.
</ul>
The usual way to avoid these troubles is to code
in C or C++ rather than Tcl/Tk.
C is fast and well-structured.
Compiled C code is difficult for users to read.
And statically-linked C programs will run
on any binary-compatible computer, independent of other software.
</p>
<p>
But programming a graphical user interface in pure C is time-consuming and
error-prone.
The job can be made somewhat easier by
using Tcl/Tk's C interface, and having your C program call
the Tcl/Tk library routines directly.
Many people have done this, some successfully.
The task is still tough, though, because
unlike its scripting language, Tcl/Tk's C interface is not easy to use.
Properly initializing the Tcl/Tk interpreter takes skill and
finesse, and calling the interpreter from C is a dull chore.
</p>
<p>
And so the problem remains: Do you go for the speed and structure
of C or the power and simplicity of Tcl/Tk?
</p>
<p>
The Embedded Tk system (hereafter <quote>ET</quote>) was
created to resolve this conundrum.
ET is a simple preprocessor and small interface library that make it
easy to mix Tcl/Tk and C in the same program.
With ET, you can put a few commands of
Tcl/Tk code in the middle of a C routine.
ET also makes it very easy to write C functions that work as
new Tcl/Tk commands -- effectively allowing you to put pieces of
C code in the middle of your Tcl script.
These features gives you
the speed and structure of C with the power and simplicity of Tcl/Tk.
As an added benefit,
an application written using ET will compile into a stand-alone
executable that
will run on any binary-compatible computer, even
if the other computer doesn't have Tcl/Tk installed.
<h1 tag=helloworld>An Example: ``Hello, World!''</h1>
<p>
The ET system is designed to be easy to use.
To see this, let's look at the classic <quote>Hello, World!</quote>
program, coded using ET.
<pre>
void main(int argc, char **argv){
Et_Init(&argc,argv);
ET( button .b -text {Hello, World!} -command exit; pack .b );
Et_MainLoop();
}
</pre>
If you compile and link these 5 lines, you'll get
a stand-alone executable that pops up a
<quote>Hello, World!</quote>
button, and goes away when the button is clicked.
</p>
<p>
Let's take this program apart to see how it works.
The first thing it does is call the <func>Et_Init</func> procedure.
This procedure performs the tedious and confusing work needed
to start up the Tcl/Tk interpreter, initialize widget bindings,
create the main window <quote>.</quote>, and so forth.
The last line is a call to another procedure <func>Et_MainLoop</func>
that implements the event loop.
(If you don't know what an event loop is, don't worry.
We'll have more to say about event loops in section <ref tag=eventloop>.)
The most interesting part of the example is the middle line, the
one that looks like a call to a function named <func>ET</func>.
The <func>ET</func> function is special.
It looks and is used like a regular C function, but takes a Tcl/Tk
script as its argument instead of a C expression.
Its function is to execute the enclosed Tcl/Tk.
In this particular example, the <func>ET</func> function
creates the <quote>Hello, World!</quote> button.
</p>
<p>
Because of the <func>ET</func> function, we can't give the
<quote>Hello, World!</quote> source code directly to a C compiler
and expect it to work.
We have to run it through a preprocessor first. Like this:
<pre>
et2c hello.c > hello_.c
</pre>
The <cmd>et2c</cmd> preprocessor converts the <func>ET</func>
function into real, compilable C code.
The preprocessor also takes care of some
other housekeeping details, like adding prototypes to the top of the
file so that we don't have to bother with
a <code>#include</code>.
After it has been preprocessed, the source code can be compiled like
any other C program.
<pre>
cc -O -o hello hello_.c et.o -ltk -ltcl -lXll -lm
</pre>
Notice that you must link the program with
ET's <filename>et.o</filename>
library file, and with libraries for Tcl/Tk and X11.
(See section <ref tag=macandwin> for instructions on building
applications for Microsoft-Windows or Macintosh.)
</p>
<p>
And that's all there is too it!
</p>
<h1>How To Avoid Reading The Rest Of This Article</h1>
<p>
If you're restless to start programming and are the type of
person who prefers to learn at the keyboard rather than from a
book, this section is for you.
It contains a terse overview of the features of ET.
Peruse this section, glance quickly at the examples, and you'll
be ready to start coding.
You can use the rest of the article as a reference guide when you run
into trouble.
</p>
<p>
On the other hand, if you are new to graphical interface programming,
are a little unsteady with C, or just have a more deliberate and
cautious attitude toward life, then you may prefer to lightly skim
or even skip this section and focus instead on the
tutorial-like text that follows.
</p>
<p>
The ET system consists of two things: the <cmd>et2c</cmd>
preprocessor and the <filename>et.o</filename> library.
The preprocessor takes care of translating ET source code
(which looks a whole lot like C) into genuine C code that
your compiler will understand.
The <filename>et.o</filename> library contains a few support
routines.
</p>
<p>
Among the support routines in <filename>et.o</filename> are
<func>Et_Init</func> and <func>Et_MainLoop</func>
for initializing the ET package and implement the event loop, respectively.
A third routine, <func>Et_ReadStdin</func>,
allows standard input
to be read and interpreted by the Tcl/Tk interpreter at run-time.
The <filename>et.o</filename> library
defines three global C variables as a convenience.
<code>Et_Interp</code> is a pointer to the Tcl/Tk interpreter used by ET.
<code>Et_MainWindow</code> is the main window.
<code>Et_Display</code> is the <code>Display</code> pointer required
as the first argument to many Xlib routines.
ET also provides two global Tcl variables, <code>cmd_name</code> and
<code>cmd_dir</code>. These contain the name of the executable and the
directory where the executable is found.
</p>
<p>
The <cmd>et2c</cmd> preprocessor is used to convert an ET source
file into real C code.
It creates the illusion of giving the C language some new statements,
like <code>ET_INSTALL_COMMANDS</code> and <code>ET_PROC</code> and
some special new functions like <func>ET</func>.
</p>
<p>
The <func>ET</func> function is used as if it were
a regular C function, except that its argument is a
Tcl/Tk script.
The job of the <func>ET</func> is to execute the script.
<func>ET</func> returns either <code>ET_OK</code>
or <code>ET_ERROR</code>
depending upon whether the script suceeded or failed.
Similar routines <func>ET_STR</func>, <func>ET_INT</func>, and
<func>ET_DBL</func> also take a Tcl/Tk script
as their argument, but return the string, the integer,
or the double-precision floating point number
that was the result of the last Tcl/Tk command in the argument script.
</p>
<p>
Wherever the string <code>%d(x)</code> occurs inside an
<func>ET</func> function, the integer C expression <code>x</code>
is converted to ASCII and substituted in place of the
<code>%d(x)</code>.
Similarly, <code>%s(x)</code> can be used to substitute
a character string, and <code>%f(x)</code> substitutes
a floating point value.
The string <code>%q(x)</code> works like <code>%s(x)</code> except
that a backslash is inserted before each character
that has special meaning to Tcl/Tk.
</p>
<p>
The special construct
<code>ET_PROC( newcmd ){...}</code> defines a
C function that is invoked whenever the <code>newcmd</code>
Tcl/Tk command is executed.
Formal parameters to this function, <code>argc</code> and <code>argv</code>,
describe the arguments to the command.
The formal parameter <code>interp</code> is a pointer to the
Tcl/Tk interpreter.
If a file named <filename>aux.c</filename> contains one or more
<code>ET_PROC</code> macros, the commands associated with
those macros are registered with the Tcl/Tk interpreter by
invoking <code>ET_INSTALL_COMMANDS(aux.c)</code> after
the <func>Et_Init</func> in the
main procedure.
</p>
<p>
The statement <code>ET_INCLUDE( script.tcl )</code>
causes the Tcl/Tk
script in the file <code>script.tcl</code> to be made a part of the
C program and executed at the point where
the <func>ET_INCLUDE</func> macro is found.
The external Tcl/Tk script is normally read into the C program
at compile-time and thus becomes part of the executable.
However, if the <cmd>-dynamic</cmd>
command-line option is given to the
<cmd>et2c</cmd> preprocessor, loading of the external
Tcl/Tk script is deferred to run-time.
</p>
<p>
Finally, at the top of its output files,
the <cmd>et2c</cmd> preprocessor inserts
<code>#define</code>s that
make <code>ET_OK</code> and <code>ET_ERROR</code>
equivalent to
<code>TCL_OK</code> and <code>TCL_ERROR</code>. This often
eliminates the need
to put <quote><code>#include <tcl.h></code></quote>
at the beginning of files that use ET.
</p>
<p>
And that's everything in ET! All the rest is just detail.
</p>
<h1 tag=eventloop>A Quick Review Of Event Driven Programs</h1>
<p>
Before we delve into the details of ET, it may
be helpful to review the concept of an <em>event loop</em> and an
<em>event-driven program</em>.
Many ET users have never before written an
event-driven graphical user interface (GUI)
and may be unfamiliar with how such programs operate.
If you are such a user, you may profit from this quick review.
But if you are already familiar with event-driven programs, feel free
skip ahead to section <ref tag=basicstruct>.
</p>
<p>
The only inputs to a GUI are <quote>events.</quote>
An event is a notification that something interesting has happened.
Events arrive whenever the mouse moves, or a mouse button is
pressed or released, or a key of the keyboard is pressed,
and so forth.
A event-driven GUI differs from more familiar command-line
programs in that its inputs (e.g. events) do
not arrived in any predictable sequence.
Any kind of events can arrive at any time, and the GUI program
must be prepared to deal with them.
</p>
<p>
The code for an event-driven GUI can be divided into two parts:
the <em>initialization</em> code and the <em>event loop</em>.
The initialization code runs first and does nothing more than
allocate and initialize the internal data structures of the application.
As soon as the initialization code completes, the application enters
the event loop.
Within the event loop, the program waits for the next event to
arrive, reads the event, and processes it appropriately.
The loop then repeats.
The event loop does not exit until the program terminates.
</p>
<p>
This is a schematic view of a typical GUI program:
<pre>
main(){
/* Initialization code */
while( /* More work to do */ ){
/* Wait for the next event to arrive */
/* Read the next event */
/* Take appropriate action for the event just read */
}
}
</pre>
Don't worry about the details here.
Most of the event loop processing is handled automatically by
Tcl/Tk and ET.
The important things to know are that the event loop exists, it
runs after the initialization code,
and that it doesn't terminate until the program exits.
</p>
<p>
If you've never written an event-driven program before, and you are
like most people, then you will have a little trouble at first.
To help you get started, here are some important points to
remember:
<ol>
<li><strong>The initialization code does not interact with the user.</strong>
<p>
The initialization code does only one thing -- initialize.
It creates the main windows of the application (but it doesn't
draw the windows -- that happens in the event loop!) and it
sets up internal data structures. But the initialization code
should never wait for input or respond to an event.
Waiting and reading inputs and responding to events should happen
only in the event loop.
</p>
<li><strong>All user-initiated processing occurs in callbacks.</strong>
<p>
Everything that a GUI program does is in response to some
event. Any C procedure or Tcl/Tk command that is called in
response to an event is referred to as a <em>callback</em>.
Because all inputs to a GUI program are in the form of events,
the only place for user-initiated processing to occur is within
the callback routines.
</p>
<li><strong>Don't let a callback compute for more than
a fraction of a second.</strong>
<p>
A callback should do its job quickly and then return.
Otherwise, the event loop will not be able to respond to new
events as they arrive,
and the program will appear to <quote>hang</quote>.
If you have a callback that needs to execute for more than a
few hundred milliseconds, you should either invoke the
<quote>update idletasks</quote> Tcl/Tk command periodically within
the callback, or you should break the callback's calculations
up into several separate routines that can be invoked by
separate events.
</p>
<li><strong>Don't leak memory.</strong>
<p>
Once started, GUI programs tend to run for a long time -- hours, days,
weeks or even months. Hence, you should take special care to avoid
<em>memory leaks</em>. A memory leak occurs when you allocate a
chunk of memory from the heap using <func>malloc</func> but don't
return that memory to the heap using <func>free</func> when you are
done with it.
Because the memory was not released by <func>free</func> it can never
be reused.
When this happens, the amount of memory required by your application
will constantly increase, until at some point it will consume all
memory available, and then die.
Memory leaks are probably the most common bug in GUI programs (which is
why I mention them.)
</p>
</ol>
<h1 tag=basicstruct>The Main Body Of An ET Application</h1>
<p>
The <func>main</func> routines for ET applications all look
pretty much alike.
Here's a template:
<pre>
void main(int argc, char **argv){
Et_Init(&argc,argv); /* Start the Tcl/Tk interpreter */
/* Create new Tcl/Tk commands here */
/* Initialize data structures here */
/* Create windows for the application here */
Et_MainLoop(); /* The event loop */
}
</pre>
When you need to write an ET application, but you aren't
sure where to begin, this template is a good starting point.
Type in the above
template and make sure you can successfully compile and run it.
(The program that results from compiling the template creates a
blank window that doesn't respond to any mouse or keyboard
inputs. Its the equivalent of <quote><cmd>wish /dev/null</cmd></quote>.)
After you get the template running,
slowly begin adding bits of code, recompiling and testing
as you go, until you have a complete application.
</p>
<p>
Let's take a closer look at each line of the
template, so that you can better understand what is going on.
</p>
<p>
The first line of <func>main</func>
is a call to the <func>Et_Init</func> procedure.
The <func>Et_Init</func> procedure initializes the ET
system and the Tcl/Tk interpreter.
It must be called before any other ET function or statement.
The parameters are the <code>argc</code>
and <code>argv</code> formal parameters of <func>main</func>.
<func>Et_Init</func> uses these parameters to look for command-line
options.
ET currently understands four:
<ul>
<li><cmd>-display</cmd> designates the X server to use.
The value of this
option will override your <cmd>DISPLAY</cmd> environment variable.
<li><cmd>-name</cmd> changes the application name for the program.
By default, the application name is same as the the filename
of the executable itself.
The application name is used to derive the Tcl/Tk
interpreter name for use with the Tcl/Tk <code>send</code> command.
The application name is also used for processing X11 resources,
and as the default text on the application's title bar.
<li><cmd>-geometry</cmd> changes the starting size and/or position
of the program.
<li><cmd>-sync</cmd> turns on synchronous mode in the X server.
This makes the program run a lot slower, but is sometimes useful
when debugging. It is very rarely used.
</ul>
</p>
<p>
Notice the <quote><code>&</code></quote> before the <code>argc</code>
parameter to <func>Et_Init</func>.
The number of command line arguments is passed to <func>Et_Init</func>
by address, not by value.
This is so <func>Et_Init</func> can change the value of <code>argc</code>.
Whenever <func>Et_Init</func> sees one of the above command-line options,
it removes that option from the option list in <code>argc</code>
and <code>argv</code>.
Hence, after <func>Et_Init</func> returns, only application-specific
command line options remain.
</p>
<p>
For example, suppose you invoke an ET program like this:
<pre>
myapp -quiet -display stego:0 file1.data
</pre>
The values of <code>argc</code> and <code>argv</code>
passed into the <func>Et_Init</func> function are:
<pre>
argc = 5
argv = { "myapp", "-quiet", "-display", "stego:0", "file1.data", 0 }
</pre>
The <func>Et_Init</func> function will see the <cmd>-display stego:0</cmd>
part and act upon it accordingly. It will then remove those fields
from the argument list, so that after <func>Et_Init</func> returns,
the values are these:
<pre>
argc = 3
argv = { "myapp", "-quiet", "file1.data", 0 }
</pre>
In this way, the initialization code that follows <func>Et_Init</func>
never sees the ET-specific command line arguments.
</p>
<p>
After the <func>Et_Init</func> procedure comes the initialization
code.
Normally, you begin the initialization by creating and registering
all the new Tcl/Tk commands you will need.
The details are described in section <ref tag=etproc>.
Basically it involves replacing the comment in the template with
one or more <code>ET_INSTALL_COMMANDS</code> statements.
Once you've created the new Tcl/Tk commands, you may need to
construct internal C data structures, or create linkages between
C variables and Tcl variables using Tcl's <func>Tcl_LinkVar</func>
function.
Command-line options that haven't been removed by
<func>Et_Init</func> are often processed here, as well.
Finally, you will probably want to create the initial windows for
the application.
The <func>ET</func> function (see section <ref tag=et>)
and <func>ET_INCLUDE</func> procedure (see section <ref tag=etinclude>)
are both good for this.
</p>
<p>
Of course, this is only a suggested outline of how to initialize
your application.
You should feel free to do something different if your program
requires it.
The only ground rule is that the initialization code shouldn't
try to interact with the user.
Instead, use callback routines to respond to user inputs.
</p>
<p>
The last line of <func>main</func> is a call to the
<func>Et_MainLoop</func> procedure.
<func>Et_MainLoop</func> implements the event loop.
It will not return until the program is ready to exit.
</p>
<h1 tag=etproc>Writing Tcl/Tk Routines In C</h1>
<p>
One of the first things people tend to do with ET is
create new Tcl/Tk commands, written in C, that do computations that
are either too slow or impossible with a pure Tcl.
This is a two-step process.
First you have to write the C code using the <code>ET_PROC</code>
construct.
Then you have to register your new Tcl/Tk command with the Tcl/Tk
interpreter using the <code>ET_INSTALL_COMMANDS</code> statement.
We will consider each of these steps in turn.
</p>
<h2>The Decimal Clock Sample Program</h2>
<p>
To help illustrate the concepts, this section introduces a
new sample program: the <em>decimal clock</em>.
The decimal clock displays the current time of day as a decimal
number of hours. For instance,
8:30am displays as <quote><code>8.500</code></quote>.
11:15pm shows as <quote><code>23.250</code></quote>.
And so forth.
A screen shot of this program is shown
in figure <ref tag=decimalclock>.
</p>
<image gif=dclock.gif eps=dclock.ps tag=decimalclock
caption="Typical appearance of the decimal clock">
<p>
We'll begin by looking at the main procedure for the decimal
clock program.
<pre>
void main(int argc, char **argv){
Et_Init(&argc, argv);
ET_INSTALL_COMMANDS;
ET(
label .x -bd 2 -relief raised -width 7
pack .x
proc Update {} {
.x config -text [DecimalTime]
after 3600 Update
}
Update
);
Et_MainLoop();
}
</pre>
As you can see, the main procedure is just a copy of the
program template from section <ref tag=basicstruct>, with some
of the comments replaced by actual initialization code.
The first initialization action is to invoke the special ET statement
<code>ET_INSTALL_COMMANDS</code>. Don't worry about what this
does just yet -- we'll return to it a little later.
The second initialization action is a single
<func>ET</func> function containing 7 lines of Tcl/Tk.
This Tcl/Tk code does three things:
<ul>
<li> It creates a label widget in which to show the decimal time.
<li> It creates a new Tcl/Tk procedure named <code>Update</code> that
updates the label widget to show the current time, and then arranges
to call itself again after 0.001 hours (3.6 seconds).
<li> It invokes the <code>Update</code> procedure once in order to
initialize the text of the label widget, and to start the
periodic updates.
</ul>
Like all well-behaved ET programs, the main procedure for the
decimal clock concludes by entering the event loop.
</p>
<h2>The ET_PROC Statement</h2>
<p>
The core of the decimal clock program is a new Tcl/Tk command,
<code>DecimalTime</code>, that returns the current time of day
as a decimal number of hours.
This new command is written in C, using the special
<code>ET_PROC</code> construct of ET. The code looks like this:
<pre>
#include "tcl.h"
#include <time.h>
ET_PROC( DecimalTime ){
struct tm *pTime; /* The time of day decoded */
time_t now; /* Number of seconds since the epoch */
now = time(0);
pTime = localtime(&now);
sprintf(interp->result,"%2d.%03d",pTime->tm_hour,
(pTime->tm_sec + 60*pTime->tm_min)*10/36);
return ET_OK;
}
</pre>
The magic is in the <code>ET_PROC</code> keyword.
The <cmd>et2c</code> preprocessor recognizes this keyword and
converts the code that follows it into a compilable C function that
implements the Tcl/Tk command.
In general, you can create new Tcl/Tk commands using a
template like this:
<pre>
ET_PROC( name-of-the-new-command ){
/* C code to implement the command */
}
</pre>
You could, of course, construct approprate C functions by hand, but
that involves writing a bunch of messy details that detract
from the legibility of the code.
The <code>ET_PROC</code> mechanism is much easier to write and
understand, and much less subject to error.
</p>
<p>
Though they do not appear explicitly in the source code, every
function created using <code>ET_PROC</code> has four formal parameters.
</p>
<ul>
<li><strong>argc</strong>
<p>
This parameter is an integer that holds the number of arguments
on the Tcl command that invokes the function.
Its role is exactly the same as the <code>argc</code> parameter to
the <func>main</func> function of a standard C program.
</p>
<li><strong>argv</strong>
<p>
Like <code>argc</code> before it, this parameter works just like the
<code>argv</code> parameter to <func>main</func>.
The variable <code>argv[0]</code> contains the name of the the
command itself (<quote><code>DecimalTime</code></quote> in this
example), <code>argv[1]</code> contains the name of the first
argument, <code>argv[2]</code> contains the name of the second
argument, and so forth up to <code>argv[argc]</code> which is a
null pointer.
</p>
<li><strong>interp</strong>
<p>
This parameter is a pointer to the Tcl/Tk interpreter.
It has type <quote><code>Tcl_Interp*</code></quote>.
The <code>interp</code>
parameter has many uses, but is most often used to set the
return value of the Tcl/Tk function.
(Note that you have to <code>#include</code> either
<code><tcl.h></code> or
<code><tk.h></code> somewhere in your
source file in order to use the <code>interp</code> parameter, since
one of these header files are needed to define the fields of the
<code>Tcl_Interp</code> structure.)
</p>
<li><strong>clientData</strong>
<p>
This is a pointer to the <code>Tk_Window</code> structure that defines
the main window (e.g. the <quote>.</quote> window) of the application.
It has a type of <quote><code>void*</code></quote> and will need to be
typecast before it is used.
On the other hand, it is seldom used, so this isn't normally a problem.
</p>
</ul>
<p>
The decimal clock example uses the <code>interp</code>
formal parameter on the sixth line of the <code>ET_PROC</code>
function.
In particular, the <code>DecimalTime</code> function writes its
result (e.g. the time as a decimal number) into the <code>result</code>
field of <code>interp</code>.
It's OK to write up to about 200 bytes of text into
the <code>result</code> field of the <code>interp</code> parameter,
and that text will become the return value of the Tcl/Tk
command.
If you need to return more than about 200 bytes of text, then
you should set the result using one of the routines from the
Tcl library designed for that purpose:
<func>Tcl_SetResult</func>,
<func>Tcl_AppendResult</func>, or
<func>Tcl_AppendElement</func>.
(These routines are documented by Tcl's manual pages
under the name <quote>SetResult</quote>.)
If all this seems too complicated, then you can choose to
do nothing at all, in which case
the return value defaults to an empty string.
</p>
<p>
Another important feature of every <code>ET_PROC</code> function is
its return value.
Every <code>ET_PROC</code> should return either <code>ET_OK</code>
or <code>ET_ERROR</code>, depending on whether or not the
function encountered any errors.
(<code>ET_OK</code> and <code>ET_ERROR</code> are <code>#define</code>
constants
inserted by <cmd>et2c</code> and have the save values as
<code>TCL_OK</code> and <code>TCL_ERROR</code>.)
It is impossible for the <code>DecimalClock</code> function to fail,
so it always returns <code>ET_OK</code>, but most <code>ET_PROC</code>
functions can return either result.
</p>
<p>
Part of Tcl's <em>result protocol</em> is that if a command
returns <code>ET_ERROR</code> it should put an error message in
the <code>interp->result</code> field.
If we had wanted to be pedantic, we could have put a test in the
<code>DecimalTime</code> function to make sure it is called with
no arguments.
Like this:
<pre>
ET_PROC( DecimalTime ){
struct tm *pTime; /* The time of day decoded */
time_t now; /* Number of seconds since the epoch */
if( argc!=1 ){
Tcl_AppendResult(interp,"The ",argv[0],
" command should have no argument!",0);
return ET_ERROR;
}
/* The rest of the code is omitted ... */
}
</pre>
New Tcl/Tk commands that take a fixed format normally
need to have some checks like this, to make sure they aren't called
with too many or too few arguments.
</p>
<h2>The ET_INSTALL_COMMANDS statement</h2>
<p>
We've seen how
the <code>ET_PROC</code> constuct will <em>create</em> a new
Tcl/Tk command.
But that command must still be <em>registered</em>
with the Tcl interpreter before it can be used.
Fortunately, ET makes this very easy.
</p>
<p>
ET uses the <code>ET_INSTALL_COMMANDS</code> keyword to register
<code>ET_PROC</code> commands with the Tcl interpreter.
The <cmd>et2c</cmd> preprocessor converts the
<code>ET_INSTALL_COMMANDS</code> keyword into a sequence of C
instructions that register every <code>ET_PROC</code>
in the current file.
In the <func>main</func> procedure of the
decimal clock example, the <code>ET_INSTALL_COMMANDS</code>
keyword that immediately follows the <func>Et_Init</func> function
is used to register the <code>DecimalTime</code> command.
As it turns out, <code>DecimalTime</code> is the only <code>ET_PROC</code>
function in the same source file, but even if there had be 100
others, they would have all been registered by that single
<code>ET_INSTALL_COMMANDS</code> statement.
</p>
<p>
The <code>ET_INSTALL_COMMANDS</code> keyword can also be used to
register <code>ET_PROC</code> functions in separate source files,
simply by putting the name of the source file in
parentheses after the <code>ET_INSTALL_COMMANDS</code> keyword.
Like this:
<pre>
ET_INSTALL_COMMANDS( otherfile.c );
</pre>
A larger program will typically have many <code>ET_INSTALL_COMMANDS</code>
statements immediately following the <func>Et_Init</func> function,
one statement for each file that contains <code>ET_PROC</code>
functions.
One recent commercial project used 33
<code>ET_INSTALL_COMMANDS</code> statements following the
<func>Et_Init</func> function!
</p>
<h2>Summary Of Writing Tcl/Tk Commands In C</h2>
<p>
Before leaving this section,
let's briefly summarize the steps needed to create new Tcl/Tk commands
in C using ET.
First you create one or more commands using the <code>ET_PROC</code>
construct, as follows:
<pre>
ET_PROC( name-of-the-new-command ){
/* C code to implement the command */
return ET_OK; /* Don't forget the return value! */
}
</pre>
Then, you register these commands with the Tcl interpreter using
an <code>ET_INSTALL_COMMANDS</code> statement after the
<func>Et_Init</func> function call within <func>main</func>.
Like this:
<pre>
ET_INSTALL_COMMANDS( name-of-file-containing-ET_PROCs.c );
</pre>
And that's all you have to do!
</p>
<p>
The <code>ET_PROC</code> construct lets you
put a C routine in the middle of Tcl/Tk.
The next section will take a closer look at <func>ET</func> which
allows you to put Tcl/Tk in the middle of a C routine.
</p>
<h1 tag=et>The ET() Function And Its Siblings</h1>
<p>
If you've been keeping up with the examples, you've already seen
the <func>ET</func> function used twice to insert a few lines of
Tcl/Tk in the middle of a C procedure.
But the <func>ET</func> function can do a lot more, as this
section will show.
</p>
<h2>Moving Information From Tcl/Tk To C</h2>
<p>
The first thing to note about <func>ET</func> is that, just like
a real C function, it has a return value.
<func>ET</func> returns an integer status code which is either
<code>ET_OK</code> or <code>ET_ERROR</code> depending on whether the
enclosed Tcl/Tk was successful or failed.
(<func>ET</func> might also return
<code>TCL_RETURN</code>, <code>TCL_BREAK</code>,
or <code>TCL_CONTINUE</code> under rare circumstances.)
</p>
<p>
The status return of <func>ET</func> is nice, but in practice it
turns out to be mostly useless.
What you really need is the string value returned by the enclosed Tcl/Tk
script.
That's the purpose of the <func>ET_STR</func> function.
</p>
<p>
The <func>ET_STR</func> function works a lot like <func>ET</func>.
You put in a Tcl/Tk script as the argument, and the script gets executed.
But instead of returning a status code, <func>ET_STR</func> returns a
pointer to a string that was the result of the last Tcl/Tk command
in its argument.
</p>
<p>
The <func>ET_STR</func> function turns out to be a very handy mechanism
for querying values from Tcl/Tk.
For instance, suppose your program has an entry widget named
<quote><code>.entry</code></quote> and some piece of C code needs
to know the current contents of the entry.
You can write this:
<pre>
char *entryText = ET_STR(.entry get);
</pre>
Or imagine that you need to know the current size and position of
your main window.
You might use code like this:
<pre>
int width, height, x, y;
sscanf(ET_STR(wm geometry .),"%dx%d+%d+%d",&width,&height,&x,&y);
</pre>
Does your C routine need to know the value of a Tcl variable?
You could use the cumbersome <func>Tcl_GetVar</func> function,
but it's much easier to say:
<pre>
char *zCustomerName = ET_STR(set CustomerName);
</pre>
Possible uses for <func>ET_STR</func> seem limitless.
</p>
<p>
But, there are two subtleties with <func>ET_STR</func> that programmers
should always keep in mind.
The first is that the Tcl/Tk script in the argument is executed at
Tcl's global variable context level.
This means that all of the Tcl/Tk variables <func>ET_STR</func>
creates, and the only Tcl/Tk variables it can access, are global
variables.
This limitation also applies to the regular <func>ET</func> function, and to
two other function we haven't talked about yet:
<func>ET_INT</func> and <func>ET_DBL</func>.
ET provides no means for C code to access or modify local variables.
On the other hand, this has not proven to be a serious hardship in
practice.
</p>
<p>
The second subtlety with <func>ET_STR</func> is more dangerous, but
fortunately applies to <func>ET_STR</func> only.
Recall that <func>ET_STR</func> returns a <em>pointer</em> to a string,
not the string itself.
The string actually resides in memory that is held deep within the
bowels of Tcl/Tk.
The danger is that the next Tcl/Tk command may choose
to change, deallocate, or reuse this memory, corrupting the value
returned by <func>ET_STR</func>.
We say that the return value of <func>ET_STR</func> is
<quote>ephemeral.</quote>
</p>
<p>
One way to overcome the ephemerality of <func>ET_STR</func>
is by making a copy of the returned string.
The <func>strdup</func> function is good for this.
(Unfortunately, <func>strdup</func> is missing from a lot of C libraries.
You may have to write your own string duplicator.)
In place of the examples given above, you might write
<pre>
char *entryText = strdup( ET_STR(.entry get) );
</pre>
or
<pre>
char *zCustomerName = strdup( ET_STR(set CustomerName) );
</pre>
The <func>strdup</func> function uses <func>malloc</func> to get the
memory it needs, so if you use this approach,
be sure to <func>free</func> the value when you
are done to avoid a memory leak!
</p>
<p>
The other way to overcome the ephemerality of <func>ET_STR</func>
is simply not to use the returned string for very long.
You should be safe in using the returned string as long as you don't
invoke any other Tcl/Tk commands, or return to the event loop.
Code like this
<pre>
sscanf(ET_STR(wm geometry .),"%dx%d+%d+%d",&width,&height,&x,&y);
</pre>
is OK since we need the return value only for the duration of
the <func>sscanf</func> function and <func>sscanf</func> doesn't
use Tcl/Tk.
</p>
<p>
In addition to <func>ET</func> and <func>ET_STR</func>, the
ET system provides two other functions named <func>ET_INT</func>
and <func>ET_DBL</func>.
Both take a Tcl/Tk script for their argument, as you would expect.
But <func>ET_INT</func> returns an integer result and
<func>ET_DBL</func> returns a floating-point value (a
<code>double</code>).
In a sense, these two functions are extensions of <func>ET_STR</func>.
In fact, <func>ET_INT</func> does essentially the same thing as
<pre>
int v = strtol( ET_STR(...), 0, 0);
</pre>
and <func>ET_DBL</func> is equivalent to
<pre>
double r = strtod( ET_STR(...), 0);
</pre>
Because <func>ET_INT</func> and <func>ET_DBL</func> return a value, not
a pointer, their results are not ephemeral nor subject to the
problems that can come up with <func>ET_STR</func>.
</p>
<h2>Moving Information From C To Tcl/Tk</h2>
<p>
We've seen how <func>ET_STR</func>, <func>ET_INT</func> and
<func>ET_DBL</func> can be used to pass values from Tcl/Tk back
to C.
But how do you go the other way and transfer C variable values
into Tcl/Tk?
ET has a mechanism to accomplish this too, of course.
</p>
<p>
Within the argument to any <func>ET</func> function (or
<func>ET_STR</func> or <func>ET_INT</func> or <func>ET_DBL</func>),
the string <quote><code>%d(x)</code></quote> is special.
When ET sees such a string, it evalutes the integer C expression
<quote><code>x</code></quote>, converts the resulting integer into
decimal, and substitutes the integer's decimal value for the original
string.
For example, suppose you want to initialize the Tcl/Tk variable
named <code>nPayment</code> to be twelve times the value of a C
variable called <code>nYear</code>.
You might write the following code:
<pre>
ET( set nPayment %d(12*nYear) );
</pre>
As another example, suppose you want to draw a circle
on the canvas <code>.cnvs</code> centered at (x,y) with radius r.
You could say:
<pre>
id = ET_INT( .cnvs create oval %d(x-r) %d(y-r) %d(x+r) %d(y+r) );
</pre>
Notice here how the <func>ET_INT</func> function was used to record
the integer object ID returned by the Tcl/Tk canvas <code>create</code>
command.
This allows us to later delete or modify the circle by
referring to its ID.
For example, to change the fill color of the circle, we could execute
the following:
<pre>
ET( .cnvs itemconfig %d(id) -fill skyblue );
</pre>
</p>
<p>
If you want to substitute a string or floating-point value into
an <func>ET</func> argument, you can use <code>%s(x)</code>
and <code>%f(x)</code> in place of <code>%d(x)</code>.
The names of these substitutions phrases were inspired by the
equivalent substitution tokens in the standard C library
function <func>printf</func>.
Note, however, that you cannot specify a field-width, precision, or
option flag in <func>ET</func> like you can in <func>printf</func>.
In other words, you can use conversions like
<code>%-10.3f</code> in <func>prinf</func> but not in <func>ET</func>.
The <func>ET</func> function will accepts only
specification, such as <code>%f</code>.
</p>
<p>
But the <func>ET</func> function does support a conversion specifier
that standard <func>printf</func> does not:
the <code>%q(x)</code> substitution.
The <code>%q</code> works like <code>%s</code>
in that it expects its argument to be a null-terminated string,
but unlike <code>%s</code> the <code>%q</code> converter
inserts extra backslash characters into the string in order
to escape characters that have special meaning to Tcl/Tk.
Consider an example.
<pre>
char *s = "The price is $1.45";
ET( puts "%q(s)" );
</pre>
Because <code>%q(s)</code> was used instead of
<code>%s(s)</code>, an extra backslash
is inserted immediately before the <quote><code>$</code></quote>.
The command string passed to the Tcl/Tk interpreter is therefore:
<pre>
puts "The price is \$1.45"
</pre>
This gives the expected result. Without the extra backslash, Tcl/Tk would
have tried to expand <quote><code>$1</code></quote> as a variable, resulting in an error message like this:
<pre>
can't read "1": no such variable
</pre>
In general, it is always a good idea to use
<code>%q(...)</code> instead of <code>%s(...)</code>
around strings that originate from outside the program--you never know
when such strings may contain a character that needs to be escaped.
</p>
<h2>Summary Of The ET() Function</h2>
<p>
And that's everything there is to know about the
<func>ET</func> function and its siblings.
In case you missed something amid all the details, here's a
10-second review of the essential facts:
<ul>
<li>The <func>ET</func> executes Tcl/Tk code and returns a
success/failure code.
<li><func>ET_STR</func>, <func>ET_INT</func> and <func>ET_DBL</func>
do the same, but return a string, and integer or a double which
was the result of the last Tcl/Tk command executed.
<li>The return value from <func>ET_STR</func> is ephemeral.
<li>The strings <code>%s(...)</code>, <code>%d(...)</code> and
<code>%f(...)</code> insert string, integer and double C expressions
into the argument of <func>ET</func> and its siblings.
<li>The string <code>%q(...)</code> works like <code>%s(...)</code>
but adds backslashes before characters that are special to Tcl/Tk.
</ul>
Now lets move on and talk about a similar construct,
<func>ET_INCLUDE</func>, that allows you incorporate whole
files full of Tcl/Tk into your application.
</p>
<h1 tag=etinclude>Including External Tcl/Tk Scripts In A C Program</h1>
<p>
In the sample programs seen so far in this article,
Tcl/Tk code in an
<func>ET</func> function was used to construct the main window.
This works fine for the examples, since their windows are
uncomplicated and can be constructed with a few lines code.
But in a real application, or even a more complex example,
the amount of Tcl/Tk code needed to initialize the program's windows
can quickly grow to hundreds or thousands of lines.
It is impractical and irksome to put this much code into an
<func>ET</func> statement, so the ET system provides another
way to get the job done: the <func>ET_INCLUDE</func> statement.
</p>
<p>
The <func>ET_INCLUDE</func> statement is similar in concept to the
<code>#include</code> statement in the C preprocessor.
Both take a filename as their argument, and both read the named
file into the original source program.
The <func>ET_INCLUDE</func> statement expects its file to
be pure Tcl/Tk code, though.
Its job is to turn the Tcl/Tk source into a form that the
C compiler can understand, and to arrange for the Tcl/Tk to
be executed when control reaches the <func>ET_INCLUDE</func>
statement.
</p>
<p>
An example may help to clarify this idea.
In the decimal clock program (way back
at the beginning of section <ref tag=etproc>),
there are 7 lines of Tcl/Tk in an <func>ET</func> function used
to create the application's main window.
Now suppose we move those 7 lines of Tcl/Tk into a separate
file named <filename>dclock.tcl</filename>.
Then we could replace the <func>ET</func> function with an
<func>ET_INCLUDE</func> statement that references the new file
like this:
<pre>
void main(int argc, char **argv){
Et_Init(&argc, argv);
ET_INSTALL_COMMANDS;
ET_INCLUDE( dclock.tcl );
Et_MainLoop();
}
</pre>
When the <cmd>et2c</cmd> preprocessor sees the <func>ET_INCLUDE</func>
statement, it locates the specified file, reads that file into the
C program, and makes arrangements for the text of the file to be
executed as if it had all appeared within an <func>ET</func> function.
</p>
<p>
Well, <em>almost</em> like an <func>ET</func> function.
There are a couple of minor differences.
The <func>ET_INCLUDE</func> does not understand
the various <code>%s(...)</code> substitutions as <func>ET</func> does.
Also, <func>ET_INCLUDE</func> is a true procedure, not a function.
It doesn't return a value like <func>ET</func> so
you can't use an <func>ET_INCLUDE</func> in an expression.
</p>
<p>
It is important to understand the difference between an
<func>ET_INCLUDE</func> statement like this
<pre>
ET_INCLUDE( dclock.tcl );
</pre>
and the <code>source</code> command of Tcl/Tk, used as follows:
<pre>
ET( source dclock.tcl );
</pre>
The <func>ET_INCLUDE</func> statement reads the Tcl/Tk into the program at
compile-time, effectively making the Tcl/Tk code part of the executable.
The Tcl <code>source</code> command, on the other hand, opens and
reads the file at run-time, as the application executes. This
makes the executable a little smaller, but it also means that the
file containing the Tcl/Tk must be available to the executable
whenever it runs.
If you move just the executable, but not the Tcl/Tk file, to
another computer, or even another directory, then it will no
longer work because it won't be able to locate and read the Tcl/Tk file.
</p>
<p>
The ability to read an external Tcl/Tk script and make it part of the
executable program is an important feature of ET.
But while you are developing and testing a program, it is sometimes
convenient to turn this feature off and to have the application read
its scripts at run-time instead of compile-time.
That way, you can make changes to the Tcl/Tk script and rerun your
program with the changes, but without having to recompile.
You can do this using the <cmd>-dynamic</cmd> option to the
<cmd>et2c</cmd> proprocessor.
Whenever you run <cmd>et2c</cmd> with the <cmd>-dynamic</cmd> command-line
option, it effective turns instances of the statement
<pre>
ET_INCLUDE( filename.tcl );
</pre>
into the statement
<pre>
ET( source filename.tcl );
</pre>
This feature has proven very helpful during development.
But be careful to turn it off before doing your final build, or else
you won't be able to move your executable to other machines!
</p>
<p>
There is just one other feature of the <func>ET_INCLUDE</func>
statement that we need to discuss before moving on, and that is
the algorithm it uses to locate the Tcl/Tk source code files.
Just like the C preprocessor's <code>#include</code> statement, the
<func>ET_INCLUDE</func> mechanism can include
files found in other directories.
</p>
<p>
The <cmd>et2c</cmd> preprocessor always looks first in the working
directory for files named by an <func>ET_INCLUDE</func> statement.
If the file is found there, no further search is made.
But if the file is not found, then <cmd>et2c</cmd> will also look
in all directories named in <cmd>-I</cmd> command line options.
For example, if you run <cmd>et2c</cmd> like this:
<pre>
et2c -I../tcl -I/usr/local/lib/tcl app.c >app_.c
</pre>
and the <filename>app.c</filename> file contains a line of the form:
<pre>
ET_INCLUDE( setup.tcl );
</pre>
then <cmd>et2c</cmd> will search for the <filename>setup.tcl</filename>
first in the <quote><filename>.</filename></quote> directory, then
in <filename>../tcl</filename> and in
<filename>/usr/local/lib/tcl</filename>.
It will use the first instance of <filename>setup.tcl</filename> that
it finds.
</p>
<h1 tag=globals>Global Variables In ET</h1>
<p>
The <filename>et.o</filename> library for ET defines three
global C variables that are sometimes of use to programmers.
In addition, <func>Et_Init</func> creates two new global
Tcl/Tk variables that many programs find useful.
This section will describe what all of these variables do, and
suggest ways that they can be used.
</p>
<h2>Global C Variables Created By ET</h2>
<p>
Perhaps the most useful of the global variables available in
ET is <code>Et_Interp</code>.
This variable is a pointer to the Tcl/Tk interpreter, the one
created by <func>Et_Init</func> and used to execute all Tcl/Tk
commands within the program.
The <code>Et_Interp</code> variable has the same value as the
<code>interp</code> formal parameter found in every <func>ET_PROC</func>
function.
</p>
<p>
The <code>Et_Interp</code> variable is useful because you may
often want to call C routines in the Tcl/Tk library, and most
of these routines require a pointer to the interpreter as their
first parameter.
For instance, suppose in the initialization code you want to create
a link between the global C variable <code>nClients</code> and a
Tcl/Tk variable by the same name.
Using the <code>Et_Interp</code> variable as the first parameter
to the Tcl function <func>Tcl_LinkVar</func>, you could write:
<pre>
Tcl_LinkVar(Et_Interp,"nClients",(char*)&nClients,TCL_LINK_INT);
</pre>
Having done this, any changes to the C <code>nClients</code> variable
will be reflected in the Tcl/Tk variable, and vice versa.
</p>
<p>
Perhaps the second most useful global varible is <code>Et_Display</code>.
This variable contains the <code>Display</code> pointer required as
the first argument to most Xlib routines.
It is used by daring, down-to-the-bare-metal programmers who like to
call Xlib directly.
</p>
<p>
Here's an example.
Suppose you want to create a new Tcl/Tk command,
<code>PitchedBell</code>, that makes
the X terminal emit a beep with a pitch specified by its sole argument.
Once such a command is implemented, then the following Tcl/Tk code
would emit a single tone at the pitch of concert A:
<pre>
PitchedBell 440
</pre>
Here a short piece of Tcl/Tk code that plays the opening bar of
Beethoven's Fifth Symphony:
<pre>
foreach pitch {784 784 784 659} {
PitchedBell $pitch
after 200
}
</pre>
You probably get the idea.
Here's the code that implements the <code>PitchedBell</code> command:
<pre>
#include <tk.h> /* Will also pickup <Xlib.h> */
ET_PROC( PitchedBell ){
XKeyboardControl ctrl; /* For changing the bell pitch */
if( argc!=2 ){
interp->result =
"Wrong # args. Should be: ``PitchedBell PITCH''";
return ET_ERROR;
}
ctrl.bell_pitch = atoi( argv[1] );
XChangeKeyboardControl(Et_Display,KBBellPitch,&ctrl);
XBell(Et_Display,0);
XFlush(Et_Display);
return ET_OK;
}
</pre>
After checking to make sure it has exactly one argument, the
<code>PitchedBell</code> command uses the
<func>XChangeKeyboardControl</func>
function of Xlib to change the bell pitch.
It then rings the bell using the <func>XBell</func> Xlib function, and
finally flushes the Xlib message queue using <func>XFlush</func> to
force the bell to be rung immediately.
All three of these Xlib functions require a <code>Display</code>
pointer as their first argument, a role that is perfectly filled
by the <code>Et_Display</code> global variable.
</p>
<p>
The third and final global C variable in ET is <code>Et_MainWindow</code>.
This variable is a pointer to
the Tcl/Tk structure that defines the application's
main window.
Back in the days of Tk3.6, several Tcl/Tk library functions
required this value as a parameter.
But the Tcl/Tk library interface changed in the move to Tk4.0, so that
the main window pointer is no longer required.
Hence, the <code>Et_MainWindow</code> variable isn't used much any more.
It has been kept around as an historical artifact.
</p>
<h2>Tcl/Tk Variables Created By ET</h2>
<p>
Besides the 3 global C variables, ET also provides two
Tcl/Tk variables that are of frequent use:
<code>cmd_name</code> and <code>cmd_dir</code>.
The <code>cmd_name</code> variable contains the name of the file
holding the executable for the application, and <code>cmd_dir</code>
is the name of the directory containing that file.
</p>
<p>
The <code>cmd_name</code> and <code>cmd_dir</code> variables are
useful to programs that need to read or write auxiliary data files.
In order to open an auxiliary file, the program needs to know the
files pathname, but it is not a good idea to hard-code a complete
pathname into the program.
Otherwise, the auxiliary file can't be moved without recompiling
the program.
By careful use of <code>cmd_name</code> and/or <code>cmd_dir</code>,
we can arrange to have auxiliary files located in a directory
relative to the executable, rather that at some fixed location.
That way, a system adminstrator is free to move the auxiliary file
to a different directory as long as the executable moves with it.
</p>
<p>
For example, suppose you are writing a program named
<filename>acctrec</filename> that needs to access a data file
named <filename>acctrec.db</filename>.
Furthermore, suppose the data file is located in a directory
<filename>../data</filename> relative to the executable.
Then to open the data file for reading, a program could
write:
<pre>
char *fullName = ET_STR( return $cmd_dir/../data/$cmd_name.db );
FILE *fp = fopen(fullName,"r");
</pre>
Using this scheme, both the executable and the datafile can be placed
anywhere in the filesystem, as long as they are in the same position
relative to one another.
They can also be renamed, so long as they retain the same base name.
This flexibility is a boon to system adminstraters, and also
make the program less sensitive to installation errors.
</p>
<h1 tag=readstdin>Reading From Standard Input</h1>
<p>
There's one last feature of ET that we haven't discussed:
the <func>Et_ReadStdin</func> procedure.
If this procedure is called (with no arguments) in between
the calls to <func>Et_Init</func> and <func>Et_MainLoop</func>,
ET will make arrangements to read all data that appears
on standard input and interpret that data as Tcl/Tk commands.
</p>
<p>
You can use the <func>Et_ReadStdin</func> to implement the
interactive <cmd>wish</cmd> interpreter for Tcl/Tk.
The code would look like this:
<pre>
main(int argc, char **argv){
Et_Init(&argc,argv);
Et_ReadStdin();
Et_MainLoop();
}
</pre>
Let's call this program <cmd>etwish</cmd> in order to distinguish
it from the standard <cmd>wish</cmd> that comes with Tcl/tk.
The <cmd>etwish</cmd> program differs from <cmd>wish</cmd> in two ways.
First, <cmd>wish</cmd> reads a set of 15 or so Tcl/Tk scripts from
a well-known directory when it first starts up.
Thus, to install <cmd>wish</cmd>, you have to have both
the <filename>wish</filename> executable
and the 15 startup scripts.
But with <cmd>etwish</cmd>, the 15 startup scripts are compiled
into the executable (using <func>ET_INCLUDE</func> statements inside
the <func>Et_Init</func> function) so the external scripts are
no longer required. This does make the <cmd>etwish</cmd> executable
slightly larger (by about 64K bytes), but it also makes the
program much easier to install and administer.
</p>
<p>
The second difference between <cmd>wish</cmd> and the <cmd>etwish</cmd>
program shown above is that <cmd>etwish</cmd> is always interactive.
It will not read a script from a file given as a command line argument
like standard <cmd>wish</cmd> will.
But we can remove that difference using a little more code.
<pre>
main(int argc, char **argv){
Et_Init(&argc,argv);
if( argc>2 && (strcmp(argv[1],"-f")==0 || strcmp(argv[1],"-file")==0) ){
ET( source "%q(argv[2])" );
}else if( argc>1 ){
ET( source "%q(argv[1])" );
}else{
Et_ReadStdin();
}
Et_MainLoop();
}
</pre>
This revised program serves as a great template
for building customized editions of <cmd>wish</cmd> that have one
or more new Tcl/Tk commands written in C.
All you have to do is code the new commands using
the <func>ET_PROC</func> mechanism and insert a single
<code>ET_INSTALL_COMMANDS</code> statement right after the
<func>Et_Init</func>.
</p>
<h1 tag=compiling>Compiling ET Applications</h1>
<p>
We've already discussed the basics of compiling ET applications
back in section <ref tag=helloworld> when we put together the
<quote>Hello, World!</quote> example.
Basically, all you do is preprocess your source files with <cmd>et2c</cmd>
and then run the results through the C compiler.
But that synopsis omits a lot of detail.
This section fills in the missing information.
</p>
<h2>Compiling ET Itself</h2>
<p>
But before we begin talking about how to compile ET applications,
we need to first mention how to compile ET itself --
the <cmd>et2c</cmd> preprocessor and the <filename>et.o</filename>
library.
(If you have one of the platforms supported by the
CD-ROM in the back of this book, then you already have precompiled versions
of <cmd>et2c</cmd> and <filename>et.o</filename> and
can skip this step.)
</p>
<p>
The source code to the <cmd>et2c</cmd> preprocessor is contained
in a single file named <cmd>et2c.c</cmd>.
The preprocessor is written in highly portable K&R C and should
compile without change on just about any 32-bit architecture.
All you have to do is this:
<pre>
cc -O -o et2c et2c.c
</pre>
</p>
<p>
Compiling the <filename>et.o</filename> library is a little more
problematic, but still not difficult.
There are three steps.
First you have to select an appropriate source code file.
There are different versions of the source code (sometimes radically
different) depending on which version of Tcl/Tk you are using.
For Tk version 3.6, choose <filename>et36.c</filename>.
For Tk version 4.0, choose <filename>et40.c</filename>.
For Tk version 4.1 on UNIX and X11, choose <filename>et41.c</filename>.
Your ET distribution may also have other options, such as versions for
MS-Windows or Macintosh, or versions with built-in support for
various Tcl extensions.
</p>
<p>
Let's suppose, for the sake of discussion, that you selected the
source file <filename>et41.c</filename>.
The next step is to preprocess this file using <cmd>et2c</cmd>.
This step is a little tricky because we have to use the
<cmd>-I</cmd> option to <cmd>et2c</cmd> to tell
the preprocessor where to find the Tcl/Tk startup scripts.
</p>
<p>
Recall that the stardard Tcl/Tk interpreter program,
<cmd>wish</cmd>, reads and executes a series of Tcl/Tk scripts
when it first starts up.
These scripts set up default widget bindings, create procedures
for handling menus, and so forth.
The names of the directories from which these scripts are loaded are
hard-coded in the <cmd>wish</cmd> executable.
There are about 15 different startup scripts (the number
varies from one version of Tcl/Tk to the next) and <cmd>wish</cmd>
will not run without them.
</p>
<p>
But ET applications don't read the startup scripts at run-time.
Instead, a series of <func>ET_INCLUDE</func> statements inside
the <func>Et_Init</func> function bind the startup scripts into
an ET executable at compile-time.
This feature is what enables ET applications to run on machines that
do not have Tcl/Tk installed.
</p>
<p>
It is because of 15 or so startup scripts included by
<func>ET_INCLUDE</func> statements in the
ET library that we have to preprocess the library source code
using <cmd>et2c</cmd>.
But we also have to tell <cmd>et2c</cmd> what directories to use
when searching for the startup scripts.
If Tcl/Tk has already been installed on your system, then you
can find out the names of the startup script directories by executing the
following <cmd>wish</cmd> script:
<pre>
#! wish
puts $tk_library
puts $tcl_library
</pre>
Let's suppose that the startup scripts are located in the
directories <filename>/usr/local/lib/tcl</filename>
and <filename>/usr/local/lib/tk</filename>.
Then the command to preprocess the ET library source code
would be the following:
<pre>
et2c -I/usr/local/lib/tcl -I/usr/local/lib/tk et41.c >et.c
</pre>
</p>
<p>
After preprocessing the library source code, all that remains
is to compile it.
The library references the <code><tk.h></code> header file,
which in turn references <code><tcl.h></code>, so you
may have to add some <cmd>-I</cmd> options to the compiler command
line to specify the directories where these header files are located.
The following is typical:
<pre>
cc -c -o et.o -I/usr/include/tcl -I/usr/include/tk et.c
</pre>
</p>
<h2>Compiling The Application Code</h2>
<p>
Once you get <cmd>et2c</cmd> and <filename>et.o</filename> compiled,
the hard work is done.
To build your application, simply run each source file through
the <cmd>et2c</cmd> preprocessor before compiling it, and add
the <filename>et.o</filename> library with the final link.
For example, the steps to compile a program from two source files,
<filename>appmain.c</filename> and <filename>appaux.c</filename>,
are the something like following on most systems:
<pre>
et2c appmain.c >temp.c
cc -c temp.c -o appmain.o
et2c appaux.c >temp.c
cc -c temp.c -o appaux.o
cc appmain.o appaux.o et.o -ltk -ltcl -lX11 -lm
</pre>
</p>
<p>
If you're using a Makefile, you might want to redefine the default
rule for converting C source code into object code to incorporate
the <cmd>et2c</cmd> preprocessor step.
Like this:
<pre>
.c.o:
et2c $< >temp.c
cc -c -o $@ temp.c
</pre>
The <cmd>et2c</cmd> does not harm files that don't use ET constructs,
so this rule will work for every file in your project.
</p>
<h2>Turning Off Script Compression In The Preprocessor</h2>
<p>
The <cmd>et2c</cmd> preprocessor attempts to save memory and
improve performance of your application by removing comments and
unnecessary spaces from the Tcl/Tk code inside
<func>ET</func> functions and loaded by <func>ET_INCLUDE</func>
statements.
This mechanism works well most of the time, but it is not
foolproof.
It is theoretically possible for a valid Tcl/Tk script to be corrupted
by <cmd>et2c</cmd>'s compression attempts.
If you experience trouble, and suspect that
<cmd>et2c</cmd> is messing up your Tcl/Tk code, then you can
turn script compression off using the
<cmd>-nocompress</cmd> command line option.
</p>
<h2>Compiling Using An Older K&R Compiler</h2>
<p>
If it is your misfortune not to have an ANSI C compiler, you can
still use ET.
The source code to <code>et2c</code> is pure K&R C and should work
fine under older compilers.
The source code to <filename>et.o</filename> is another matter.
To compile the library using an older compiler you need to
first give a <code>-K+R</code> option to <code>et2c</code> and then give
a <code>-DK_AND_R</code> option to the C compiler:
<pre>
et2c -K+R -I/usr/local/lib/tcl -I/usr/local/lib/tk et40.c >et.c
cc -DK_AND_R -I/usr/include/tcl -I/usr/include/tk -c et.c
</pre>
When compiling application code with an older compiler, just
give the <code>-K+R</code> option to <code>et2c</code>.
It is not necessary to give the <code>-DK_AND_R</code> option to the
C compiler when compiling objects other than <code>et.c</code>.
</p>
<h1 tag=examples>Other ET Sample Programs</h1>
<p>
Besides the very simple <quote>Hello, World!</quote> and
decimal clock programs presented above, ET is distributed
with a number of non-trivial sample programs.
This section will briefly overview what several of these
example programs do, and why ET was important to their implementation.
We won't try to explain the details of how the programs work, though.
You can figure that out for yourself by looking at the source code.
</p>
<h2>The Color Chooser</h2>
<p>
There is a color chooser tool for X11 called
<cmd>color</cmd>.
The sources to <cmd>color</cmd> are in the files
<filename>color.c</filename> and <filename>color.tcl</filename>.
A screen image of the program is shown in
figure <ref tag=colorprog>.
</p>
<image gif=colorprog.gif eps=colorprog.eps width=4in tag=colorprog
caption="Typical appearance of the color program">
<p>
The X11 Window System supports displays with over 280 quadrillion
distinct colors (48 bits per pixel).
But from this vast number, a few hundred colors are assigned
English names like <quote>blue</quote> or
<quote>turquoise</quote> or <quote>peachpuff</quote>.
All the rest are given arcane hexadecimal designations like
<quote>#b22cd8517f32</quote>.
It is best to use colors with English names
whenever possible.
</p>
<p>
The purpose of the <cmd>color</cmd> program it to help select
colors with English names.
At the top of the application is large swatch showing one of the
280 quadrillion X11 colors, together with either its English name
(if it has one) or its hexadecimal value.
Sliders on the lower left side of the window allow the user to vary
the color of the swatch by changing various color components.
On the lower right side of the window are six smaller swatches that
show colors with English names that are similar to the color in the
main swatch.
Moving any of the six color component sliders causes the colors in
all swatches, and the other sliders, to update in real time.
Clicking on any of the smaller swatches transfers its color to the
main swatch, updating all of the sliders and swatches appropriately.
</p>
<p>
In theory, there is nothing to prevent the <cmd>color</cmd> program
from being coded in pure Tcl/Tk, but in practice, such an
implementation would be much too slow.
For this reason, two key routines are coded in C.
The <code>ET_PROC</code> command <code>ChangeComponent</code> is
called whenever one of the color component sliders is moved.
This routine moves the other sliders, changes the color of the main
swatch, then computes close colors for the smaller swatches.
Another <code>ET_PROC</code> command named <code>ChangeColor</code>
is called whenever the user clicks on one of the smaller swatches.
This routine changes the color of the main swatch, then updates
the sliders and the smaller swatches accordingly.
</p>
<h2>The VT100 Terminal Emulator</h2>
<p>
The example named <cmd>tkterm</cmd> implements a VT100 terminal emulator.
The <cmd>tkterm</cmd> program can be used as a direct replacement
for the more familiar emulator programs <cmd>xterm</cmd> or <cmd>rxvt</cmd>.
</p>
<p>
The sources for <cmd>tkterm</cmd> are contained in three separate files.
The main procedure is in <filename>tkterm.c</filename>.
Tcl/Tk for constructing the main window for the application is in
<filename>tkterm.tcl</filename>.
Finally, the file <filename>getpty.c</filename> takes care of the
messy details of allocating a pseudo-TTY for the emulator and
invoking a shell in the pseudo-TTY.
(Much of the code in <filename>getpty.c</filename> was copied from
<cmd>rxvt</cmd>.)
</p>
<p>
The <cmd>tkterm</cmd> program simulates the VT100 display using
an ordinary Tcl/Tk text widget.
C routines in <filename>tkterm.c</filename> interpret the characters
and escape sequences coming into the program and use <func>ET</func>
functions to insert characters into their proper
places within the text widget.
The <filename>tkterm.c</filename> file is almost 1000 lines long, and is
mostly devoted to interpreting the VT100 escape codes.
</p>
<p>
The <cmd>tkterm</cmd> program is an example of an application that could
not be coded in pure Tcl/Tk, since Tcl/Tk has no provisions for dealing
with pseudo-TTYs or TTYs in <quote>raw</quote> mode.
But even if it could, we would probably still want to use some C code,
since it seems unlikely that a Tcl/Tk script would be able to process
the VT100 escape sequences efficiently.
</p>
<h2>A Real-Time Performance Monitor For Linux</h2>
<p>
The <cmd>perfmon</cmd> program is a system performance monitor for
the Linux operating system.
It uses bar graphs to shows the amount of memory, swap space, CPU time
currently being used.
The display is updated 10 times per second.
There are two source code files for this application:
<filename>perfmon.c</filename> and <filename>perfmon.tcl</filename>.
</p>
<p>
The main display of the <cmd>perfmon</cmd> program is implemented using
a Tcl/Tk canvas widget.
But for efficiency's sake, the logic that computes the current memory,
swap space, and CPU usages is all coded in C.
The C code obtains the system performance data by reading the files
<filename>/proc/stat</filename> and <filename>/proc/meminfo</filename>.
It then processes this information into the desired preformance
measurements and makes appropriate changes to the Tcl/Tk bar graphs using
<func>ET</func> function calls.
</p>
<p>
On a 90MHz Pentium and with an update frequency of 10 times per
second, the <cmd>prefmon</cmd> program uses a negligible amount of
the CPU time.
So in addition to being a nifty desktop utility for a Linux workstation,
this example demonstrates that Tcl/Tk applications can be very efficient.
</p>
<h2>An ASCII Text Editor And A File Browser</h2>
<p>
The two programs <cmd>tkedit</cmd> and <cmd>browser</cmd> implement,
respectively, an ASCII text editor and a UNIX file browser utility.
Source code to these programs is in the files
<filename>tkedit.c</filename>, <filename>tkedit.tcl</filename>,
<filename>browser.c</filename> and <filename>browser.tcl</filename>.
</p>
<p>
Both of these programs could just as well have been implemented as
pure Tcl/Tk scripts, with no loss of features or performance.
(In fact, the browser can be used as pure script by invoking the
<filename>browser.tcl</filename> using <cmd>wish</cmd>.)
But, sometimes you want a program to be a real executable, not a
script.
For instance, you may want to be able to run the program on machines
that do not have Tcl/Tk installed.
Or, perhaps you want the programs to run on machines that have a
different, incompatible version of Tcl/Tk installed.
</p>
<p>
The <cmd>tkedit</cmd> and <cmd>browser</cmd> programs are examples of
how to convert a pure Tcl/Tk script into a stand-alone program using
ET.
The idea is very simple.
Your C code simply initializes ET, invokes your script using a
single <func>ET_INCLUDE</func> statement, and then enters the event
loop.
Like this:
<pre>
void main(int argc, char **argv){
Et_Init(&argc,argv);
ET_INCLUDE( browser.tcl );
Et_MainLoop();
}
</pre>
Compiling this code results in a stand-alone application that can
be run on any binary-compatible machine.
</p>
<h1 tag=macandwin>Using ET To Build MS-Windows And Macintosh Applications</h1>
<p>
ET, like Tcl/Tk, was originally written to support the open X11 windowing
system only.
But nowadays, people often need to write applications for popular
proprietary windowing systems such as Windows95 or Macintosh.
Beginning with release 4.1, Tcl/Tk supports these proprietary products,
and so does ET.
(Actually, only Windows95 is supported as of this writing.
The author has no access to a Macintosh system on which to
develop and test a Macintosh port.)
</p>
<p>
On a Macintosh, ET applications that don't call Xlib directly should
compile with little or no change.
The Mac won't support the <func>Et_ReadStdin</func> routine, or the
<code>Et_Display</code> global variable, but then again, neither of
these make much sense on a Mac.
The application will compile in much the same way as it does for X11,
except that you should use the <filename>et41mac.c</filename> source
file to the <filename>et.o</filename> library.
</p>
<p>
More change is required to support Windows95, however.
The Windows version of ET doesn't contain either
<func>Et_Init</func> or <func>Et_MainLoop</func>.
Instead these functions will be invoked automatically.
An ET program for Windows should contain a single <func>Et_Main</func>
procedure definition to do all its setup, and nothing more.
Hence, if your application used to look like this:
<pre>
void main(int argc, char **argv){
Et_Init(&argc,argv);
/* Your setup code here */
Et_MainLoop();
}
</pre>
then under Windows, it will look like this instead:
<pre>
void Et_Main(int argc, char **argv){
/* Your setup code here */
}
</pre>
Besides that, and the obvious fact that <code>Et_Display</code> is
not supported, a Windows ET application should work just like an
X11 ET application.
It is compiled in the same way, except that you should use the
<filename>et41win.c</filename> source file for the
<filename>et.o</filename> library.
</p>
<h1>Summary And Acknowledgements</h1>
<p>
Over the past two years, many people have used ET to build
programs from a mixture of Tcl/Tk and C.
Projects have ranged in size from student programming assignments up to
large-scale (100,000+ lines) commercial development efforts.
In all cases, ET has proven to be
an effective alternative to other GUI toolkits.
</p>
<p>
The original implementation of ET grew out of a programming contract
from Lucent Technologies (formerly AT&T Bell Laboratories).
Lucent Technologies was in turn funded under a contract from the United
States Navy.
Many thanks go to Richard Blanchard at Lucent Technologies and
to Charlie Roop, Dave Toms and Clair Guthrie at PMO-428 for allowing ET to be
released to the public domain.
</p>
<p>
The author can be reached at:
</p>
<p>
D. Richard Hipp<br>
Hipp, Wyrick & Company, Inc.<br>
6200 Maple Cove Lane<br>
Charlotte, NC 28269<br>
704-948-4565<br>
drh@vnet.net<br>
</p>
|