What follows is a description of the files that you must or may ship with a Smalltalk MT executable:
|strtdllx.DLL||runtime routines for ST/MT (15K)||required|
|xxx||your executable (EXE or DLL)||required|
|stmsg.DLL||error messages (roll your own)||should be integrated into executable|
|stsrl.DLL||serialization routines (60K)||optional|
Note: strtdllx.DLL is named strtdll20.dll in Smalltalk MT 2.0 and strtdll25.dll in version 2.5/2.6. Strictly speaking, runtime Dlls are not version-dependant. We changed the functionality exposed by the runtime Dll. In addition, the latest Dlls have been compiled with VC 6, which resulted in an increase in code size.
|Underscores||Underscores identify private messages, classes, or pseudo-messages (inlined messages).|
|Class libraries||Smalltalk MT only implements classes / methods that are also used by the development environment or samples. That way, all source code has been tested.|
The message #_asCinteger is sent implicitly to API arguments, and returns a 32 bit representation of an object. If the object cannot be passed to an API (for example nil), it raises an exception.
You can bypass this message with one of the messages #basicAddress, #_asInteger, or
more generally with an inline message that returns a known type (such as #_longAtOffset:).
Note that the address manipulation methods (#atAddress:put: etc.) do not send the message, so you may have to send it yourself if the parameter is not an integer or LONG type. Example: MemoryManager atAddress: ppEnumConnectionPoints put: ienum _asCinteger.
It would be similar to what you do in C:
module := HModule loadLibrary:
module callProc: szAPI with: arg1 with: arg2
You can take a look at #callProc:with:with: and add more parameters as you
go. It is based on the pseudo-message
(which generates inline code). Use
#stdcallAPI for the C calling convention.
Each window implements its own window procedure (although most windows inherit the default window procedure in Window), and exports it to the operating system.
A Smalltalk-created window other than a dialog box reserves private data in its window creation structure, and stores the address of the Smalltalk object in that slot (remember: in ST/MT, objects are never moved).
A DialogBox functions exactly like a Dialog Box in C/C++ ; each instance has its own dialog procedure that references the Smalltalk object.
'STOBJ'that is associated with a window?
The purpose of this property is to be able to retrieve the Smalltalk object associated with a window, given the window handle. If the window is not maintained by Smalltalk (i.e., the window procedure is implemented elsewhere), the property will not be set.
[ ^self ] fork, and it raises an exception
The return statement in a block returns from the method that defines the block. If you create a thread that executes this block, the new thread would attempt to return from a method context in a parallel thread, therefore switching the stack and crashing. And your original thread would crash as well because the other thread corrupted its stack. Therefore, the code that actually returns verifies that the return is valid, and raises a software exception if it cannot return.
A forked block exits naturally. If you created the thread with one of the #fork messages, there is no need to worry about exiting the thread. For example:
[ | a | 1000 timesRepeat: [ a := a + 1 ] ] fork
exits when it is done.
In Win32, the only general way to kill a thread is to use TerminateThread. This works on Smalltalk MT created threads as well, but leaves some Win32 resources open (refer to the Win32 documentation).
So, terminating a thread in an orderly fashion requires some programming logic and synchronization. The Philosopher sample shows how to do this.
A Windows Application requires at least the following components:
In order to open an application such as Generic, you must first install the project file (generic.sp). The next step is to copy the resources into your path (i.e., copy generic.dll to your image directory). You can then open the application (look at the readme.txt file that comes with the sample to see how to do this).
Some projects require additional components before they can be used. The Project Browser will take care of it automatically (linking with DLLs and installing prerequisite projects).
You must first define a method ApplicationProcess>>winMain:with:with:with: that starts up your main window (or whatever you want to do in the process). Define this method in a file called winmain.sm and place the file in the project directory.
The Project Browser checks for a winmain.sm file in the current project directory before it enables the build EXE menu item.
The generated executable raises an exception at runtime:
An instance of "Undefined Object" does not understand the
message "Symbol(16r1000ACA)". The message was sent
by an instance of "TestClient"
If you enable all error assertions you'll see the message box of the type "an instance of XXX did not understand Symbol(yyy), the message was sent by an instance of ZZZ".
In the development, evaluate
Symbol value: 16r1000ACA
and it returns #show:.
You can also run the executable in WinDbg or use DBMON on Windows NT to view debugging messages.
a) #basicAddress always returns an integer that is the object's address.
b) once you take an object's address, you must ensure that it is still referenced. The following is wrong:
| findStruct |
findStruct := FINDTEXT new.
findStruct lpstrText: 'Courier',' New'.
The reason is that 'Courier',' New' creates a new string which is stored in a structure. The structure is a byte object and stores the pointer as a 32-bit value, meaning that there are no more references to the string, which can be collected. So, use the code below instead:
| findStruct szText|
findStruct := FINDTEXT new.
szText := 'Courier',' New'.
findStruct lpstrText: szFont.
self doSomethingWith: findStruct.
^'and now szText gets out of scope'
c) to be sure an object is not discarded, you can:
- use #registerObject / #releaseObject
- allocate it on the external heap:
(String heapAlloc: szFont size) replaceFrom: 1 to: szFont size with: szFont; yourself
and free it using heapFree.
ApplicationProcess is instantiated
with the message
heapAlloc. The method
allInstances only returns instances allocated
on the Smalltalk heap.
Back to Top
To process a window message such as WM_MOUSEMOVE, just implement a method named WM_XXX:with: (for a WM_XXX message). Don't forget to return a value as specified by the documentation of the message.
If the window that receives the message is a Windows control, the method above will not directly work because the window procedure is implemented by the control. First, check whether the control send notification events to its parent. In this case, install an event handler for the notification (using #when:in:perform:). Otherwise, create a Smalltalk subclass of the control that implements the windows message in question, and send it the message #subclassWindow upon creation. The TwinEdit sample on the distribution set demonstrates this.
Back to Top
To exit the development environment programmatically, evaluate:
In Smalltalk MT, the standard serialization methods use memory-mapping technology. MappedObjectStream implements an easy-to use interface. An instance of MappedObjectStream maps a set of objects into the address space of the process. The top-level object is called the root object. There is only one root object, but it can be arbitrary, and in particular it can be a collection.
If you open an existing file in RW mode, you can modify any object that resides in the file. When the file is saved, external references are automatically appended to the end of the file. If you wish to add more objects, you can add them to the root collection or to any other object in the file.
See also Object Serialization Example.
The garbage collector (GC) scans all object references. If you close the mapped file while keeping a reference, an otherwise valid reference will suddenly point to nowhere. The GC algorithm assumes that an object reference is always valid. Otherwise, object traversal would be much slower.
See also Object Serialization Example.
Loading and unloading ActiveX Controls dynamically
It is possible to load and unload ActiveX controls at runtime. One benefit is that an application can delay loading an ActiveX control until it is actually needed, which reduces the application startup time.
The following code fragment loads an ActiveX control (the Microsoft Web Browser Control) dynamically. The application must first create an OleContainerWindow.
| oleContainer if |
oleContainer := self childAt: IDC_CONTROL1.
(if := IClassFactory coCreateInstance: (GUID IIDFromString:
interface: IUnknown) notNil ifTrue: [
oleContainer insertObject: if.
oleContainer invoke: 500 with: 'D:\\index.htm'.
To unload the control: