win32-api 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,38 @@
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
+
1
36
  = 1.2.0 - 22-Jul-2008
2
37
  * Added support for the 'S' (string) prototype and return type. It can be
3
38
  used instead of 'P' (pointer) for const char*.
data/MANIFEST CHANGED
@@ -1,10 +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
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
10
  * test/test_win32_api_function.rb
data/README CHANGED
@@ -70,7 +70,7 @@
70
70
  instance variable names with proper accessors, removing support for lower
71
71
  case prototype and return value characters that no one used in practice,
72
72
  better naming conventions, the addition of RDoc ready comments and,
73
- especially, callback support.
73
+ especially, callback and raw function pointer support.
74
74
 
75
75
  Most importantly, we can now add, modify and fix any features that we feel
76
76
  best benefit our end users.
@@ -2,7 +2,7 @@
2
2
  #include <windows.h>
3
3
 
4
4
  #define MAX_BUF 1024
5
- #define WINDOWS_API_VERSION "1.2.0"
5
+ #define WINDOWS_API_VERSION "1.3.0"
6
6
 
7
7
  #define _T_VOID 0
8
8
  #define _T_LONG 1
@@ -11,13 +11,14 @@
11
11
  #define _T_CALLBACK 4
12
12
  #define _T_STRING 5
13
13
 
14
- VALUE cAPIError, cCallbackError;
14
+ VALUE cAPIError, cAPIProtoError, cAPILoadError;
15
+ static VALUE ActiveCallback = Qnil;
15
16
 
16
17
  typedef struct {
17
18
  HANDLE library;
18
19
  FARPROC function;
19
20
  int return_type;
20
- int prototype[16];
21
+ int prototype[20];
21
22
  } Win32API;
22
23
 
