wdm 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,73 +1,73 @@
1
- #include <Windows.h>
2
-
3
- #include "entry.h"
4
-
5
- #ifndef WDM_QUEUE_H
6
- #define WDM_QUEUE_H
7
-
8
- #ifdef __cplusplus
9
- extern "C" {
10
- #endif // __cplusplus
11
-
12
- // ---------------------------------------------------------
13
- // Types
14
- // ---------------------------------------------------------
15
-
16
- typedef enum {
17
- WDM_QUEUE_ITEM_TYPE_ERROR,
18
- WDM_QUEUE_ITEM_TYPE_DATA
19
- } WDM_QueueItemType;
20
-
21
- typedef struct {
22
- WDM_PEntryUserData user_data;
23
- BYTE buffer[WDM_BUFFER_SIZE];
24
- } WDM_QueueItemData, *WDM_PQueueItemData;
25
-
26
- typedef struct {
27
- VALUE exception_klass;
28
- LPSTR message;
29
- } WDM_QueueItemError, *WDM_PQueueItemError;
30
-
31
- typedef struct WDM_QueueItem {
32
- WDM_QueueItemType type;
33
- union {
34
- WDM_PQueueItemData data;
35
- WDM_PQueueItemError error;
36
- };
37
- struct WDM_QueueItem* next;
38
- } WDM_QueueItem, *WDM_PQueueItem;
39
-
40
- typedef struct {
41
- CRITICAL_SECTION lock;
42
- WDM_PQueueItem front;
43
- WDM_PQueueItem rear;
44
- } WDM_Queue, *WDM_PQueue;
45
-
46
- // ---------------------------------------------------------
47
- // Prototypes
48
- // ---------------------------------------------------------
49
-
50
- WDM_PQueueItemError wdm_queue_item_error_new(VALUE, LPCSTR, ...);
51
- void wdm_queue_item_error_free(WDM_PQueueItemError);
52
-
53
- WDM_PQueueItemData wdm_queue_item_data_new();
54
- void wdm_queue_item_data_free(WDM_PQueueItemData);
55
-
56
- WDM_PQueueItem wdm_queue_item_new(WDM_QueueItemType);
57
- void wdm_queue_item_free(WDM_PQueueItem);
58
-
59
- WDM_PQueue wdm_queue_new();
60
- void wdm_queue_free(WDM_PQueue);
61
-
62
- void wdm_queue_enqueue(WDM_PQueue, WDM_PQueueItem);
63
- WDM_PQueueItem wdm_queue_dequeue(WDM_PQueue);
64
- void wdm_queue_empty(WDM_PQueue);
65
- BOOL wdm_queue_is_empty(WDM_PQueue);
66
-
67
- // ---------------------------------------------------------
68
-
69
- #ifdef __cplusplus
70
- }
71
- #endif // __cplusplus
72
-
1
+ #include <Windows.h>
2
+
3
+ #include "entry.h"
4
+
5
+ #ifndef WDM_QUEUE_H
6
+ #define WDM_QUEUE_H
7
+
8
+ #ifdef __cplusplus
9
+ extern "C" {
10
+ #endif // __cplusplus
11
+
12
+ // ---------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------
15
+
16
+ typedef enum {
17
+ WDM_QUEUE_ITEM_TYPE_ERROR,
18
+ WDM_QUEUE_ITEM_TYPE_DATA
19
+ } WDM_QueueItemType;
20
+
21
+ typedef struct {
22
+ WDM_PEntryUserData user_data;
23
+ BYTE buffer[WDM_BUFFER_SIZE];
24
+ } WDM_QueueItemData, *WDM_PQueueItemData;
25
+
26
+ typedef struct {
27
+ VALUE exception_klass;
28
+ LPSTR message;
29
+ } WDM_QueueItemError, *WDM_PQueueItemError;
30
+
31
+ typedef struct WDM_QueueItem {
32
+ WDM_QueueItemType type;
33
+ union {
34
+ WDM_PQueueItemData data;
35
+ WDM_PQueueItemError error;
36
+ };
37
+ struct WDM_QueueItem* next;
38
+ } WDM_QueueItem, *WDM_PQueueItem;
39
+
40
+ typedef struct {
41
+ CRITICAL_SECTION lock;
42
+ WDM_PQueueItem front;
43
+ WDM_PQueueItem rear;
44
+ } WDM_Queue, *WDM_PQueue;
45
+
46
+ // ---------------------------------------------------------
47
+ // Prototypes
48
+ // ---------------------------------------------------------
49
+
50
+ WDM_PQueueItemError wdm_queue_item_error_new(VALUE, LPCSTR, ...);
51
+ void wdm_queue_item_error_free(WDM_PQueueItemError);
52
+
53
+ WDM_PQueueItemData wdm_queue_item_data_new();
54
+ void wdm_queue_item_data_free(WDM_PQueueItemData);
55
+
56
+ WDM_PQueueItem wdm_queue_item_new(WDM_QueueItemType);
57
+ void wdm_queue_item_free(WDM_PQueueItem);
58
+
59
+ WDM_PQueue wdm_queue_new();
60
+ void wdm_queue_free(WDM_PQueue);
61
+
62
+ void wdm_queue_enqueue(WDM_PQueue, WDM_PQueueItem);
63
+ WDM_PQueueItem wdm_queue_dequeue(WDM_PQueue);
64
+ void wdm_queue_empty(WDM_PQueue);
65
+ BOOL wdm_queue_is_empty(WDM_PQueue);
66
+
67
+ // ---------------------------------------------------------
68
+
69
+ #ifdef __cplusplus
70
+ }
71
+ #endif // __cplusplus
72
+
73
73
  #endif // WDM_QUEUE_H
