win32-api 1.3.0-x86-mswin32-80

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,88 @@
1
+ = 1.3.0 - 1-Jan-2009
2
+ * Fixed RubyForge bug #23395, which was caused by inadvertently modifying
3
+ a variable within a loop. This caused callbacks to fail in certain
4
+ situations.
5
+ * Added the Win32::API::LoadLibraryError and Win32::API::PrototypeError classes
6
+ to provide more fine grained handling of possible error conditions in the
7
+ constructor. These are both subclasses of Win32::API::Error.
8
+ * Removed the Win32::API::CallbackError class.
9
+ * Changed the upper limit on prototypes from 16 to 20. It turns out that
10
+ there are actually Windows functions with more than 16 prototypes.
11
+ * Refactored a high iteration test so that it counts as only one test
12
+ instead of 1000.
13
+
14
+ = 1.2.2 - 27-Nov-2008
15
+ * Fixed bug in the error message for illegal prototypes and illegal return
16
+ types where the character in question would come out as empty or garbage.
17
+ * Passing a bad return type to Win32::API::Callback now raises an error.
18
+ * Updated the error message for illegal return types to say, "Illegal return
19
+ type" instead of "Illegal prototype" as it did previously.
20
+ * The error message for a bad function name passed to Win32::API.new now
21
+ matches JRuby's FFI error message.
22
+ * Improved the handling of msvcrt functions with regards to skipping 'A'
23
+ and 'W' checks. Previously it was checking against the literal string
24
+ 'msvcrt'. Now it checks against any string that starts with 'msvcr'.
25
+ * Added test-unit 2.x as a prerequisite.
26
+ * Added tests for the Win32::API::Callback#address method.
27
+ * Added tests to all Win32::API classes that explicitly check error messages.
28
+
29
+ = 1.2.1 - 14-Nov-2008
30
+ * Fixed and updated callback handling.
31
+ * Fixed wide string return handling for pointers and strings.
32
+ * Added the Win32::API::Callback#address instance method.
33
+ * All errors are now in English instead of your native language, because
34
+ that's what Ruby itself does.
35
+
36
+ = 1.2.0 - 22-Jul-2008
37
+ * Added support for the 'S' (string) prototype and return type. It can be
38
+ used instead of 'P' (pointer) for const char*.
39
+ * Some internal refactoring. The attempts to load ANSI and/or Wide character
40
+ versions of functions are skipped for MSVCRT functions, since they do not
41
+ exist. This eliminates some unnecessary LoadLibrary() calls.
42
+ * Added a couple of gem building Rake tasks.
43
+ * Added a few more tests.
44
+
45
+ = 1.1.0 - 12-Jun-2008
46
+ * Added the Windows::API::Function class. This is a subclass of Win32::API
47
+ meant only for use with raw function pointers.
48
+ * Some documentation updates in the source and README files.
49
+
50
+ = 1.0.6 - 18-Apr-2008
51
+ * Added the effective_function_name method. This allows you to see what the
52
+ actual function name is that was defined, e.g. GetUserNameA vs GetUserNameW.
53
+ * Replaced an instance of _tcscmp with strcmp. The case in question was always
54
+ going to be ASCII.
55
+ * Cleaned up some -W3 warnings.
56
+ * Added the build_manifest task to the Rakefile, which is automatically run if
57
+ you're using a version of Ruby built with VC++ 8 or later. This builds a
58
+ ruby.exe.manifest file (if it doesn't already exist).
59
+
60
+ = 1.0.5 - 20-Nov-2007
61
+ * The API.new method now defaults to "W" (wide character functions) before "A"
62
+ (ANSI functions) if the $KCODE global variable is set to 'u' (UTF8).
63
+ * Minor improvements to the Rakefile.
64
+
65
+ = 1.0.4 - 26-Oct-2007
66
+ * Fixed a bug where methods that returned pointers ('P') could choke if the
67
+ resulting pointer was 0 or NULL. In this case, nil is now returned instead.
68
+ * Tweak to the extconf.rb file that helps the gem build it from source
69
+ properly.
70
+
71
+ = 1.0.3 - 28-Sep-2007
72
+ * Fixed a subtle but dangerous copy-on-write bug in the API#call method.
73
+
74
+ = 1.0.2 - 28-Sep-2007
75
+ * Fixed a bug in an internal struct member that was causing segfaults. Thanks
76
+ go to Lars Olsson for the spot.
77
+ * Fixed the 'install' task in the Rakefile. This only affected native builds,
78
+ not the prebuilt binary.
79
+ * Added a few more tests.
80
+
81
+ = 1.0.1 - 27-Sep-2007
82
+ * Functions declared with a void prototype no longer require an explicit nil
83
+ argument to fulfill the arity requirement. You can still call them with an
84
+ explicit nil if you wish, however.
85
+ * Fixed the gemspec for the native build.
86
+
87
+ = 1.0.0 - 14-Sep-2007
88
+ * Initial release
data/MANIFEST ADDED
@@ -0,0 +1,10 @@
1
+ * CHANGES
2
+ * MANIFEST
3
+ * README
4
+ * Rakefile
5
+ * win32-api.gemspec
6
+ * ext/extconf.rb
7
+ * ext/win32/api.c
8
+ * test/test_win32_api.rb
9
+ * test/test_win32_api_callback.rb
10
+ * test/test_win32_api_function.rb
data/README ADDED
@@ -0,0 +1,103 @@
1
+ = Description
2
+ This is a drop-in replacement for the Win32API library currently part of
3
+ Ruby's standard library.
4
+
5
+ = Synopsis
6
+ require 'win32/api'
7
+ include Win32
8
+
9
+ # Typical example - Get user name
10
+ buf = 0.chr * 260
11
+ len = [buf.length].pack('L')
12
+
13
+ GetUserName = API.new('GetUserName', 'PP', 'I', 'advapi32')
14
+ GetUserName.call(buf, len)
15
+
16
+ puts buf.strip
17
+
18
+ # Callback example - Enumerate windows
19
+ EnumWindows = API.new('EnumWindows', 'KP', 'L', 'user32')
20
+ GetWindowText = API.new('GetWindowText', 'LPI', 'I', 'user32')
21
+ EnumWindowsProc = API::Callback.new('LP', 'I'){ |handle, param|
22
+ buf = "\0" * 200
23
+ GetWindowText.call(handle, buf, 200);
24
+ puts buf.strip unless buf.strip.empty?
25
+ buf.index(param).nil? ? true : false
26
+ }
27
+
28
+ EnumWindows.call(EnumWindowsProc, 'UEDIT32')
29
+
30
+ # Raw function pointer example - System beep
31
+ LoadLibrary = API.new('LoadLibrary', 'P', 'L')
32
+ GetProcAddress = API.new('GetProcAddress', 'LP', 'L')
33
+
34
+ hlib = LoadLibrary.call('user32')
35
+ addr = GetProcAddress.call(hlib, 'MessageBeep')
36
+ func = Win32::API::Function.new(addr, 'L', 'L')
37
+ func.call(0)
38
+
39
+ = Differences between win32-api and Win32API
40
+ * This library has callback support
41
+ * This library supports raw function pointers.
42
+ * This library supports a separate string type for const char* (S).
43
+ * Argument order change. The DLL name is now last, not first.
44
+ * Removed the 'N' and 'n' prototypes. Always use 'L' for longs now.
45
+ * Sensible default arguments for the prototype, return type and DLL name.
46
+ * Reader methods for the function name, effective function name, prototype,
47
+ return type and DLL.
48
+ * Removed the support for lower case prototype and return types. Always
49
+ use capital letters.
50
+ * Resorts to wide character functions (where possible) when $KCODE is set
51
+ to UTF8.
52
+
53
+ = Developer's Notes
54
+ The current Win32API library that ships with the standard library has been
55
+ slated for removal from Ruby 2.0 and it will not receive any updates in the
56
+ Ruby 1.8.x branch. I have far too many libraries invested in it to let it
57
+ die at this point.
58
+
59
+ In addition, the current Win32API library was written in the bad old Ruby
60
+ 1.6.x days, which means it doesn't use the newer allocation framework.
61
+ There were several other refactorings that I felt it needed to more closely
62
+ match how it was actually being used in practice.
63
+
64
+ The first order of business was changing the order of the arguments. By
65
+ moving the DLL name from first to last, I was able to provide reasonable
66
+ default arguments for the prototype, return type and the DLL. Only the
67
+ function name is required now.
68
+
69
+ There was a laundry list of other refactorings that were needed: sensical
70
+ instance variable names with proper accessors, removing support for lower
71
+ case prototype and return value characters that no one used in practice,
72
+ better naming conventions, the addition of RDoc ready comments and,
73
+ especially, callback and raw function pointer support.
74
+
75
+ Most importantly, we can now add, modify and fix any features that we feel
76
+ best benefit our end users.
77
+
78
+ = Documentation
79
+ The source file contains inline RDoc documentation. If you installed
80
+ this file as a gem, then you have the docs.
81
+
82
+ = Warranty
83
+ This package is provided "as is" and without any express or
84
+ implied warranties, including, without limitation, the implied
85
+ warranties of merchantability and fitness for a particular purpose.
86
+
87
+ = Known Issues
88
+ Possible callback issues when dealing with multi-threaded applications.
89
+ We are working on this for the next 1.2.x release.
90
+
91
+ Please submit any bug reports to the project page at
92
+ http://www.rubyforge.org/projects/win32utils.
93
+
94
+ = Copyright
95
+ (C) 2003-2008 Daniel J. Berger
96
+ All Rights Reserved
97
+
98
+ = License
99
+ Ruby's
100
+
101
+ = Authors
102
+ Daniel J. Berger
103
+ Park Heesob
data/ext/win32/api.c ADDED
@@ -0,0 +1,877 @@
1
+ #include <ruby.h>
2
+ #include <windows.h>
3
+
4
+ #define MAX_BUF 1024
5
+ #define WINDOWS_API_VERSION "1.3.0"
6
+
7
+ #define _T_VOID 0
8
+ #define _T_LONG 1
9
+ #define _T_POINTER 2
10
+ #define _T_INTEGER 3
11
+ #define _T_CALLBACK 4
12
+ #define _T_STRING 5
13
+
14
+ VALUE cAPIError, cAPIProtoError, cAPILoadError;
15
+ static VALUE ActiveCallback = Qnil;
16
+
17
+ typedef struct {
18
+ HANDLE library;
19
+ FARPROC function;
20
+ int return_type;
21
+ int prototype[20];
22
+ } Win32API;
23
+
24
+ static void api_free(Win32API* ptr){
25
+ if(ptr->library)
26
+ FreeLibrary(ptr->library);
27
+
28
+ if(ptr)
29
+ free(ptr);
30
+ }
31
+
32
+ static VALUE api_allocate(VALUE klass){
33
+ Win32API* ptr = malloc(sizeof(Win32API));
34
+ return Data_Wrap_Struct(klass, 0, api_free, ptr);
35
+ }
36
+
37
+ /* Helper function that converts the error number returned by GetLastError()
38
+ * into a human readable string. Note that we always use English for error
39
+ * output because that's what Ruby itself does.
40
+ *
41
+ * Internal use only.
42
+ */
43
+ char* StringError(DWORD dwError){
44
+ LPVOID lpMsgBuf;
45
+ static char buf[MAX_PATH];
46
+ DWORD dwLen;
47
+
48
+ /* Assume ASCII error messages from the Windows API */
49
+ dwLen = FormatMessageA(
50
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
51
+ FORMAT_MESSAGE_FROM_SYSTEM |
52
+ FORMAT_MESSAGE_IGNORE_INSERTS,
53
+ NULL,
54
+ dwError,
55
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
56
+ (LPSTR)&lpMsgBuf,
57
+ 0,
58
+ NULL
59
+ );
60
+
61
+ if(!dwLen)
62
+ rb_raise(cAPIError, "Attempt to format message failed");
63
+
64
+ memset(buf, 0, MAX_PATH);
65
+
66
+ /* remove \r\n */
67
+ #ifdef HAVE_STRNCPY_S
68
+ strncpy_s(buf, MAX_PATH, lpMsgBuf, dwLen - 2);
69
+ #else
70
+ strncpy(buf, lpMsgBuf, dwLen - 2);
71
+ #endif
72
+
73
+ LocalFree(lpMsgBuf);
74
+
75
+ return buf;
76
+ }
77
+
78
+ /*
79
+ * call-seq:
80
+ * Win32::API::Callback.new(prototype, return='L'){ |proto| ... }
81
+ *
82
+ * Creates and returns a new Win32::API::Callback object. The prototype
83
+ * arguments are yielded back to the block in the same order they were
84
+ * declared.
85
+ *
86
+ * The +prototype+ is the function prototype for the callback function. This
87
+ * is a string. The possible valid characters are 'I' (integer), 'L' (long),
88
+ * 'V' (void), 'P' (pointer) or 'S' (string). Unlike API objects, API::Callback
89
+ * objects do not have a default prototype.
90
+ *
91
+ * The +return+ argument is the return type for the callback function. The
92
+ * valid characters are the same as for the +prototype+. The default is
93
+ * 'L' (long).
94
+ *
95
+ * Example:
96
+ * require 'win32/api'
97
+ * include Win32
98
+ *
99
+ * EnumWindows = API.new('EnumWindows', 'KP', 'L', 'user32')
100
+ * GetWindowText = API.new('GetWindowText', 'LPI', 'I', 'user32')
101
+ *
102
+ * EnumWindowsProc = API::Callback.new('LP', 'I'){ |handle, param|
103
+ * buf = "\0" * 200
104
+ * GetWindowText.call(handle, buf, 200);
105
+ * puts buf.strip unless buf.strip.empty?
106
+ * buf.index(param).nil? ? true : false
107
+ * }
108
+ *
109
+ * EnumWindows.call(EnumWindowsProc, 'UEDIT32')
110
+ */
111
+ static VALUE callback_init(int argc, VALUE* argv, VALUE self)
112
+ {
113
+ extern void *CallbackTable[];
114
+ VALUE v_proto, v_return, v_proc;
115
+ int i;
116
+
117
+ rb_scan_args(argc, argv, "11&", &v_proto, &v_return, &v_proc);
118
+
119
+ /* Validate prototype characters */
120
+ for(i = 0; i < RSTRING(v_proto)->len; i++){
121
+ switch(RSTRING(v_proto)->ptr[i]){
122
+ case 'I': case 'L': case 'P': case 'V': case 'S':
123
+ break;
124
+ default:
125
+ rb_raise(cAPIProtoError, "Illegal prototype '%c'",
126
+ RSTRING(v_proto)->ptr[i]
127
+ );
128
+ }
129
+ }
130
+
131
+ if(NIL_P(v_return) || RARRAY(v_return)->len == 0){
132
+ v_return = rb_str_new2("L");
133
+ }
134
+ else{
135
+ switch(*(char*)RSTRING(v_return)->ptr){
136
+ case 'I': case 'L': case 'P': case 'V': case 'S':
137
+ break;
138
+ default:
139
+ rb_raise(cAPIProtoError, "Illegal return type '%s'",
140
+ RSTRING(v_return)->ptr
141
+ );
142
+ }
143
+ }
144
+
145
+ rb_iv_set(self, "@function", v_proc);
146
+ rb_iv_set(self, "@prototype", v_proto);
147
+ rb_iv_set(self, "@return_type", v_return);
148
+ rb_iv_set(self, "@address", ULONG2NUM((LPARAM)CallbackTable[RSTRING(v_proto)->len]));
149
+ ActiveCallback = self;
150
+
151
+ return self;
152
+ }
153
+
154
+ /*
155
+ * call-seq:
156
+ * Win32::API.new(function, prototype='V', return='L', dll='kernel32')
157
+ *
158
+ * Creates and returns a new Win32::API object. The +function+ is the name
159
+ * of the Windows function.
160
+ *
161
+ * The +prototype+ is the function prototype for +function+. This can be a
162
+ * string or an array of characters. The possible valid characters are 'I'
163
+ * (integer), 'L' (long), 'V' (void), 'P' (pointer), 'K' (callback) or 'S'
164
+ * (string).
165
+ *
166
+ * The default is void ('V').
167
+ *
168
+ * Constant (const char*) strings should use 'S'. Pass by reference string
169
+ * buffers should use 'P'. The former is faster, but cannot be modified.
170
+ *
171
+ * The +return+ argument is the return type for the function. The valid
172
+ * characters are the same as for the +prototype+. The default is 'L' (long).
173
+ *
174
+ * The +dll+ is the name of the DLL file that the function is exported from.
175
+ * The default is 'kernel32'.
176
+ *
177
+ * If the function cannot be found then an API::Error is raised (a subclass
178
+ * of RuntimeError).
179
+ *
180
+ * Example:
181
+ *
182
+ * require 'win32/api'
183
+ * include Win32
184
+ *
185
+ * buf = 0.chr * 260
186
+ * len = [buf.length].pack('L')
187
+ *
188
+ * GetUserName = API.new('GetUserName', 'PP', 'I', 'advapi32')
189
+ * GetUserName.call(buf, len)
190
+ *
191
+ * puts buf.strip
192
+ */
193
+ static VALUE api_init(int argc, VALUE* argv, VALUE self)
194
+ {
195
+ HMODULE hLibrary;
196
+ FARPROC fProc;
197
+ Win32API* ptr;
198
+ int i;
199
+ char* first = "A";
200
+ char* second = "W";
201
+ VALUE v_proc, v_proto, v_return, v_dll;
202
+
203
+ rb_scan_args(argc, argv, "13", &v_proc, &v_proto, &v_return, &v_dll);
204
+
205
+ Data_Get_Struct(self, Win32API, ptr);
206
+
207
+ // Convert a string prototype to an array of characters
208
+ if(rb_respond_to(v_proto, rb_intern("split")))
209
+ v_proto = rb_str_split(v_proto, "");
210
+
211
+ // Convert a nil or empty prototype to 'V' (void) automatically
212
+ if(NIL_P(v_proto) || RARRAY(v_proto)->len == 0){
213
+ v_proto = rb_ary_new();
214
+ rb_ary_push(v_proto, rb_str_new2("V"));
215
+ }
216
+
217
+ // Set an arbitrary limit of 20 parameters
218
+ if(20 < RARRAY(v_proto)->len)
219
+ rb_raise(rb_eArgError, "too many parameters: %d\n", RARRAY(v_proto)->len);
220
+
221
+ // Set the default dll to 'kernel32'
222
+ if(NIL_P(v_dll))
223
+ v_dll = rb_str_new2("kernel32");
224
+
225
+ // Set the default return type to 'L' (DWORD)
226
+ if(NIL_P(v_return))
227
+ v_return = rb_str_new2("L");
228
+
229
+ SafeStringValue(v_dll);
230
+ SafeStringValue(v_proc);
231
+
232
+ hLibrary = LoadLibrary(TEXT(RSTRING(v_dll)->ptr));
233
+
234
+ // The most likely cause of failure is a bad DLL load path
235
+ if(!hLibrary){
236
+ rb_raise(cAPILoadError, "LoadLibrary() function failed for '%s': %s",
237
+ RSTRING(v_dll)->ptr,
238
+ StringError(GetLastError())
239
+ );
240
+ }
241
+
242
+ ptr->library = hLibrary;
243
+
244
+ /* Attempt to get the function. If it fails, try again with an 'A'
245
+ * appended. If that fails, try again with a 'W' appended. If that
246
+ * still fails, raise an API::Error.
247
+ */
248
+ fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_proc)->ptr));
249
+
250
+ // The order of 'A' and 'W' is reversed if $KCODE is set to 'UTF8'.
251
+ if(!strcmp(rb_get_kcode(), "UTF8")){
252
+ first = "W";
253
+ second = "A";
254
+ }
255
+
256
+ // Skip the ANSI and Wide function checks for MSVCRT functions.
257
+ if(!fProc){
258
+ if(strstr(RSTRING(v_dll)->ptr, "msvcr")){
259
+ rb_raise(
260
+ cAPILoadError,
261
+ "Unable to load function '%s'",
262
+ RSTRING(v_proc)->ptr,
263
+ StringError(GetLastError())
264
+ );
265
+ }
266
+ else{
267
+ VALUE v_ascii = rb_str_new3(v_proc);
268
+ v_ascii = rb_str_cat(v_ascii, first, 1);
269
+ fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_ascii)->ptr));
270
+
271
+ if(!fProc){
272
+ VALUE v_unicode = rb_str_new3(v_proc);
273
+ v_unicode = rb_str_cat(v_unicode, second, 1);
274
+ fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_unicode)->ptr));
275
+
276
+ if(!fProc){
277
+ rb_raise(
278
+ cAPILoadError,
279
+ "Unable to load function '%s', '%s', or '%s'",
280
+ RSTRING(v_proc)->ptr,
281
+ RSTRING(v_ascii)->ptr,
282
+ RSTRING(v_unicode)->ptr
283
+ );
284
+ }
285
+ else{
286
+ rb_iv_set(self, "@effective_function_name", v_unicode);
287
+ }
288
+ }
289
+ else{
290
+ rb_iv_set(self, "@effective_function_name", v_ascii);
291
+ }
292
+ }
293
+ }
294
+ else{
295
+ rb_iv_set(self, "@effective_function_name", v_proc);
296
+ }
297
+
298
+ ptr->function = fProc;
299
+
300
+ // Push the numeric prototypes onto our int array for later use.
301
+
302
+ for(i = 0; i < RARRAY(v_proto)->len; i++){
303
+ SafeStringValue(RARRAY(v_proto)->ptr[i]);
304
+ switch(*(char*)StringValuePtr(RARRAY(v_proto)->ptr[i])){
305
+ case 'L':
306
+ ptr->prototype[i] = _T_LONG;
307
+ break;
308
+ case 'P':
309
+ ptr->prototype[i] = _T_POINTER;
310
+ break;
311
+ case 'I': case 'B':
312
+ ptr->prototype[i] = _T_INTEGER;
313
+ break;
314
+ case 'V':
315
+ ptr->prototype[i] = _T_VOID;
316
+ break;
317
+ case 'K':
318
+ ptr->prototype[i] = _T_CALLBACK;
319
+ break;
320
+ case 'S':
321
+ ptr->prototype[i] = _T_STRING;
322
+ break;
323
+ default:
324
+ rb_raise(cAPIProtoError, "Illegal prototype '%s'",
325
+ StringValuePtr(RARRAY(v_proto)->ptr[i])
326
+ );
327
+ }
328
+ }
329
+
330
+ // Store the return type for later use.
331
+
332
+ // Automatically convert empty strings or nil to type void.
333
+ if(NIL_P(v_return) || RSTRING(v_return)->len == 0){
334
+ v_return = rb_str_new2("V");
335
+ ptr->return_type = _T_VOID;
336
+ }
337
+ else{
338
+ SafeStringValue(v_return);
339
+ switch(*RSTRING(v_return)->ptr){
340
+ case 'L':
341
+ ptr->return_type = _T_LONG;
342
+ break;
343
+ case 'P':
344
+ ptr->return_type = _T_POINTER;
345
+ break;
346
+ case 'I': case 'B':
347
+ ptr->return_type = _T_INTEGER;
348
+ break;
349
+ case 'V':
350
+ ptr->return_type = _T_VOID;
351
+ break;
352
+ case 'S':
353
+ ptr->return_type = _T_STRING;
354
+ break;
355
+ default:
356
+ rb_raise(cAPIProtoError, "Illegal return type '%s'",
357
+ RSTRING(v_return)->ptr
358
+ );
359
+ }
360
+ }
361
+
362
+ rb_iv_set(self, "@dll_name", v_dll);
363
+ rb_iv_set(self, "@function_name", v_proc);
364
+ rb_iv_set(self, "@prototype", v_proto);
365
+ rb_iv_set(self, "@return_type", v_return);
366
+
367
+ return self;
368
+ }
369
+
370
+ /*
371
+ * call-seq:
372
+ *
373
+ * API::Function.new(address, prototype = 'V', return_type = 'L')
374
+ *
375
+ * Creates and returns an API::Function object. This object is similar to an
376
+ * API object, except that instead of a character function name you pass a
377
+ * function pointer address as the first argument, and there's no associated
378
+ * DLL file.
379
+ *
380
+ * Once you have your API::Function object you can then call it the same way
381
+ * you would an API object.
382
+ *
383
+ * Example:
384
+ *
385
+ * require 'win32/api'
386
+ * include Win32
387
+ *
388
+ * LoadLibrary = API.new('LoadLibrary', 'P', 'L')
389
+ * GetProcAddress = API.new('GetProcAddress', 'LP', 'L')
390
+ *
391
+ * # Play a system beep
392
+ * hlib = LoadLibrary.call('user32')
393
+ * addr = GetProcAddress.call(hlib, 'MessageBeep')
394
+ * func = Win32::API::Function.new(addr, 'L', 'L')
395
+ * func.call(0)
396
+ */
397
+ static VALUE func_init(int argc, VALUE* argv, VALUE self){
398
+ Win32API* ptr;
399
+ int i;
400
+ VALUE v_address, v_proto, v_return;
401
+
402
+ rb_scan_args(argc, argv, "12", &v_address, &v_proto, &v_return);
403
+
404
+ Data_Get_Struct(self, Win32API, ptr);
405
+
406
+ // Convert a string prototype to an array of characters
407
+ if(rb_respond_to(v_proto, rb_intern("split")))
408
+ v_proto = rb_str_split(v_proto, "");
409
+
410
+ // Convert a nil or empty prototype to 'V' (void) automatically
411
+ if(NIL_P(v_proto) || RARRAY(v_proto)->len == 0){
412
+ v_proto = rb_ary_new();
413
+ rb_ary_push(v_proto, rb_str_new2("V"));
414
+ }
415
+
416
+ // Set an arbitrary limit of 20 parameters
417
+ if(20 < RARRAY(v_proto)->len)
418
+ rb_raise(rb_eArgError, "too many parameters: %d\n", RARRAY(v_proto)->len);
419
+
420
+ // Set the default return type to 'L' (DWORD)
421
+ if(NIL_P(v_return))
422
+ v_return = rb_str_new2("L");
423
+
424
+ ptr->function = (FARPROC)NUM2LONG(v_address);
425
+
426
+ // Push the numeric prototypes onto our int array for later use.
427
+
428
+ for(i = 0; i < RARRAY(v_proto)->len; i++){
429
+ SafeStringValue(RARRAY(v_proto)->ptr[i]);
430
+ switch(*(char*)StringValuePtr(RARRAY(v_proto)->ptr[i])){
431
+ case 'L':
432
+ ptr->prototype[i] = _T_LONG;
433
+ break;
434
+ case 'P':
435
+ ptr->prototype[i] = _T_POINTER;
436
+ break;
437
+ case 'I': case 'B':
438
+ ptr->prototype[i] = _T_INTEGER;
439
+ break;
440
+ case 'V':
441
+ ptr->prototype[i] = _T_VOID;
442
+ break;
443
+ case 'K':
444
+ ptr->prototype[i] = _T_CALLBACK;
445
+ break;
446
+ case 'S':
447
+ ptr->prototype[i] = _T_STRING;
448
+ break;
449
+ default:
450
+ rb_raise(cAPIProtoError, "Illegal prototype '%s'",
451
+ StringValuePtr(RARRAY(v_proto)->ptr[i])
452
+ );
453
+ }
454
+ }
455
+
456
+ // Store the return type for later use.
457
+
458
+ // Automatically convert empty strings or nil to type void.
459
+ if(NIL_P(v_return) || RSTRING(v_return)->len == 0){
460
+ v_return = rb_str_new2("V");
461
+ ptr->return_type = _T_VOID;
462
+ }
463
+ else{
464
+ SafeStringValue(v_return);
465
+ switch(*RSTRING(v_return)->ptr){
466
+ case 'L':
467
+ ptr->return_type = _T_LONG;
468
+ break;
469
+ case 'P':
470
+ ptr->return_type = _T_POINTER;
471
+ break;
472
+ case 'I': case 'B':
473
+ ptr->return_type = _T_INTEGER;
474
+ break;
475
+ case 'V':
476
+ ptr->return_type = _T_VOID;
477
+ break;
478
+ case 'S':
479
+ ptr->return_type = _T_STRING;
480
+ break;
481
+ default:
482
+ rb_raise(cAPIProtoError, "Illegal return type '%s'",
483
+ RSTRING(v_return)->ptr
484
+ );
485
+ }
486
+ }
487
+
488
+ rb_iv_set(self, "@address", v_address);
489
+ rb_iv_set(self, "@prototype", v_proto);
490
+ rb_iv_set(self, "@return_type", v_return);
491
+
492
+ return self;
493
+ }
494
+
495
+ typedef struct {
496
+ DWORD params[20];
497
+ } CALLPARAM;
498
+
499
+
500
+ DWORD CallbackFunction(CALLPARAM param)
501
+ {
502
+ VALUE v_proto, v_return, v_proc, v_retval;
503
+ VALUE argv[20];
504
+ int i, argc;
505
+ char *a_proto;
506
+ char *a_return;
507
+
508
+ if(!NIL_P(ActiveCallback)){
509
+ v_proto = rb_iv_get(ActiveCallback, "@prototype");
510
+ a_proto = RSTRING(v_proto)->ptr;
511
+
512
+ v_return = rb_iv_get(ActiveCallback, "@return_type");
513
+ a_return = RSTRING(v_return)->ptr;
514
+
515
+ v_proc = rb_iv_get(ActiveCallback, "@function");
516
+ argc = RSTRING(v_proto)->len;
517
+
518
+ for(i=0; i < RSTRING(v_proto)->len; i++){
519
+ argv[i] = Qnil;
520
+ switch(a_proto[i]){
521
+ case 'L':
522
+ argv[i] = ULONG2NUM(param.params[i]);
523
+ break;
524
+ case 'P':
525
+ if(param.params[i])
526
+ argv[i] = rb_str_new2((char *)param.params[i]);
527
+ break;
528
+ case 'I':
529
+ argv[i] = INT2NUM(param.params[i]);
530
+ break;
531
+ default:
532
+ rb_raise(cAPIProtoError, "Illegal prototype '%s'",
533
+ RSTRING(a_proto[i])->ptr
534
+ );
535
+ }
536
+ }
537
+
538
+ v_retval = rb_funcall2(v_proc, rb_intern("call"), argc, argv);
539
+
540
+ /* Handle true and false explicitly, as some CALLBACK functions
541
+ * require TRUE or FALSE to break out of loops, etc.
542
+ */
543
+ if(v_retval == Qtrue)
544
+ return TRUE;
545
+ else if(v_retval == Qfalse)
546
+ return FALSE;
547
+
548
+ switch (*a_return) {
549
+ case 'I':
550
+ return NUM2INT(v_retval);
551
+ break;
552
+ case 'L':
553
+ return NUM2ULONG(v_retval);
554
+ break;
555
+ case 'S':
556
+ return (unsigned long)RSTRING(v_retval)->ptr;
557
+ break;
558
+ case 'P':
559
+ if(NIL_P(v_retval)){
560
+ return 0;
561
+ }
562
+ else if(FIXNUM_P(v_retval)){
563
+ return NUM2ULONG(v_retval);
564
+ }
565
+ else{
566
+ StringValue(v_retval);
567
+ rb_str_modify(v_retval);
568
+ return (unsigned long)StringValuePtr(v_retval);
569
+ }
570
+ break;
571
+ }
572
+ }
573
+
574
+ return 0;
575
+ }
576
+
577
+ DWORD CALLBACK CallbackFunction0() {
578
+ CALLPARAM param = {0};
579
+ param.params[0] = 0;
580
+ return CallbackFunction(param);
581
+ }
582
+
583
+ DWORD CALLBACK CallbackFunction1(DWORD p1) {
584
+ CALLPARAM param = {p1};
585
+ return CallbackFunction(param);
586
+ }
587
+
588
+ DWORD CALLBACK CallbackFunction2(DWORD p1, DWORD p2){
589
+ CALLPARAM param = {p1,p2};
590
+ return CallbackFunction(param);
591
+ }
592
+
593
+ DWORD CALLBACK CallbackFunction3(DWORD p1, DWORD p2, DWORD p3){
594
+ CALLPARAM param = {p1,p2,p3};
595
+ return CallbackFunction(param);
596
+ }
597
+
598
+ DWORD CALLBACK CallbackFunction4(DWORD p1, DWORD p2, DWORD p3, DWORD p4){
599
+ CALLPARAM param = {p1,p2,p3,p4};
600
+ return CallbackFunction(param);
601
+ }
602
+
603
+ DWORD CALLBACK CallbackFunction5(
604
+ DWORD p1, DWORD p2, DWORD p3, DWORD p4, DWORD p5
605
+ )
606
+ {
607
+ CALLPARAM param = {p1,p2,p3,p4,p5};
608
+ return CallbackFunction(param);
609
+ }
610
+
611
+ DWORD CALLBACK CallbackFunction6(
612
+ DWORD p1, DWORD p2, DWORD p3, DWORD p4, DWORD p5, DWORD p6
613
+ )
614
+ {
615
+ CALLPARAM param = {p1,p2,p3,p4,p5,p6};
616
+ return CallbackFunction(param);
617
+ }
618
+
619
+ DWORD CALLBACK CallbackFunction7(
620
+ DWORD p1, DWORD p2, DWORD p3, DWORD p4, DWORD p5, DWORD p6, DWORD p7)
621
+ {
622
+ CALLPARAM param = {p1,p2,p3,p4,p5,p6,p7};
623
+ return CallbackFunction(param);
624
+ }
625
+
626
+ DWORD CALLBACK CallbackFunction8(
627
+ DWORD p1, DWORD p2, DWORD p3, DWORD p4,
628
+ DWORD p5, DWORD p6, DWORD p7, DWORD p8
629
+ )
630
+ {
631
+ CALLPARAM param = {p1,p2,p3,p4,p5,p6,p7,p8};
632
+ return CallbackFunction(param);
633
+ }
634
+
635
+ DWORD CALLBACK CallbackFunction9(
636
+ DWORD p1, DWORD p2, DWORD p3, DWORD p4, DWORD p5,
637
+ DWORD p6, DWORD p7, DWORD p8, DWORD p9
638
+ )
639
+ {
640
+ CALLPARAM param = {p1,p2,p3,p4,p5,p6,p7,p8,p9};
641
+ return CallbackFunction(param);
642
+ }
643
+
644
+ void *CallbackTable[] = {
645
+ CallbackFunction0,
646
+ CallbackFunction1,
647
+ CallbackFunction2,
648
+ CallbackFunction3,
649
+ CallbackFunction4,
650
+ CallbackFunction5,
651
+ CallbackFunction6,
652
+ CallbackFunction7,
653
+ CallbackFunction8,
654
+ CallbackFunction9
655
+ };
656
+
657
+ /*
658
+ * call-seq:
659
+ * Win32::API#call(arg1, arg2, ...)
660
+ *
661
+ * Calls the function pointer with the given arguments (if any). Note that,
662
+ * while this method will catch some prototype mismatches (raising a TypeError
663
+ * in the process), it is not fulproof. It is ultimately your job to make
664
+ * sure the arguments match the +prototype+ specified in the constructor.
665
+ *
666
+ * For convenience, nil is converted to NULL, true is converted to TRUE (1)
667
+ * and false is converted to FALSE (0).
668
+ */
669
+ static VALUE api_call(int argc, VALUE* argv, VALUE self){
670
+ VALUE v_proto, v_args, v_arg, v_return;
671
+ Win32API* ptr;
672
+ unsigned long return_value;
673
+ int i = 0;
674
+ int len;
675
+
676
+ struct{
677
+ unsigned long params[20];
678
+ } param;
679
+
680
+ Data_Get_Struct(self, Win32API, ptr);
681
+
682
+ rb_scan_args(argc, argv, "0*", &v_args);
683
+
684
+ v_proto = rb_iv_get(self, "@prototype");
685
+
686
+ // For void prototypes, allow either no args or an explicit nil
687
+ if(RARRAY(v_proto)->len != RARRAY(v_args)->len){
688
+ char* c = StringValuePtr(RARRAY(v_proto)->ptr[0]);
689
+ if(!strcmp(c, "V")){
690
+ rb_ary_push(v_args, Qnil);
691
+ }
692
+ else{
693
+ rb_raise(rb_eArgError,
694
+ "wrong number of parameters: expected %d, got %d",
695
+ RARRAY(v_proto)->len, RARRAY(v_args)->len
696
+ );
697
+ }
698
+ }
699
+
700
+ len = RARRAY(v_proto)->len;
701
+
702
+ for(i = 0; i < len; i++){
703
+ v_arg = RARRAY(v_args)->ptr[i];
704
+
705
+ // Convert nil to NULL. Otherwise convert as appropriate.
706
+ if(NIL_P(v_arg))
707
+ param.params[i] = (unsigned long)NULL;
708
+ else if(v_arg == Qtrue)
709
+ param.params[i] = TRUE;
710
+ else if(v_arg == Qfalse)
711
+ param.params[i] = FALSE;
712
+ else
713
+ switch(ptr->prototype[i]){
714
+ case _T_LONG:
715
+ param.params[i] = NUM2ULONG(v_arg);
716
+ break;
717
+ case _T_INTEGER:
718
+ param.params[i] = NUM2INT(v_arg);
719
+ break;
720
+ case _T_POINTER:
721
+ if(FIXNUM_P(v_arg)){
722
+ param.params[i] = NUM2ULONG(v_arg);
723
+ }
724
+ else{
725
+ StringValue(v_arg);
726
+ rb_str_modify(v_arg);
727
+ param.params[i] = (unsigned long)StringValuePtr(v_arg);
728
+ }
729
+ break;
730
+ case _T_CALLBACK:
731
+ ActiveCallback = v_arg;
732
+ v_proto = rb_iv_get(ActiveCallback, "@prototype");
733
+ param.params[i] = (LPARAM)CallbackTable[RSTRING(v_proto)->len];
734
+ break;
735
+ case _T_STRING:
736
+ param.params[i] = (unsigned long)RSTRING(v_arg)->ptr;
737
+ break;
738
+ default:
739
+ param.params[i] = NUM2ULONG(v_arg);
740
+ }
741
+ }
742
+
743
+ /* Call the function, get the return value */
744
+ return_value = ptr->function(param);
745
+
746
+
747
+ /* Return the appropriate type based on the return type specified
748
+ * in the constructor.
749
+ */
750
+ switch(ptr->return_type){
751
+ case _T_INTEGER:
752
+ v_return = INT2NUM(return_value);
753
+ break;
754
+ case _T_LONG:
755
+ v_return = LONG2NUM(return_value);
756
+ break;
757
+ case _T_VOID:
758
+ v_return = Qnil;
759
+ break;
760
+ case _T_POINTER:
761
+ if(!return_value){
762
+ v_return = Qnil;
763
+ }
764
+ else{
765
+ VALUE v_efunc = rb_iv_get(self, "@effective_function_name");
766
+ char* efunc = RSTRING(v_efunc)->ptr;
767
+ if(efunc[strlen(efunc)-1] == 'W'){
768
+ v_return = rb_str_new(
769
+ (TCHAR*)return_value,
770
+ wcslen((wchar_t*)return_value)*2
771
+ );
772
+ }
773
+ else{
774
+ v_return = rb_str_new2((TCHAR*)return_value);
775
+ }
776
+ }
777
+ break;
778
+ case _T_STRING:
779
+ {
780
+ VALUE v_efunc = rb_iv_get(self, "@effective_function_name");
781
+ char* efunc = RSTRING(v_efunc)->ptr;
782
+
783
+ if(efunc[strlen(efunc)-1] == 'W'){
784
+ v_return = rb_str_new(
785
+ (TCHAR*)return_value,
786
+ wcslen((wchar_t*)return_value)*2
787
+ );
788
+ }
789
+ else{
790
+ v_return = rb_str_new2((TCHAR*)return_value);
791
+ }
792
+ }
793
+ break;
794
+ default:
795
+ v_return = INT2NUM(0);
796
+ }
797
+
798
+ return v_return;
799
+ }
800
+
801
+ /*
802
+ * Wraps the Windows API functions in a Ruby interface.
803
+ */
804
+ void Init_api(){
805
+ VALUE mWin32, cAPI, cCallback, cFunction;
806
+
807
+ /* Modules and Classes */
808
+
809
+ /* The Win32 module serves as a namespace only */
810
+ mWin32 = rb_define_module("Win32");
811
+
812
+ /* The API class encapsulates a function pointer to Windows API function */
813
+ cAPI = rb_define_class_under(mWin32, "API", rb_cObject);
814
+
815
+ /* The API::Callback class encapsulates a Windows CALLBACK function */
816
+ cCallback = rb_define_class_under(cAPI, "Callback", rb_cObject);
817
+
818
+ /* The API::Function class encapsulates a raw function pointer */
819
+ cFunction = rb_define_class_under(cAPI, "Function", cAPI);
820
+
821
+ /* The API::Error class serves as a base class for other errors */
822
+ cAPIError = rb_define_class_under(cAPI, "Error", rb_eRuntimeError);
823
+
824
+ /* The LoadError class is raised if a function cannot be found or loaded */
825
+ cAPILoadError = rb_define_class_under(cAPI, "LoadLibraryError", cAPIError);
826
+
827
+ /* The PrototypeError class is raised if an invalid prototype is passed */
828
+ cAPIProtoError = rb_define_class_under(cAPI, "PrototypeError", cAPIError);
829
+
830
+ /* Miscellaneous */
831
+ rb_define_alloc_func(cAPI, api_allocate);
832
+
833
+ /* Win32::API Instance Methods */
834
+ rb_define_method(cAPI, "initialize", api_init, -1);
835
+ rb_define_method(cAPI, "call", api_call, -1);
836
+
837
+ /* Win32::API::Callback Instance Methods */
838
+ rb_define_method(cCallback, "initialize", callback_init, -1);
839
+
840
+ /* Win32::API::Function Instance Methods */
841
+ rb_define_method(cFunction, "initialize", func_init, -1);
842
+
843
+ /* The name of the DLL that exports the API function */
844
+ rb_define_attr(cAPI, "dll_name", 1, 0);
845
+
846
+ /* The name of the function passed to the constructor */
847
+ rb_define_attr(cAPI, "function_name", 1, 0);
848
+
849
+ /* The name of the actual function that is returned by the constructor.
850
+ * For example, if you passed 'GetUserName' to the constructor, then the
851
+ * effective function name would be either 'GetUserNameA' or 'GetUserNameW'.
852
+ */
853
+ rb_define_attr(cAPI, "effective_function_name", 1, 0);
854
+
855
+ /* The prototype, returned as an array of characters */
856
+ rb_define_attr(cAPI, "prototype", 1, 0);
857
+
858
+ /* The return type, returned as a single character, S, P, L, I, V or B */
859
+ rb_define_attr(cAPI, "return_type", 1, 0);
860
+
861
+ /* Win32::API::Callback Instance Methods */
862
+
863
+ /* The prototype, returned as an array of characters */
864
+ rb_define_attr(cCallback, "prototype", 1, 0);
865
+
866
+ /* The return type, returned as a single character, S, P, L, I, V or B */
867
+ rb_define_attr(cCallback, "return_type", 1, 0);
868
+
869
+ /* The numeric address of the function pointer */
870
+ rb_define_attr(cCallback, "address", 1, 0);
871
+ rb_define_attr(cFunction, "address", 1, 0);
872
+
873
+ /* Constants */
874
+
875
+ /* 1.2.2: The version of this library, returned as a String */
876
+ rb_define_const(cAPI, "VERSION", rb_str_new2(WINDOWS_API_VERSION));
877
+ }