23
24
  static void api_free(Win32API* ptr){
@@ -34,7 +35,10 @@ static VALUE api_allocate(VALUE klass){
34
35
  }
35
36
 
36
37
  /* Helper function that converts the error number returned by GetLastError()
37
- * into a human readable string. Internal use only.
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.
38
42
  */
39
43
  char* StringError(DWORD dwError){
40
44
  LPVOID lpMsgBuf;
@@ -48,7 +52,7 @@ char* StringError(DWORD dwError){
48
52
  FORMAT_MESSAGE_IGNORE_INSERTS,
49
53
  NULL,
50
54
  dwError,
51
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
55
+ MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
52
56
  (LPSTR)&lpMsgBuf,
53
57
  0,
54
58
  NULL
@@ -74,71 +78,86 @@ char* StringError(DWORD dwError){
74
78
  /*
75
79
  * call-seq:
76
80
  * Win32::API::Callback.new(prototype, return='L'){ |proto| ... }
77
- *
81
+ *
78
82
  * Creates and returns a new Win32::API::Callback object. The prototype
79
83
  * arguments are yielded back to the block in the same order they were
80
84
  * declared.
81
- *
85
+ *
82
86
  * The +prototype+ is the function prototype for the callback function. This
83
87
  * is a string. The possible valid characters are 'I' (integer), 'L' (long),
84
88
  * 'V' (void), 'P' (pointer) or 'S' (string). Unlike API objects, API::Callback
85
89
  * objects do not have a default prototype.
86
- *
90
+ *
87
91
  * The +return+ argument is the return type for the callback function. The
88
92
  * valid characters are the same as for the +prototype+. The default is
89
93
  * 'L' (long).
90
- *
94
+ *
91
95
  * Example:
92
96
  * require 'win32/api'
93
97
  * include Win32
94
- *
98
+ *
95
99
  * EnumWindows = API.new('EnumWindows', 'KP', 'L', 'user32')
96
100
  * GetWindowText = API.new('GetWindowText', 'LPI', 'I', 'user32')
97
- *
101
+ *
98
102
  * EnumWindowsProc = API::Callback.new('LP', 'I'){ |handle, param|
99
103
  * buf = "\0" * 200
100
104
  * GetWindowText.call(handle, buf, 200);
101
105
  * puts buf.strip unless buf.strip.empty?
102
106
  * buf.index(param).nil? ? true : false
103
107
  * }
104
- *
108
+ *
105
109
  * EnumWindows.call(EnumWindowsProc, 'UEDIT32')
106
110
  */
107
111
  static VALUE callback_init(int argc, VALUE* argv, VALUE self)
108
112
  {
113
+ extern void *CallbackTable[];
109
114
  VALUE v_proto, v_return, v_proc;
110
115
  int i;
111
-
116
+
112
117
  rb_scan_args(argc, argv, "11&", &v_proto, &v_return, &v_proc);
113
-
118
+
114
119
  /* Validate prototype characters */
115
120
  for(i = 0; i < RSTRING(v_proto)->len; i++){
116
121
  switch(RSTRING(v_proto)->ptr[i]){
117
122
  case 'I': case 'L': case 'P': case 'V': case 'S':
118
123
  break;
119
124
  default:
120
- rb_raise(cCallbackError, "Illegal prototype '%c'",
121
- RSTRING(v_proto)->ptr[i]);
125
+ rb_raise(cAPIProtoError, "Illegal prototype '%c'",
126
+ RSTRING(v_proto)->ptr[i]
127
+ );
122
128
  }
123
129
  }
124
130
 
125
- if(NIL_P(v_return) || RARRAY(v_return)->len == 0)
131
+ if(NIL_P(v_return) || RARRAY(v_return)->len == 0){
126
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
+ }
127
144
 
128
145
  rb_iv_set(self, "@function", v_proc);
129
146
  rb_iv_set(self, "@prototype", v_proto);
130
147
  rb_iv_set(self, "@return_type", v_return);
131
-
148
+ rb_iv_set(self, "@address", ULONG2NUM((LPARAM)CallbackTable[RSTRING(v_proto)->len]));
149
+ ActiveCallback = self;
150
+
132
151
  return self;
133
152
  }
134
153
 
135
154
  /*
136
155
  * call-seq:
137
156
  * Win32::API.new(function, prototype='V', return='L', dll='kernel32')
138
- *
157
+ *
139
158
  * Creates and returns a new Win32::API object. The +function+ is the name
140
159
  * of the Windows function.
141
- *
160
+ *
142
161
  * The +prototype+ is the function prototype for +function+. This can be a
143
162
  * string or an array of characters. The possible valid characters are 'I'
144
163
  * (integer), 'L' (long), 'V' (void), 'P' (pointer), 'K' (callback) or 'S'
@@ -148,27 +167,27 @@ static VALUE callback_init(int argc, VALUE* argv, VALUE self)
148
167
  *
149
168
  * Constant (const char*) strings should use 'S'. Pass by reference string
150
169
  * buffers should use 'P'. The former is faster, but cannot be modified.
151
- *
170
+ *
152
171
  * The +return+ argument is the return type for the function. The valid
153
172
  * characters are the same as for the +prototype+. The default is 'L' (long).
154
- *
173
+ *
155
174
  * The +dll+ is the name of the DLL file that the function is exported from.
156
175
  * The default is 'kernel32'.
157
- *
176
+ *
158
177
  * If the function cannot be found then an API::Error is raised (a subclass
159
178
  * of RuntimeError).
160
- *
179
+ *
161
180
  * Example:
162
- *
181
+ *
163
182
  * require 'win32/api'
164
183
  * include Win32
165
- *
184
+ *
166
185
  * buf = 0.chr * 260
167
186
  * len = [buf.length].pack('L')
168
- *
187
+ *
169
188
  * GetUserName = API.new('GetUserName', 'PP', 'I', 'advapi32')
170
189
  * GetUserName.call(buf, len)
171
- *
190
+ *
172
191
  * puts buf.strip
173
192
  */
174
193
  static VALUE api_init(int argc, VALUE* argv, VALUE self)
@@ -180,9 +199,9 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
180
199
  char* first = "A";
181
200
  char* second = "W";
182
201
  VALUE v_proc, v_proto, v_return, v_dll;
183
-
202
+
184
203
  rb_scan_args(argc, argv, "13", &v_proc, &v_proto, &v_return, &v_dll);
185
-
204
+
186
205
  Data_Get_Struct(self, Win32API, ptr);
187
206
 
188
207
  // Convert a string prototype to an array of characters
@@ -195,17 +214,17 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
195
214
  rb_ary_push(v_proto, rb_str_new2("V"));
196
215
  }
197
216
 
198
- // Set an arbitrary limit of 16 parameters
199
- if(16 < RARRAY(v_proto)->len)
217
+ // Set an arbitrary limit of 20 parameters
218
+ if(20 < RARRAY(v_proto)->len)
200
219
  rb_raise(rb_eArgError, "too many parameters: %d\n", RARRAY(v_proto)->len);
201
-
220
+
202
221
  // Set the default dll to 'kernel32'
203
222
  if(NIL_P(v_dll))
204
- v_dll = rb_str_new2("kernel32");
205
-
223
+ v_dll = rb_str_new2("kernel32");
224
+
206
225
  // Set the default return type to 'L' (DWORD)
207
226
  if(NIL_P(v_return))
208
- v_return = rb_str_new2("L");
227
+ v_return = rb_str_new2("L");
209
228
 
210
229
  SafeStringValue(v_dll);
211
230
  SafeStringValue(v_proc);
@@ -214,7 +233,7 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
214
233
 
215
234
  // The most likely cause of failure is a bad DLL load path
216
235
  if(!hLibrary){
217
- rb_raise(cAPIError, "LoadLibrary() function failed for '%s': %s",
236
+ rb_raise(cAPILoadError, "LoadLibrary() function failed for '%s': %s",
218
237
  RSTRING(v_dll)->ptr,
219
238
  StringError(GetLastError())
220
239
  );
@@ -236,10 +255,10 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
236
255
 
237
256
  // Skip the ANSI and Wide function checks for MSVCRT functions.
238
257
  if(!fProc){
239
- if(!strcmp(RSTRING(v_dll)->ptr, "msvcrt")){
258
+ if(strstr(RSTRING(v_dll)->ptr, "msvcr")){
240
259
  rb_raise(
241
- cAPIError,
242
- "GetProcAddress() failed for '%s': %s",
260
+ cAPILoadError,
261
+ "Unable to load function '%s'",
243
262
  RSTRING(v_proc)->ptr,
244
263
  StringError(GetLastError())
245
264
  );
@@ -256,12 +275,11 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
256
275
 
257
276
  if(!fProc){
258
277
  rb_raise(
259
- cAPIError,
260
- "GetProcAddress() failed for '%s', '%s' and '%s': %s",
278
+ cAPILoadError,
279
+ "Unable to load function '%s', '%s', or '%s'",
261
280
  RSTRING(v_proc)->ptr,
262
281
  RSTRING(v_ascii)->ptr,
263
- RSTRING(v_unicode)->ptr,
264
- StringError(GetLastError())
282
+ RSTRING(v_unicode)->ptr
265
283
  );
266
284
  }
267
285
  else{
@@ -303,7 +321,9 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
303
321
  ptr->prototype[i] = _T_STRING;
304
322
  break;
305
323
  default:
306
- rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
324
+ rb_raise(cAPIProtoError, "Illegal prototype '%s'",
325
+ StringValuePtr(RARRAY(v_proto)->ptr[i])
326
+ );
307
327
  }
308
328
  }
309
329
 
@@ -333,7 +353,9 @@ static VALUE api_init(int argc, VALUE* argv, VALUE self)
333
353
  ptr->return_type = _T_STRING;
334
354
  break;
335
355
  default:
336
- rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
356
+ rb_raise(cAPIProtoError, "Illegal return type '%s'",
357
+ RSTRING(v_return)->ptr
358
+ );
337
359
  }
338
360
  }
339
361
 
@@ -391,13 +413,13 @@ static VALUE func_init(int argc, VALUE* argv, VALUE self){
391
413
  rb_ary_push(v_proto, rb_str_new2("V"));
392
414
  }
393
415
 
394
- // Set an arbitrary limit of 16 parameters
395
- if(16 < RARRAY(v_proto)->len)
416
+ // Set an arbitrary limit of 20 parameters
417
+ if(20 < RARRAY(v_proto)->len)
396
418
  rb_raise(rb_eArgError, "too many parameters: %d\n", RARRAY(v_proto)->len);
397
419
 
398
420
  // Set the default return type to 'L' (DWORD)
399
421
  if(NIL_P(v_return))
400
- v_return = rb_str_new2("L");
422
+ v_return = rb_str_new2("L");
401
423
 
402
424
  ptr->function = (FARPROC)NUM2LONG(v_address);
403
425
 
@@ -425,7 +447,9 @@ static VALUE func_init(int argc, VALUE* argv, VALUE self){
425
447
  ptr->prototype[i] = _T_STRING;
426
448
  break;
427
449
  default:
428
- rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
450
+ rb_raise(cAPIProtoError, "Illegal prototype '%s'",
451
+ StringValuePtr(RARRAY(v_proto)->ptr[i])
452
+ );
429
453
  }
430
454
  }
431
455
 
@@ -455,7 +479,9 @@ static VALUE func_init(int argc, VALUE* argv, VALUE self){
455
479
  ptr->return_type = _T_STRING;
456
480
  break;
457
481
  default:
458
- rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
482
+ rb_raise(cAPIProtoError, "Illegal return type '%s'",
483
+ RSTRING(v_return)->ptr
484
+ );
459
485
  }
460
486
  }
461
487
 
@@ -467,95 +493,176 @@ static VALUE func_init(int argc, VALUE* argv, VALUE self){
467
493
  }
468
494
 
469
495
  typedef struct {
470
- DWORD params[1];
471
- } PARAM;
496
+ DWORD params[20];
497
+ } CALLPARAM;
472
498
 
473
- static VALUE ActiveCallback;
474
499
 
475
- DWORD CallbackFunction(PARAM param)
500
+ DWORD CallbackFunction(CALLPARAM param)
476
501
  {
477
502
  VALUE v_proto, v_return, v_proc, v_retval;
478
- VALUE argv[16];
503
+ VALUE argv[20];
479
504
  int i, argc;
480
505
  char *a_proto;
481
506
  char *a_return;
482
-
507
+
483
508
  if(!NIL_P(ActiveCallback)){
484
509
  v_proto = rb_iv_get(ActiveCallback, "@prototype");
485
510
  a_proto = RSTRING(v_proto)->ptr;
486
-
511
+
487
512
  v_return = rb_iv_get(ActiveCallback, "@return_type");
488
513
  a_return = RSTRING(v_return)->ptr;
489
-
514
+
490
515
  v_proc = rb_iv_get(ActiveCallback, "@function");
491
516
  argc = RSTRING(v_proto)->len;
492
-
517
+
493
518
  for(i=0; i < RSTRING(v_proto)->len; i++){
494
519
  argv[i] = Qnil;
495
520
  switch(a_proto[i]){
496
- case 'L':
497
- argv[i] = ULONG2NUM(param.params[i]);
498
- break;
499
- case 'P':
500
- if(param.params[i])
501
- argv[i] = rb_str_new2((char *)param.params[i]);
502
- break;
503
- case 'I':
504
- argv[i] = INT2NUM(param.params[i]);
505
- break;
506
- default:
507
- rb_raise(cCallbackError, "Illegal prototype '%c'", 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
+ );
508
535
  }
509
536
  }
510
-
537
+
511
538
  v_retval = rb_funcall2(v_proc, rb_intern("call"), argc, argv);
512
-
539
+
513
540
  /* Handle true and false explicitly, as some CALLBACK functions
514
541
  * require TRUE or FALSE to break out of loops, etc.
515
542
  */
516
- if(v_retval == Qtrue)
543
+ if(v_retval == Qtrue)
517
544
  return TRUE;
518
- else if(v_retval == Qfalse)
545
+ else if(v_retval == Qfalse)
519
546
  return FALSE;
520
-
547
+
521
548
  switch (*a_return) {
522
549
  case 'I':
523
550
  return NUM2INT(v_retval);
524
551
  break;
525
552
  case 'L':
526
- return NUM2ULONG(v_retval);
553
+ return NUM2ULONG(v_retval);
527
554
  break;
528
555
  case 'S':
529
556
  return (unsigned long)RSTRING(v_retval)->ptr;
530
557
  break;
531
558
  case 'P':
532
- if(NIL_P(v_retval)){
533
- return 0;
534
- }
535
- else if(FIXNUM_P(v_retval)){
536
- return NUM2ULONG(v_retval);
537
- }
538
- else{
539
- StringValue(v_retval);
540
- rb_str_modify(v_retval);
541
- return (unsigned long)StringValuePtr(v_retval);
542
- }
543
- break;
544
- }
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
+ }
545
572
  }
546
-
573
+
547
574
  return 0;
548
575
  }
549
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
+
550
657
  /*
551
658
  * call-seq:
552
659
  * Win32::API#call(arg1, arg2, ...)
553
- *
660
+ *
554
661
  * Calls the function pointer with the given arguments (if any). Note that,
555
662
  * while this method will catch some prototype mismatches (raising a TypeError
556
663
  * in the process), it is not fulproof. It is ultimately your job to make
557
664
  * sure the arguments match the +prototype+ specified in the constructor.
558
- *
665
+ *
559
666
  * For convenience, nil is converted to NULL, true is converted to TRUE (1)
560
667
  * and false is converted to FALSE (0).
561
668
  */
@@ -564,9 +671,10 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
564
671
  Win32API* ptr;
565
672
  unsigned long return_value;
566
673
  int i = 0;
674
+ int len;
567
675
 
568
676
  struct{
569
- unsigned long params[16];
677
+ unsigned long params[20];
570
678
  } param;
571
679
 
572
680
  Data_Get_Struct(self, Win32API, ptr);
@@ -575,7 +683,7 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
575
683
 
576
684
  v_proto = rb_iv_get(self, "@prototype");
577
685
 
578
- /* For void prototypes, allow either no args or an explicit nil */
686
+ // For void prototypes, allow either no args or an explicit nil
579
687
  if(RARRAY(v_proto)->len != RARRAY(v_args)->len){
580
688
  char* c = StringValuePtr(RARRAY(v_proto)->ptr[0]);
581
689
  if(!strcmp(c, "V")){
@@ -589,10 +697,12 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
589
697
  }
590
698
  }
591
699
 
592
- for(i = 0; i < RARRAY(v_proto)->len; i++){
700
+ len = RARRAY(v_proto)->len;
701
+
702
+ for(i = 0; i < len; i++){
593
703
  v_arg = RARRAY(v_args)->ptr[i];
594
704
 
595
- /* Convert nil to NULL. Otherwise convert as appropriate. */
705
+ // Convert nil to NULL. Otherwise convert as appropriate.
596
706
  if(NIL_P(v_arg))
597
707
  param.params[i] = (unsigned long)NULL;
598
708
  else if(v_arg == Qtrue)
@@ -619,7 +729,8 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
619
729
  break;
620
730
  case _T_CALLBACK:
621
731
  ActiveCallback = v_arg;
622
- param.params[i] = (LPARAM)CallbackFunction;
732
+ v_proto = rb_iv_get(ActiveCallback, "@prototype");
733
+ param.params[i] = (LPARAM)CallbackTable[RSTRING(v_proto)->len];
623
734
  break;
624
735
  case _T_STRING:
625
736
  param.params[i] = (unsigned long)RSTRING(v_arg)->ptr;
@@ -632,6 +743,7 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
632
743
  /* Call the function, get the return value */
633
744
  return_value = ptr->function(param);
634
745
 
746
+
635
747
  /* Return the appropriate type based on the return type specified
636
748
  * in the constructor.
637
749
  */
@@ -646,13 +758,38 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
646
758
  v_return = Qnil;
647
759
  break;
648
760
  case _T_POINTER:
649
- if(!return_value)
761
+ if(!return_value){
650
762
  v_return = Qnil;
651
- else
652
- v_return = rb_str_new2((TCHAR*)return_value);
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
+ }
653
777
  break;
654
778
  case _T_STRING:
655
- v_return = rb_str_new2((TCHAR*)return_value);
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
+ }
656
793
  break;
657
794
  default:
658
795
  v_return = INT2NUM(0);
@@ -661,35 +798,38 @@ static VALUE api_call(int argc, VALUE* argv, VALUE self){
661
798
  return v_return;
662
799
  }
663
800
 
664
- /*
801
+ /*
665
802
  * Wraps the Windows API functions in a Ruby interface.
666
803
  */
667
804
  void Init_api(){
668
805
  VALUE mWin32, cAPI, cCallback, cFunction;
669
-
806
+
670
807
  /* Modules and Classes */
671
-
808
+
672
809
  /* The Win32 module serves as a namespace only */
673
810
  mWin32 = rb_define_module("Win32");
674
-
811
+
675
812
  /* The API class encapsulates a function pointer to Windows API function */
676
813
  cAPI = rb_define_class_under(mWin32, "API", rb_cObject);
677
-
678
- /* The API::Callback class encapsulates a Windows CALLBACK function */
814
+
815
+ /* The API::Callback class encapsulates a Windows CALLBACK function */
679
816
  cCallback = rb_define_class_under(cAPI, "Callback", rb_cObject);
680
817
 
681
818
  /* The API::Function class encapsulates a raw function pointer */
682
819
  cFunction = rb_define_class_under(cAPI, "Function", cAPI);
683
-
684
- /* The API::Error class is raised if the constructor fails */
820
+
821
+ /* The API::Error class serves as a base class for other errors */
685
822
  cAPIError = rb_define_class_under(cAPI, "Error", rb_eRuntimeError);
686
823
 
687
- /* The API::Callback::Error class is raised if the constructor fails */
688
- cCallbackError = rb_define_class_under(cCallback, "Error", rb_eRuntimeError);
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);
689
829
 
690
830
  /* Miscellaneous */
691
831
  rb_define_alloc_func(cAPI, api_allocate);
692
-
832
+
693
833
  /* Win32::API Instance Methods */
694
834
  rb_define_method(cAPI, "initialize", api_init, -1);
695
835
  rb_define_method(cAPI, "call", api_call, -1);
@@ -699,38 +839,39 @@ void Init_api(){
699
839
 
700
840
  /* Win32::API::Function Instance Methods */
701
841
  rb_define_method(cFunction, "initialize", func_init, -1);
702
-
842
+
703
843
  /* The name of the DLL that exports the API function */
704
844
  rb_define_attr(cAPI, "dll_name", 1, 0);
705
-
845
+
706
846
  /* The name of the function passed to the constructor */
707
847
  rb_define_attr(cAPI, "function_name", 1, 0);
708
-
848
+
709
849
  /* The name of the actual function that is returned by the constructor.
710
850
  * For example, if you passed 'GetUserName' to the constructor, then the
711
851
  * effective function name would be either 'GetUserNameA' or 'GetUserNameW'.
712
852
  */
713
853
  rb_define_attr(cAPI, "effective_function_name", 1, 0);
714
-
854
+
715
855
  /* The prototype, returned as an array of characters */
716
856
  rb_define_attr(cAPI, "prototype", 1, 0);
717
-
857
+
718
858
  /* The return type, returned as a single character, S, P, L, I, V or B */
719
859
  rb_define_attr(cAPI, "return_type", 1, 0);
720
-
860
+
721
861
  /* Win32::API::Callback Instance Methods */
722
-
862
+
723
863
  /* The prototype, returned as an array of characters */
724
864
  rb_define_attr(cCallback, "prototype", 1, 0);
725
-
865
+
726
866
  /* The return type, returned as a single character, S, P, L, I, V or B */
727
867
  rb_define_attr(cCallback, "return_type", 1, 0);
728
868
 
729
869
  /* The numeric address of the function pointer */
870
+ rb_define_attr(cCallback, "address", 1, 0);
730
871
  rb_define_attr(cFunction, "address", 1, 0);
731
-
872
+
732
873
  /* Constants */
733
-
734
- /* 1.2.0: The version of this library, returned as a String */
874
+
875
+ /* 1.2.2: The version of this library, returned as a String */
735
876
  rb_define_const(cAPI, "VERSION", rb_str_new2(WINDOWS_API_VERSION));
736
877
  }
@@ -4,6 +4,9 @@
4
4
  # Test case for the Win32::API class. You should run this as Rake task,
5
5
  # i.e. 'rake test', instead of running it directly.
6
6
  ############################################################################
7
+ require 'rubygems'
8
+ gem 'test-unit'
9
+
7
10
  require 'win32/api'
8
11
  require 'test/unit'
9
12
  include Win32
@@ -11,13 +14,13 @@ include Win32
11
14
  class TC_Win32_API < Test::Unit::TestCase
12
15
  def setup
13
16
  @buf = 0.chr * 260
14
- @api = API.new('GetCurrentDirectory', 'LP')
17
+ @gcd = API.new('GetCurrentDirectory', 'LP')
15
18
  @gle = API.new('GetLastError', 'V', 'L')
16
19
  @str = API.new('strstr', 'PP', 'P', 'msvcrt')
17
20
  end
18
21
 
19
22
  def test_version
20
- assert_equal('1.2.0', API::VERSION)
23
+ assert_equal('1.3.0', API::VERSION)
21
24
  end
22
25
 
23
26
  def test_constructor_basic
@@ -28,8 +31,8 @@ class TC_Win32_API < Test::Unit::TestCase
28
31
  end
29
32
 
30
33
  def test_call
31
- assert_respond_to(@api, :call)
32
- assert_nothing_raised{ @api.call(@buf.length, @buf) }
34
+ assert_respond_to(@gcd, :call)
35
+ assert_nothing_raised{ @gcd.call(@buf.length, @buf) }
33
36
  assert_equal(Dir.pwd.tr('/', "\\"), @buf.strip)
34
37
  end
35
38
 
@@ -39,78 +42,93 @@ class TC_Win32_API < Test::Unit::TestCase
39
42
  end
40
43
 
41
44
  def test_dll_name
42
- assert_respond_to(@api, :dll_name)
43
- assert_equal('kernel32', @api.dll_name)
45
+ assert_respond_to(@gcd, :dll_name)
46
+ assert_equal('kernel32', @gcd.dll_name)
44
47
  end
45
48
 
46
49
  def test_function_name
47
- assert_respond_to(@api, :function_name)
48
- assert_equal('GetCurrentDirectory', @api.function_name)
50
+ assert_respond_to(@gcd, :function_name)
51
+ assert_equal('GetCurrentDirectory', @gcd.function_name)
49
52
  assert_equal('strstr', @str.function_name)
50
53
  end
51
54
 
52
- def test_effective_function_name
53
- assert_respond_to(@api, :effective_function_name)
54
- assert_equal('GetCurrentDirectoryA', @api.effective_function_name)
55
+ def test_effective_function_name_default
56
+ assert_respond_to(@gcd, :effective_function_name)
57
+ assert_equal('GetCurrentDirectoryA', @gcd.effective_function_name)
55
58
  assert_equal('strstr', @str.effective_function_name)
59
+ end
56
60
 
57
- @api = API.new('GetCurrentDirectoryA', 'LP')
58
- assert_equal('GetCurrentDirectoryA', @api.effective_function_name)
61
+ def test_effective_function_name_default_explicit_ansi
62
+ @gcd = API.new('GetCurrentDirectoryA', 'LP')
63
+ assert_equal('GetCurrentDirectoryA', @gcd.effective_function_name)
64
+ end
59
65
 
60
- @api = API.new('GetCurrentDirectoryW', 'LP')
61
- assert_equal('GetCurrentDirectoryW', @api.effective_function_name)
66
+ def test_effective_function_name_default_explicit_wide
67
+ @gcd = API.new('GetCurrentDirectoryW', 'LP')
68
+ assert_equal('GetCurrentDirectoryW', @gcd.effective_function_name)
62
69
  end
63
70
 
64
71
  def test_prototype
65
- assert_respond_to(@api, :prototype)
66
- assert_equal(['L', 'P'], @api.prototype)
72
+ assert_respond_to(@gcd, :prototype)
73
+ assert_equal(['L', 'P'], @gcd.prototype)
67
74
  end
68
75
 
69
76
  def test_return_type
70
- assert_respond_to(@api, :return_type)
71
- assert_equal('L', @api.return_type)
77
+ assert_respond_to(@gcd, :return_type)
78
+ assert_equal('L', @gcd.return_type)
72
79
  end
73
80
 
74
81
  def test_constructor_high_iteration
75
- 1000.times{
76
- assert_nothing_raised{ API.new('GetUserName', 'P', 'P', 'advapi32') }
82
+ assert_nothing_raised{
83
+ 1000.times{ API.new('GetUserName', 'P', 'P', 'advapi32') }
77
84
  }
78
85
  end
79
86
 
80
87
  def test_constructor_expected_failures
81
88
  assert_raise(ArgumentError){ API.new }
82
- assert_raise(API::Error){ API.new('GetUserName', 'PL', 'I', 'foo') }
83
- assert_raise(API::Error){ API.new('GetUserName', 'X', 'I', 'kernel32') }
84
- assert_raise(API::Error){ API.new('GetUserName', 'PL', 'X', 'kernel32') }
89
+ assert_raise(ArgumentError){ API.new('GetUserName', ('L' * 21), 'X') }
90
+ assert_raise(API::LoadLibraryError){ API.new('GetUserName', 'PL', 'I', 'foo') }
91
+ assert_raise(API::PrototypeError){ API.new('GetUserName', 'X', 'I', 'advapi32') }
92
+ assert_raise(API::PrototypeError){ API.new('GetUserName', 'PL', 'X', 'advapi32') }
85
93
  end
86
94
 
87
- # Compare MSVCRT error messages vs regular error messages. This validates
88
- # that we're skipping the 'A' and 'W' lookups for MSVCRT functions.
89
95
  def test_constructor_expected_failure_messages
90
- begin
91
- API.new('GetBlah')
92
- rescue API::Error => err
93
- expected = "GetProcAddress() failed for 'GetBlah', 'GetBlahA' and "
94
- expected += "'GetBlahW': The specified procedure could not be found."
95
- assert_equal(expected, err.to_s)
96
- end
96
+ assert_raise_message("Unable to load function 'Zap', 'ZapA', or 'ZapW'"){
97
+ API.new('Zap')
98
+ }
97
99
 
98
- begin
99
- API.new('strxxx', 'P', 'P', 'msvcrt')
100
- rescue API::Error => err
101
- expected = "GetProcAddress() failed for 'strxxx': The specified "
102
- expected += "procedure could not be found."
103
- assert_equal(expected, err.to_s)
104
- end
100
+ assert_raise_message("Unable to load function 'strxxx'"){
101
+ API.new('strxxx', 'P', 'L', 'msvcrt')
102
+ }
103
+
104
+ assert_raise_message("Illegal prototype 'X'"){
105
+ API.new('GetUserName', 'X', 'I', 'advapi32')
106
+ }
107
+
108
+ assert_raise_message("Illegal return type 'Y'"){
109
+ API.new('GetUserName', 'PL', 'Y', 'advapi32')
110
+ }
105
111
  end
106
-
112
+
107
113
  def test_call_expected_failures
108
- assert_raise(TypeError){ @api.call('test', @buf) }
114
+ assert_raise(TypeError){ @gcd.call('test', @buf) }
109
115
  end
110
-
116
+
117
+ def test_error_classes
118
+ assert_not_nil(Win32::API::Error)
119
+ assert_not_nil(Win32::API::LoadLibraryError)
120
+ assert_not_nil(Win32::API::PrototypeError)
121
+ end
122
+
123
+ def test_error_class_relationships
124
+ assert_kind_of(RuntimeError, Win32::API::Error.new)
125
+ assert_kind_of(Win32::API::Error, Win32::API::LoadLibraryError.new)
126
+ assert_kind_of(Win32::API::Error, Win32::API::PrototypeError.new)
127
+ end
128
+
111
129
  def teardown
112
130
  @buf = nil
113
- @api = nil
131
+ @gcd = nil
114
132
  @gle = nil
115
133
  @str = nil
116
134
  end
@@ -4,6 +4,9 @@
4
4
  # Test case for the Win32::API::Callback class. You should run this as Rake
5
5
  # task, i.e. 'rake test', instead of running it directly.
6
6
  ############################################################################
7
+ require 'rubygems'
8
+ gem 'test-unit'
9
+
7
10
  require 'win32/api'
8
11
  require 'test/unit'
9
12
  include Win32
@@ -33,6 +36,13 @@ class TC_Win32_API_Callback < Test::Unit::TestCase
33
36
  assert_respond_to(@callback, :return_type)
34
37
  assert_equal('I', @callback.return_type)
35
38
  end
39
+
40
+ def test_address
41
+ assert_nothing_raised{ @callback = API::Callback.new('LP', 'I') }
42
+ assert_respond_to(@callback, :address)
43
+ assert_kind_of(Fixnum, @callback.address)
44
+ assert_equal(true, @callback.address > 0)
45
+ end
36
46
 
37
47
  def test_callback
38
48
  assert_nothing_raised{
@@ -46,7 +56,13 @@ class TC_Win32_API_Callback < Test::Unit::TestCase
46
56
  end
47
57
 
48
58
  def test_constructor_expected_errors
49
- assert_raise(API::Callback::Error){ API::Callback.new('X') }
59
+ assert_raise(API::PrototypeError){ API::Callback.new('X') }
60
+ assert_raise(API::PrototypeError){ API::Callback.new('L', 'Y') }
61
+ end
62
+
63
+ def test_constructor_expected_error_messages
64
+ assert_raise_message("Illegal prototype 'X'"){ API::Callback.new('X') }
65
+ assert_raise_message("Illegal return type 'Y'"){ API::Callback.new('L', 'Y') }
50
66
  end
51
67
 
52
68
  def teardown
@@ -4,8 +4,12 @@
4
4
  # Test case for the Win32::API::Function class. You should run these
5
5
  # tests via the 'rake test' task.
6
6
  ########################################################################
7
+ require 'rubygems'
8
+ gem 'test-unit'
9
+
7
10
  require 'test/unit'
8
11
  require 'win32/api'
12
+ include Win32
9
13
 
10
14
  class TC_Win32_API_Function < Test::Unit::TestCase
11
15
  def setup
@@ -38,8 +42,22 @@ class TC_Win32_API_Function < Test::Unit::TestCase
38
42
  assert_equal('L', @func.return_type)
39
43
  end
40
44
 
41
- def test_expected_errors
45
+ def test_expected_errors_from_arguments
42
46
  assert_raise(ArgumentError){ Win32::API::Function.new }
47
+ assert_raise(TypeError){ Win32::API::Function.new('L') }
48
+ assert_raise(Win32::API::PrototypeError){ Win32::API::Function.new(1, 'X') }
49
+ assert_raise(Win32::API::PrototypeError){ Win32::API::Function.new(1, 'X', 'Y') }
50
+ end
51
+
52
+ def test_expected_error_messages
53
+ assert_raise_message("Illegal prototype 'X'"){ API::Function.new(1, 'X') }
54
+ assert_raise_message("Illegal return type 'Y'"){ API::Function.new(1, 'L', 'Y') }
55
+ end
56
+
57
+ def test_expected_errors_from_call
58
+ assert_raise(ArgumentError){ @func.call }
59
+ assert_raise(ArgumentError){ @func.call(1) }
60
+ assert_raise(ArgumentError){ @func.call(1, 'a', 2) }
43
61
  end
44
62
 
45
63
  def teardown
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Berger
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2008-07-22 00:00:00 -06:00
13
+ date: 2009-01-04 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -55,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
55
  requirements: []
56
56
 
57
57
  rubyforge_project: win32utils
58
- rubygems_version: 1.2.0
58
+ rubygems_version: 1.3.1
59
59
  signing_key:
60
60
  specification_version: 2
61
61
  summary: A superior replacement for Win32API