From noreply at rubyforge.org Fri Apr 3 15:02:49 2009 From: noreply at rubyforge.org (noreply at rubyforge.org) Date: Fri, 3 Apr 2009 15:02:49 -0400 (EDT) Subject: [Win32utils-devel] [ win32utils-Bugs-25155 ] win32-open3 always returns nil in block form Message-ID: <20090403190249.8A0FB1858122@rubyforge.org> Bugs item #25155, was opened at 2009-04-03 12:02 You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=411&aid=25155&group_id=85 Category: win32-open3 Group: Code Status: Open Resolution: None Priority: 3 Submitted By: Daniel Berger (djberg96) Assigned to: Park Heesob (phasis68) Summary: win32-open3 always returns nil in block form Initial Comment: Hi, On Unix the following code returns 1. On Windows the block form appears to always return nil. cmd = File::ALT_SEPARATOR ? 'dir' : 'ls' rv = Open3.popen3(cmd) do |stdin, stdout, stderr| 1 end puts "RV: #{rv}" # 1 on Unix, nil on Windows Regards, Dan ---------------------------------------------------------------------- You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=411&aid=25155&group_id=85 From djberg96 at gmail.com Fri Apr 17 01:08:12 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 16 Apr 2009 23:08:12 -0600 Subject: [Win32utils-devel] Please update your rubygems Message-ID: <000301c9bf1a$829f9080$87deb180$@com> Hi folks, I know, it's been a while since I posted. I've devoted most of my energy in the last few months to the latest release of rubygems. I highly recommend upgrading to 1.3.2 if you haven't done so already, as it fixes an issue brought up by Montgomery Kosma where require'ing any file after require'ing win32-file would cause a LoadError. It also fixed an issue with rubygems creating extraneous quotation marks when generating .bat files. In other news, one of the next issues in rubygems we will need to tackle (brought up by Luis Lavena) is how to tag gems for specific versions of Ruby. Specifically, if you want to generate two separate binary gems with the same name, but for different versions of ruby, there's no way to distinguish the gems short of using a different version number. This is largely an MS Windows issue, so I thought it worthwhile to bring up here. See http://rubyforge.org/pipermail/rubygems-developers/2009-April/004522.html and following. Suggestions welcome. Regards, Dan From djberg96 at gmail.com Fri Apr 17 02:05:51 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Fri, 17 Apr 2009 00:05:51 -0600 Subject: [Win32utils-devel] win32-clipboard issues and support for additional formats Message-ID: <000401c9bf22$90f5a220$b2e0e660$@com> Hi, Currently the GetClipboardData() function is declared in windows-pr so that it returns a pointer. I guess my reasoning was that I was only worried about dealing with text at the time, so it was the easiest thing to do. But, I'd like to support more formats beyond text, i.e. images. However, this can cause a segfault. If you copy a jpeg image to the clipboard first, for example, and try to run the following snippet you'll see what I mean. Win32::Clipboard.data(Clipboard::CF_ENHMETAFILE) I think the first issue is that we need to redefine GetClipboardData() to return a long (HANDLE). But that means manually converting the handle back to a string using memcpy() when it comes to dealing with text. But, since we don't know in advance how much text is on the clipboard, we'll need to dynamically allocate a string buffer. How do you know if you've allocated enough? BTW, I don't think we can use memcpy_s() in practice, since it's not supported by VC++ 6. Then we need a generic way to handle text or images: # something like this def self.data(format = TEXT) begin self.open if IsClipboardFormatAvailable(format) clipdata = GetClipboardData(format) case format when text_format clipdata = get_text_data(clipdata) when image_format clipdata = get_image_data(clipdata) when other_format # ??? end else clipdata = '' end ensure self.close end clipdata end Any suggestions? Regards, Dan From phasis at gmail.com Fri Apr 17 02:31:08 2009 From: phasis at gmail.com (Heesob Park) Date: Fri, 17 Apr 2009 15:31:08 +0900 Subject: [Win32utils-devel] win32-clipboard issues and support for additional formats In-Reply-To: <000401c9bf22$90f5a220$b2e0e660$@com> References: <000401c9bf22$90f5a220$b2e0e660$@com> Message-ID: Hi, 2009/4/17 Daniel Berger : > Hi, > > Currently the GetClipboardData() function is declared in windows-pr so that > it returns a pointer. I guess my reasoning was that I was only worried about > dealing with text at the time, so it was the easiest thing to do. But, I'd > like to support more formats beyond text, i.e. images. > > However, this can cause a segfault. If you copy a jpeg image to the > clipboard first, for example, and try to run the following snippet you'll > see what I mean. > > Win32::Clipboard.data(Clipboard::CF_ENHMETAFILE) > > I think the first issue is that we need to redefine GetClipboardData() to > return a long (HANDLE). But that means manually converting the handle back > to a string using memcpy() when it comes to dealing with text. But, since we > don't know in advance how much text is on the clipboard, we'll need to > dynamically allocate a string buffer. How do you know if you've allocated > enough? BTW, I don't think we can use memcpy_s() in practice, since it's not > supported by VC++ 6. > I guess the memory size of the HANDLE can be retrieved using GlobalSize(). Regards, Park Heesob From djberg96 at gmail.com Fri Apr 17 03:16:44 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Fri, 17 Apr 2009 01:16:44 -0600 Subject: [Win32utils-devel] win32-clipboard issues and support for additional formats In-Reply-To: References: <000401c9bf22$90f5a220$b2e0e660$@com> Message-ID: <6037b70c0904170016o11362690te7c3bc6ca8fd4d9c@mail.gmail.com> On Fri, Apr 17, 2009 at 12:31 AM, Heesob Park wrote: > Hi, > > 2009/4/17 Daniel Berger : >> Hi, >> >> Currently the GetClipboardData() function is declared in windows-pr so that >> it returns a pointer. I guess my reasoning was that I was only worried about >> dealing with text at the time, so it was the easiest thing to do. But, I'd >> like to support more formats beyond text, i.e. images. >> >> However, this can cause a segfault. If you copy a jpeg image to the >> clipboard first, for example, and try to run the following snippet you'll >> see what I mean. >> >> Win32::Clipboard.data(Clipboard::CF_ENHMETAFILE) >> >> I think the first issue is that we need to redefine GetClipboardData() to >> return a long (HANDLE). But that means manually converting the handle back >> to a string using memcpy() when it comes to dealing with text. But, since we >> don't know in advance how much text is on the clipboard, we'll need to >> dynamically allocate a string buffer. How do you know if you've allocated >> enough? BTW, I don't think we can use memcpy_s() in practice, since it's not >> supported by VC++ 6. >> > I guess the memory size of the HANDLE can be retrieved using GlobalSize(). Awesome. Worked perfectly. Thanks, Dan From djberg96 at gmail.com Fri Apr 17 11:42:23 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Fri, 17 Apr 2009 09:42:23 -0600 Subject: [Win32utils-devel] Getting image size for win32-clipboard Message-ID: <000601c9bf73$1ba7dd20$52f79760$@com> Hi, I'm having some trouble getting the image size for win32-clipboard. The basic approach is this: bmi = 0.chr * 44 # BITMAPIFO handle = GetClipboardData(CF_DIB) address = GlobalLock(handle) memcpy(bmi, address, bmi.size) size_image = bmi[20,4].unpack('L').first # 0 ??? This generally seems to work. I copied a small 24-bit color jpg image into my clipboard and can validate that the height and width are correct. However, the size_image always ends up 0. What am I doing wrong? The docs did mention that 24-bit RGB images might return 0. If that's the case, how do I get the size? Thanks, Dan From phasis at gmail.com Fri Apr 17 19:18:41 2009 From: phasis at gmail.com (Heesob Park) Date: Sat, 18 Apr 2009 08:18:41 +0900 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: <000601c9bf73$1ba7dd20$52f79760$@com> References: <000601c9bf73$1ba7dd20$52f79760$@com> Message-ID: Hi, 2009/4/18 Daniel Berger : > Hi, > > I'm having some trouble getting the image size for win32-clipboard. The > basic approach is this: > > ? bmi ? ? = 0.chr * 44 # BITMAPIFO > ? handle ?= GetClipboardData(CF_DIB) > ? address = GlobalLock(handle) > ? memcpy(bmi, address, bmi.size) > ? size_image = bmi[20,4].unpack('L').first # 0 ??? > > This generally seems to work. I copied a small 24-bit color jpg image into > my clipboard and can validate that the height and width are correct. > However, the size_image always ends up 0. > > What am I doing wrong? The docs did mention that 24-bit RGB images might > return 0. If that's the case, how do I get the size? > In my test with mspaint.exe, it works fine. What's your copy process? How do I reproduce the bug? Regards, Park Heesob From djberg96 at gmail.com Fri Apr 17 21:24:30 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Fri, 17 Apr 2009 19:24:30 -0600 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: References: <000601c9bf73$1ba7dd20$52f79760$@com> Message-ID: <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> On Fri, Apr 17, 2009 at 5:18 PM, Heesob Park wrote: > Hi, > > 2009/4/18 Daniel Berger : >> Hi, >> >> I'm having some trouble getting the image size for win32-clipboard. The >> basic approach is this: >> >> ? bmi ? ? = 0.chr * 44 # BITMAPIFO >> ? handle ?= GetClipboardData(CF_DIB) >> ? address = GlobalLock(handle) >> ? memcpy(bmi, address, bmi.size) >> ? size_image = bmi[20,4].unpack('L').first # 0 ??? >> >> This generally seems to work. I copied a small 24-bit color jpg image into >> my clipboard and can validate that the height and width are correct. >> However, the size_image always ends up 0. >> >> What am I doing wrong? The docs did mention that 24-bit RGB images might >> return 0. If that's the case, how do I get the size? >> > In my test with mspaint.exe, it works fine. > > What's your copy process? > How do I reproduce the bug? I opened up a small .jpg file with Photoshop Elements 5. I did a Ctrl-A + Ctrl-C. This was the result: Header Size: 40 Width: 570 Height: 570 Planes: 1 Bit Count: 24 Compression: 0 Image Size: 0 X Per Meter: 112205 Y Per Meter: 112205 Color Used: 0 Color Import: 0 Ok...this is strange. I opened up the *same* image in Paint, and did a Ctrl-A + Ctrl-C. This was the result: Header Size: 40 Width: 570 Height: 570 Planes: 1 Bit Count: 24 Compression: 0 Image Size: 975840 X Per Meter: 112200 Y Per Meter: 112200 Color Used: 0 Color Import: 0 Maybe because it was a jpg instead of a bmp? I'm not sure. In any case, I've got the initial implementation in CVS. I am having some trouble actually copying clipboard contents. I tried this: File.open("test.bmp", "wb"){ |fh| fh.write Clipboard.data(Clipboard::DIB) } The file size is identical, but I can't open the resulting file - MS Paint says it doesn't recognize the format. Please take a look at the code in CVS (and use the latest windows-pr) and see if everything looks alright. Thanks, Dan From phasis at gmail.com Sat Apr 18 05:19:52 2009 From: phasis at gmail.com (Heesob Park) Date: Sat, 18 Apr 2009 18:19:52 +0900 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> References: <000601c9bf73$1ba7dd20$52f79760$@com> <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> Message-ID: Hi, 2009/4/18 Daniel Berger : > > I opened up a small .jpg file with Photoshop Elements 5. I did a > Ctrl-A + Ctrl-C. This was the result: > > Header Size: 40 > Width: 570 > Height: 570 > Planes: 1 > Bit Count: 24 > Compression: 0 > Image Size: 0 > X Per Meter: 112205 > Y Per Meter: 112205 > Color Used: 0 > Color Import: 0 > > Ok...this is strange. I opened up the *same* image in Paint, and did a > Ctrl-A + Ctrl-C. This was the result: > > Header Size: 40 > Width: 570 > Height: 570 > Planes: 1 > Bit Count: 24 > Compression: 0 > Image Size: 975840 > X Per Meter: 112200 > Y Per Meter: 112200 > Color Used: 0 > Color Import: 0 > > Maybe because it was a jpg instead of a bmp? I'm not sure. > > In any case, I've got the initial implementation in CVS. I am having > some trouble actually copying clipboard contents. I tried this: > > File.open("test.bmp", "wb"){ |fh| fh.write Clipboard.data(Clipboard::DIB) } > > The file size is identical, but I can't open the resulting file - MS > Paint says it doesn't recognize the format. > > Please take a look at the code in CVS (and use the latest windows-pr) > and see if everything looks alright. > Here is a patch. --- clipboard.rb 2009-04-18 18:11:44.000000000 +0900 +++ clipboard.rb.new 2009-04-18 18:12:03.000000000 +0900 @@ -234,6 +234,7 @@ begin address = GlobalLock(handle) + buf_size = GlobalSize(handle) memcpy(bmi, address, bmi.length) size = bmi[0,4].unpack('L').first # biSize @@ -241,6 +242,7 @@ compression = bmi[16,4].unpack('L').first # biCompression size_image = bmi[20,4].unpack('L').first # biSizeImage clr_used = bmi[32,4].unpack('L').first # biClrUsed + size_image = buf_size+16 if size_image==0 # Calculate the header size case bit_count @@ -264,9 +266,10 @@ raise Error, "invalid bit count" end # case - buf_size = size + 14 + (table_size * 4) + size_image + offset = 0x36 + (table_size*4) buf = 0.chr * buf_size memcpy(buf, address, buf.size) + buf = "\x42\x4D" + [size_image].pack('L') + 0.chr * 4 + [offset].pack('L') + buf ensure GlobalUnlock(handle) end Regards, Park Heesob From djberg96 at gmail.com Sat Apr 18 09:03:28 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Sat, 18 Apr 2009 07:03:28 -0600 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: References: <000601c9bf73$1ba7dd20$52f79760$@com> <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> Message-ID: <6037b70c0904180603w651e7b9dra0ed65577ea78f7f@mail.gmail.com> On Sat, Apr 18, 2009 at 3:19 AM, Heesob Park wrote: > Hi, > > 2009/4/18 Daniel Berger : >> >> I opened up a small .jpg file with Photoshop Elements 5. I did a >> Ctrl-A + Ctrl-C. This was the result: >> >> Header Size: 40 >> Width: 570 >> Height: 570 >> Planes: 1 >> Bit Count: 24 >> Compression: 0 >> Image Size: 0 >> X Per Meter: 112205 >> Y Per Meter: 112205 >> Color Used: 0 >> Color Import: 0 >> >> Ok...this is strange. I opened up the *same* image in Paint, and did a >> Ctrl-A + Ctrl-C. This was the result: >> >> Header Size: 40 >> Width: 570 >> Height: 570 >> Planes: 1 >> Bit Count: 24 >> Compression: 0 >> Image Size: 975840 >> X Per Meter: 112200 >> Y Per Meter: 112200 >> Color Used: 0 >> Color Import: 0 >> >> Maybe because it was a jpg instead of a bmp? I'm not sure. >> >> In any case, I've got the initial implementation in CVS. I am having >> some trouble actually copying clipboard contents. I tried this: >> >> File.open("test.bmp", "wb"){ |fh| fh.write Clipboard.data(Clipboard::DIB) } >> >> The file size is identical, but I can't open the resulting file - MS >> Paint says it doesn't recognize the format. >> >> Please take a look at the code in CVS (and use the latest windows-pr) >> and see if everything looks alright. >> > Here is a patch. > > --- clipboard.rb ? ? ? ?2009-04-18 18:11:44.000000000 +0900 > +++ clipboard.rb.new ? ?2009-04-18 18:12:03.000000000 +0900 > @@ -234,6 +234,7 @@ > > ? ? ? ? ?begin > ? ? ? ? ? ? address = GlobalLock(handle) > + ? ? ? ? ? buf_size = GlobalSize(handle) > ? ? ? ? ? ? memcpy(bmi, address, bmi.length) > > ? ? ? ? ? ? size ? ? ? ?= bmi[0,4].unpack('L').first ?# biSize > @@ -241,6 +242,7 @@ > ? ? ? ? ? ? compression = bmi[16,4].unpack('L').first # biCompression > ? ? ? ? ? ? size_image ?= bmi[20,4].unpack('L').first # biSizeImage > ? ? ? ? ? ? clr_used ? ?= bmi[32,4].unpack('L').first # biClrUsed > + ? ? ? ? ? size_image ?= buf_size+16 if size_image==0 > > ? ? ? ? ? ? # Calculate the header size > ? ? ? ? ? ? case bit_count > @@ -264,9 +266,10 @@ > ? ? ? ? ? ? ? ? ? raise Error, "invalid bit count" > ? ? ? ? ? ? end # case > > - ? ? ? ? ? ?buf_size = size + 14 + (table_size * 4) + size_image > + ? ? ? ? ? offset = 0x36 + (table_size*4) > ? ? ? ? ? ? buf = 0.chr * buf_size > ? ? ? ? ? ? memcpy(buf, address, buf.size) > + ? ? ? ? ? ?buf = "\x42\x4D" + [size_image].pack('L') + 0.chr * 4 + > [offset].pack('L') + buf > ? ? ? ? ?ensure > ? ? ? ? ? ? GlobalUnlock(handle) > ? ? ? ? ?end Thanks Heesob, that worked. There are a couple of things I still don't understand, though. I used the same code as before: File.open('clippy.bmp', 'wb'){ |fh| fh.write Clipboard.data(Clipboard::DIB) } The resulting file is larger than the original file in my experiments by a few bytes. Why? I can't visually detect any differences in the image, but I thought it was curious. Also, it seems like we ought to be able to remove the memcpy near the end, since 'buf' is reassigned anyway. But, if I remove it, I get an "out of memory" error if I attempt to open the file. Last, if I replace Clipboard::DIB with Clipboard::ENHMETAFILE I get a segfault. Regards, Dan From phasis at gmail.com Sat Apr 18 10:20:10 2009 From: phasis at gmail.com (Heesob Park) Date: Sat, 18 Apr 2009 23:20:10 +0900 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: <6037b70c0904180603w651e7b9dra0ed65577ea78f7f@mail.gmail.com> References: <000601c9bf73$1ba7dd20$52f79760$@com> <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> <6037b70c0904180603w651e7b9dra0ed65577ea78f7f@mail.gmail.com> Message-ID: 2009/4/18 Daniel Berger : > On Sat, Apr 18, 2009 at 3:19 AM, Heesob Park wrote: > > Thanks Heesob, that worked. There are a couple of things I still don't > understand, though. I used the same code as before: > > File.open('clippy.bmp', 'wb'){ |fh| > ? fh.write Clipboard.data(Clipboard::DIB) > } > > The resulting file is larger than the original file in my experiments > by a few bytes. Why? I can't visually detect any differences in the > image, but I thought it was curious. > I guess the difference is just a dummy data for the data alignment. > Also, it seems like we ought to be able to remove the memcpy near the > end, since 'buf' is reassigned anyway. But, if I remove it, I get an > "out of memory" error if I attempt to open the file. > I'm not sure why you want to remove it. it is an essential part. > Last, if I replace Clipboard::DIB with Clipboard::ENHMETAFILE I get a segfault. > ENHMETAFILE is nothing to do with DIB or BITMAT. it must be handled differently. After insert GetEnhMetaFileBits API to window-pr/lib/windows/gdi/bitmat.rb API.new('GetEnhMetaFileBits', 'LLP', 'L', 'gdi32') Test with File.open('aaa.wmf','wb') { |f| f.write(Clipboard.data(Clipboard::ENHMETAFILE)) } Here is a patch for ENHMETAFILE --- clipboard.rb 2009-04-18 23:11:05.000000000 +0900 +++ clipboard.rb.new 2009-04-18 23:08:38.000000000 +0900 @@ -3,6 +3,7 @@ require 'windows/error' require 'windows/shell' require 'windows/msvcrt/buffer' +require 'windows/gdi/bitmap' # The Win32 module serves as a namespace only. module Win32 @@ -18,12 +19,14 @@ include Windows::Error include Windows::Shell include Windows::MSVCRT::Buffer + include Windows::GDI::Bitmap extend Windows::Clipboard extend Windows::Memory extend Windows::Error extend Windows::Shell extend Windows::MSVCRT::Buffer + extend Windows::GDI::Bitmap # The version of this library VERSION = '0.5.0' @@ -93,8 +96,10 @@ clip_data.strip! when HDROP clip_data = get_file_list(handle) - when DIB, BITMAP, ENHMETAFILE + when DIB, BITMAP clip_data = get_image_data(handle) + when ENHMETAFILE + clip_data = get_meta_data(handle) else raise Error, 'format not supported' end @@ -281,6 +286,14 @@ buf end + def self.get_meta_data(handle) + buf = nil + buf_size = GetEnhMetaFileBits(handle,0,nil) + buf = 0.chr * buf_size + GetEnhMetaFileBits(handle,buf_size,buf) + buf + end + # Get and return an array of file names that have been copied. # def self.get_file_list(handle) Regards, Park Heesob From djberg96 at gmail.com Wed Apr 22 01:44:09 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Tue, 21 Apr 2009 23:44:09 -0600 Subject: [Win32utils-devel] Getting image size for win32-clipboard In-Reply-To: References: <000601c9bf73$1ba7dd20$52f79760$@com> <6037b70c0904171824s7e56a087gafac8db8868ac49e@mail.gmail.com> <6037b70c0904180603w651e7b9dra0ed65577ea78f7f@mail.gmail.com> Message-ID: <001501c9c30d$5c776710$15663530$@com> > -----Original Message----- > From: win32utils-devel-bounces at rubyforge.org [mailto:win32utils-devel- > bounces at rubyforge.org] On Behalf Of Heesob Park > Sent: Saturday, April 18, 2009 8:20 AM > To: Development and ideas for win32utils projects > Subject: Re: [Win32utils-devel] Getting image size for win32-clipboard > > 2009/4/18 Daniel Berger : > > On Sat, Apr 18, 2009 at 3:19 AM, Heesob Park > wrote: > > > > Thanks Heesob, that worked. There are a couple of things I still > don't > > understand, though. I used the same code as before: > > > > File.open('clippy.bmp', 'wb'){ |fh| > > fh.write Clipboard.data(Clipboard::DIB) > > } > > > > The resulting file is larger than the original file in my experiments > > by a few bytes. Why? I can't visually detect any differences in the > > image, but I thought it was curious. > > > I guess the difference is just a dummy data for the data alignment. > > > Also, it seems like we ought to be able to remove the memcpy near the > > end, since 'buf' is reassigned anyway. But, if I remove it, I get an > > "out of memory" error if I attempt to open the file. > > > I'm not sure why you want to remove it. it is an essential part. > > > Last, if I replace Clipboard::DIB with Clipboard::ENHMETAFILE I get a > segfault. > > > ENHMETAFILE is nothing to do with DIB or BITMAT. > it must be handled differently. > > After insert GetEnhMetaFileBits API to window- > pr/lib/windows/gdi/bitmat.rb > API.new('GetEnhMetaFileBits', 'LLP', 'L', 'gdi32') > Test with > File.open('aaa.wmf','wb') { |f| > f.write(Clipboard.data(Clipboard::ENHMETAFILE)) } > > Here is a patch for ENHMETAFILE > --- clipboard.rb 2009-04-18 23:11:05.000000000 +0900 > +++ clipboard.rb.new 2009-04-18 23:08:38.000000000 +0900 > @@ -3,6 +3,7 @@ > require 'windows/error' > require 'windows/shell' > require 'windows/msvcrt/buffer' > +require 'windows/gdi/bitmap' > > # The Win32 module serves as a namespace only. > module Win32 > @@ -18,12 +19,14 @@ > include Windows::Error > include Windows::Shell > include Windows::MSVCRT::Buffer > + include Windows::GDI::Bitmap > > extend Windows::Clipboard > extend Windows::Memory > extend Windows::Error > extend Windows::Shell > extend Windows::MSVCRT::Buffer > + extend Windows::GDI::Bitmap > > # The version of this library > VERSION = '0.5.0' > @@ -93,8 +96,10 @@ > clip_data.strip! > when HDROP > clip_data = get_file_list(handle) > - when DIB, BITMAP, ENHMETAFILE > + when DIB, BITMAP > clip_data = get_image_data(handle) > + when ENHMETAFILE > + clip_data = get_meta_data(handle) > else > raise Error, 'format not supported' > end > @@ -281,6 +286,14 @@ > buf > end > > + def self.get_meta_data(handle) > + buf = nil > + buf_size = GetEnhMetaFileBits(handle,0,nil) > + buf = 0.chr * buf_size > + GetEnhMetaFileBits(handle,buf_size,buf) > + buf > + end > + > # Get and return an array of file names that have been copied. > # > def self.get_file_list(handle) Thank you for this! I've committed the change (with some modifications). Instead of putting those methods in the Windows::GDI::Bitmap module, I've created a separate module called Windows::GDI::MetaFile. It's now in CVS. Regards, Dan From noreply at rubyforge.org Tue Apr 21 08:44:10 2009 From: noreply at rubyforge.org (noreply at rubyforge.org) Date: Tue, 21 Apr 2009 08:44:10 -0400 (EDT) Subject: [Win32utils-devel] [ win32utils-Support Requests-21110 ] Having problems creating junction on mounted network folder Message-ID: <20090421124410.18190159802C@rubyforge.org> Support Requests item #21110, was opened at 2008-07-07 17:50 You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=412&aid=21110&group_id=85 Category: win32-dir Group: v1.0 (example) Status: Open Resolution: None Priority: 3 Submitted By: Nobody (None) Assigned to: Nobody (None) Summary: Having problems creating junction on mounted network folder Initial Comment: Hey guys, I'm getting a permission denied error when trying to create a junction on a network folder that I have mounted.. I can stop the code right before the DeviceIoControl call (inside Dir.create_junction) and everything seems good (link has been created as a directory and filehandle is still open).. but then I get a: dir.rb:120:in `create_junction': DeviceIoControl() failed: Access is denied. (RuntimeError) from dir.rb:167 I'm using VERSION = '0.3.2' of dir.rb The code works fine on my local disk, so there is something different about the network permissions.. Do you guys have any advice? Thanks! -Blair BTW: I found that if you switch the order of the closing filehandle and removing directory it will remove the directory (which was going to be a link).. dir.rb: 116 unless bool error = 'DeviceIoControl() failed: ' + get_last_error CloseHandle(handle) RemoveDirectory(to) raise error end ---------------------------------------------------------------------- Comment By: Chris Roby (croby) Date: 2009-04-21 12:44 Message: I'm having this problem also - I have permissions on the network folder as I'm creating directories and copying folders, but I'm getting an 'Access is denied' error when trying to create a junction (in a piece of the file system i've been copying into and creating folders on). Are there specific restrictions around create_junction and permissions? Thanks! ---------------------------------------------------------------------- Comment By: Daniel Berger (djberg96) Date: 2008-07-07 20:49 Message: Hi, Hm, I'm not sure right off. Are you able to create a regular folder on the network folder without issue? Thanks, Dan ---------------------------------------------------------------------- You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=412&aid=21110&group_id=85 From djberg96 at gmail.com Wed Apr 22 19:29:52 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Wed, 22 Apr 2009 17:29:52 -0600 Subject: [Win32utils-devel] Creating a clipboard monitor Message-ID: <000301c9c3a2$3d9d3140$b8d793c0$@com> Hi, I see the Win32::Clipboard module has a WaitForChange() method that waits for a change in the Windows clipboard. I'd like to setup something similar for the win32-clipboard library, perhaps call it notify_change to match win32-eventlog. I looked at the implementation, and it's similar to others I found online. But, it seems like it ought to be easier. Instead of RegisterClass(), CreateWindow(), etc, can't we just use the handle of the current process instead of injecting a new Window into the clipboard chain? In any case, I'm not having much luck trying to implement this. I did add some methods to the Windows::Window::Classes module that might be necessary. Any help here would be greatly appreciated. In other news, it seems GetWindowLongPtr and SetWindowLongPtr are not exported by the runtime that ships with the one-click. Regards, Dan PS - It's a shame they don't provide a NotifyChangeClipboard() function to make life easier. :) From phasis at gmail.com Thu Apr 23 02:47:56 2009 From: phasis at gmail.com (Heesob Park) Date: Thu, 23 Apr 2009 15:47:56 +0900 Subject: [Win32utils-devel] Creating a clipboard monitor In-Reply-To: <000301c9c3a2$3d9d3140$b8d793c0$@com> References: <000301c9c3a2$3d9d3140$b8d793c0$@com> Message-ID: Hi, 2009/4/23 Daniel Berger : > Hi, > > I see the Win32::Clipboard module has a WaitForChange() method that waits > for a change in the Windows clipboard. I'd like to setup something similar > for the win32-clipboard library, perhaps call it notify_change to match > win32-eventlog. > > I looked at the implementation, and it's similar to others I found online. > But, it seems like it ought to be easier. Instead of RegisterClass(), > CreateWindow(), etc, can't we just use the handle of the current process > instead of injecting a new Window into the clipboard chain? > > In any case, I'm not having much luck trying to implement this. I did add > some methods to the Windows::Window::Classes module that might be necessary. > Any help here would be greatly appreciated. > > In other news, it seems GetWindowLongPtr and SetWindowLongPtr are not > exported by the runtime that ships with the one-click. > > Regards, > > Dan > > PS - It's a shame they don't provide a NotifyChangeClipboard() function to > make life easier. :) > As you expected, here is a sample code :) require 'win32/api' require 'windows/clipboard' require 'windows/window/classes' require 'windows/window/message' require 'windows/library' require 'windows/window' require 'win32/clipboard' include Win32 include Windows::Window include Windows::Window::Classes include Windows::Window::Message include Windows::Library include Windows::Clipboard handle = CreateWindow('static','test',0,0,0,0,0,0,0,0,0) my_wnd_proc = API::Callback.new('LLLL', 'L') do |hwnd,uMsg,wParam,lParam| case uMsg when 0x0308 # WM_DRAWCLIPBOARD puts 'Clipboard Changed' puts Clipboard.data next_viewer = GetWindowLongPtr(hwnd,GWL_USERDATA) if next_viewer != 0 PostMessage(next_viewer,uMsg,wParam,lParam) end r = 0 when 0x030D # WM_CHANGECBCHAIN puts 'WM_CHANGECBCHAIN' next_viewer = lParam if next_viewer == wParam if next_viewer != 0 PostMessage(next_viewer,uMsg,wParam,lParam) end r = 0 else r = DefWindowProc(hwnd,uMsg,wParam,lParam) end r end old_wnd_proc = SetWindowLongPtr(handle,GWL_WNDPROC,my_wnd_proc.address) next_viewer = SetClipboardViewer(handle) SetWindowLongPtr(handle,GWL_USERDATA,next_viewer) msg = 0.chr * 100 while true while(PeekMessage(msg,handle,0,0,1)) TranslateMessage(msg) DispatchMessage(msg) end sleep 0.1 end In order to run this code, several changes are required. 1. GetWindowLongPtr and SetWindowLongPtr In 32bit Windows, GetWindowLongPtr and SetWindowLongPtr are just a macro of GetWindowLog and SetWindowLong. Modify windows/window/classes.rb like this would be enough: begin API.new('GetWindowLongPtr', 'LI', 'L', 'user32') API.new('SetWindowLongPtr', 'LIP', 'L', 'user32') rescue Win32::API::LoadLibraryError alias :GetWindowLongPtr :GetWindowLong alias :SetWindowLongPtr :SetWindowLong end 2. CreateWindow In recent version of user32.dll, CreateWindow is not a function but a macro of CreateWindowEx. Modify windows/window.rb something like this begin API.new('CreateWindow', 'PPLIIIILLLL', 'L', 'user32') rescue Win32::API::LoadLibraryError def CreateWindow(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) CreateWindowEx(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) end def CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) CreateWindowExA(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) end def CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) CreateWindowExW(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) end end 3. GetUpdatedClipboardFormats GetUpdatedClipboardFormats is supported for Vista or later. Modify windows/clipboard.rb something like this begin API.new('GetUpdatedClipboardFormats', 'PIP', 'I', 'user32') API.new('AddClipboardFormatListener', 'L', 'B', 'user32') API.new('RemoveClipboardFormatListener', 'L', 'B', 'user32') rescue Win32::API::LoadLibraryError # Windows Vista or later end Regards, Park Heesob From djberg96 at gmail.com Thu Apr 23 15:42:57 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 23 Apr 2009 13:42:57 -0600 Subject: [Win32utils-devel] Creating a clipboard monitor In-Reply-To: References: <000301c9c3a2$3d9d3140$b8d793c0$@com> Message-ID: <49F0C4C1.3040407@gmail.com> Heesob Park wrote: > Hi, > > 2009/4/23 Daniel Berger : >> Hi, >> >> I see the Win32::Clipboard module has a WaitForChange() method that waits >> for a change in the Windows clipboard. I'd like to setup something similar >> for the win32-clipboard library, perhaps call it notify_change to match >> win32-eventlog. >> >> I looked at the implementation, and it's similar to others I found online. >> But, it seems like it ought to be easier. Instead of RegisterClass(), >> CreateWindow(), etc, can't we just use the handle of the current process >> instead of injecting a new Window into the clipboard chain? >> >> In any case, I'm not having much luck trying to implement this. I did add >> some methods to the Windows::Window::Classes module that might be necessary. >> Any help here would be greatly appreciated. >> >> In other news, it seems GetWindowLongPtr and SetWindowLongPtr are not >> exported by the runtime that ships with the one-click. >> >> Regards, >> >> Dan >> >> PS - It's a shame they don't provide a NotifyChangeClipboard() function to >> make life easier. :) >> > As you expected, here is a sample code :) > > require 'win32/api' > require 'windows/clipboard' > require 'windows/window/classes' > require 'windows/window/message' > require 'windows/library' > require 'windows/window' > require 'win32/clipboard' > > include Win32 > include Windows::Window > include Windows::Window::Classes > include Windows::Window::Message > include Windows::Library > include Windows::Clipboard > > handle = CreateWindow('static','test',0,0,0,0,0,0,0,0,0) > > my_wnd_proc = API::Callback.new('LLLL', 'L') do |hwnd,uMsg,wParam,lParam| > case uMsg > when 0x0308 # WM_DRAWCLIPBOARD > puts 'Clipboard Changed' > puts Clipboard.data > next_viewer = GetWindowLongPtr(hwnd,GWL_USERDATA) > if next_viewer != 0 > PostMessage(next_viewer,uMsg,wParam,lParam) > end > r = 0 > when 0x030D # WM_CHANGECBCHAIN > puts 'WM_CHANGECBCHAIN' > next_viewer = lParam if next_viewer == wParam > if next_viewer != 0 > PostMessage(next_viewer,uMsg,wParam,lParam) > end > r = 0 > else > r = DefWindowProc(hwnd,uMsg,wParam,lParam) > end > r > end > > old_wnd_proc = SetWindowLongPtr(handle,GWL_WNDPROC,my_wnd_proc.address) > next_viewer = SetClipboardViewer(handle) > SetWindowLongPtr(handle,GWL_USERDATA,next_viewer) > > msg = 0.chr * 100 > while true > while(PeekMessage(msg,handle,0,0,1)) > TranslateMessage(msg) > DispatchMessage(msg) > end > sleep 0.1 > end > > > In order to run this code, several changes are required. > 1. GetWindowLongPtr and SetWindowLongPtr > > In 32bit Windows, GetWindowLongPtr and SetWindowLongPtr are just a > macro of GetWindowLog and SetWindowLong. > Modify windows/window/classes.rb like this would be enough: > > begin > API.new('GetWindowLongPtr', 'LI', 'L', 'user32') > API.new('SetWindowLongPtr', 'LIP', 'L', 'user32') > rescue Win32::API::LoadLibraryError > alias :GetWindowLongPtr :GetWindowLong > alias :SetWindowLongPtr :SetWindowLong > end > > 2. CreateWindow > > In recent version of user32.dll, CreateWindow is not a function but a > macro of CreateWindowEx. > Modify windows/window.rb something like this > > begin > API.new('CreateWindow', 'PPLIIIILLLL', 'L', 'user32') > rescue Win32::API::LoadLibraryError > def CreateWindow(lpClassName, lpWindowName, dwStyle, x, y, > nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) > CreateWindowEx(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, > nHeight, hWndParent, hMenu, hInstance, lpParam) > end > def CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y, > nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) > CreateWindowExA(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, > nHeight, hWndParent, hMenu, hInstance, lpParam) > end > def CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y, > nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) > CreateWindowExW(0,lpClassName, lpWindowName, dwStyle, x, y, nWidth, > nHeight, hWndParent, hMenu, hInstance, lpParam) > end > end > > 3. GetUpdatedClipboardFormats > GetUpdatedClipboardFormats is supported for Vista or later. > Modify windows/clipboard.rb something like this > > begin > API.new('GetUpdatedClipboardFormats', 'PIP', 'I', 'user32') > API.new('AddClipboardFormatListener', 'L', 'B', 'user32') > API.new('RemoveClipboardFormatListener', 'L', 'B', 'user32') > rescue Win32::API::LoadLibraryError > # Windows Vista or later > end Many thanks for this. I've made the changes in windows-pr (including the addition of DefProcName in Windows::Window::Message) The only issue I had was that it always returns at least 1 notification when I first run the program, even when the clipboard is clear. I could add an internal counter easily enough and only yield after the 1st notification, but I wanted to make sure there wasn't a more appropriate solution first. Regards, Dan From djberg96 at gmail.com Thu Apr 23 17:50:07 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 23 Apr 2009 15:50:07 -0600 Subject: [Win32utils-devel] Creating a clipboard monitor In-Reply-To: References: <000301c9c3a2$3d9d3140$b8d793c0$@com> Message-ID: <49F0E28F.1090103@gmail.com> Paul Rogers wrote: > I could realy do with a clipboard monitor right now. Is there anything I > can do to help out with this? Thanks Paul. I think we'll have a new release out within a week that implements this. Regards, Dan From phasis at gmail.com Thu Apr 23 20:53:37 2009 From: phasis at gmail.com (Heesob Park) Date: Fri, 24 Apr 2009 09:53:37 +0900 Subject: [Win32utils-devel] Creating a clipboard monitor In-Reply-To: <49F0C4C1.3040407@gmail.com> References: <000301c9c3a2$3d9d3140$b8d793c0$@com> <49F0C4C1.3040407@gmail.com> Message-ID: 2009/4/24 Daniel Berger : > > Many thanks for this. I've made the changes in windows-pr (including the > addition of DefProcName in Windows::Window::Message) > > The only issue I had was that it always returns at least 1 notification when > I first run the program, even when the clipboard is clear. > > I could add an internal counter easily enough and only yield after the 1st > notification, but I wanted to make sure there wasn't a more appropriate > solution first. > I guess the first notification is for giving the chance of display the current clipboard content to the new attached clipboard viewer. Thus Initializing flag would be sufficient. Regards, Park Heesob From djberg96 at gmail.com Sun Apr 26 08:08:16 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Sun, 26 Apr 2009 06:08:16 -0600 Subject: [Win32utils-devel] Multiple clipboard formats Message-ID: <000b01c9c667$afbc1580$0f344080$@com> Hi, I already made a change since 0.5.0. The Clipboard.register_format now returns the format number returned by the RegisterClipboardFormat() function. This change is in CVS. It works fine, but one thing I wasn't sure of was how to register multiple clipboard formats simultaneously. Specifically, I was looking at this article: http://www.codeguru.com/cpp/w-p/clipboard/print.php/c3015 Where he registers both RTF and Csv formats, and when he copies and pastes to Word or Excel, it just does the right thing. I'm also wondering if we need to modify the Clipboard.set_data method so that it accepts a nil argument. According to MSD, the 'hMem' parameter can be NULL, indicating that the window provides data in the specified clipboard format (renders the format) upon request. If a window delays rendering, it must process the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages. I'm debating what sort of interface we could provide for this. Any suggestions? Regards, Dan From noreply at rubyforge.org Sun Apr 26 10:30:16 2009 From: noreply at rubyforge.org (noreply at rubyforge.org) Date: Sun, 26 Apr 2009 10:30:16 -0400 (EDT) Subject: [Win32utils-devel] [ win32utils-Feature Requests-25681 ] Adding HTML Clipboard Format support to win32-clipboard Message-ID: <20090426143016.8B5C518580F4@rubyforge.org> Feature Requests item #25681, was opened at 2009-04-26 23:30 You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=414&aid=25681&group_id=85 Category: None Group: None Status: Open Resolution: None Priority: 3 Submitted By: Park Heesob (phasis68) Assigned to: Nobody (None) Summary: Adding HTML Clipboard Format support to win32-clipboard Initial Comment: Adding HTML Clipboard Format support to win32-clipboard would be helpful. Windows supports a clipboard format called "HTML Format". This format allows various fragments of html formatted text to be copy and pasted between applications. The overview about this format is [1]. UltraEdit has this feature as well as Word, Excel. You can copy some fragment of Web browser and paste HTML source to UltraEdit by special pasting menu. There are C# implementation[2] and Python implementation[3] for reference. [1] http://msdn.microsoft.com/en-us/library/ms649015(VS.85).aspx [2] http://blogs.msdn.com/jmstall/pages/sample-code-html-clipboard.aspx [3] http://code.activestate.com/recipes/474121/ Regards, Park Heesob ---------------------------------------------------------------------- You can respond by visiting: http://rubyforge.org/tracker/?func=detail&atid=414&aid=25681&group_id=85 From phasis at gmail.com Mon Apr 27 02:22:11 2009 From: phasis at gmail.com (Heesob Park) Date: Mon, 27 Apr 2009 15:22:11 +0900 Subject: [Win32utils-devel] Multiple clipboard formats In-Reply-To: <000b01c9c667$afbc1580$0f344080$@com> References: <000b01c9c667$afbc1580$0f344080$@com> Message-ID: Hi, 2009/4/26 Daniel Berger : > Hi, > > I already made a change since 0.5.0. The Clipboard.register_format now > returns the format number returned by the RegisterClipboardFormat() > function. This change is in CVS. > > It works fine, but one thing I wasn't sure of was how to register multiple > clipboard formats simultaneously. Specifically, I was looking at this > article: > > http://www.codeguru.com/cpp/w-p/clipboard/print.php/c3015 > > Where he registers both RTF and Csv formats, and when he copies and pastes > to Word or Excel, it just does the right thing. > Here is both CF_HTML and CF_TEXT format handling sample code: require 'win32/clipboard' include Win32 module Win32 class HtmlClipboard < Clipboard MARKER_BLOCK_OUTPUT = "Version:1.0\r\n" \ "StartHTML:%09d\r\n" \ "EndHTML:%09d\r\n" \ "StartFragment:%09d\r\n" \ "EndFragment:%09d\r\n" \ "StartSelection:%09d\r\n" \ "EndSelection:%09d\r\n" \ "SourceURL:%s\r\n" MARKER_BLOCK_EX = 'Version:(\S+)\s+' \ 'StartHTML:(\d+)\s+' \ 'EndHTML:(\d+)\s+' \ 'StartFragment:(\d+)\s+' \ 'EndFragment:(\d+)\s+' \ 'StartSelection:(\d+)\s+' \ 'EndSelection:(\d+)\s+' \ 'SourceURL:(\S+)' MARKER_BLOCK_EX_RE = Regexp.new(MARKER_BLOCK_EX,Regexp::MULTILINE) MARKER_BLOCK = 'Version:(\S+)\s+' \ 'StartHTML:(\d+)\s+' \ 'EndHTML:(\d+)\s+' \ 'StartFragment:(\d+)\s+' \ 'EndFragment:(\d+)\s+' \ 'SourceURL:(\S+)' MARKER_BLOCK_RE = Regexp.new(MARKER_BLOCK,Regexp::MULTILINE) DEFAULT_HTML_BODY = "\r\n" \ "%s" CF_HTML = RegisterClipboardFormat("HTML Format") def initialize @html = nil @fragment = nil @selection = nil @source = nil @version = nil end def self.has_html_format? format_available?(CF_HTML) end def self.data begin self.open if IsClipboardFormatAvailable(CF_HTML) handle = GetClipboardData(CF_HTML) clip_data = 0.chr * GlobalSize(handle) memcpy(clip_data, handle, clip_data.size) clip_data.strip! clip_data = decode_data(clip_data) else clip_data = '' end ensure self.close end clip_data end def self.decode_data(src) if (matches = MARKER_BLOCK_EX_RE.match(src)) @prefix = matches[0] @version = matches[1] @html = src[matches[2].to_i ... matches[3].to_i] @fragment = src[matches[4].to_i ... matches[5].to_i] @selection = src[matches[6].to_i ... matches[7].to_i] @source = matches[8] elsif (matches = MARKER_BLOCK_RE.match(src)) @prefix = matches[0] @version = matches[1] @html = src[matches[2].to_i ... matches[3].to_i] @fragment = src[matches[4].to_i ... matches[5].to_i] @source = matches[6] @selection = @fragment end @fragment end def self.dump_data clip_data = data puts "prefix=>>>#{@prefix}<<>>#{@version}<<>>#{@selection}<<>>#{@fragment}<<>>#{@html}<<>>#{@source}<<]+?>/,'') hmem2 = GlobalAlloc(GHND, clip_data2.length + 4) mem2 = GlobalLock(hmem2) memcpy(mem2, clip_data2, clip_data2.length) # Set the new data begin if SetClipboardData(CF_HTML, hmem) == 0 raise Error, "SetClipboardData() failed: " + get_last_error end if SetClipboardData(CF_TEXT, hmem2) == 0 raise Error, "SetClipboardData() failed: " + get_last_error end ensure GlobalFree(hmem) GlobalFree(hmem2) self.close end self end end end if $0 == __FILE__ data = "

