|
"Easy Windows DLL"
|
Owner:
| mutteringgoblin
| Showcase Entry:
| None | |
Total Entries: 6 | mutteringgoblin Entry: 6 12/02/2002 10:13 PM
| Well the header files problem is pretty much sorted out. However the project is still on the back-burner cos I'm writing a novel at the moment so that's taking up all my time. Hopefully I'll get back to the DLL at some point in the next couple of weeks. | mutteringgoblin Entry: 5 11/15/2002 10:14 PM
| Project temporarily on hold - the program reached 1000 lines of code so I decided I needed to split it into separate files. The compiler is being uncooperative. I'm getting redefined variables and undefined symbols all over the place. Hopefully it will be sorted out soon... | mutteringgoblin Entry: 4 11/11/2002 09:05 PM
| Well, good and bad news today. The good news is I've got the icon system working. You can now load an image in Blitz, and use it as an icon on a separate window (on a button, or on the window's title bar). The bad news is, it only works if the number of pixels in the image is a multiple of 8.
I'm trying to ascertain whether this is a bug in my code, or something to do with the Windows icon format....? | mutteringgoblin Entry: 3 11/10/2002 01:38 AM
| It's been a really bad day for DLL-writing. I've been concentrating on finding a way to pass bitmaps from Blitz to the DLL, and convert them to icon format in memory. It could have all been fairly painless if it wasnt for the pitiful state of the WinAPI documentation.
The ICONINFO/CreateIconIndirect() documentation says you need to create two arrays for each icon; one array contains the RGB values of all the pixels in the image, and the other is a bitmask array indicating which pixels are transparent.
What they forgot to mention is that while the first array is an array of integers, the second one is an array of bits. Yes, bits. Both arrays are declared in the ICONINFO struct as void* pointers, which means they could point to absolutely anything. I assumed (with no other information to go on) that since one array was all integers, the other one would be all integers too. Pretty reasonable logic if you ask me.
The result was that all my Blitz-created icons came out either completely unmasked, or psychedelic.
I finally got to the root of the problem at about 2:30am. I asked the DLL to fill a Blitz bank with the mask info for the standard Windows logo, and after a lot of trial and error (peeking bytes and manipulating them with and/shl) I managed to get the bitmap mask to display in the Blitz window.
WARNING: I advise any Microsoft developers to steer well clear of me for at least the next twenty years.
So anyway, tonight wasnt completely unproductive - I should have the icon system working soon. If I do get it working, it will be a huge breakthrough because it will mean...
1) The ability to put icons in the system tray. 2) The ability to replace the default icon on any window. 3) Customised pushbuttons with images on them (yeah!!!)
Right, I think if youve read this far you deserve some light relief so heres a poem I wrote earlier...
Here I sit, exposed and afraid, Accosted by fiendish parrots, Tormented by flappy wings.
| mutteringgoblin Entry: 2 11/09/2002 09:40 PM
| Hi again readers. :D
Well I made some progress with the edit box functions today. I've decided to use a single function for creating both single and multi-line edit boxes. The function has 11 (count 'em) parameters, but the last 5 are optional, and mostly concern multi-line edit controls. The Blitz side now looks like this (I'm adding line breaks to make it clearer)...
CreateEditBox( Parent,ID,x,y,Width,Height, Multiline = False, HScroll = False, VScroll = False, HScrollBar = False, VScrollBar = False )
So now you can easily add scroll bars to an edit control, with an auto-scrolling option! I'm still trying to figure out how to get the align-client style like in Delphi...
Aaaaaa-nyway, today I added a few more general functions for communicating with controls (edit boxes, buttons etc).
GetControlText$(Control) GetControlTextFast(Control) GetControlTextLength(Control) GetControlTextLimit(Control) SetControlTextLimit(Control,Limit) EnableControl(Control,Enable = True) DisableControl(Control, Disable = True) ControlEnabled(Control) ControlDisabled(Control)
GetControlText() returns the control's text as a Blitz string. It's a bit slow but plenty fast enough for single line edit boxes or buttons. GetControlTextFast() returns the text in a Blitz bank. This version is about 750 times faster so it's a better option for grabbing the contents of a multi-line edit box and saving it to a file. GetControlTextLength() returns the number of characters currently in an edit box. The SetControlTextLimit() function lets you control how much text the user can enter. This is useful if you only want 3-digit numbers, etc.
The Enable/Disable functions are useful because you can use them together to toggle a control on and off.
If KeyHit(57) EnableControl(MyEdit,ControlDisabled(MyEdit)) EndIf
My next assignment is bitmap buttons... this should be a cool addition. I'm going to make it so you can pass an image handle to a Blitz function, and it will convert the image to Windows format so the DLL can use it on a Windows pushbutton. Hooray! Hopefully I can do this without too many problems - the main issue is preserving the image's mask color.
Unfortunately there's still the dreaded message loop to contend with. The issue here is that Windows normally sends two or three messages for every event - I somehow have to either (a) get all the messages back to Blitz or (b) pick out the most useful messages, store them and discard the rest. I'd prefer the second option because I don't want the Blitz side of things to get too complicated. I'm just not sure yet if it can be done. Another annoyance I've been trying to get round is handles and IDs - Windows has this lame system where controls are identified by a handle and an ID number. (Yes... why???). At the moment you have to make use of both identifiers on the Blitz side, but I'm trying to figure out a way to dispense with ID numbers altogether. Windows is so messy behind the scenes it's unbelievable.
On a more positive note, I just added a very useful FindBlitzWindow() function, which should find the right window even if there are multiple instances of the Blitz app running! That's a serious breakthrough because last time I tried it I kept getting linker errors.
In other news (hehe) I'm now pretty confident about simulating modal dialog boxes (the ones that freeze the rest of the program until you respond to them). Dialogs are pretty essential for choosing options and stuff, or taking the user through a 'wizard' type process. The function I'm writing should eliminate potential problems where the user ignores the dialog and plays around in the other windows. The syntax will look probably something like this:
ForceInput(MyWindow) ; loop goes here EndForceInput()
I've done some tests and discovered that the Blitz window continues with animations, background calculations etc. even when it's disabled, so that's a bonus.
Okay that's all for now. I'm suffering from a nasty case of tramp-foot, and I need to make a trip to the all-night chemist... | mutteringgoblin Entry: 1 11/07/2002 11:46 PM
| Well this is the first installment of the worklog for my Blitz/Windows DLL. I started work on this about 3 weeks ago. I would say I'm roughly 30% along the way to completing the first working version. The entire thing is being programmed in CodeWarrior C++ with API calls, no MFC.
The idea was pretty simple - to make a Windows wrapper that anyone could use in their Blitz programs, without needing to understand Blitz types or the WinAPI. Part of the challenge for me is to make this program as easy to use as it can be. I remember when I first started out I was mystified by types, and I was kind of annoyed when I found out that all the GUI libraries relied on them. It basically meant I couldn't use those libraries. The DLL is basically an offshoot of a map editor I was making for my Asteroids style game. All my projects seem to end roughly when I start writing the editor, because I find GUI stuff intensely boring (I'd rather read a telephone directory). In the end I decided it would be easier to write a Windows DLL than to make my own GUI.
Here's a list of the features I'm hoping to incorporate:
Windows Menus Buttons List Boxes Combo Boxes Edit Controls (not rich-edit) Status Bar (simplified)
File Requester Choose Path Dialog Color Picker Font Requester
And some more advanced stuff that I may or may not be able to do:
Toolbars Customised Buttons (using images provided by the Blitz program) Floating desktop images (not a priority but I might get round to it) Tree/List View (doubtful)
On top of all that stuff I'll probably add some basic clipboard access, and functions to get various system info. I will NOT be adding any kind of runtime dialog functions or scripting facilities, because I don't know how, and I don't particularly want to find out. :)
Anyway, the story so far... I got a basic DLL going pretty quickly, with the ability to create windows, pushbuttons and (slightly buggy) edit controls from within Blitz. The functions implemented at this stage were:
CreateWindow(Parent,x,y,Width,Height,Resize = False,Maximize = False,Minimize = False,Title = "") ShowWindow(Window) HideWindow(Window) PositionWindow(Window,X,Y) SetFocus(Window) KillFocus(Window) FreeWindow(Window) UpdateWindow(Window) CreateMenu() CreateMenuBar(Window) AddMenuItem(Menu,Index,ID,ItemText$,SubMenu = False) AddMenuSeparator(Menu,Index,ID) RemoveMenuItem(Menu,ID) FreeMenu(Menu) CreateEditBoxSingle(Window,ID,x,y,Width,Height,HScroll = True) CreatePushButton(Window,ID,x,y,Width,Height,ButtonText$) GetForegroundWindow()
The above funcs are mostly wrappers for their WinAPI equivalents, so they were easy to write. They're fairly versatile, but they do take some of the complexities out of the WinAPI versions - for example, registering a window class is not necessary because it's dealt with 'behind the scenes'.
Then I ran into a problem. Apparently Blitz DLLs are loaded as extensions of the main Blitz program, so any CPU time allocated to Blitz has to be shared with the DLL. So if something CPU-intensive happens in the DLL, it will cause Blitz to lock up. The most CPU-hungry Windows control is the edit box, because edit boxes have to constantly check for keyboard input using a 'While' loop like the main game loop in Blitz. If you type into an edit box created by a standard Blitz DLL, any animations going on in the Blitz window will stall until you've finished typing. Not very useful or professional-looking if your program is a map editor or a paint application.
Okay so, after a lot of shouting, swearing and WinAPI documentation-reading I found out I could create a separate thread to handle my DLL's input processing. (Threads are a way of sharing processing time between applications. Windows suspends and resumes threads all the time to give the impression everything is happening at once.)
Enter problem number two (this one's a real killer)... a thread isn't meant to go poking about creating windows inside another thread. Because the interface with Blitz forms part of the main thread, any windows created inside DLL export functions automatically become tied to that thread. Obviously this isn't very useful because the DLL windows need their own CPU time.
The solution is to send code from the interface function to the private window thread, and have the code execute there instead. The instructions are passed from Blitz to the DLL, and then to the private thread as shown here:
Blitz Basic -> DLL Interface Function -> Window Thread
To get the code to execute remotely, I designed a class called BlitzRequest, with a virtual function BlitzRequest::Execute(). Then, in the DLLInit() function, I registered a new window message called BB_WIN32_USER. The interface function (eg. BlitzCreateWindow()) sends this message to a 'relay' window in the window thread, with a pointer to the BlitzRequest object stored in the lParam parameter.
[bugger, the code tags don't work]
DllExport int __cdecl BlitzCreateWindow(const void* InputBank,int inSize) { BlitzRequest* Request = (BlitzRequest*) new Request_CreateWindowEx(InputBank); int Result = Request->Send(RelayWindow); delete Request; return Result; }
The code that receives the message looks like this:
if(Message == BB_WIN32_USER) { BlitzRequest* Request = (BlitzRequest*)lParam; ReplyMessage(Request->Execute()); }
The object pointer arrives in the thread as a long integer. The relay window's procedure casts it to a BlitzRequest pointer and executes it, returning the result of the message to the calling thread, which then returns it to Blitz. I'm planning to inline these functions later to save CPU time (though they seem pretty fast anyway).
There's something quite appealing about sending an object into enemy territory with instructions to execute itself on arrival. Okay, maybe that's just me. :D
Here's the code for the BlitzRequest class:
class BlitzRequest { public: int SetData(const void*); int Send(HWND); virtual int Execute(void) = 0;
protected: const void* data; };
int BlitzRequest::Send(HWND window) { return SendMessage(window,BB_WIN32_USER,0,(long)this); }
int BlitzRequest::SetData(const void* bank) { data = bank; }
The next major challenge will be finding a way to get messages from the DLL windows back to Blitz. The likely technique will involve allocating extra memory for each window, and using it to store a pointer to a linked list of stored window messages. The hardest part will be returning meaningful messages to Blitz without relying on types or global variables.
Okay, that's all I'm writing for today, hopefully there will be another update soon.
PS I've only been learning WinAPI programming for a couple of weeks (and I'm using the 1997 release of the API docs!) so if anyone knows something that might make this project easier, I'd be interested to hear from you. |
|
|
|