@@ -1,199 +1,199 @@
1
- #include <stdlib.h>
2
- #include <wchar.h>
3
-
4
- #include "wdm.h"
5
-
6
- #include "utils.h"
7
-
8
- #include "rb_change.h"
9
-
10
- // ---------------------------------------------------------
11
- // Internal constants
12
- // ---------------------------------------------------------
13
-
14
- // The _wsplitpat constants account for two NULL chars, so subtract 1 because we only need one!
15
- #define WDM_MAX_FILENAME (_MAX_FNAME + _MAX_EXT - 1)
16
-
17
- // ----------------------------------------------------------
18
- // Global variables
19
- // ----------------------------------------------------------
20
-
21
- VALUE cWDM_Change;
22
-
23
- static ID wdm_rb_sym_at_path;
24
- static ID wdm_rb_sym_at_type;
25
- static ID wdm_rb_sym_added;
26
- static ID wdm_rb_sym_modified;
27
- static ID wdm_rb_sym_removed;
28
- static ID wdm_rb_sym_renamed_old_file;
29
- static ID wdm_rb_sym_renamed_new_file;
30
-
31
- // ----------------------------------------------------------
32
- // Prototypes of static functions
33
- // ----------------------------------------------------------
34
-
35
- static VALUE extract_absolute_path_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
36
- static VALUE extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION);
37
-
38
- // ----------------------------------------------------------
39
-
40
- // TODO:
41
- // 1. this function uses a lot of 'alloca' calls, which AFAIK is not recommended! Can this be avoided?
42
- // 2. all wcscat calls can be done faster with memcpy, but is it worth sacrificing the readability?
43
- static VALUE
44
- extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
45
- {
46
- LPWSTR buffer, absolute_filepath;
47
- WCHAR file[_MAX_FNAME], ext[_MAX_EXT], filename[WDM_MAX_FILENAME];
48
- DWORD filename_len, absolute_filepath_len;
49
- LPSTR multibyte_filepath;
50
- int multibyte_filepath_buffer_size;
51
- VALUE path;
52
-
53
- filename_len = info->FileNameLength/sizeof(WCHAR);
54
-
55
- // The file in the 'info' struct is NOT null-terminated, so add 1 extra char to the allocation
56
- buffer = ALLOCA_N(WCHAR, filename_len + 1);
57
-
58
- memcpy(buffer, info->FileName, info->FileNameLength);
59
-
60
- // Null-terminate the string
61
- buffer[filename_len] = L'\0';
62
-
63
- WDM_WDEBUG("change in: '%s'", buffer);
64
-
65
- absolute_filepath_len = wcslen(base_dir) + filename_len;
66
- absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + 1); // 1 for NULL
67
- absolute_filepath[0] = L'\0';
68
-
69
- wcscat(absolute_filepath, base_dir);
70
- wcscat(absolute_filepath, buffer);
71
-
72
- WDM_WDEBUG("absolute path is: '%s'", absolute_filepath);
73
-
74
- _wsplitpath(buffer, NULL, NULL, file, ext);
75
-
76
- // TODO: Extracting the file name from 'buffer' is only needed when watching sub-dirs
77
- filename[0] = L'\0';
78
- if ( file[0] != L'\0' ) wcscat(filename, file);
79
- if ( ext[0] != L'\0' ) wcscat(filename, ext);
80
-
81
- WDM_WDEBUG("filename: '%s'", filename);
82
-
83
- filename_len = wcslen(filename);
84
-
85
- // The maximum length of an 8.3 filename is twelve, including the dot.
86
- if (filename_len <= 12 && wcschr(filename, L'~'))
87
- {
88
- LPWSTR unicode_absolute_filepath;
89
- WCHAR absolute_long_filepath[WDM_MAX_WCHAR_LONG_PATH];
90
- BOOL is_unc_path;
91
-
92
- is_unc_path = wdm_utils_is_unc_path(absolute_filepath);
93
-
94
- unicode_absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + (is_unc_path ? 8 : 4) + 1); // 8 for "\\?\UNC\" or 4 for "\\?\", and 1 for \0
95
-
96
- unicode_absolute_filepath[0] = L'\0';
97
- wcscat(unicode_absolute_filepath, L"\\\\?\\");
98
-
99
- if ( is_unc_path ) {
100
- wcscat(unicode_absolute_filepath, L"UNC\\");
101
- wcscat(unicode_absolute_filepath, absolute_filepath + 2); // +2 to skip the begin of a UNC path
102
- }
103
- else {
104
- wcscat(unicode_absolute_filepath, absolute_filepath);
105
- }
106
-
107
- // Convert to the long filename form. Unfortunately, this
108
- // does not work for deletions, so it's an imperfect fix.
109
- if (GetLongPathNameW(unicode_absolute_filepath, absolute_long_filepath, WDM_MAX_WCHAR_LONG_PATH) != 0) {
110
- absolute_filepath = absolute_long_filepath + 4; // Skip first 4 pointers of "\\?\"
111
- absolute_filepath_len = wcslen(absolute_filepath);
112
- WDM_WDEBUG("Short path converted to long: '%s'", absolute_filepath);
113
- }
114
- else {
115
- WDM_DEBUG("Can't convert short path to long: '%s'", rb_w32_strerror(GetLastError()));
116
- }
117
- }
118
-
119
- // The convention in Ruby is to use forward-slashes to separate dirs on all platforms.
120
- wdm_utils_convert_back_to_forward_slashes(absolute_filepath, absolute_filepath_len + 1);
121
-
122
- // Convert the path from WCHAR to multibyte CHAR to use it in a ruby string
123
- multibyte_filepath_buffer_size =
124
- WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1, NULL, 0, NULL, NULL);
125
-
126
- multibyte_filepath = ALLOCA_N(CHAR, multibyte_filepath_buffer_size);
127
-
128
- if ( 0 == WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1,
129
- multibyte_filepath, multibyte_filepath_buffer_size, NULL, NULL) ) {
130
- rb_raise(eWDM_Error, "Failed to add the change file path to the event!");
131
- }
132
-
133
- WDM_DEBUG("will report change in: '%s'", multibyte_filepath);
134
-
135
- path = rb_enc_str_new(multibyte_filepath,
136
- multibyte_filepath_buffer_size - 1, // -1 because this func takes the chars count, not bytes count
137
- wdm_rb_enc_utf8);
138
-
139
- OBJ_TAINT(path);
140
-
141
- return path;
142
- }
143
-
144
- static VALUE
145
- extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info)
146
- {
147
- ID type;
148
-
149
- switch(info->Action) {
150
- case FILE_ACTION_ADDED: type = wdm_rb_sym_added; break;
151
- case FILE_ACTION_REMOVED: type = wdm_rb_sym_removed; break;
152
- case FILE_ACTION_MODIFIED: type = wdm_rb_sym_modified; break;
153
- case FILE_ACTION_RENAMED_OLD_NAME: type = wdm_rb_sym_renamed_old_file; break;
154
- case FILE_ACTION_RENAMED_NEW_NAME: type = wdm_rb_sym_renamed_new_file; break;
155
- default:
156
- rb_raise(eWDM_Error, "Unknown change happened to a file in a watched directory!");
157
- }
158
-
159
- #if WDM_DEBUG_ENABLED // Used to avoid the func call when in release mode
160
- WDM_DEBUG("change type: '%s'", rb_id2name(type));
161
- #endif
162
-
163
- return ID2SYM(type);
164
- }
165
-
166
- VALUE
167
- wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
168
- {
169
- VALUE change;
170
-
171
- change = rb_class_new_instance(0, NULL, cWDM_Change);
172
-
173
- // Set '@type' to the change type
174
- rb_ivar_set(change, wdm_rb_sym_at_type, extract_change_type_from_notification(info));
175
-
176
- // Set '@path' to the absolute path of the changed file/directory
177
- rb_ivar_set(change, wdm_rb_sym_at_path, extract_absolute_path_from_notification(base_dir, info));
178
-
179
- return change;
180
- }
181
-
182
- void
183
- wdm_rb_change_init()
184
- {
185
- WDM_DEBUG("Registering WDM::Event with Ruby!");
186
-
187
- wdm_rb_sym_at_path = rb_intern("@path");
188
- wdm_rb_sym_at_type = rb_intern("@type");
189
- wdm_rb_sym_added = rb_intern("added");
190
- wdm_rb_sym_modified = rb_intern("modified");
191
- wdm_rb_sym_removed = rb_intern("removed");
192
- wdm_rb_sym_renamed_old_file = rb_intern("renamed_old_file");
193
- wdm_rb_sym_renamed_new_file = rb_intern("renamed_new_file");
194
-
195
- cWDM_Change = rb_define_class_under(mWDM, "Change", rb_cObject);
196
-
197
- rb_define_attr(cWDM_Change, "path", 1, 0);
198
- rb_define_attr(cWDM_Change, "type", 1, 0);
1
+ #include <stdlib.h>
2
+ #include <wchar.h>
3
+
4
+ #include "wdm.h"
5
+
6
+ #include "utils.h"
7
+
8
+ #include "rb_change.h"
9
+
10
+ // ---------------------------------------------------------
11
+ // Internal constants
12
+ // ---------------------------------------------------------
13
+
14
+ // The _wsplitpat constants account for two NULL chars, so subtract 1 because we only need one!
15
+ #define WDM_MAX_FILENAME (_MAX_FNAME + _MAX_EXT - 1)
16
+
17
+ // ----------------------------------------------------------
18
+ // Global variables
19
+ // ----------------------------------------------------------
20
+
21
+ VALUE cWDM_Change;
22
+
23
+ static ID wdm_rb_sym_at_path;
24
+ static ID wdm_rb_sym_at_type;
25
+ static ID wdm_rb_sym_added;
26
+ static ID wdm_rb_sym_modified;
27
+ static ID wdm_rb_sym_removed;
28
+ static ID wdm_rb_sym_renamed_old_file;
29
+ static ID wdm_rb_sym_renamed_new_file;
30
+
31
+ // ----------------------------------------------------------
32
+ // Prototypes of static functions
33
+ // ----------------------------------------------------------
34
+
35
+ static VALUE extract_absolute_path_from_notification(const LPWSTR, const PFILE_NOTIFY_INFORMATION);
36
+ static VALUE extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION);
37
+
38
+ // ----------------------------------------------------------
39
+
40
+ // TODO:
41
+ // 1. this function uses a lot of 'alloca' calls, which AFAIK is not recommended! Can this be avoided?
42
+ // 2. all wcscat calls can be done faster with memcpy, but is it worth sacrificing the readability?
43
+ static VALUE
44
+ extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
45
+ {
46
+ LPWSTR buffer, absolute_filepath;
47
+ WCHAR file[_MAX_FNAME], ext[_MAX_EXT], filename[WDM_MAX_FILENAME];
48
+ DWORD filename_len, absolute_filepath_len;
49
+ LPSTR multibyte_filepath;
50
+ int multibyte_filepath_buffer_size;
51
+ VALUE path;
52
+
53
+ filename_len = info->FileNameLength/sizeof(WCHAR);
54
+
55
+ // The file in the 'info' struct is NOT null-terminated, so add 1 extra char to the allocation
56
+ buffer = ALLOCA_N(WCHAR, filename_len + 1);
57
+
58
+ memcpy(buffer, info->FileName, info->FileNameLength);
59
+
60
+ // Null-terminate the string
61
+ buffer[filename_len] = L'\0';
62
+
63
+ WDM_WDEBUG("change in: '%s'", buffer);
64
+
65
+ absolute_filepath_len = wcslen(base_dir) + filename_len;
66
+ absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + 1); // 1 for NULL
67
+ absolute_filepath[0] = L'\0';
68
+
69
+ wcscat(absolute_filepath, base_dir);
70
+ wcscat(absolute_filepath, buffer);
71
+
72
+ WDM_WDEBUG("absolute path is: '%s'", absolute_filepath);
73
+
74
+ _wsplitpath(buffer, NULL, NULL, file, ext);
75
+
76
+ // TODO: Extracting the file name from 'buffer' is only needed when watching sub-dirs
77
+ filename[0] = L'\0';
78
+ if ( file[0] != L'\0' ) wcscat(filename, file);
79
+ if ( ext[0] != L'\0' ) wcscat(filename, ext);
80
+
81
+ WDM_WDEBUG("filename: '%s'", filename);
82
+
83
+ filename_len = wcslen(filename);
84
+
85
+ // The maximum length of an 8.3 filename is twelve, including the dot.
86
+ if (filename_len <= 12 && wcschr(filename, L'~'))
87
+ {
88
+ LPWSTR unicode_absolute_filepath;
89
+ WCHAR absolute_long_filepath[WDM_MAX_WCHAR_LONG_PATH];
90
+ BOOL is_unc_path;
91
+
92
+ is_unc_path = wdm_utils_is_unc_path(absolute_filepath);
93
+
94
+ unicode_absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + (is_unc_path ? 8 : 4) + 1); // 8 for "\\?\UNC\" or 4 for "\\?\", and 1 for \0
95
+
96
+ unicode_absolute_filepath[0] = L'\0';
97
+ wcscat(unicode_absolute_filepath, L"\\\\?\\");
98
+
99
+ if ( is_unc_path ) {
100
+ wcscat(unicode_absolute_filepath, L"UNC\\");
101
+ wcscat(unicode_absolute_filepath, absolute_filepath + 2); // +2 to skip the begin of a UNC path
102
+ }
103
+ else {
104
+ wcscat(unicode_absolute_filepath, absolute_filepath);
105
+ }
106
+
107
+ // Convert to the long filename form. Unfortunately, this
108
+ // does not work for deletions, so it's an imperfect fix.
109
+ if (GetLongPathNameW(unicode_absolute_filepath, absolute_long_filepath, WDM_MAX_WCHAR_LONG_PATH) != 0) {
110
+ absolute_filepath = absolute_long_filepath + 4; // Skip first 4 pointers of "\\?\"
111
+ absolute_filepath_len = wcslen(absolute_filepath);
112
+ WDM_WDEBUG("Short path converted to long: '%s'", absolute_filepath);
113
+ }
114
+ else {
115
+ WDM_DEBUG("Can't convert short path to long: '%s'", rb_w32_strerror(GetLastError()));
116
+ }
117
+ }
118
+
119
+ // The convention in Ruby is to use forward-slashes to separate dirs on all platforms.
120
+ wdm_utils_convert_back_to_forward_slashes(absolute_filepath, absolute_filepath_len + 1);
121
+
122
+ // Convert the path from WCHAR to multibyte CHAR to use it in a ruby string
123
+ multibyte_filepath_buffer_size =
124
+ WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1, NULL, 0, NULL, NULL);
125
+
126
+ multibyte_filepath = ALLOCA_N(CHAR, multibyte_filepath_buffer_size);
127
+
128
+ if ( 0 == WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1,
129
+ multibyte_filepath, multibyte_filepath_buffer_size, NULL, NULL) ) {
130
+ rb_raise(eWDM_Error, "Failed to add the change file path to the event!");
131
+ }
132
+
133
+ WDM_DEBUG("will report change in: '%s'", multibyte_filepath);
134
+
135
+ path = rb_enc_str_new(multibyte_filepath,
136
+ multibyte_filepath_buffer_size - 1, // -1 because this func takes the chars count, not bytes count
137
+ wdm_rb_enc_utf8);
138
+
139
+ OBJ_TAINT(path);
140
+
141
+ return path;
142
+ }
143
+
144
+ static VALUE
145
+ extract_change_type_from_notification(const PFILE_NOTIFY_INFORMATION info)
146
+ {
147
+ ID type;
148
+
149
+ switch(info->Action) {
150
+ case FILE_ACTION_ADDED: type = wdm_rb_sym_added; break;
151
+ case FILE_ACTION_REMOVED: type = wdm_rb_sym_removed; break;
152
+ case FILE_ACTION_MODIFIED: type = wdm_rb_sym_modified; break;
153
+ case FILE_ACTION_RENAMED_OLD_NAME: type = wdm_rb_sym_renamed_old_file; break;
154
+ case FILE_ACTION_RENAMED_NEW_NAME: type = wdm_rb_sym_renamed_new_file; break;
155
+ default:
156
+ rb_raise(eWDM_Error, "Unknown change happened to a file in a watched directory!");
157
+ }
158
+
159
+ #if WDM_DEBUG_ENABLED // Used to avoid the func call when in release mode
160
+ WDM_DEBUG("change type: '%s'", rb_id2name(type));
161
+ #endif
162
+
163
+ return ID2SYM(type);
164
+ }
165
+
166
+ VALUE
167
+ wdm_rb_change_new_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info)
168
+ {
169
+ VALUE change;
170
+
171
+ change = rb_class_new_instance(0, NULL, cWDM_Change);
172
+
173
+ // Set '@type' to the change type
174
+ rb_ivar_set(change, wdm_rb_sym_at_type, extract_change_type_from_notification(info));
175
+
176
+ // Set '@path' to the absolute path of the changed file/directory
177
+ rb_ivar_set(change, wdm_rb_sym_at_path, extract_absolute_path_from_notification(base_dir, info));
178
+
179
+ return change;
180
+ }
181
+
182
+ void
183
+ wdm_rb_change_init()
184
+ {
185
+ WDM_DEBUG("Registering WDM::Event with Ruby!");
186
+
187
+ wdm_rb_sym_at_path = rb_intern("@path");
188
+ wdm_rb_sym_at_type = rb_intern("@type");
189
+ wdm_rb_sym_added = rb_intern("added");
190
+ wdm_rb_sym_modified = rb_intern("modified");
191
+ wdm_rb_sym_removed = rb_intern("removed");
192
+ wdm_rb_sym_renamed_old_file = rb_intern("renamed_old_file");
193
+ wdm_rb_sym_renamed_new_file = rb_intern("renamed_new_file");
194
+
195
+ cWDM_Change = rb_define_class_under(mWDM, "Change", rb_cObject);
196
+
197
+ rb_define_attr(cWDM_Change, "path", 1, 0);
198
+ rb_define_attr(cWDM_Change, "type", 1, 0);
199
199
  }