|
From: Jason P. <ma...@wa...> - 2009-05-22 07:52:03
|
May I be lucky enough to pick my ass up and put it back onto my chair, then
also say thanks for the well documented and detailed example!
I myself have toyed with OpenGL via perl before, but have never thought of
combining it with Win32::GUI myself. Seeing as I'm a maintainer for a game,
and some people have asked for updated tools with animations from said
game.. I may someday put this example to a very literal use.
Thanks again,
Jason P
On Thu, May 21, 2009 at 11:18 PM, Kevin Marshall <kej...@ho...>wrote:
> Hey,
>
> This lengthy post is about how to use the Win32::GUI module together with
> the Perl OpenGL module (POGL).
>
> I would first like to thank the developers of Perl-Win32-GUI module. I have
> been using this module for a while now, and prefer using this module than
> coding the whole thing in C/C++. Thanks. I have decided to give back to the
> Win32::GUI community by way of sharing a solution for using OpenGL in
> conjunction with the Win32::GUI module. I hope that someone out there will
> find this useful.
>
> After reading the book OpenGL Game Programming (Premier Press), I decided
> to port some of the code examples to Perl. Since the Win32::GUI module makes
> the creation of windows easy, it was simply a matter of converting the
> relevant C/C++ code into Perl. A basic knowledge of OpenGL is needed in
> order to understand the example that I have provided. The example contains
> comments that should explain what each section does, but I'll provide a
> brief overview of the process. I'll only be discussing the Windows+OpenGL
> specific code, so if you don't have a good grasp of OpenGL, I recommend
> finding a good book or website about OpenGL programming before going any
> further. Detailed information regarding Windows API functions, and OpenGL
> functions can be found in the Windows Software Development Kit
> Documentation.
>
> OpenGL is a powerful, low-level rendering and modelling software library.
> OpenGL does not provide higher functionality, such as windowing, in order to
> remain platform independent. OpenGL uses a rendering context to store OpenGL
> commands and settings. This means that each platform must provide
> functionality to specify a rendering context for OpenGL. For UNIX systems,
> this is provided by GLX. For Windows this is provided by a set of functions
> affectionately known as 'wiggle' functions, since most of these start with
> 'wgl'. There are a number of other Win32 API functions that are also needed.
> These 'wiggle' and Win32 API functions relate Windows Device Contexts with
> the OpenGL rendering contexts. The Win32::API module is needed to import
> these functions for use in the program.
>
> Example Program Description:
>
> Lines 21-51 show some setup required for the functions that are to be
> imported. This includes creating the structure PIXELFORMATDESCRIPTOR used
> by the SetPixelFormat() function and creating a typedef for the
> wglMakeCurrent() function. Lines 52-63 show the functions that need to be
> imported. Info on these functions can be found in the SDK docs.
>
> Lines 64-100 creates the main window and sets its icon. This should be
> fairly basic for anyone experienced with the Win32::GUI module, so I'll just
> cover the important parts. Line 68 sets the -left of the window to
> CW_USEDEFAULT. This specifies that the system should position the window.
> Lines 69-71 add the window styles WS_CLIPCHILDREN and WS_CLIPSIBLINGS.
> These affect how the window will be painted. For more info about these
> styles, see the SDK docs. Lines 72-76 setup an -onTerminate event handler.
> This will be called when the window is destroyed. At this point the
> rendering context is deselected from the main window Device Context and is
> then deleted. More on these functions later. Just know that each takes a
> handle to a device or rendering context. The sub returns -1 to exit the main
> loop. For those of you familiar with C/C++ Windows programming, this
> function roughly corresponds to the WM_CLOSE message. Lines 77-90 setup an
> -onResize event handler. This is called whenever the window is resized and
> corresponds to the WM_SIZE message. The functions in the sub are OpenGL
> specific and basically just reset the viewport to the new dimensions and
> resets the perspective. Refer to the SDK docs or a good OpenGL resource for
> info about these functions. Lines 91-95 setup a -onKeyDown event handler
> which exits the program when the ESC key is pressed.
>
> Lines 101-106 are where the device and rendering contexts are setup. Line
> 101 gets the device context of the main window and stores it in a global
> variable. Lines 102-104 calls the SetupPixelFormat() function, passing it
> the handle to the main window device context. If this sub fails the program
> exits. Lines 116-157 show the SetupPixelFormat() function. Line 119 creates
> a new PIXELFORMATDESCRIPTOR structure and lines 120-147 fills the structure
> with appropriate data. See the SDK docs for more info about this structure.
> Lines 148-151 calls the ChoosePixelFormat() function passing the handle to
> the DC and the PIXELFORMATDESCRIPTOR structure. This function chooses the
> best matching pixel format for the DC from the data specified in the
> PIXELFORMATDESCRIPTOR structure. The function returns 0 if not pixel format
> can be found. Lines 152-155 set the pixel format of the DC to the format
> returned from the ChoosePixelFormat() function and returns 0 if it fails.
> The sub returns 1 to show that it succeeded. Line 105 creates an OpenGL
> rendering context from the specified DC using the wglCreateContext()
> function. The function is passed the handle to the main window DC and
> returns the handle to a rendering context. Line 106 selects the rendering
> context into the device context using the wglMakeCurrent() function. This
> functions is passed the handle to the main window DC and the handle to the
> rendering context created with wglCreateContext(). Passing 0 as the
> rendering context causes the rendering context to be deselected, such as in
> the -onTerminate event handler above. The wglDeleteContext() function is
> used to delete a rendering context and should be used after the rendering
> context has been deselected. The deselection and deletion of a rendering
> context should be performed when a window is destroyed, which is why this is
> done in the -onTerminate event handler above. Lines 101-106 could be thought
> of as equivalent to the WM_CREATE message (you could even use the Hook()
> method to implement this).
>
> Lines 107-111 show some basic setup of OpenGL before any rendering is done.
> These are OpenGL specific, so if you are unsure of what these do, refer to
> the docs.
>
> Lines 113-115 setup the main message loop. Win32::GUI::Dialog() can't be
> used here because the Render() function has to be called every frame. This
> loop is essentially the same, but a few differences are present which may
> affect applications with multiple windows, since the Win32::GUI::Dialog()
> functions does more behind the scenes. It would be nice if the
> Win32::GUI::Dialog() function accepted a sub ref which could be called every
> frame, but I'm not sure how easy that would be to create. Anyway, this does
> the job fine, but there are probably better methods.
>
> Lines 158-172 and 173-188 are the DrawCube() and Render() functions,
> respectively. These are used to draw the cube every frame and are OpenGL
> specific. Refer to the docs about what the OpenGL functions do if you are
> unsure.
>
> Line 189-193 creates a Handle() method in the Win32::GUI::DC package. This
> method returns the handle of the object passed in. Putting it in the DC
> package allows both windows and DCs to access it. This method is used when
> the OpenGL/Win32API function requires a handle. Since the objects store in
> handle internally, it makes it really easy to pass this value to the
> functions that need it.
>
> Here is my code example:
>
>
> ################################################################################
> #
> # Win32::GUI + OpenGL example
> #
> # This program demonstrates a basic example of using the Perl Win32::GUI
> # module in conjunction with the Perl OpenGL module to render a spinning
> # cube of points in the window.
> #
> # Requirements:
> # Perl,
> # Win32::GUI,
> # Win32::GUI::Carp,
> # Win32::API, and
> # OpenGL
> #
> # This program was written using ActiveState Perl 5.8.8 Build 820 running
> on
> # Windows XP and using Win32::GUI v1.06, Win32::GUI::Carp v1.01,
> # Win32::API v0.46, and OpenGL v0.56
> #
> # Parts of this program are based on example code from the book OpenGL Game
> # Programming (Premier Press, 2004) from the Premier Press Game
> # Development Series. I recommend this book for anyone interested in using
> # OpenGL for developing games on Windows. The book is written for the
> # development of games written in C/C++ and assumes an advanced knowledge
> # of the language but provides an in-depth look at OpenGL programming on
> # Windows platforms, as well as a look at using DirectInput and
> # DirectX Audio.
> #
>
> ################################################################################
>
> 1: use strict;
> 2: use warnings;
> #these constants are needed for SetPixelFormat() but aren't defined in
> Win32::GUI
> 3: use constant {
> 4: PFD_TYPE_RGBA => 0,
> 5: PFD_DOUBLEBUFFER => 0x00000001,
> 6: PFD_DRAW_TO_WINDOW => 0x00000004,
> 7: PFD_SUPPORT_OPENGL => 0x00000020,
> 8: PFD_MAIN_PLANE => 0,
> 9: };
>
> 10: use OpenGL qw(:glfunctions :glconstants :glufunctions);
> 11: use Win32::API;
> 12: use Win32::GUI qw();
> 13: use Win32::GUI::Carp qw(warningsToDialog fatalsToDialog
> immediateWarnings winwarn windie);
> 14: use Win32::GUI::Constants qw(IDI_APPLICATION WS_CLIPCHILDREN
> WS_CLIPSIBLINGS
> 15: WM_CREATE WM_SIZE WM_CLOSE VK_ESCAPE CW_USEDEFAULT MB_OK
> MB_ICONEXCLAMATION);
>
> 16: my $g_HDC; #global handle to device context
> 17: my $hRC; #handle to rendering context
>
> #keep track of rotation of cube
> 18: my $objectXRot = 0.0;
> 19: my $objectYRot = 0.0;
> 20: my $objectZRot = 0.0;
>
> #define PIXELFORMATDESCRIPTOR struct used for the SetPixelFormat function
> #refer to the Windows SDK documentation for more info about this structure
> 21: Win32::API::Struct->typedef(
> 22: PIXELFORMATDESCRIPTOR => qw(
> 23: WORD nSize;
> 24: WORD nVersion;
> 25: DWORD dwFlags;
> 26: BYTE iPixelType;
> 27: BYTE cColorBits;
> 28: BYTE cRedBits;
> 29: BYTE cRedShift;
> 30: BYTE cGreenBits;
> 31: BYTE cGreenShift;
> 32: BYTE cBlueBits;
> 33: BYTE cBlueShift;
> 34: BYTE cAlphaBits;
> 35: BYTE cAlphaShift;
> 36: BYTE cAccumBits;
> 37: BYTE cAccumRedBits;
> 38: BYTE cAccumGreenBits;
> 39: BYTE cAccumBlueBits;
> 40: BYTE cAccumAlphaBits;
> 41: BYTE cDepthBits;
> 42: BYTE cStencilBits;
> 43: BYTE cAuxBuffers;
> 44: BYTE iLayerType;
> 45: BYTE bReserved;
> 46: DWORD dwLayerMask;
> 47: DWORD dwVisibleMask;
> 48: DWORD dwDamageMask;
> 49: )
> 50: );
>
> #needed for the wglMakeCurrent functions
> 51: Win32::API::Type->typedef('HGLRC', 'HANDLE');
>
> #import some extra functions
> #more info can be found in the Windows SDK documentation
>
> #exchanges the front and back buffers of the current pixel format
> 52: Win32::API->Import('gdi32', 'BOOL SwapBuffers(HDC hdc);')
> 53: or windie "Win32::API->Import(SwapBuffers): $^E";
>
> #attempts to match an appropriate pixel format supported by a device
> context to
> # a given pixel format specification.
> 54: Win32::API->Import('gdi32', 'int ChoosePixelFormat(HDC hdc,
> PIXELFORMATDESCRIPTOR * ppfd);')
> 55: or windie "Win32::API->Import(ChoosePixelFormat): $^E";
>
> #sets the pixel format of the specified device context to the format
> specified
> # by the iPixelFormat index returned from ChoosePixelFormat().
> 56: Win32::API->Import('gdi32', 'BOOL SetPixelFormat(HDC hdc, int
> iPixelFormat, PIXELFORMATDESCRIPTOR * ppfd);')
> 57: or windie "Win32::API->Import(SetPixelFormat): $^E";
>
> #creates a new OpenGL rendering context, which is suitable for drawing on
> the
> # device referenced by hdc.
> 58: Win32::API->Import('opengl32', 'HGLRC wglCreateContext(HDC hdc);')
> 59: or windie "Win32::API->Import(wglCreateContext): $^E";
>
> #makes a specified OpenGL rendering context the calling thread's current
> # rendering context.
> 60: Win32::API->Import('opengl32', 'BOOL wglMakeCurrent(HDC hdc, HGLRC
> hglrc);')
> 61: or windie "Win32::API->Import(wglMakeCurrent): $^E";
>
> #deletes a specified OpenGL rendering context.
> 62: Win32::API->Import('opengl32', 'BOOL wglDeleteContext(HGLRC hglrc);')
> 63: or windie "Win32::API->Import(wglDeleteContext): $^E";
>
> #create main window
> 64: my $main = Win32::GUI::Window->new(
> 65: -name => "main",
> 66: -text => "OpenGL Example: Colour Cube",
> 67: -size => [640,480],
> 68: -left => CW_USEDEFAULT, #let system position window
> 69: -pushstyle =>
> #Excludes the area occupied by child windows when drawing occurs
> # within the parent window.
> 70: WS_CLIPCHILDREN |
> #When a particular child window needs to be painted, all other
> overlapping
> # child windows are clipped out of the region of the child window to be
> updated.
> 71: WS_CLIPSIBLINGS,
> 72: -onTerminate => sub { #WM_CLOSE
> 73: wglMakeCurrent($g_HDC->Handle(), 0); #deselect rendering context from
> $hDC
> 74: wglDeleteContext($hRC); #delete rendering context $hRC
> 75: return -1; #exit main loop
> 76: },
> 77: -onResize => sub { #WM_SIZE
> 78: my $self = shift;
> 79: return 0 unless $self;
> 80: my $height = $self->Height(); #get height and width
> 81: my $width = $self->Width();
> 82: $height = 1 if $height == 0; #don't divide by 0
> 83: glViewport(0,0,$width,$height); #set viewport to new dimensions
> 84: glMatrixMode(GL_PROJECTION); #set matrix mode to projection matrix
> 85: glLoadIdentity(); #reset projection matrix
> 86: gluPerspective(54.0, $width / $height, 1.0, 1000.0); #calculate
> aspect ratio of window
> 87: glMatrixMode(GL_MODELVIEW); #set modelview matrix
> 88: glLoadIdentity(); #reset modelview matrix
> 89: return 1;
> 90: },
> 91: -onKeyDown => sub { #WM_KEYDOWN
> 92: my ($self, $flags, $key) = @_;
> 93: return -1 if $key == VK_ESCAPE; #exit if escape key pressed
> 94: return 1;
> 95: },
> 96: );
> 97: unless($main){
> 98: windie("Cannot create window: $^E");
> 99: }
> 100: $main->SetIcon(Win32::GUI::Icon->new(IDI_APPLICATION)); #set window
> icon
>
> #WM_CREATE
> 101: $g_HDC = $main->GetDC(); #set global device context to device
> context of main window
> 102: unless(SetupPixelFormat($g_HDC->Handle())){ #setup pixel format for
> device context
> 103: exit 1; #exit if setup fails
> 104: }
> 105: $hRC = wglCreateContext($g_HDC->Handle()); #create rendering context
> used by OpenGL to draw
> 106: wglMakeCurrent($g_HDC->Handle(), $hRC); #select rendering context
> $hRC into $g_HDC
>
> #Initialize OpenGL
> 107: glShadeModel(GL_SMOOTH); #set shading to smooth
> 108: glEnable(GL_DEPTH_TEST); #do depth comparisons and update the
> depth buffer
> 109: glEnable(GL_CULL_FACE); #cull polygons based on their winding in
> window coordinates
> 110: glFrontFace(GL_CCW); #The orientation of front-facing polygons
> 111: glClearColor(0.0, 0.0, 0.0, 0.0); #values that glClear uses to
> clear the colour buffers
>
> 112: $main->Show(); #show window
>
> #message event
> #This is necessary because Render() needs to be called every frame.
> #This can produce interesting results in more complex applications (with
> more
> # than one window) since the Win32::GUI::Dialog() function does more than
> just
> # check if a sub has returned -1 (perhaps the ability to call a code block
> every
> # iteration of Win32::GUI::Dialog() would be useful)
> 113: while(Win32::GUI::DoEvents() != -1){
> 114: Render();
> 115: }
>
> #This function is used to set the pixel format for the device context.
> # Accepts a handle to the device context of the window and returns true if
> succeeds
> # or false if fails.
> #Adapted from code from OpenGL Game Programming (Premier Press, 2004)
> 116: sub SetupPixelFormat {
> 117: my $hDC = shift; #is a handle to DC
> 118: my $nPixelFormat;
> 119: my $pfd = Win32::API::Struct->new('PIXELFORMATDESCRIPTOR'); #create
> new structure
> 120: $pfd->{nSize} = $pfd->sizeof(); #return sizeof structure
> 121: $pfd->{nVersion} = 1; #default version
> 122: $pfd->{dwFlags} = PFD_DRAW_TO_WINDOW | #window drawing support
> 123: PFD_SUPPORT_OPENGL | #OpenGL support
> 124: PFD_DOUBLEBUFFER; #double buffering support
> 125: $pfd->{iPixelType} = PFD_TYPE_RGBA; #rgba colour mode
> 126: $pfd->{cColorBits} = 32; #32 bit colour mode
> 127: $pfd->{cRedBits} = 0; #ignore colour bits
> 128: $pfd->{cRedShift} = 0; #
> 129: $pfd->{cGreenBits} = 0; #
> 130: $pfd->{cGreenShift} = 0; #
> 131: $pfd->{cBlueBits} = 0; #
> 132: $pfd->{cBlueShift} = 0; #
> 133: $pfd->{cAlphaBits} = 0; #not alpha buffer
> 134: $pfd->{cAlphaShift} = 0; #ignore alpha shift bit
> 135: $pfd->{cAccumBits} = 0; #no accumulation buffer
> 136: $pfd->{cAccumRedBits} = 0; #ignore accumulation bits
> 137: $pfd->{cAccumGreenBits} = 0; #
> 138: $pfd->{cAccumBlueBits} = 0; #
> 139: $pfd->{cAccumAlphaBits} = 0; #
> 140: $pfd->{cDepthBits} = 16; #16 bit z-buffer size
> 141: $pfd->{cStencilBits} = 0; #no stencil buffer
> 142: $pfd->{cAuxBuffers} = 0; #no auxiliary buffer
> 143: $pfd->{iLayerType} = PFD_MAIN_PLANE; #main drawing plane
> 144: $pfd->{bReserved} = 0; #reserved
> 145: $pfd->{dwLayerMask} = 0; #layer masks ignored
> 146: $pfd->{dwVisibleMask} = 0; #
> 147: $pfd->{dwDamageMask} = 0; #
> # choose best matching pixel format
> 148: unless( $nPixelFormat = ChoosePixelFormat($hDC, $pfd) ){
> 149: winwarn("Can't find an appropriate pixel format");
> 150: return 0;
> 151: }
> # set pixel format to device context
> 152: unless( SetPixelFormat($hDC, $nPixelFormat, $pfd) ){
> 153: winwarn("Unable to set pixel format");
> 154: return 0;
> 155: }
> 156: return 1;
> 157: }
>
> #This function draws the cube of points. The colour of each point is based
> on its position
> #Adapted from code from OpenGL Game Programming (Premier Press, 2004)
> 158: sub DrawCube {
> 159: glPointSize(2.0); #set size of points drawn
> 160: glPushMatrix();
> 161: glBegin(GL_POINTS);
> 162: for(my $x = 0.0; $x <= 1.0; $x += 0.1){
> 163: for(my $y = 0.0; $y <= 1.0; $y += 0.1){
> 164: for(my $z = 0.0; $z <= 1.0; $z += 0.1){
> 165: glColor3f($x,$y,$z);
> #move and scale vertexes so that cube rotates about centre
> 166: glVertex3f($x*50-25,$y*50-25,$z*50-25);
> 167: }
> 168: }
> 169: }
> 170: glEnd();
> 171: glPopMatrix();
> 172: }
>
> #This function is used to draw the cube and is called every frame
> #Adapted from code from OpenGL Game Programming (Premier Press, 2004)
> 173: sub Render {
> 174: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #clear color and
> depth buffer
> 175: glLoadIdentity(); #replaces the current matrix with the
> identity matrix.
> 176: glTranslatef(0.0, 0.0, -150.0); #move to 0,0,-150
> 177: glPushMatrix(); #save current matrix
> 178: glRotatef($objectXRot, 1.0, 0.0, 0.0); #rotate around x axis
> 179: glRotatef($objectYRot, 0.0, 1.0, 0.0); #rotate around y axis
> 180: glRotatef($objectZRot, 0.0, 0.0, 1.0); #rotate around z axis
> 181: DrawCube(); #draw cube
> 182: glPopMatrix(); #restore matrix
> 183: glFlush(); #clear buffers
> 184: SwapBuffers($g_HDC->Handle()); #exchange front and back buffers
> of device context
> 185: $objectXRot += 0.01; #increment rotation
> 186: $objectYRot += 0.02;
> 187: $objectZRot += 0.01;
> 188: }
>
> #Conveniently, the Windows specific functions for setup of OpenGL accept
> and return
> # handles to windows or contexts, which are just numbers, and the handles
> to
> # these are stored in the Window and DC objects created by Win32::GUI. This
> method
> # provides an easy access to this value. Placing the method in the
> Win32::GUI::DC
> # package allows both Windows and DCs to use it.
> 189: package Win32::GUI::DC;
> 190: sub Handle {
> 191: return shift->{-handle};
> 192: }
> 193: __END__
>
> Here are some tips and tricks regarding using Win32::GUI module and the
> OpenGL module that I have come across on my travels:
>
> Win32::GUI:
>
>
> - It is possible to create child windows that are rendered to using
> OpenGL instead of rendering to the parent window, but requires a lot more
> setup. One of the tricks needed is to specify the right styles for the
> window. I have yet to perfect this technique, but perhaps when I get it
> working correctly I'll post an example.
>
>
> OpenGL:
>
> - try to use the *_p variants of functions, if they exist. The function
> has been tweaked to behave more like a regular Perl function, such as the
> ability to accept and return arrays. This is a lot more convenient than
> having to pack() and unpack() your a arguments. Some functions don't have a
> *_p variant, so the next best thing is to use the *_c variant, which
> accept OpenGL::Array objects. The use of OpenGL::Array is not documented in
> the module, but docs can be found on the website for the module (just search
> the web for POGL). I haven't included an example of this here, since it
> requires more knowledge of OpenGL, but experienced OpenGL programmers should
> have no problems using them.
>
>
> As an alternative to creating windows using Win32::GUI, windows can be
> created using the GLUT(OpenGL Utility Toolkit) functions supplied with
> OpenGL. These can create windows that a platform-independent, as well as a
> lot of other stuff. A lot of the examples supplied for the OpenGL module use
> the GLUT, making them more portable, but OpenGL needs to be compiled with
> support for GLUT, requiring the GLUT libraries. Since I can't seem to get
> XS-implemented modules to compile properly on my machine (I use PPM
> instead), I just stick with Win32::GUI. Its all about personal preference.
>
> Well, that's it for my Win32::GUI+OpenGL example. I hope someone finds it
> useful. I'm no expert at OpenGL or the Win32 API, so there is probably a
> better way of doing this. So far this model has worked for basic
> implementations but don't expect to be able to make anything to big, such as
> games, but you never know. I'd love to hear any questions or comments about
> this example, as well as any examples of anything anyone else has done.
>
> As a side note, I'm new to posting messages on the mailing lists. I was
> wondering whether I can send pictures attached (I was hoping to show a
> screen shot of my program). Also, how do I post a reply to an existing
> thread. Any help would be much appreciated.
>
> Sorry about any typos in advance. Contact me if you find any errors with
> this post (such as with the sample program).
>
> Thanks.
>
> Kevin Marshall
>
> (kejohm88 AT hotmail DOT com)
>
>
>
> ------------------------------
> Click Here View photos of singles in your area<http://a.ninemsn.com.au/b.aspx?URL=http%3A%2F%2Fdating%2Eninemsn%2Ecom%2Eau%2Fsearch%2Fsearch%2Easpx%3Fexec%3Dgo%26tp%3Dq%26gc%3D2%26tr%3D1%26lage%3D18%26uage%3D55%26cl%3D14%26sl%3D0%26dist%3D50%26po%3D1%26do%3D2%26trackingid%3D1046138%26r2s%3D1&_t=773166090&_r=Hotmail_Endtext&_m=EXT>
>
>
> ------------------------------------------------------------------------------
> Register Now for Creativity and Technology (CaT), June 3rd, NYC. CaT
> is a gathering of tech-side developers & brand creativity professionals.
> Meet
> the minds behind Google Creative Lab, Visual Complexity, Processing, &
> iPhoneDevCamp asthey present alongside digital heavyweights like Barbarian
> Group, R/GA, & Big Spaceship. http://www.creativitycat.com
> _______________________________________________
> Perl-Win32-GUI-Users mailing list
> Per...@li...
> https://lists.sourceforge.net/lists/listinfo/perl-win32-gui-users
> http://perl-win32-gui.sourceforge.net/
>
--
Maximus*
WarheadsSE
MaxSource
|