Writing to the clipboard is easy with this code.

" HtmlClipboard.set_data(data) if HtmlClipboard.data == data puts "passed" else puts "failed" end end > I'm also wondering if we need to modify the Clipboard.set_data method so > that it accepts a nil argument. According to MSD, the 'hMem' parameter can > be NULL, indicating that the window provides data in the specified clipboard > format (renders the format) upon request. If a window delays rendering, it > must process the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages. > > I'm debating what sort of interface we could provide for this. Any > suggestions? > I think, in real world applications, delayed rendering is unreliable and useless feature. Can you guarantee the clipboard owner is always active? Regards, Park Heesob From djberg96 at gmail.com Thu Apr 30 09:33:11 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 30 Apr 2009 07:33:11 -0600 Subject: [Win32utils-devel] Making win32-api better? Message-ID: <000401c9c998$35eebc60$a1cc3520$@com> Hi all, During a conversation with 'flexo' on IRC (not sure of his real name), he complained that win32-api was unstable. He wrote his own interface and maintains that it is very stable and works better with multi-threaded apps. Please take a look and tell me what you think (below the C code is the Ruby interface): /* w32thunk.c */ #include #include #include #include "rubysig.h" #pragma warning(push,4) enum { CONV_CDECL, CONV_STDCALL }; struct worker { void *func; unsigned *argv; int argc; int conv; unsigned ret; volatile int done; HANDLE thread; HANDLE event; }; #undef printf #undef vprintf // #define LOG printf #undef LOG void mylog(const char *fmt, ...) { RUBY_CRITICAL({ char buf[256]; DWORD written; va_list va; va_start(va, fmt); vsprintf(buf, fmt, va); va_end(va); WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &written, NULL); }); } #define LOG mylog #define LOG2 printf #define LOG #define LOG2 static unsigned perform_call(unsigned add_esp, INT_PTR func, unsigned argc, INT_PTR argv) { unsigned retv; __asm { mov ebx, func mov ecx, argc mov edx, argv mov esi, add_esp L_1: test ecx, ecx jz L_2 push [edx] add edx, 4 dec ecx jmp L_1 L_2: call ebx add esp, esi mov retv, eax } LOG2("perform_call() => %ux\n", retv); return retv; } static unsigned val2unsigned(VALUE v) { unsigned ret; VALUE str = rb_big2str(v, 10); char *pend; ret = strtoul(STR2CSTR(str), &pend, 10); if(ret == ULONG_MAX || !pend || *pend != '\0') { rb_fatal("strtoul(\"%s\", ...) failed", STR2CSTR(str)); } return ret; } static unsigned __stdcall worker_func(struct worker *p) { unsigned add_esp; unsigned i; LOG2("{%d} starting worker_func\n", GetCurrentThreadId()); LOG2("{%d} test\n", GetCurrentThreadId()); wait_again: WaitForSingleObject(p->event, INFINITE); LOG2("{%d} got event, calling %p in conv %d using %d args\n", GetCurrentThreadId(), p->func, p->conv, p->argc); if(p->conv == CONV_CDECL) { add_esp = p->argc * 4; } else if(p->conv == CONV_STDCALL) { add_esp = 0; } else { LOG2("Unknown calling convention %d\n", p->conv); return 0; } LOG2("{%d} add_esp: %d, argc: %d\n", GetCurrentThreadId(), add_esp, p->argc); for(i = 0; i < p->argc; i++) { LOG2("arg %d: %x\n", i, p->argv[i]); } p->ret = perform_call(add_esp, p->func, p->argc, p->argv); LOG2("{%d} %p retv=%x\n", GetCurrentThreadId(), p, p->ret); LOG2("{%d}, %p(%p) call done: %x\n", GetCurrentThreadId(), p, &p->ret, p->ret); p->done = 1; goto wait_again; } static VALUE start_call(unsigned argc, VALUE *argv) { VALUE v_worker, v_lib, v_conv, v_name, v_args; HMODULE hlib; struct worker *p; int i, j = 0; rb_scan_args(argc, argv, "5", &v_worker, &v_lib, &v_name, &v_conv, &v_args); p = (void *)val2unsigned(v_worker); LOG("{%d} loading lib '%s'\n", GetCurrentThreadId(), STR2CSTR(v_lib)); RUBY_CRITICAL({ hlib = LoadLibrary(STR2CSTR(v_lib)); }); if(!hlib) { rb_fatal("LoadLibrary() failed, GetLastError()=%d", GetLastError()); } p->argc = RARRAY(v_args)->len; p->argv = malloc(sizeof(int) * p->argc); for(i = RARRAY(v_args)->len - 1; i >= 0; i--) { VALUE v_arg = RARRAY(v_args)->ptr[i]; if(TYPE(v_arg) != T_FIXNUM && TYPE(v_arg) != T_BIGNUM) { rb_fatal("bad type for arg %d (expected T_FIXNUM or T_BIGNUM)", i); } p->argv[j++] = val2unsigned(v_arg); LOG("start_call() arg %d: 0x%x\n", i, val2unsigned(v_arg)); } p->conv = val2unsigned(v_conv); p->done = 0; RUBY_CRITICAL({ p->func = (void*)GetProcAddress(hlib, STR2CSTR(v_name)); }); if(!p->func) { rb_fatal("GetProcAddress() failed"); } LOG("{%d} got function '%s' at %p, raising event\n", GetCurrentThreadId(), STR2CSTR(v_name), p->func); RUBY_CRITICAL({ SetEvent(p->event); }); return Qnil; } static VALUE create_worker(unsigned argc, VALUE *argv) { struct worker *p = malloc(sizeof(struct worker)); rb_scan_args(argc, argv, "0"); RUBY_CRITICAL({ p->event = CreateEvent(NULL, FALSE, FALSE, NULL); }); if(p->event == NULL) { rb_fatal("CreateEvent() failed, GetLastError()=%d", GetLastError()); } LOG("Creating worker thread\n"); RUBY_CRITICAL({ p->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)worker_func, p, 0, /*CREATE_SUSPENDED,*/ NULL); }); if(p->thread == NULL) { rb_fatal("CreateThread() failed, GetLastError()=%d", GetLastError()); } return INT2NUM((int)p); } static VALUE run_worker(unsigned argc, VALUE *argv) { VALUE v_worker, v_time; struct worker *p; unsigned time; rb_scan_args(argc, argv, "2", &v_worker, &v_time); p = (void*)val2unsigned(v_worker); time = val2unsigned(v_time); #undef Sleep RUBY_CRITICAL({ unsigned ret; ret = ResumeThread(p->thread); if(ret == -1) { rb_fatal("ResumeThread() failed, GetLastError()=%d", GetLastError()); } else { //printf("Resume=%d\n", ret); } // printf("yielding for %d ms\n", time); Sleep(time); ret = SuspendThread(p->thread); if(ret == -1) { rb_fatal("SuspendThread() failed, GetLastError()=%d", GetLastError()); } else { //printf("Suspend=%d\n", ret); } }); return Qnil; } static VALUE get_call_result(unsigned argc, VALUE *argv) { VALUE v_worker, v_ret = Qnil; struct worker *p; rb_scan_args(argc, argv, "1", &v_worker); p = (void*)val2unsigned(v_worker); if(p->done) { LOG("getting ret of %p (%p): %d\n", p, &p->ret, p->ret); v_ret = INT2NUM(p->ret); } return v_ret; } static VALUE write_memory(unsigned argc, VALUE *argv) { VALUE v_ptr, v_str; void *ptr; int len; int bad; rb_scan_args(argc, argv, "2", &v_ptr, &v_str); ptr = (void*)val2unsigned(v_ptr); len = RSTRING(v_str)->len; RUBY_CRITICAL({ bad = IsBadWritePtr(ptr, len); }); if(bad) { rb_fatal("IsBadWritePtr(%p, %d)", ptr, len); } memcpy((void*)val2unsigned(v_ptr), RSTRING(v_str)->ptr, RSTRING(v_str)->len); return Qnil; } static VALUE read_memory(unsigned argc, VALUE *argv) { VALUE v_ptr, v_len; void *ptr; int len; rb_scan_args(argc, argv, "2", &v_ptr, &v_len); ptr = (void*)val2unsigned(v_ptr); len = val2unsigned(v_len); LOG("Reading %d bytes from %p\n", len, ptr); if(len == 0) { int bad; RUBY_CRITICAL({ bad = IsBadStringPtr(ptr, ~0); }); if(bad) { rb_fatal("IsBadStringPtr(%p, ~0)", ptr); } return rb_str_new((void*)val2unsigned(v_ptr), strlen((void*)val2unsigned(v_ptr))); } else { int bad; RUBY_CRITICAL({ bad = IsBadReadPtr(ptr, len); }); if(bad) { rb_fatal("IsBadStringPtr(%p, %d)", ptr, len); } return rb_str_new((void*)val2unsigned(v_ptr), len); } } struct callback { char thunk[16]; unsigned argc; unsigned *argv; unsigned conv; unsigned called; unsigned ret; unsigned disabled; CRITICAL_SECTION cs; HANDLE event; }; __declspec(naked) static unsigned callback(struct callback *p, ...) { int i, retv; va_list va; int cleanup; int initial_esp, final_esp; __asm { push ebp mov ebp,esp sub esp,0x200 // make some room mov initial_esp, ebp pusha } if(p->disabled) { goto skip; } LOG2("initial_esp: 0x%x\n", initial_esp); if(p->argc < 0) { LOG2("p->argc == %d", p->argc); abort(); } LOG2("callback(p=%p, ...) p->argc=%d\n", p, p->argc); EnterCriticalSection(&p->cs); p->argv = malloc(p->argc * sizeof(int)); va_start(va, p); for(i = 0; i < p->argc; i++) { p->argv[i] = va_arg(va, int); LOG2("callback(p=%p, ...) arg %d: %d\n", p, i, p->argv[i]); } va_end(va); p->called = 1; WaitForSingleObject(p->event, INFINITE); retv = p->ret; LOG2("callback(p=%p, ...) ret %d\n", p, retv); LeaveCriticalSection(&p->cs); skip: /** * Clean up the callback parameter added by the thunk code. */ cleanup = 4; if(p->conv == CONV_STDCALL) { cleanup += p->argc * 4; } LOG2("callback(p=%p, ...) cleanup=%d\n", p, cleanup); __asm { mov final_esp, ebp add final_esp, 4 mov edx, cleanup add final_esp, edx } LOG2("final_esp: 0x%x\n", final_esp); __asm { popa mov edx, cleanup mov eax, retv mov esp, ebp pop ebp pop ecx add esp, edx jmp ecx } } static VALUE create_callback(unsigned argc, VALUE *argv) { VALUE v_conv, v_argc; struct callback *p; RUBY_CRITICAL({ p = malloc(sizeof(struct callback)); }); rb_scan_args(argc, argv, "2", &v_conv, &v_argc); if(val2unsigned(v_argc) < 0) { rb_fatal("v_argc = %d", val2unsigned(v_argc)); } if(val2unsigned(v_conv) != CONV_STDCALL && val2unsigned(v_conv) != CONV_CDECL) { rb_fatal("v_conv = %d", val2unsigned(v_conv)); } p->called = 0; p->disabled = 0; p->argc = val2unsigned(v_argc); p->conv = val2unsigned(v_conv); p->thunk[0] = 0xb8; *(unsigned *)&p->thunk[1] = (int)p; // p *(unsigned *)&p->thunk[5] = 0x50240487; p->thunk[9] = 0xe9; // jmp rel32 *(unsigned *)&p->thunk[10] = ((char *)callback) - &p->thunk[14]; RUBY_CRITICAL({ InitializeCriticalSection(&p->cs); p->event = CreateEvent(NULL, FALSE, FALSE, NULL); }); return INT2NUM(((int)p)); } static VALUE exec_callback(unsigned argc, VALUE *argv) { VALUE v_callback, v_args; struct callback *p; int i; rb_scan_args(argc, argv, "1", &v_callback); p = (void *)val2unsigned(v_callback); if(!p->called) { return Qfalse; } v_args = rb_ary_new(); for(i = 0; i < p->argc; i++) { rb_ary_push(v_args, INT2NUM(p->argv[i])); } return v_args; } static VALUE disable_callback(unsigned argc, VALUE *argv) { VALUE v_callback; struct callback *p; rb_scan_args(argc, argv, "1", &v_callback); p = (void *)val2unsigned(v_callback); p->disabled = TRUE; RUBY_CRITICAL({ SetEvent(p->event); }); return Qnil; } static VALUE fini_callback(unsigned argc, VALUE *argv) { VALUE v_callback, v_ret; struct callback *p; rb_scan_args(argc, argv, "2", &v_callback, &v_ret); p = (void *)val2unsigned(v_callback); p->ret = val2unsigned(v_ret); p->called = FALSE; RUBY_CRITICAL({ SetEvent(p->event); }); return Qnil; } void Init_w32thunk() { VALUE m_w32thunk = rb_define_module("W32Thunk"); rb_define_module_function(m_w32thunk, "_create_worker", create_worker, -1); rb_define_module_function(m_w32thunk, "_run_worker", run_worker, -1); rb_define_module_function(m_w32thunk, "_start_call", start_call, -1); rb_define_module_function(m_w32thunk, "_get_call_result", get_call_result, -1); rb_define_module_function(m_w32thunk, "_write_memory", write_memory, -1); rb_define_module_function(m_w32thunk, "_read_memory", read_memory, -1); rb_define_module_function(m_w32thunk, "_create_callback", create_callback, -1); rb_define_module_function(m_w32thunk, "_exec_callback", exec_callback, -1); rb_define_module_function(m_w32thunk, "_disable_callback", disable_callback, -1); rb_define_module_function(m_w32thunk, "_fini_callback", fini_callback, -1); } # w32thunk.rb require 'w32thunk/w32thunk.so' Thread.abort_on_exception = true class Fixnum def to_hex "0x" + self.to_s(16) end end module Kernel32 PROCESS_ALL_ACCESS = 0x1f0fff def self.method_missing(method, *args) W32Thunk::call("kernel32", method.to_s, W32Thunk::STDCALL, *args) end end def my_inspect(y) if y.is_a? Array "[#{y.map { |x| my_inspect(x) }.join(", ")}]" elsif y.is_a? Fixnum "0x#{y.to_s(16)}" else y.inspect end end module W32Thunk CDECL = 0 STDCALL = 1 @@workers = {} @@callbacks = [] def W32Thunk.debug=(x) $w32thunk_debug = x end def W32Thunk.dbg(args) if $w32thunk_debug Thread.exclusive do $thread_ids ||= {} $thread_ids[Thread.current] ||= $thread_ids.keys.size puts "<#{$thread_ids[Thread.current]}> #{args}" end end end class Buffer attr_reader :ptr attr_reader :len def Buffer.free_ptr(ptr) lambda do W32Thunk.dbg "/// Freeing buffer #{my_inspect(ptr)}" Kernel32.LocalFree(ptr) end end def initialize(arg) init = nil if arg.is_a? Fixnum @len = arg else raise "Bad type" unless arg.is_a? String @len = arg.size init = arg end @ptr = Kernel32.LocalAlloc(0x40, len + 1) W32Thunk.dbg "/// Created buffer #{my_inspect(@ptr)} (Size: #{len})" if @ptr.to_i == 0 raise "LocalAlloc() failed to alloc #{len} bytes: #{Kernel32.GetLastError}" end ObjectSpace.define_finalizer(self, Buffer.free_ptr(@ptr)) if init dump = ((init.size > 16) ? "#{init[0..16].inspect} ..." : "#{init.inspect}") W32Thunk.dbg "Writing #{init.size} bytes to #{my_inspect(@ptr)} (#{dump})" W32Thunk::_write_memory(@ptr, init) end @ptr end def free Buffer.free_ptr(@ptr).call ObjectSpace.undefine_finalizer(self) @ptr = 0 end def to_s W32Thunk.dbg "Reading #{@len == 0 ? "" : @len} bytes from #{my_inspect(@ptr)}" W32Thunk::_read_memory(@ptr, @len) end end class IntBuf < Buffer def initialize(n = 0) super([n].pack("V")) end def to_i to_s.unpack("V")[0] end end class Pointer def initialize(i) @i = i end def to_i @i end def to_s(len = 0) W32Thunk.dbg "Reading #{len == 0 ? "" : len} bytes from #{my_inspect(@i)}" W32Thunk::_read_memory(@i, len) end def inspect "0x" + @i.to_s(16) end end def self.call(lib, func, conv, *args) begin dbg "CALL !!! Preparing #{lib}.dll!#{conv == 0 ? "" : ""}#{func}(#{my_inspect(args)})" buffers = [] args.map! { |arg| if arg.is_a? String dbg "CALL --- Mapping string #{arg.inspect}" buf = Buffer.new(arg) buffers << buf buf.ptr elsif arg.is_a? Buffer dbg "CALL --- Mapping buffer #{arg}" arg.ptr elsif arg.is_a? Callback dbg "CALL --- Mapping callback #{arg}" arg.ptr else dbg "CALL --- Pushing #{arg}" arg end } dbg "CALL >>> Calling #{lib}.dll!#{conv == 0 ? "" : ""}#{func}(#{my_inspect(args)})" worker = @@workers[Thread.current] ||= W32Thunk::_create_worker # puts "--- calling #{func}(#{args.inspect})" W32Thunk::_start_call(worker, lib, func, conv, args) loop do # W32Thunk::_run_worker(worker, 10) ret = W32Thunk::_get_call_result(worker) unless ret.nil? dbg "CALL <<< Returned #{my_inspect(ret)}" return ret end sleep 0.02 end end dbg "CALL --- GC after call to #{func}" # GC.start dbg "CALL --- GC done" end class Callback attr_reader :ptr def Callback.destruct(handle) lambda do W32Thunk.dbg "CALLBACK !!! Disabling #{my_inspect(handle)}" W32Thunk::_disable_callback(handle) end end def initialize(conv, block) argc = block.arity argc = 0 if argc < 0 cb = W32Thunk::_create_callback(conv, argc) W32Thunk.dbg "CALLBACK +++ Created #{my_inspect(cb)}" @thread = Thread.new(cb) do |cb| loop do args = W32Thunk::_exec_callback(cb) if args W32Thunk.dbg "CALLBACK >>> Invoked #{my_inspect(cb)} ... (#{my_inspect(args)})" args.map! { |x| if x > 0xffff Pointer.new(x) else x end } args = args.first if args.size == 1 begin ret = block.call(args) rescue p $@ puts "Exception in callback: #{$!}" # Process.abort end ret = 0 if ret.nil? W32Thunk.dbg "CALLBACK <<< Returning #{my_inspect(ret)}" W32Thunk::_fini_callback(cb, ret) end sleep 0.02 # GC.start end end @ptr = cb ObjectSpace.define_finalizer(self, Callback.destruct(cb)) end end def self.callback(conv = nil, &block) conv ||= W32Thunk::STDCALL Callback.new(conv, block) end end From phasis at gmail.com Thu Apr 30 11:38:38 2009 From: phasis at gmail.com (Heesob Park) Date: Fri, 1 May 2009 00:38:38 +0900 Subject: [Win32utils-devel] Making win32-api better? In-Reply-To: <000401c9c998$35eebc60$a1cc3520$@com> References: <000401c9c998$35eebc60$a1cc3520$@com> Message-ID: Hi, 2009/4/30 Daniel Berger : > Hi all, > > During a conversation with 'flexo' on IRC (not sure of his real name), he > complained that win32-api was unstable. He wrote his own interface and > maintains that it is very stable and works better with multi-threaded apps. > > Please take a look and tell me what you think (below the C code is the Ruby > interface): > Great work indeed. The code seems to consist of the intensive use of inline assembly and thread manipulations. I guess it will not work for ruby 1.9.x 64bit version. And it has some superfluous overheads in thread handling and sleeping delay. Anyway, I expect it will work on ruby 1.8.6 in most cases. But, here are some issue. W32Thunk.call('user32','MessageBeep',1,-1) failed with the message ./w32thunk.rb:146:in `_start_call': strtoul("-1", ...) failed (fatal) from ./w32thunk.rb:146:in `call' This callback test blocks on GetWindowTextA calling. call = W32Thunk.callback {|handle,param| buf = 0.chr * 200 W32Thunk.call('user32','GetWindowTextA',1,handle,buf,200) buf.index(param).nil? ? 1 : 0 } W32Thunk.call('user32','EnumWindows',1,call,'UEDIT32') Regards, Park Heesob From djberg96 at gmail.com Thu Apr 30 12:25:54 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 30 Apr 2009 10:25:54 -0600 Subject: [Win32utils-devel] Making win32-api better? In-Reply-To: References: <000401c9c998$35eebc60$a1cc3520$@com> Message-ID: <49F9D112.6080004@gmail.com> Heesob Park wrote: > Hi, > > 2009/4/30 Daniel Berger : >> Hi all, >> >> During a conversation with 'flexo' on IRC (not sure of his real name), he >> complained that win32-api was unstable. He wrote his own interface and >> maintains that it is very stable and works better with multi-threaded apps. >> >> Please take a look and tell me what you think (below the C code is the Ruby >> interface): >> > > > Great work indeed. > > The code seems to consist of the intensive use of inline assembly and > thread manipulations. > I guess it will not work for ruby 1.9.x 64bit version. I didn't think x64 Windows was supported anyway. Has that changed? > And it has some superfluous overheads in thread handling and sleeping delay. The author says that it was a bit of a hack. He says one of the problems is that there's a problem with that code - w32thunk.rb syncs with native by those sleep 0.02 busy waiting loops and it therefore can't dispatch more than 100 (in reality even less) callbacks into ruby per second. > > Anyway, I expect it will work on ruby 1.8.6 in most cases. > But, here are some issue. > > W32Thunk.call('user32','MessageBeep',1,-1) failed with the message > ./w32thunk.rb:146:in `_start_call': strtoul("-1", ...) failed (fatal) > from ./w32thunk.rb:146:in `call' > > This callback test blocks on GetWindowTextA calling. > call = W32Thunk.callback {|handle,param| > buf = 0.chr * 200 > W32Thunk.call('user32','GetWindowTextA',1,handle,buf,200) > buf.index(param).nil? ? 1 : 0 > } > W32Thunk.call('user32','EnumWindows',1,call,'UEDIT32') Ok, so there are some issues. Is there anything we can borrow? Regards, Dan From phasis at gmail.com Thu Apr 30 20:35:34 2009 From: phasis at gmail.com (Heesob Park) Date: Fri, 1 May 2009 09:35:34 +0900 Subject: [Win32utils-devel] Making win32-api better? In-Reply-To: <49F9D112.6080004@gmail.com> References: <000401c9c998$35eebc60$a1cc3520$@com> <49F9D112.6080004@gmail.com> Message-ID: 2009/5/1 Daniel Berger : > Heesob Park wrote: >> >> Hi, >> >> 2009/4/30 Daniel Berger : >>> >>> Hi all, >>> >>> During a conversation with 'flexo' on IRC (not sure of his real name), he >>> complained that win32-api was unstable. He wrote his own interface and >>> maintains that it is very stable and works better with multi-threaded >>> apps. >>> >>> Please take a look and tell me what you think (below the C code is the >>> Ruby >>> interface): >>> >> >> >> Great work indeed. >> >> The code seems to consist of the intensive use of inline assembly and >> thread manipulations. >> I guess it will not work for ruby 1.9.x 64bit version. > > I didn't think x64 Windows was supported anyway. Has that changed? > What do you mean by that? I think we can support x64 Windows. The x64 Windows version of Ruby is available at http://www.garbagecollect.jp/ruby/mswin32/en/download/release.html and x64 windows version of win32-api is at http://121.78.227.9/win32-api-1.4.0/lib/win32/api.so Refer to http://www.ruby-forum.com/topic/180322 >> And it has some superfluous overheads in thread handling and sleeping >> delay. > > The author says that it was a bit of a hack. He says one of the problems is > that there's a problem with that code - w32thunk.rb syncs with native by > those sleep 0.02 busy waiting loops and it therefore can't dispatch more > than 100 (in reality even less) callbacks into ruby per second. > >> >> Anyway, I expect it will work on ruby 1.8.6 in most cases. >> But, here are some issue. >> >> W32Thunk.call('user32','MessageBeep',1,-1) failed with the message >> ./w32thunk.rb:146:in `_start_call': strtoul("-1", ...) failed (fatal) >> ? ? ? ?from ./w32thunk.rb:146:in `call' >> >> This callback test blocks on GetWindowTextA calling. >> call = W32Thunk.callback {|handle,param| >> ? ? ? ?buf = 0.chr * 200 >> ? ? ? ?W32Thunk.call('user32','GetWindowTextA',1,handle,buf,200) >> ? ? ? ?buf.index(param).nil? ? 1 : 0 >> } >> W32Thunk.call('user32','EnumWindows',1,call,'UEDIT32') > > Ok, so there are some issues. Is there anything we can borrow? > First, let me know what is the issues of the current win32-api? Regards, Park Heesob From djberg96 at gmail.com Thu Apr 30 20:44:38 2009 From: djberg96 at gmail.com (Daniel Berger) Date: Thu, 30 Apr 2009 18:44:38 -0600 Subject: [Win32utils-devel] Making win32-api better? In-Reply-To: References: <000401c9c998$35eebc60$a1cc3520$@com> <49F9D112.6080004@gmail.com> Message-ID: <6037b70c0904301744l7cbce77euf70ad3f5d66a3d5f@mail.gmail.com> On Thu, Apr 30, 2009 at 6:35 PM, Heesob Park wrote: > 2009/5/1 Daniel Berger : >> Heesob Park wrote: >> Ok, so there are some issues. Is there anything we can borrow? >> > First, let me know what is the issues of the current win32-api? His complaint was that win32-api wasn't threadsafe (I think he was referring to callbacks) and the hardcoded callback limit we have in place. Regards, Dan