@devscholar/node-ps1-dotnet 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -11
- package/__tests__/basic.test.ts +24 -0
- package/__tests__/dotnet-console.test.ts +30 -0
- package/__tests__/dotnet-enums.test.ts +55 -0
- package/__tests__/dotnet-io.test.ts +57 -0
- package/__tests__/dotnet-misc.test.ts +95 -0
- package/__tests__/dotnet-runtime.test.ts +99 -0
- package/__tests__/dotnet-task.test.ts +35 -0
- package/__tests__/gui-events.test.ts +147 -0
- package/__tests__/ipc.test.ts +43 -0
- package/__tests__/winforms.test.ts +136 -0
- package/__tests__/wpf.test.ts +135 -0
- package/docs/testing.md +30 -0
- package/jest.config.js +31 -0
- package/package.json +16 -5
- package/scripts/PsBridge/Reflection.cs +14 -0
- package/src/index.ts +53 -369
- package/src/namespace.ts +116 -0
- package/src/proxy.ts +330 -0
- package/src/state.ts +17 -0
- package/src/types.ts +2 -0
- package/test-utils.ts +64 -0
- package/tsconfig.json +15 -7
- package/tsconfig.tsbuildinfo +1 -0
- package/types/index.d.ts +17 -0
- package/types/ipc.d.ts +17 -0
- package/types/namespace.d.ts +2 -0
- package/types/proxy.d.ts +16 -0
- package/types/state.d.ts +16 -0
- package/types/types.d.ts +21 -0
- package/types/utils.d.ts +1 -0
package/src/index.ts
CHANGED
|
@@ -1,52 +1,44 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
1
|
import * as fs from 'node:fs';
|
|
3
2
|
import * as path from 'node:path';
|
|
4
3
|
import * as cp from 'node:child_process';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { getPowerShellPath } from './utils.
|
|
7
|
-
import { IpcSync } from './ipc.
|
|
5
|
+
import { getPowerShellPath } from './utils.js';
|
|
6
|
+
import { IpcSync } from './ipc.js';
|
|
7
|
+
import { getIpc, setIpc, getProc, setProc, getInitialized, setInitialized, getCachedRuntimeInfo, setCachedRuntimeInfo } from './state.js';
|
|
8
|
+
import { callbackRegistry, createProxyWithInlineProps, createProxy, setNodePs1Dotnet } from './proxy.js';
|
|
9
|
+
import { createNamespaceProxy, createExportNamespaceProxy } from './namespace.js';
|
|
8
10
|
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
|
|
12
|
-
const gcRegistry = new FinalizationRegistry((id: string) => {
|
|
13
|
-
try { node_ps1_dotnet._release(id); } catch {}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const callbackRegistry = new Map<string, Function>();
|
|
17
|
-
const typeMetadataCache = new Map<string, Map<string, string>>();
|
|
18
|
-
const globalTypeCache = new Map<string, Map<string, string>>();
|
|
19
|
-
const pendingInspectRequests = new Map<string, string[]>();
|
|
20
|
-
const pendingInspectTimeout = new Map<string, NodeJS.Timeout>();
|
|
21
|
-
|
|
22
|
-
let ipc: IpcSync | null = null;
|
|
23
|
-
let proc: cp.ChildProcess | null = null;
|
|
24
|
-
let initialized = false;
|
|
25
|
-
let cachedRuntimeInfo: { frameworkMoniker: string; runtimeVersion: string } | null = null;
|
|
11
|
+
export const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
export const __dirname = path.dirname(__filename);
|
|
26
13
|
|
|
27
14
|
function cleanup() {
|
|
28
|
-
if (!
|
|
29
|
-
|
|
15
|
+
if (!getInitialized()) return;
|
|
16
|
+
setInitialized(false);
|
|
30
17
|
|
|
18
|
+
const ipc = getIpc();
|
|
31
19
|
if (ipc) {
|
|
32
20
|
try {
|
|
33
21
|
ipc.close();
|
|
34
22
|
} catch {}
|
|
35
23
|
}
|
|
36
24
|
|
|
25
|
+
const proc = getProc();
|
|
37
26
|
if (proc && !proc.killed) {
|
|
38
27
|
try {
|
|
39
|
-
// Windows 下强制杀死进程,防止标准流卡死 Node.js 的事件循环
|
|
40
28
|
proc.kill('SIGKILL');
|
|
41
29
|
} catch {}
|
|
42
30
|
}
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
32
|
+
setProc(null);
|
|
33
|
+
setIpc(null);
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
function
|
|
49
|
-
if (
|
|
36
|
+
function doInitialize() {
|
|
37
|
+
if (getInitialized()) return;
|
|
38
|
+
|
|
39
|
+
if (process.platform !== 'win32') {
|
|
40
|
+
throw new Error('node-ps1-dotnet is only supported on Windows. Use node-with-gjs for Linux/macOS.');
|
|
41
|
+
}
|
|
50
42
|
|
|
51
43
|
const pipeName = `PsNode_${process.pid}_${Math.floor(Math.random() * 10000)}`;
|
|
52
44
|
const scriptPath = path.join(__dirname, '..', 'scripts', 'PsHost.ps1');
|
|
@@ -56,20 +48,18 @@ function initialize() {
|
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
const powerShellPath = getPowerShellPath();
|
|
59
|
-
proc = cp.spawn(powerShellPath, [
|
|
51
|
+
const proc = cp.spawn(powerShellPath, [
|
|
60
52
|
'-NoProfile', '-ExecutionPolicy', 'Bypass',
|
|
61
53
|
'-Command', `& '${scriptPath}' -PipeName '${pipeName}'`
|
|
62
54
|
], { stdio: 'inherit', windowsHide: false });
|
|
63
55
|
|
|
64
|
-
|
|
56
|
+
setProc(proc);
|
|
65
57
|
proc.unref();
|
|
66
58
|
|
|
67
59
|
proc.on('exit', (code) => {
|
|
68
60
|
process.exit(0);
|
|
69
61
|
});
|
|
70
62
|
|
|
71
|
-
// 修复脚本跑完后无法停机卡死的 Bug:
|
|
72
|
-
// 当 V8 执行到底,事件循环清空时,触发 beforeExit -> 强杀子进程并立刻退出
|
|
73
63
|
process.on('beforeExit', () => {
|
|
74
64
|
cleanup();
|
|
75
65
|
process.exit(0);
|
|
@@ -95,7 +85,7 @@ function initialize() {
|
|
|
95
85
|
process.exit(1);
|
|
96
86
|
});
|
|
97
87
|
|
|
98
|
-
ipc = new IpcSync(pipeName, (res: any) => {
|
|
88
|
+
const ipc = new IpcSync(pipeName, (res: any) => {
|
|
99
89
|
const cb = callbackRegistry.get(res.callbackId!);
|
|
100
90
|
if (cb) {
|
|
101
91
|
const wrappedArgs = (res.args || []).map((arg: any) => {
|
|
@@ -110,323 +100,27 @@ function initialize() {
|
|
|
110
100
|
});
|
|
111
101
|
|
|
112
102
|
ipc.connect();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const typeNameCache = new Map<string, string>();
|
|
117
|
-
|
|
118
|
-
function getObjectTypeName(id: string): string | null {
|
|
119
|
-
if (typeNameCache.has(id)) {
|
|
120
|
-
return typeNameCache.get(id)!;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
const res = ipc!.send({ action: 'GetTypeName', targetId: id });
|
|
124
|
-
if (res && res.typeName) {
|
|
125
|
-
typeNameCache.set(id, res.typeName);
|
|
126
|
-
return res.typeName;
|
|
127
|
-
}
|
|
128
|
-
} catch {}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getTypeMembers(typeName: string, memberNames: string[]): Map<string, string> | null {
|
|
133
|
-
if (globalTypeCache.has(typeName)) {
|
|
134
|
-
const cached = globalTypeCache.get(typeName)!;
|
|
135
|
-
const result = new Map<string, string>();
|
|
136
|
-
let allFound = true;
|
|
137
|
-
for (const name of memberNames) {
|
|
138
|
-
if (cached.has(name)) {
|
|
139
|
-
result.set(name, cached.get(name)!);
|
|
140
|
-
} else {
|
|
141
|
-
allFound = false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (allFound) return result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const res = ipc!.send({ action: 'InspectType', typeName, memberNames });
|
|
149
|
-
if (res && res.members) {
|
|
150
|
-
if (!globalTypeCache.has(typeName)) {
|
|
151
|
-
globalTypeCache.set(typeName, new Map());
|
|
152
|
-
}
|
|
153
|
-
const cache = globalTypeCache.get(typeName)!;
|
|
154
|
-
for (const [name, type] of Object.entries(res.members as Record<string, string>)) {
|
|
155
|
-
cache.set(name, type);
|
|
156
|
-
}
|
|
157
|
-
return new Map(Object.entries(res.members as Record<string, string>));
|
|
158
|
-
}
|
|
159
|
-
} catch {}
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function ensureMemberTypeCached(id: string, memberName: string, memberCache: Map<string, string>) {
|
|
164
|
-
if (memberCache.has(memberName)) return;
|
|
165
|
-
|
|
166
|
-
const typeName = getObjectTypeName(id);
|
|
167
|
-
if (typeName) {
|
|
168
|
-
const members = getTypeMembers(typeName, [memberName]);
|
|
169
|
-
if (members && members.has(memberName)) {
|
|
170
|
-
memberCache.set(memberName, members.get(memberName)!);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
const inspectRes = ipc!.send({ action: 'Inspect', targetId: id, memberName });
|
|
177
|
-
memberCache.set(memberName, inspectRes.memberType);
|
|
178
|
-
} catch {
|
|
179
|
-
memberCache.set(memberName, 'method');
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function createProxyWithInlineProps(meta: any): any {
|
|
184
|
-
if (meta.type !== 'ref') return createProxy(meta);
|
|
185
|
-
|
|
186
|
-
const id = meta.id!;
|
|
187
|
-
const inlineProps = meta.props || {};
|
|
188
|
-
|
|
189
|
-
if (!typeMetadataCache.has(id)) {
|
|
190
|
-
typeMetadataCache.set(id, new Map());
|
|
191
|
-
}
|
|
192
|
-
const memberCache = typeMetadataCache.get(id)!;
|
|
193
|
-
|
|
194
|
-
class Stub {}
|
|
195
|
-
|
|
196
|
-
const proxy = new Proxy(Stub, {
|
|
197
|
-
get: (target: any, prop: string) => {
|
|
198
|
-
if (prop === '__ref') return id;
|
|
199
|
-
if (prop === '__inlineProps') return inlineProps;
|
|
200
|
-
if (typeof prop !== 'string') return undefined;
|
|
201
|
-
|
|
202
|
-
if (inlineProps.hasOwnProperty(prop)) {
|
|
203
|
-
memberCache.set(prop, 'property');
|
|
204
|
-
return inlineProps[prop];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (prop.startsWith('add_')) {
|
|
208
|
-
const eventName = prop.substring(4);
|
|
209
|
-
return (callback: Function) => {
|
|
210
|
-
const cbId = `cb_${Date.now()}_${Math.random()}`;
|
|
211
|
-
callbackRegistry.set(cbId, callback);
|
|
212
|
-
ipc!.send({ action: 'AddEvent', targetId: id, eventName, callbackId: cbId });
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
let memType = memberCache.get(prop);
|
|
217
|
-
|
|
218
|
-
if (!memType) {
|
|
219
|
-
ensureMemberTypeCached(id, prop, memberCache);
|
|
220
|
-
memType = memberCache.get(prop);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (memType === 'property') {
|
|
224
|
-
const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [] });
|
|
225
|
-
return createProxy(res);
|
|
226
|
-
} else {
|
|
227
|
-
return (...args: any[]) => {
|
|
228
|
-
const netArgs = args.map((a: any) => {
|
|
229
|
-
if (a && a.__ref) return { __ref: a.__ref };
|
|
230
|
-
if (typeof a === 'function') {
|
|
231
|
-
const cbId = `cb_arg_${Date.now()}_${Math.random()}`;
|
|
232
|
-
callbackRegistry.set(cbId, a);
|
|
233
|
-
return { type: 'callback', callbackId: cbId };
|
|
234
|
-
}
|
|
235
|
-
return a;
|
|
236
|
-
});
|
|
237
|
-
const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: netArgs });
|
|
238
|
-
return createProxy(res);
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
set: (target: any, prop: string, value: any) => {
|
|
244
|
-
if (typeof prop !== 'string') return false;
|
|
245
|
-
const netArg = (value && value.__ref) ? { __ref: value.__ref } : value;
|
|
246
|
-
ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [netArg] });
|
|
247
|
-
memberCache.set(prop, 'property');
|
|
248
|
-
return true;
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
construct: (target: any, args: any[]) => {
|
|
252
|
-
const netArgs = args.map((a: any) => {
|
|
253
|
-
if (a && a.__ref) return { __ref: a.__ref };
|
|
254
|
-
if (typeof a === 'function') {
|
|
255
|
-
const cbId = `cb_ctor_${Date.now()}_${Math.random()}`;
|
|
256
|
-
callbackRegistry.set(cbId, a);
|
|
257
|
-
return { type: 'callback', callbackId: cbId };
|
|
258
|
-
}
|
|
259
|
-
return a;
|
|
260
|
-
});
|
|
261
|
-
const res = ipc!.send({ action: 'New', typeId: id, args: netArgs });
|
|
262
|
-
return createProxy(res);
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
apply: () => { throw new Error("Cannot call .NET object as a function. Need 'new'?"); }
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
gcRegistry.register(proxy, id);
|
|
269
|
-
return proxy;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function createLazyArray(arr: any[]): any {
|
|
273
|
-
return new Proxy(arr, {
|
|
274
|
-
get(target, prop) {
|
|
275
|
-
if (typeof prop === 'symbol') return target[prop];
|
|
276
|
-
const index = Number(prop);
|
|
277
|
-
if (!isNaN(index) && index >= 0 && index < target.length) {
|
|
278
|
-
return createProxy(target[index]);
|
|
279
|
-
}
|
|
280
|
-
if (prop === 'length') return target.length;
|
|
281
|
-
if (prop === 'map' || prop === 'filter' || prop === 'forEach' || prop === 'reduce') {
|
|
282
|
-
return (...args: any[]) => {
|
|
283
|
-
const transformed = target.map((item: any) => createProxy(item));
|
|
284
|
-
return transformed[prop](...args);
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
if (prop === 'slice') {
|
|
288
|
-
return (...args: any[]) => {
|
|
289
|
-
const sliced = target.slice(...args);
|
|
290
|
-
return sliced.map((item: any) => createProxy(item));
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
return undefined;
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const LARGE_ARRAY_THRESHOLD = 50;
|
|
299
|
-
|
|
300
|
-
function createProxy(meta: any): any {
|
|
301
|
-
if (meta.type === 'primitive' || meta.type === 'null') return meta.value;
|
|
302
|
-
|
|
303
|
-
if (meta.type === 'array') {
|
|
304
|
-
const arr = meta.value;
|
|
305
|
-
if (arr.length <= LARGE_ARRAY_THRESHOLD) {
|
|
306
|
-
return arr.map((item: any) => createProxy(item));
|
|
307
|
-
}
|
|
308
|
-
return createLazyArray(arr);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (meta.type === 'task') {
|
|
312
|
-
const taskId = meta.id;
|
|
313
|
-
return new Promise((resolve, reject) => {
|
|
314
|
-
try {
|
|
315
|
-
const res = ipc!.send({ action: 'AwaitTask', taskId: taskId });
|
|
316
|
-
resolve(createProxy(res));
|
|
317
|
-
} catch (e) {
|
|
318
|
-
reject(e);
|
|
319
|
-
} finally {
|
|
320
|
-
try { ipc!.send({ action: 'Release', targetId: taskId }); } catch {}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (meta.type === 'namespace') {
|
|
326
|
-
const nsName = meta.value;
|
|
327
|
-
return new Proxy({}, {
|
|
328
|
-
get: (target: any, prop: string) => {
|
|
329
|
-
if (typeof prop !== 'string') return undefined;
|
|
330
|
-
return node_ps1_dotnet._load(`${nsName}.${prop}`);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (meta.type !== 'ref') return null;
|
|
336
|
-
|
|
337
|
-
const id = meta.id!;
|
|
338
|
-
|
|
339
|
-
if (!typeMetadataCache.has(id)) {
|
|
340
|
-
typeMetadataCache.set(id, new Map());
|
|
341
|
-
}
|
|
342
|
-
const memberCache = typeMetadataCache.get(id)!;
|
|
343
|
-
|
|
344
|
-
class Stub {}
|
|
345
|
-
|
|
346
|
-
const proxy = new Proxy(Stub, {
|
|
347
|
-
get: (target: any, prop: string) => {
|
|
348
|
-
if (prop === '__ref') return id;
|
|
349
|
-
if (typeof prop !== 'string') return undefined;
|
|
350
|
-
|
|
351
|
-
if (prop.startsWith('add_')) {
|
|
352
|
-
const eventName = prop.substring(4);
|
|
353
|
-
return (callback: Function) => {
|
|
354
|
-
const cbId = `cb_${Date.now()}_${Math.random()}`;
|
|
355
|
-
callbackRegistry.set(cbId, callback);
|
|
356
|
-
ipc!.send({ action: 'AddEvent', targetId: id, eventName, callbackId: cbId });
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
let memType = memberCache.get(prop);
|
|
361
|
-
|
|
362
|
-
if (!memType) {
|
|
363
|
-
ensureMemberTypeCached(id, prop, memberCache);
|
|
364
|
-
memType = memberCache.get(prop);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (memType === 'property') {
|
|
368
|
-
const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [] });
|
|
369
|
-
return createProxy(res);
|
|
370
|
-
} else {
|
|
371
|
-
return (...args: any[]) => {
|
|
372
|
-
const netArgs = args.map((a: any) => {
|
|
373
|
-
if (a && a.__ref) return { __ref: a.__ref };
|
|
374
|
-
if (typeof a === 'function') {
|
|
375
|
-
const cbId = `cb_arg_${Date.now()}_${Math.random()}`;
|
|
376
|
-
callbackRegistry.set(cbId, a);
|
|
377
|
-
return { type: 'callback', callbackId: cbId };
|
|
378
|
-
}
|
|
379
|
-
return a;
|
|
380
|
-
});
|
|
381
|
-
const res = ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: netArgs });
|
|
382
|
-
return createProxy(res);
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
set: (target: any, prop: string, value: any) => {
|
|
388
|
-
if (typeof prop !== 'string') return false;
|
|
389
|
-
const netArg = (value && value.__ref) ? { __ref: value.__ref } : value;
|
|
390
|
-
ipc!.send({ action: 'Invoke', targetId: id, methodName: prop, args: [netArg] });
|
|
391
|
-
memberCache.set(prop, 'property');
|
|
392
|
-
return true;
|
|
393
|
-
},
|
|
394
|
-
|
|
395
|
-
construct: (target: any, args: any[]) => {
|
|
396
|
-
const netArgs = args.map((a: any) => {
|
|
397
|
-
if (a && a.__ref) return { __ref: a.__ref };
|
|
398
|
-
if (typeof a === 'function') {
|
|
399
|
-
const cbId = `cb_ctor_${Date.now()}_${Math.random()}`;
|
|
400
|
-
callbackRegistry.set(cbId, a);
|
|
401
|
-
return { type: 'callback', callbackId: cbId };
|
|
402
|
-
}
|
|
403
|
-
return a;
|
|
404
|
-
});
|
|
405
|
-
const res = ipc!.send({ action: 'New', typeId: id, args: netArgs });
|
|
406
|
-
return createProxy(res);
|
|
407
|
-
},
|
|
408
|
-
|
|
409
|
-
apply: () => { throw new Error("Cannot call .NET object as a function. Need 'new'?"); }
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
gcRegistry.register(proxy, id);
|
|
413
|
-
return proxy;
|
|
103
|
+
setIpc(ipc);
|
|
104
|
+
setInitialized(true);
|
|
414
105
|
}
|
|
415
106
|
|
|
416
107
|
export const node_ps1_dotnet = {
|
|
417
108
|
_load(typeName: string): any {
|
|
418
|
-
|
|
109
|
+
doInitialize();
|
|
110
|
+
const ipc = getIpc();
|
|
419
111
|
const res = ipc!.send({ action: 'GetType', typeName });
|
|
420
112
|
return createProxy(res);
|
|
421
113
|
},
|
|
422
114
|
|
|
423
115
|
_release(id: string) {
|
|
116
|
+
const ipc = getIpc();
|
|
424
117
|
if (ipc) {
|
|
425
118
|
try { ipc!.send({ action: 'Release', targetId: id }); } catch {}
|
|
426
119
|
}
|
|
427
120
|
},
|
|
428
121
|
|
|
429
122
|
_close() {
|
|
123
|
+
const proc = getProc();
|
|
430
124
|
if (proc) proc.kill();
|
|
431
125
|
cleanup();
|
|
432
126
|
},
|
|
@@ -436,32 +130,27 @@ export const node_ps1_dotnet = {
|
|
|
436
130
|
},
|
|
437
131
|
|
|
438
132
|
_loadAssembly(assemblyName: string): any {
|
|
439
|
-
|
|
133
|
+
doInitialize();
|
|
134
|
+
const ipc = getIpc();
|
|
440
135
|
const res = ipc!.send({ action: 'LoadAssembly', assemblyName });
|
|
441
136
|
return createProxy(res);
|
|
442
137
|
},
|
|
443
138
|
|
|
444
139
|
_getRuntimeInfo(): { frameworkMoniker: string; runtimeVersion: string } {
|
|
445
|
-
if (
|
|
446
|
-
|
|
140
|
+
if (getCachedRuntimeInfo()) return getCachedRuntimeInfo()!;
|
|
141
|
+
doInitialize();
|
|
142
|
+
const ipc = getIpc();
|
|
447
143
|
const res = ipc!.send({ action: 'GetRuntimeInfo' });
|
|
448
|
-
|
|
144
|
+
const info = {
|
|
449
145
|
frameworkMoniker: res.frameworkMoniker || 'netstandard2.0',
|
|
450
146
|
runtimeVersion: res.runtimeVersion || '0.0.0'
|
|
451
147
|
};
|
|
452
|
-
|
|
148
|
+
setCachedRuntimeInfo(info);
|
|
149
|
+
return info;
|
|
453
150
|
}
|
|
454
151
|
};
|
|
455
152
|
|
|
456
|
-
|
|
457
|
-
return new Proxy({}, {
|
|
458
|
-
get: (target: any, prop: string) => {
|
|
459
|
-
if (typeof prop !== 'string') return undefined;
|
|
460
|
-
if (prop === 'then') return undefined; // 处理 Promise 检查
|
|
461
|
-
return node_ps1_dotnet._load(`${assemblyName}.${prop}`);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
}
|
|
153
|
+
setNodePs1Dotnet(() => node_ps1_dotnet);
|
|
465
154
|
|
|
466
155
|
const dotnetProxy = new Proxy(function() {} as any, {
|
|
467
156
|
get: (target: any, prop: string) => {
|
|
@@ -477,34 +166,29 @@ const dotnetProxy = new Proxy(function() {} as any, {
|
|
|
477
166
|
return node_ps1_dotnet._getRuntimeInfo().runtimeVersion;
|
|
478
167
|
}
|
|
479
168
|
if (prop === '__inspect') {
|
|
169
|
+
const ipc = getIpc();
|
|
480
170
|
return (targetId: string, memberName: string) => ipc!.send({ action: 'Inspect', targetId, memberName });
|
|
481
171
|
}
|
|
482
172
|
return node_ps1_dotnet._load(prop);
|
|
483
173
|
},
|
|
484
174
|
apply: (target: any, argArray: any[], newTarget: any) => {
|
|
485
|
-
return createNamespaceProxy(argArray[0]);
|
|
175
|
+
return createNamespaceProxy(argArray[0], node_ps1_dotnet);
|
|
486
176
|
}
|
|
487
177
|
});
|
|
488
178
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const fullName = `${namespacePrefix}.${prop}`;
|
|
498
|
-
if (cache.has(fullName)) {
|
|
499
|
-
return cache.get(fullName);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const result = node_ps1_dotnet._load(fullName);
|
|
503
|
-
cache.set(fullName, result);
|
|
504
|
-
return result;
|
|
505
|
-
}
|
|
506
|
-
});
|
|
179
|
+
export default dotnetProxy;
|
|
180
|
+
|
|
181
|
+
let _System: any;
|
|
182
|
+
export function getSystem(): any {
|
|
183
|
+
if (!_System) {
|
|
184
|
+
_System = createExportNamespaceProxy('System', node_ps1_dotnet);
|
|
185
|
+
}
|
|
186
|
+
return _System;
|
|
507
187
|
}
|
|
508
188
|
|
|
509
|
-
export
|
|
510
|
-
|
|
189
|
+
export const System = new Proxy({} as any, {
|
|
190
|
+
get: (target: any, prop: string) => {
|
|
191
|
+
if (prop === 'then') return undefined;
|
|
192
|
+
return getSystem()[prop];
|
|
193
|
+
}
|
|
194
|
+
});
|
package/src/namespace.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { getIpc } from './state.js';
|
|
2
|
+
import { createProxy } from './proxy.js';
|
|
3
|
+
|
|
4
|
+
export function createNamespaceProxy(assemblyName: string, dotnet: any) {
|
|
5
|
+
return new Proxy({}, {
|
|
6
|
+
get: (target: any, prop: string) => {
|
|
7
|
+
if (typeof prop !== 'string') return undefined;
|
|
8
|
+
if (prop === 'then') return undefined;
|
|
9
|
+
|
|
10
|
+
const fullName = `${assemblyName}.${prop}`;
|
|
11
|
+
const loaded = dotnet._load(fullName);
|
|
12
|
+
|
|
13
|
+
let typeId: string | null = null;
|
|
14
|
+
try {
|
|
15
|
+
const ref = loaded.__ref;
|
|
16
|
+
if (typeof ref === 'string' && ref.length > 0) {
|
|
17
|
+
typeId = ref;
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
if (typeId) {
|
|
22
|
+
return new Proxy({}, {
|
|
23
|
+
get: (target2: any, prop2: string) => {
|
|
24
|
+
if (typeof prop2 !== 'string') return undefined;
|
|
25
|
+
|
|
26
|
+
if (prop2 === '__ref') {
|
|
27
|
+
return typeId;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (prop2 === 'value__') {
|
|
31
|
+
const ipc = getIpc();
|
|
32
|
+
try {
|
|
33
|
+
const res = ipc!.send({ action: 'Invoke', targetId: typeId, methodName: prop2, args: [] });
|
|
34
|
+
if (res && res.type === 'primitive') {
|
|
35
|
+
return res.value;
|
|
36
|
+
}
|
|
37
|
+
} catch {}
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ipc = getIpc();
|
|
42
|
+
try {
|
|
43
|
+
const res = ipc!.send({ action: 'Invoke', targetId: typeId, methodName: prop2, args: [] });
|
|
44
|
+
if (res) {
|
|
45
|
+
if (res.type === 'primitive') {
|
|
46
|
+
return res.value;
|
|
47
|
+
}
|
|
48
|
+
if (res.type === 'ref') {
|
|
49
|
+
try {
|
|
50
|
+
const valueRes = ipc!.send({ action: 'Invoke', targetId: res.id, methodName: 'value__', args: [] });
|
|
51
|
+
if (valueRes && valueRes.type === 'primitive') {
|
|
52
|
+
return valueRes.value;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const typeNameRes = ipc!.send({ action: 'GetTypeName', targetId: res.id });
|
|
59
|
+
const typeName = typeNameRes?.typeName || '';
|
|
60
|
+
|
|
61
|
+
if (typeName && !typeName.startsWith('System.')) {
|
|
62
|
+
try {
|
|
63
|
+
const hashRes = ipc!.send({ action: 'Invoke', targetId: res.id, methodName: 'GetHashCode', args: [] });
|
|
64
|
+
if (hashRes && hashRes.type === 'primitive') {
|
|
65
|
+
const proxy = createProxy(res);
|
|
66
|
+
(proxy as any).__value = hashRes.value;
|
|
67
|
+
return proxy;
|
|
68
|
+
}
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
|
|
73
|
+
return createProxy(res);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
|
|
78
|
+
return (...args: any[]) => {
|
|
79
|
+
const ipc = getIpc();
|
|
80
|
+
const netArgs = args.map((a: any) => {
|
|
81
|
+
if (a && a.__ref) return { __ref: a.__ref };
|
|
82
|
+
return a;
|
|
83
|
+
});
|
|
84
|
+
const res = ipc!.send({ action: 'Invoke', targetId: typeId, methodName: prop2, args: netArgs });
|
|
85
|
+
return createProxy(res);
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
set: () => false,
|
|
89
|
+
apply: () => { throw new Error("Cannot call .NET enum as function"); }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return loaded;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createExportNamespaceProxy(namespacePrefix: string, dotnet: any) {
|
|
99
|
+
const cache = new Map<string, any>();
|
|
100
|
+
|
|
101
|
+
return new Proxy({} as any, {
|
|
102
|
+
get: (target: any, prop: string) => {
|
|
103
|
+
if (typeof prop !== 'string') return undefined;
|
|
104
|
+
if (prop === 'then') return undefined;
|
|
105
|
+
|
|
106
|
+
const fullName = `${namespacePrefix}.${prop}`;
|
|
107
|
+
if (cache.has(fullName)) {
|
|
108
|
+
return cache.get(fullName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = dotnet._load(fullName);
|
|
112
|
+
cache.set(fullName, result);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|