Using DLL's |
| Where are the DLL classes? For those of you used to traditional Smalltalk you would have a wrapper class for your DLL's. This is because traditional Smalltalk requires manipulation of arguments to convert them from Smalltalk objects to external objects. This is also because a special virtual machine mechanism is required to access DLL APIs (e.g. <api: MessageBeep ulong boolean>). STMT takes a different approach by binding the DLL's statically into the image. This means a direct call to an API (in assembler the arguments are pushed onto the stack and a jmp instruction transfers execution to the DLL). In fact a DLL call is faster than a messages send. To bind the DLL into the image use the Transcript menu tools and select Image Properties. Press the Add... button to add a DLL. If the DLL is found it will appear in the Module list. Note that you can right click on the DLL and select Edit from the popup menu. This will show you all the entry points in the DLL (very useful). In order to statically bind this DLL, the image needs to be rebuilt, so you must save the image. Notice in the save image dialog that compress is enabled and grayed out. This is because the DLL import table must be rebuilt and this changes all of the API address. Because compress is enabled, STMT will exit after rebuilding the image. The next time you load it, the DLL will be bound into the image and will be available for use. So how do you use an API. The keyword is WINAPI followed by the API name and any parameters. When a DLL is loaded, STMT will follow one of two courses of action. If a .DEF file of the same name is found, this will be loaded and sets the details (return type, calling convention, number of arguments, parameters). If a .DEF file is not found, then STMT assumes an Integer return type, WINAPI calling convention, Fixed arguments and -1 parameters (which means unknown). Once the API has been called once, STMT assumes that the number of parameters specified in the call and will change the -1. To change the number of parameters, use the Transcript Tool menu under Editors and Api Editor... Here you can put in the name of an API e.g. MessageBeep and a dialog will pop up showing the API requirements. The compiler will also complain if you try and enter an incorrect call. Here is a valid example: WINAPI MessageBeep: -1 Or another example: WINAPI strlen: 'Here is a string' basicAddress Because API calls require no special handling, there is no need to provide wrapper classes. Instead API calls can be used where required. API calls that do enumeration can be used just like any other STMT statement. For example the WIN32 call EnumWindows: enumerates all top level windows currently running. The EnumWindowsProc takes two arguments (hwnd and lParam) one being a user defined value and the other being a Window handle. To call this in STMT is as easy as (in a workspace): WINAPI EnumWindows: [:hwnd :lParam| Transcript nextPutAll: hwnd asString;cr] with: NULL This would print all of the window handles to the Transcript. This executes exactly like any other block type argument. Alternatively you can provide a method to the DLL call instead of a block. The call would then look like this: WINAPI EnumWindows: (myClass methodAddressAt: #enumWindowsHandler:with:) with: NULL This method handleEnumWindows:with:with: must be a class method and must be in an .EXPORT category. Aha you say, but I am calling this from the instance side and I am trying to use instance variable. For example I am trying to see if a range of windows are currently available. How do I get to the instance side of my class when the callback method is on the class side? This is where you can use the second parameter in the EnumWindows call which will be passed to the enumeration method in lParam. But what should you pass? You should pass self of course. To do this, you would pass the address to self and then access this address in the enumeration method. This is what it would look like: WINAPI EnumWindows: (myClass methodAddressAt: #enumWindowsHandler:with:) with: self basicAddress Now we write the class method as: enumWindowsHandler: hwnd with: lParam This works because _asObject turns self basicAddress back into self. Thus we end up sending our instance side the method topLevelWindow: with a window handle. The TRUE at the end is a Windows requirement. |