UWSCのメモリーを管理する
VirtualAllocとかHeapAllocとかしてしまうと、終了時に解放する必要がある。
どちらもプロセスメモリーだから、プロセスを終了してしまうのなら特に問題にはならないけど、UWSC本体を立ち上げっぱなしで、何回も実行するスクリプトだと、そのうちメモリーがいっぱいになる。
だから私の書くモジュールには、「Disposeしてください」という表記が目立つ。
しかし、、、これは人手でやることだろうか?
否、否、否、断じて否である。
記憶力の良い阿呆(=コンピューター)が覚えていて解放すれば良いだけのこと。
これを解決する方法を考える。
方針
プロセスメモリーはプロセスが解放されない限り存在する。
だから、プロセスメモリーに管理情報を入れれば良い。
でも、そのアドレスがわからなくなる、、、。
グローバルアトムに書いてしまう?
、、、いや、次の起動時にわからない、、、。
、、、
そうだ、メモリーマップドファイルだ!
あれなら、名前でハンドルが取れる。
プロセスが終わる時にハンドルは閉じられるし、最後のハンドルが閉じればメモリーも解放される。
メモリーマップドファイルにアドレスを入れよう。
そして、次の実行ではそこを開けば良い。
最初の実行では開いてもないから、メモリーは復元されない。
次の実行で復元される、という次第。
あとは、スクリプトの変数をメモリーに格納する方法を考える必要がある。
UWSCのほとんどの数値はVARTYPEによるとdoubleらしい。
までも少しメモリーの節約は入れよう。
配列と連想配列を関数に渡すのがやっかいだけど、[]をトリッキーに使えばなんとかなる模様。
で、作りました。
スクリプト
MemMgr.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "MemMgr.uws" THEN HASHTBL datas DIM dump = "hashtbl<#CR>", size, i = MemMgr.GetEx("datas", datas) IF i <> NULL THEN MSGBOX("Read! " + i) DIM key = INPUT("hashtbl add?") IF LENGTH(key) THEN datas[key] = INPUT("value? " + key) FOR i = 0 TO LENGTH(datas) - 1 dump = dump + datas[i, HASH_KEY] + "=" + datas[i, HASH_VAL] + "<#CR>" NEXT MemMgr.Set("datas", datas) MSGBOX(dump) IF MSGBOX("Dispose?", BTN_YES OR BTN_NO) = BTN_YES THEN MemMgr.Dispose() ENDIF MODULE MemMgr DEF_DLL CreateFileMappingW(DWORD,DWORD,DWORD,DWORD,DWORD,wstring): DWORD: kernel32 DEF_DLL OpenFileMappingW(DWORD,BOOL,wstring): DWORD: kernel32 DEF_DLL CloseHandle(DWORD): BOOL: kernel32 DEF_DLL MapViewOfFile(DWORD,DWORD,DWORD,DWORD,DWORD): DWORD: kernel32 DEF_DLL UnmapViewOfFile(DWORD): BOOL: kernel32 DEF_DLL VirtualAlloc(DWORD, DWORD, DWORD, DWORD): DWORD: kernel32 DEF_DLL VirtualFree(DWORD, DWORD, DWORD): bool: kernel32 DEF_DLL GetProcessHeap(): DWORD: kernel32 DEF_DLL HeapAlloc(DWORD,DWORD,DWORD): DWORD: kernel32 DEF_DLL HeapFree(DWORD,DWORD,DWORD): BOOL: kernel32 DEF_DLL HeapReAlloc(DWORD,DWORD,DWORD,DWORD): DWORD: kernel32 CONST DEF_NAME = "UwscMemMgr" CONST MNGR_NAME = "UwscMemoryManagerReserveAreaName" CONST DEF_SIZE = 8 CONST HEAP_ALLOC_SIZE = 2048 CONST VAR_HASH = VAR_ARRAY * 2 // 勝手定義 HASHTBL _hs, _szs, _ms, _allocs, _sets DIM _alloc = NULL, _sz = 0, _heap = NULL, _mngr = FALSE DIM _dummy[0] PROCEDURE Dispose() WHILE LENGTH(_hs) Close(_hs[LENGTH(_hs)-1, HASH_KEY], FALSE) WEND WHILE LENGTH(_szs) Close(_szs[LENGTH(_szs)-1, HASH_KEY], FALSE) WEND WHILE LENGTH(_ms) Close(_ms[LENGTH(_ms)-1, HASH_VAL], FALSE) WEND WHILE LENGTH(_sets) Remove(_sets[0, HASH_KEY]) WEND WHILE LENGTH(_allocs) Free(VAL(_allocs[0, HASH_KEY])) WEND FEND FUNCTION Open(sz=DEF_SIZE, name=DEF_NAME, local=TRUE, bCreate=TRUE) OpenMngr() name = GetName(name, local) DIM hMem = 0 IFB _hs[name, HASH_EXISTS] THEN hMem = _hs[name] ELSEIF name <> NULL THEN hMem = OpenFileMappingW(2, FALSE, name) IF !hMem AND bCreate THEN hMem = CreateFileMappingW(-1, 0, 4, INT(sz / $100000000), sz MOD $100000000, name) IFB hMem <> 0 AND hMem <> _hs[name] THEN _hs[name] = hMem _szs[name] = sz ENDIF ENDIF RESULT = 0 IF hMem THEN RESULT = MapViewOfFile(hMem, 2, 0, 0, sz) IF RESULT THEN _ms[RESULT] = name UpdateMngr() FEND PROCEDURE Close(name=DEF_NAME, local=TRUE, ptr=0) OpenMngr() name = GetName(name, local) DIM res, i = 0 IFB ptr THEN UnmapViewOfFile(ptr) res = _ms[ptr, HASH_REMOVE] res = TRUE FOR i = 0 TO LENGTH(_ms) - 1 IFB _ms[i, HASH_VAL] = name THEN res = FALSE BREAK ENDIF NEXT ELSEIF name <> NULL THEN WHILE (i < LENGTH(_ms)) IFB _ms[i, HASH_VAL] = name THEN ptr = VAL(_ms[i, HASH_KEY]) res = _ms[ptr, HASH_REMOVE] UnmapViewOfFile(ptr) ELSE i = i + 1 ENDIF WEND res = TRUE ENDIF IFB res AND name <> NULL THEN IFB _hs[name, HASH_EXISTS] THEN CloseHandle(_hs[name]) res = _hs[name, HASH_REMOVE] ENDIF IF _szs[name, HASH_EXISTS] THEN res = _szs[name, HASH_REMOVE] ENDIF UpdateMngr() FEND FUNCTION Size(ptr=0, name=DEF_NAME, local=TRUE) OpenMngr() IF ptr AND _ms[ptr, HASH_EXISTS] THEN name = _ms[ptr] ELSE name = GetName(name, local) RESULT = 0 IF _szs[name, HASH_EXISTS] THEN RESULT = _szs[name] IF _allocs[ptr, HASH_EXISTS] THEN RESULT = _allocs[ptr] FEND FUNCTION GetName(name=DEF_NAME, local=TRUE) RESULT = name IF local AND name <> NULL THEN RESULT = name + STATUS(GETID(GET_THISUWSC_WIN), ST_PROCESS) FEND FUNCTION Read(ptr, bFree=TRUE, def=NULL, body=TRUE) RESULT = ReadEx(ptr, 0, _dummy, bFree, def, body) FEND FUNCTION ReadEx(ptr, var size, var hash[], bFree=TRUE, def=NULL, body=TRUE) // ptrにはSetされたものが入っている想定 RESULT = def DIM type, i = 8, j = 0, s IFB ptr <> NULL AND ptr > 0 THEN DEF_DLL RtlMoveMemory(var DWORD,DWORD,DWORD): kernel32 RtlMoveMemory(type, ptr, 4) RtlMoveMemory(size, ptr + 4, 4) ELSE EXIT ENDIF IFB body THEN SELECT type CASE VAR_EMPTY RESULT = EMPTY CASE VAR_NULL RESULT = NULL CASE VAR_INTEGER RESULT = 0 DEF_DLL RtlMoveMemory(var int,DWORD,DWORD): kernel32 RtlMoveMemory(RESULT, ptr + i, size - i) RESULT = VARTYPE(RESULT, type) CASE VAR_DOUBLE RESULT = 0 DEF_DLL RtlMoveMemory(var double,DWORD,DWORD): kernel32 RtlMoveMemory(RESULT, ptr + i, size - i) CASE VAR_BSTR RESULT = FORMAT(CHR(0), size - i) DEF_DLL RtlMoveMemory(var string,DWORD,DWORD): kernel32 RtlMoveMemory(RESULT, ptr + i, size - i) CASE VAR_BOOLEAN, VAR_DWORD RESULT = 0 DEF_DLL RtlMoveMemory(var DWORD,DWORD,DWORD): kernel32 RtlMoveMemory(RESULT, ptr + i, size - i) RESULT = VARTYPE(RESULT, type) CASE VAR_HASH RESULT = VAR_HASH IFB VarTypeEx(hash) = VAR_HASH THEN DEF_DLL RtlMoveMemory(var DWORD,DWORD,DWORD): kernel32 RtlMoveMemory(j, ptr + i, 4) i = i + 4 DIM key, val FOR j = j TO 1 STEP -1 key = ReadEx(ptr + i, s, _dummy, FALSE, def) i = i + s val = ReadEx(ptr + i, s, _dummy, FALSE, def) i = i + s hash[key] = val NEXT ENDIF DEFAULT IFB type >= VAR_ARRAY THEN s = 0 DEF_DLL RtlMoveMemory(var DWORD,DWORD,DWORD): kernel32 RtlMoveMemory(s, ptr + i, 4) i = i + 4 RESULT = VarTypeEx(hash) IFB RESULT >= VAR_ARRAY AND (LENGTH(hash) >= s OR RESULT = VAR_HASH) THEN FOR j = 0 TO s - 1 hash[j] = ReadEx(ptr + i, s, _dummy, FALSE, def) i = i + s NEXT ELSE RESULT = SAFEARRAY(0, s - 1) FOR j = 0 TO s - 1 RESULT[j] = ReadEx(ptr + i, s, _dummy, FALSE, def) i = i + s NEXT ENDIF ENDIF SELEND ELSE RESULT = size ENDIF IF bFree THEN Free(ptr) FEND FUNCTION WriteP(val, ptr=NULL) // ptr は NULLなら確保して、ポインターを返す // NULL以外なら、CalcSize(val)の大きさがあるものとする DIM type = VarTypeExP(val), size = CalcSizeP(val), i = 8, j RESULT = size IFB RESULT > 0 THEN IFB ptr = NULL THEN ptr = Alloc(size) RESULT = ptr ENDIF DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr, type, 4) RtlMoveMemory(ptr + 4, size, 4) SELECT type CASE VAR_EMPTY, VAR_NULL // nop CASE VAR_INTEGER DEF_DLL RtlMoveMemory(DWORD,var int,DWORD): kernel32 RtlMoveMemory(ptr + i, val, size - i) CASE VAR_DOUBLE DEF_DLL RtlMoveMemory(DWORD,var double,DWORD): kernel32 RtlMoveMemory(ptr + i, val, size - i) CASE VAR_BSTR DEF_DLL RtlMoveMemory(DWORD,var string,DWORD): kernel32 RtlMoveMemory(ptr + i, val, size - i) CASE VAR_BOOLEAN, VAR_DWORD DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr + i, val, size - i) DEFAULT IFB type >= VAR_ARRAY THEN DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr + i, LENGTH(val), 4) i = i + 4 FOR j = 0 TO LENGTH(val) - 1 i = i + WriteP(val[j], ptr + i) NEXT ELSE RESULT = FALSE ENDIF SELEND ENDIF FEND FUNCTION Write(val[], ptr=NULL) // ptr は NULLなら確保して、ポインターを返す // NULL以外なら、CalcSize(val)の大きさがあるものとする DIM type = VarTypeEx(val), size = CalcSize(val), i = 8, j RESULT = size IFB RESULT > 0 THEN IFB ptr = NULL THEN ptr = Alloc(size) RESULT = ptr ENDIF DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr, type, 4) RtlMoveMemory(ptr + 4, size, 4) SELECT type CASE VAR_HASH DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr + i, LENGTH(val), 4) i = i + 4 FOR j = 0 TO LENGTH(val) - 1 i = i + WriteP(val[j, HASH_KEY], ptr + i) i = i + WriteP(val[j, HASH_VAL], ptr + i) NEXT DEFAULT IFB type >= VAR_ARRAY THEN DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr + i, LENGTH(val), 4) i = i + 4 FOR j = 0 TO LENGTH(val) - 1 i = i + WriteP(val[j], ptr + i) NEXT ELSE i = WriteP(val, ptr) IFB i <> size THEN IF RESULT = ptr THEN Free(ptr) RESULT = FALSE ENDIF ENDIF SELEND ENDIF FEND FUNCTION CalcSizeP(val) RESULT = 8 // 型は、VAR_EMPTY(0) VAR_NULL(1) VAR_DOUBLE(5) VAR_BSTR(8) VAR_BOOLEAN(11) VAR_ARRAY($2000) DIM type = VarTypeExP(val), i SELECT type CASE VAR_EMPTY, VAR_NULL // nop CASE VAR_DOUBLE RESULT = RESULT + 8 CASE VAR_BSTR RESULT = RESULT + LENGTHB(val) CASE VAR_BOOLEAN, VAR_DWORD, VAR_INTEGER RESULT = RESULT + 4 DEFAULT IFB type >= VAR_ARRAY THEN RESULT = RESULT + 4 FOR i = 0 TO LENGTH(val) - 1 type = CalcSizeP(val[i]) IFB type < 0 THEN RESULT = type BREAK ENDIF RESULT = RESULT + type NEXT ELSE RESULT = -1 ENDIF SELEND FEND FUNCTION CalcSize(val[]) RESULT = 8 // 型は、VAR_EMPTY(0) VAR_NULL(1) VAR_DOUBLE(5) VAR_BSTR(8) VAR_BOOLEAN(11) VAR_ARRAY($2000) DIM type = VarTypeEx(val), i SELECT type CASE VAR_HASH RESULT = RESULT + 4 FOR i = 0 TO LENGTH(val) - 1 type = CalcSizeP(val[i, HASH_KEY]) IFB type < 0 THEN RESULT = type BREAK ENDIF RESULT = RESULT + type type = CalcSizeP(val[i, HASH_VAL]) IFB type < 0 THEN RESULT = type BREAK ENDIF RESULT = RESULT + type NEXT DEFAULT IFB type >= VAR_ARRAY THEN RESULT = RESULT + 4 FOR i = 0 TO LENGTH(val) - 1 type = CalcSizeP(val[i]) IFB type < 0 THEN RESULT = type BREAK ENDIF RESULT = RESULT + type NEXT ELSE RESULT = CalcSizeP(val) ENDIF SELEND FEND FUNCTION VarTypeExP(val) RESULT = VARTYPE(val) IFB RESULT = VAR_DOUBLE THEN IFB val = INT(val) THEN IF val >= 0 AND val < 4294967296 THEN RESULT = VAR_DWORD IF val < 0 AND val > -2147483649 THEN RESULT = VAR_INTEGER ENDIF ELSEIF RESULT = 72 OR RESULT = 256 THEN // http://www3.bigcosmic.com/board/s/board.cgi?id=umiumi&mode=all&no=2132&log=ON&cnt=18 RESULT = VAR_BSTR ENDIF FEND FUNCTION VarTypeEx(val[]) TRY RESULT = VarTypeExP(val) EXCEPT IFB POS("が定義されていません", TRY_ERRMSG) THEN RESULT = VAR_HASH ELSE RESULT = VAR_VARIANT + VAR_ARRAY ENDIF ENDTRY FEND FUNCTION Get(name, def=NULL) RESULT = GetEx(name, _dummy, def) FEND FUNCTION GetEx(name, var hash[], def=NULL) OpenMngr() RESULT = def IFB _sets[name, HASH_EXISTS] THEN DIM size RESULT = ReadEx(_sets[name], size, hash, FALSE, def) ENDIF FEND FUNCTION SetP(name, val) OpenMngr() _mngr = TRUE IF _sets[name, HASH_EXISTS] THEN Free(_sets[name]) _sets[name] = WriteP(val) RESULT = (_sets[name] > 0) _mngr = FALSE UpdateMngr() FEND FUNCTION Set(name, val[]) OpenMngr() _mngr = TRUE IF _sets[name, HASH_EXISTS] THEN Free(_sets[name]) _sets[name] = Write(val) RESULT = (_sets[name] > 0) _mngr = FALSE UpdateMngr() FEND FUNCTION Remove(name) OpenMngr() _mngr = TRUE RESULT = FALSE IFB _sets[name, HASH_EXISTS] THEN DIM ptr = _sets[name] RESULT = _sets[name, HASH_REMOVE] Free(ptr) ENDIF _mngr = FALSE UpdateMngr() FEND FUNCTION OpenMngr() RESULT = FALSE IF _mngr OR _alloc <> NULL THEN EXIT _mngr = TRUE DIM ptr = Open(DEF_SIZE, MNGR_NAME, TRUE, FALSE) _alloc = 0 IFB ptr THEN RESULT = TRUE DEF_DLL RtlMoveMemory(var DWORD,DWORD,DWORD): kernel32 RtlMoveMemory(_alloc, ptr, 4) RtlMoveMemory(_sz, ptr + 4, 4) Close(MNGR_NAME, TRUE) ENDIF DIM res, i = 0, size IFB _alloc <> NULL AND _alloc <> 0 THEN res = ReadEx(_alloc + i, size, _hs, FALSE) IF res = VAR_HASH THEN i = i + size res = ReadEx(_alloc + i, size, _szs, FALSE) IF res = VAR_HASH THEN i = i + size res = ReadEx(_alloc + i, size, _ms, FALSE) IF res = VAR_HASH THEN i = i + size res = ReadEx(_alloc + i, size, _allocs, FALSE) IF res = VAR_HASH THEN i = i + size res = ReadEx(_alloc + i, size, _sets, FALSE) IF res = VAR_HASH THEN i = i + size ENDIF _mngr = FALSE FEND PROCEDURE UpdateMngr() IF _mngr OR _alloc = NULL THEN EXIT _mngr = TRUE DIM hs = CalcSize(_hs), szs = CalcSize(_szs), ms = CalcSize(_ms), as = CalcSize(_allocs) DIM sz = hs + szs + ms + as + CalcSize(_sets), bUp = FALSE IFB _sz < sz THEN VirtualFree(_alloc, 0, $8000) _alloc = VirtualAlloc(0, sz, $1000, 4) _sz = sz bUp = TRUE ENDIF Write(_hs, _alloc) Write(_szs, _alloc + hs) Write(_ms, _alloc + hs + szs) Write(_allocs, _alloc + hs + szs + ms) Write(_sets, _alloc + hs + szs + ms + as) IFB bUp THEN DIM ptr = Open(DEF_SIZE, MNGR_NAME, TRUE, FALSE) IFB !ptr THEN ptr = Open(DEF_SIZE, MNGR_NAME, TRUE) bUp = FALSE ENDIF IFB ptr THEN DEF_DLL RtlMoveMemory(DWORD,var DWORD,DWORD): kernel32 RtlMoveMemory(ptr, _alloc, 4) RtlMoveMemory(ptr + 4, _sz, 4) IF bUp THEN Close(MNGR_NAME, TRUE) ENDIF ENDIF _mngr = FALSE FEND FUNCTION Alloc(size, ptr=NULL, pro=4, hf=8) // 4:PAGE_READWRITE 8:HEAP_ZERO_MEMORY RESULT = NULL OpenMngr() IF _heap = NULL THEN _heap = GetProcessHeap() DIM sz = HEAP_ALLOC_SIZE IFB ptr <> NULL THEN IF _allocs[ptr, HASH_EXISTS] THEN sz = _allocs[ptr] IFB size < HEAP_ALLOC_SIZE AND sz < HEAP_ALLOC_SIZE THEN RESULT = _allocs[ptr, HASH_REMOVE] RESULT = HeapReAlloc(_heap, hf, ptr, size) ptr = NULL ENDIF ENDIF IFB RESULT = NULL THEN IFB size < HEAP_ALLOC_SIZE THEN RESULT = HeapAlloc(_heap, hf, size) ELSE RESULT = VirtualAlloc(0, size, $1000, pro) // $1000:MEM_COMMIT ENDIF ENDIF IF RESULT THEN _allocs[RESULT] = size IFB ptr <> NULL THEN DIM res = _allocs[ptr, HASH_REMOVE] IF size < sz THEN sz = size DEF_DLL RtlMoveMemory(DWORD, DWORD, DWORD): kernel32 RtlMoveMemory(RESULT, ptr, sz) Free(ptr) ENDIF UpdateMngr() FEND FUNCTION Free(ptr) OpenMngr() IF _heap = NULL THEN _heap = GetProcessHeap() DIM sz = HEAP_ALLOC_SIZE IFB _allocs[ptr, HASH_EXISTS] THEN sz = _allocs[ptr] RESULT = _allocs[ptr, HASH_REMOVE] ENDIF IFB sz < HEAP_ALLOC_SIZE THEN RESULT = HeapFree(_heap, 0, VAL(ptr)) ELSE RESULT = VirtualFree(ptr, 0, $8000) ENDIF UpdateMngr() FEND ENDMODULE
お試しは、サンプルコードがあるので、そのまま実行してください。
INPUTに何か入れると、それをキーとし次のINPUTを値として、連想配列に格納します。
Disposeしなければ、次の起動で読み込みが行われます。
Disposeすると破棄されるので、読み込むものがなくなります。
使い方
例えば、modeという変数をプロセス起動中保持したい場合(初期値:258)
スクリプトの最初で
mode = MemMgr.Get("mode", 258)
とする。
保存済みであれば保存値が、保存値がなければ258が返ります。
で、modeを保持したくなったら
MemMgr.Set("mode", mode)
Setしておけば、次にGetした値が更新されます。
modeが不要になったら
MemMgr.Remove("mode")
しといてください。(通常必要なし)
ただし、値を直接Setしたい場合は、SetPしてください。
MemMgr.SetP("mode", 111)
Setではエラーになります。(連想配列のキーや値、配列の中身も)
配列や連想配列の場合もSetが使えます。
ただ、取得の際は、GetExを使います。
MemMgr.GetEx("mode", hash)
取得できたか知りたい場合は、リターンがNULL(または第三引数)でないことを確認してください。
雑感
プロセス起動中に覚えておきたいことは、これでOK。
OS起動中ずっと覚えておきたいことは、少しならグローバルアトムか。
(大量なら、グローバルアトム+ファイルか)
電源切っても覚えておきたいなら、ファイルに書いてください。
モジュール変数はDIM宣言だと、他スクリプトからは見えないけど、stuncloudさんや私が作ったInterpreterからだと見えます!
UWSCにPrivateは存在しないのです。
InterpreterでCALLしとけば、モジュールの動作確認が楽チンだったりします!
連想配列や配列を判定するVarTypeExでダサいことしてます。
UWSCの仕様が変わったら、変更が必要になりそうです。
また、VARTYPEの戻り値はヘルプにない文字列の定義があるようです。
p for primitive
関数の引数に[]をつけると、変数ならなんでも受け付けるけど、即値等がダメ。
そこがなんとかなれば、Pのついた関数は統合可能。