[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『VBA unicode結合文字の取扱いについて』(くりかぼちゃ)
VBA unicode結合文字の取扱いについて
特定のフォルダ下にある全てのワードファイル名をシートに書き出したいと思い,以下のコードを書きました。
Range("A2:B200").Clear
Dim fp As String
fp = "C:\Use...(略)\ファイル名取得" & "\"
'〜.docxのファイルのみを検索
Dim buf As String
buf = Dir(fp & "*.docx")
'ファイル名を出力
Dim cnt As Long
Do While buf <> ""
cnt = cnt + 1 Cells(cnt + 1, 1) = buf buf = Dir() Loop
動きはしたのですが,出力されたファイル名の「バ」が「バ」と出ることもあれば「ハ゛」と出ることもあります。
調べたところ見た目上同じ「バ」でもunicode上では2とおりの表記法があることがわかり,?@の書き方ですと出力したセル上の出力が「ハ゛」になるようです。
?@ バ \u30cf\u3099
?A バ \u30d0
その状態でセルに出力された「ハ゛」入りのファイル名でフォルダ下を検索して次の動作をしようとしたら,ファイルがヒットしません。
セル上の表記では,
?B ハ゛ \u30cf\u309b
として認識されているようです。
なぜ?@が勝手に?Bに変換されてセルに出力されてしまうのでしょうか?
?@を?@のままセルに出力するいい方法はないでしょうか。
動作としては,A列にファイル名一覧出力→B列に挿入したい文字列を入力→各ファイルに個別の文字を挿入 と言った動作を目指しているので,ファイル名一覧出力の動作は省けません。
調べたところJAVAやC#にはNormalizeと言う関数があるそうで,それに近い動きの関数があればいいのですが,見つけられませんでした...
もし単純で根本的な間違いがあったり,頓珍漢なこと書いていたら申し訳ないです。
ご教示お願いいたします。
< 使用 Excel:Microsoft365、使用 OS:Windows11 >
の「このグリフで引用している他のグリフ一覧」で u3099 - GlyphWiki https://glyphwiki.org/wiki/u3099 がリンクされていますね。
まあ現象そのものについては「そういうものなんだ」としか云い様がないですが、 少なくとも「Dir」の様なそもそもunicodeの事を考慮していない機能を使用すべきではないと思いますよ。
(白茶) 2023/11/08(水) 10:14:20
以前、別の掲示板で回答したものです。 正規化の参考になると思います。
https://excelfactory.net/excelboard/excelvba/excel.cgi?mode=all&namber=195659&rev=0
(まる2021) 2023/11/08(水) 11:53:01
https://learn.microsoft.com/ja-jp/windows/win32/api/winnls/nf-winnls-normalizestring
わたしも検索するときには(念のため)両方の文字列を正規化して比較したことはありますが。。。
ただ、それに関する記事もネット上にはあまり多くないなあと感じます。
(ゆたか) 2023/11/08(水) 14:34:55
(隠居Z) 2023/11/09(木) 09:25:10
使われる文字コードは時代とともに変わっってきていて、昔だったらASCIIとShiftJISだけだったので、あまり問題はなかったような(ShiftJISが1文字を表すのに可変長でなんたらって話はありましたが)
今回の話はUnicodeでは同じ文字を表すのに複数のコードが使用できるという話なので、Unicodeが使われていない昔は関係ないと思いますが、今だったら関係あるかも知れませんね。
Unicodeも時代とともにどんどん拡張されて、というか仕様が変更されて、ややこしいですね。しかも、その実装方法はいろいろある?
すでにメモ帳で意識せずにテキストファイルを保存すると中身はUnicode(UTF-8)になるようです。メモ帳にも右下にそう表示されていますが。
ただ、現在でも多くの場合、Windowsでのファイル名にはASCIIとShiftJISだけが使われているのではないかなと思うのですが、これからUnicodeが使われ始めると、さらに混乱が起こるような気もします。
ただね、昔から文字コードってややこしかったんです。わたしはオフコンとかメインフレームも触ってましたが、理解不能でした(笑)
参考
https://www.momopoem.com/?p=919
(ゆたか) 2023/11/09(木) 16:51:04
(隠居Z) 2023/11/10(金) 11:58:10
古いシステムのファイルも普通に読めるので意識していませんでしたが、ファイルシステムのフォーマットによって使うコードが違っているみたいです。OSやアプリが勝手に変換してくれているのですね。あるいは、アプリはあまり意識しなくても大丈夫なのかな。
https://learn.microsoft.com/ja-jp/windows/win32/intl/character-sets-used-in-file-names
(ゆたか) 2023/11/10(金) 12:17:35
(隠居Z) 2023/11/10(金) 12:59:10
情報提供だけ ^^;
>悩んだ結果,FileSystemObjectでファイルを引っ張って来てunicodeを保ったままセルに出力
APIの[FindFirstFileEx]を使ったら、Dir関数並み(かそれ以上)に高速で且つUnicode文字のまま列挙が出来ますよ。 ご興味おありでしたら、一度調べてみると宜しいかと思います。
VBAでファイルリストを高速に取得する関数を自作する part3 - えくせるちゅんちゅん
https://www.excel-chunchun.com/entry/2019/04/24/215335
VBAでファイルリストを高速に取得する関数を自作する part4 - えくせるちゅんちゅん
https://www.excel-chunchun.com/entry/GetFileFolderList_04
私も「試しに1回使ってみた」程度ですけど、 ネットワーク越しのファイル列挙ではびっくりする程の差が出ました。(ローカルではあんまり気にならないです) 要するにFileSystemObjectが異常に遅い。
確か200程度のファイル名取得で、 Dirで1秒、FindFirstFileExで0.6秒、FileSystemObjectだけ20秒くらいだったと記憶しています。
Dirは取得結果にUnicodeの文字化けを含んでますから、実質FindFirstFileExの一人勝ちですね。
(白茶) 2023/11/10(金) 16:51:33
自学ノート漁ったら当時のログ発見。^^;
>>Dirで1秒、FindFirstFileExで0.6秒、FileSystemObjectだけ20秒くらい いやー、そんなもんじゃなかったっス。
Dirで0.17秒、FindFirstFileExで0.07秒、FileSystemObjectが19.25秒だった模様。 (検出ファイル数183個)
(白茶) 2023/11/10(金) 18:16:25
くりかぼちゃ さん 板、お借りしてすみませんm(__)m
ありがとう御座いました。
(隠居Z) 2023/11/10(金) 18:48:14
他人が読める程度には体裁整えたので、自学ノート晒しておきます。 (せっかく発掘したんで自己満投下w)
〜めあて〜 「APIのFindFirstFileExを使って[DirW関数]を作ってみよう」 「Dir関数やFileSystemObjectと競争してみよう」 「Attributes設定の気持ち悪さを把握しておこう」 ・・・くらいの気持ちで書いた(んだと思う)
Option Explicit #If False Then Dim FINDEX_INFO_LEVELS Const FindExInfoStandard = 0 Const FindExInfoBasic = 1 Const FindExInfoMaxInfoLevel = 2 Dim FINDEX_SEARCH_OPS Const FindExSearchNameMatch = 0 Const FindExSearchLimitToDirectories = 1 Const FindExSearchLimitToDevices = 2 Const FindExSearchMaxSearchOp = 3 #End If #Const GETATTR_TEST = 0 Rem ********************************************************************************************************************************************************************************************************* Private Enum FINDEX_INFO_LEVELS '返されるデータの情報レベルを指定するために、FindFirstFileEx 関数で使用する値を定義します。 FindExInfoStandard = 0 '属性情報の標準セット。 FindExInfoBasic = 1 'FindFirstFileEx 関数は短いファイル名に対してクエリを実行しないため、全体的な列挙速度が向上します。 FindExInfoMaxInfoLevel = 2 'この値は検証に使用されます。 サポートされている値がこの値より小さい。 End Enum Private Enum FINDEX_SEARCH_OPS '実行するフィルター処理の種類を指定するために、FindFirstFileEx 関数で使用する値を定義します。 FindExSearchNameMatch = 0 '指定したファイル名と一致するファイルを検索します。 FindExSearchLimitToDirectories = 1 'これはアドバイザリ フラグです。この検索値を使用する場合、FindFirstFileEx 関数の lpSearchFilter パラメーターは NULL である必要があります。 FindExSearchLimitToDevices = 2 'このフィルター処理の種類は使用できません。 FindExSearchMaxSearchOp = 3 'この値は検証に使用されます。 サポートされている値がこの値より小さい。 End Enum Rem dwAdditionalFlags Private Const FIND_FIRST_EX_CASE_SENSITIVE = 1 '検索では大文字と小文字が区別されます。 Private Const FIND_FIRST_EX_LARGE_FETCH = 2 'ディレクトリ クエリに大きなバッファーを使用します。これにより、検索操作のパフォーマンスが向上する可能性があります。 Private Const FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = 4 '結果をディスク上の物理的なファイルに制限します。 このフラグは、ファイル仮想化フィルターが存在する場合にのみ関連します。 Private Type FILETIME LowDateTime As Long HighDateTime As Long End Type Private Type WIN32_FIND_DATA dwFileAttributes As Long ' ファイル属性 ftCreationTime As FILETIME ' 作成日 ftLastAccessTime As FILETIME ' 最終アクセス日 ftLastWriteTime As FILETIME ' 最終更新日 nFileSizeHigh As Long ' ファイルサイズ(上位32ビット) nFileSizeLow As Long ' ファイルサイズ(下位32ビット) dwReserved0 As Long ' 予約済み。リパースタグ dwReserved1 As Long ' 予約済み。未使用 cFileName(260 * 2 - 1) As Byte ' ファイル名 cAlternateFileName(14 * 2 - 1) As Byte ' 8.3形式のファイル名 End Type Private Declare PtrSafe Function FindFirstFileEx Lib "kernel32" Alias "FindFirstFileExW" (ByVal lpFileName As LongPtr, ByVal fInfoLevelId As FINDEX_INFO_LEVELS, lpFindFileData As WIN32_FIND_DATA, _ ByVal fSearchOp As FINDEX_SEARCH_OPS, ByVal lpSearchFilter As LongPtr, ByVal dwAdditionalFlags As Long) As LongPtr Private Const INVALID_HANDLE_VALUE = -1 Rem FindFirstFileExA 関数 (fileapi.h) - Win32 apps | Microsoft Learn Rem https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-findfirstfileexa Rem FindFirstFileExW 関数 (fileapi.h) - Win32 apps | Microsoft Learn Rem https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-findfirstfileexw Rem Rem lpFileName ディレクトリまたはパス、およびファイル名。 ファイル名には、アスタリスク (*) や疑問符 (?) などのワイルドカード文字を含めることができます。 Rem 既定では、名前はMAX_PATH文字に制限されています。 この制限を 32,767 文字のワイド文字に拡張するには、パスの先頭に "\\?\" を付加します。 Rem fInfoLevelId 返されるデータの情報レベル。 Rem lpFindFileData ファイル データを受信するバッファーへのポインター。 Rem fSearchOp ワイルドカード一致とは異なる、実行するフィルター処理の種類。 Rem lpSearchFilter 指定した fSearchOp に構造化された検索情報が必要な場合は、検索条件へのポインター。現時点では、サポートされている fSearchOp 値のいずれも拡張検索情報を必要としません。 したがって、このポインターは NULL である必要があります。 Rem dwAdditionalFlags 検索を制御する追加のフラグを指定します。 Rem 戻り値 関数が成功した場合、戻り値は FindNextFile または FindClose の後続の呼び出しで使用される検索ハンドルであり、 lpFindFileData パラメーターには最初に見つかったファイルまたはディレクトリに関する情報が含まれます。 Rem lpFileName パラメーター内の検索文字列からファイルを検索できないか、関数が失敗した場合、戻り値はINVALID_HANDLE_VALUEされ、lpFindFileData の内容は不確定になります。
Private Declare PtrSafe Function FindNextFile Lib "kernel32" Alias "FindNextFileW" (ByVal hFindFile As LongPtr, lpFindFileData As WIN32_FIND_DATA) As Long Rem FindNextFileW 関数 (fileapi.h) - Win32 apps | Microsoft Learn Rem https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-findnextfilew Rem Rem hFindFile FindFirstFile または FindFirstFileEx 関数の以前の呼び出しによって返される検索ハンドル。 Rem lpFindFileData 見つかったファイルまたは サブディレクトリに関 する情報を受け取るWIN32_FIND_DATA構造体へのポインター。 Rem 戻り値 関数が成功した場合、戻り値は 0 以外で、 lpFindFileData パラメーターには次に見つかったファイルまたはディレクトリに関する情報が含まれます。 Rem 関数が失敗した場合、戻り値は 0 で、 lpFindFileData の内容は不確定になります。
Private Declare PtrSafe Function FindClose Lib "kernel32" (ByVal hFindFile As LongPtr) As Long Rem FindClose 関数 (fileapi.h) - Win32 apps | Microsoft Learn Rem https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-findclose Rem Rem hFindFile ファイル検索ハンドル。 Rem 戻り値 関数が成功すると、戻り値は 0 以外になります。関数が失敗した場合は、0 を返します。 Rem
#If GETATTR_TEST Then Private Declare PtrSafe Function GetFileAttributes Lib "kernel32" Alias "GetFileAttributesW" (ByVal lpFileName As LongPtr) As Long Private Const INVALID_FILE_ATTRIBUTES = -1 Rem GetFileAttributesW 関数 (fileapi.h) - Win32 apps | Microsoft Learn Rem https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-getfileattributesw Rem 指定したファイルまたはディレクトリのファイル システム属性を取得します。 Rem Rem lpFileName ファイルまたはディレクトリの名前。既定では、名前はMAX_PATH文字に制限されています。 この制限を 32,767 文字のワイド文字に拡張するには、パスの先頭に "\\?\" を付加します。 Rem 戻り値 関数が成功した場合、戻り値には指定されたファイルまたはディレクトリの属性が含まれます。関数が失敗した場合、戻り値は INVALID_FILE_ATTRIBUTES。 #End If
'Type SYSTEMTIME ' wYear As Integer ' wMonth As Integer ' wDayOfWeek As Integer ' wDay As Integer ' wHour As Integer ' wMinute As Integer ' wSecond As Integer ' wMilliseconds As Integer 'End Type 'Private Declare PtrSafe Function FileTimeToSystemTime Lib "kernel32" (lpFileTime As FILETIME, lpSystemTime As SYSTEMTIME) As Long 'Rem FileTimeToSystemTime 関数 (timezoneapi.h) - Win32 apps | Microsoft Learn 'Rem https://learn.microsoft.com/ja-jp/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime 'Rem ファイル時刻をシステム時刻形式に変換します。 システム時刻は協定世界時 (UTC) に基づいています。 'Rem 'Rem lpFileTime システム (UTC) の日付と時刻の形式に変換されるファイル時刻を含む FILETIME 構造体へのポインター。 'Rem lpSystemTime 変換されたファイル時刻を受け取る SYSTEMTIME 構造体へのポインター。 'Rem 戻り値 関数が成功すると、戻り値は 0 以外になります。関数が失敗した場合は、0 を返します。 Rem *********************************************************************************************************************************************************************************************************
Public Function DirW(Optional PathName = "", Optional Attributes As VbFileAttribute = vbNormal) As String Static h As LongPtr, a2 As VbFileAttribute Dim p As String, d As WIN32_FIND_DATA, n As String Dim a1 As VbFileAttribute If Len(PathName) Then If h Then Call FindClose(h) h = 0& End If p = PathName If p Like "\\*" Then p = "\\?\UNC\" & Mid$(p, 3) Else p = "\\?\" & p h = FindFirstFileEx(StrPtr(p), FindExInfoBasic, d, FindExSearchNameMatch, 0&, FIND_FIRST_EX_LARGE_FETCH) If h = INVALID_HANDLE_VALUE Then h = 0& Exit Function End If a2 = (Attributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly Else If h = 0& Then Exit Function If FindNextFile(h, d) = 0 Then Call FindClose(h) h = 0& Exit Function End If End If Do n = Left$(d.cFileName, InStr(d.cFileName, Chr(0)) - 1) a1 = (d.dwFileAttributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly If ((a1 And a2) = a1) Or (a1 <= vbReadOnly) Then DirW = n Exit Function End If If FindNextFile(h, d) = 0 Then Exit Do Loop Call FindClose(h) h = 0& End Function #If GETATTR_TEST Then Public Function GetAttrW(PathName As String) As VbFileAttribute Dim p As String p = PathName If p Like "\\*" Then p = "\\?\UNC\" & Mid$(p, 3) Else p = "\\?\" & p GetAttrW = GetFileAttributes(StrPtr(p)) If GetAttrW = INVALID_FILE_ATTRIBUTES Then GetAttrW = Null Else GetAttrW = GetAttrW And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory Or vbArchive Or vbAlias) End If End Function #End If Rem 比較テスト用_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ Public Function DirColl(PathName, Optional Attributes As VbFileAttribute = vbNormal) As Collection Dim c As Collection, p As String, h As LongPtr, d As WIN32_FIND_DATA, n As String Dim a1 As VbFileAttribute, a2 As VbFileAttribute p = PathName If p Like "\\*" Then p = "\\?\UNC\" & Mid$(p, 3) Else p = "\\?\" & p h = FindFirstFileEx(StrPtr(p), FindExInfoBasic, d, FindExSearchNameMatch, 0&, FIND_FIRST_EX_LARGE_FETCH) If h = INVALID_HANDLE_VALUE Then Exit Function Set c = New Collection a2 = (Attributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly Do n = Left$(d.cFileName, InStr(d.cFileName, Chr(0)) - 1) a1 = (d.dwFileAttributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly If ((a1 And a2) = a1) Or (a1 <= vbReadOnly) Then c.Add n If FindNextFile(h, d) = 0 Then Exit Do Loop Call FindClose(h) Set DirColl = c End Function Public Function fsoFilesColl(PathName, Optional Attributes As VbFileAttribute = vbNormal) As Collection Dim c As Collection, p As String, f As Object Dim a1 As VbFileAttribute, a2 As VbFileAttribute p = PathName Set c = New Collection a2 = (Attributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly With CreateObject("Scripting.FileSystemObject") If (a2 And vbDirectory) = vbDirectory Then For Each f In .GetFolder(.GetParentFolderName(p)).SubFolders a1 = (f.Attributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly If (LCase$(f.Path) Like LCase$(p)) And ((a1 And a2) = a1) Or (a1 <= vbReadOnly) Then c.Add f.Name Next End If For Each f In .GetFolder(.GetParentFolderName(p)).Files a1 = (f.Attributes And (vbReadOnly Or vbHidden Or vbSystem Or vbDirectory)) Or vbReadOnly If (LCase$(f.Path) Like LCase$(p)) And ((a1 And a2) = a1) Or (a1 <= vbReadOnly) Then c.Add f.Name Next End With Set fsoFilesColl = c End Function Rem _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/ Sub test() Const TEST_PATH = "C:\xxx\DirTest\" Const TEST_FILE = "*" Dim i As Long, s As String, a As VbFileAttribute Dim t As Double Cells.Clear
a = 23 a = a And Not 8
t = Timer s = Dir(TEST_PATH & TEST_FILE, a) Do If Len(s) = 0 Then Exit Do i = i + 1 Cells(i, 1) = s #If GETATTR_TEST Then On Error Resume Next Cells(i, 2) = GetAttr(TEST_PATH & s) And Not vbArchive On Error GoTo 0 #End If s = Dir() Loop Debug.Print Timer - t, "Dir"
i = 0 t = Timer s = DirW(TEST_PATH & TEST_FILE, a) Do If Len(s) = 0 Then Exit Do i = i + 1 Cells(i, 3) = s #If GETATTR_TEST Then On Error Resume Next Cells(i, 4) = GetAttrW(TEST_PATH & s) And Not vbArchive On Error GoTo 0 #End If s = DirW() Loop Debug.Print Timer - t, "DirW"
Dim c As Collection
t = Timer Set c = DirColl(TEST_PATH & TEST_FILE, a) If Not c Is Nothing Then For i = 1 To c.Count Cells(i, 5) = c(i) #If GETATTR_TEST Then On Error Resume Next Cells(i, 6) = GetAttrW(TEST_PATH & c(i)) And Not vbArchive On Error GoTo 0 #End If Next End If Debug.Print Timer - t, "DirColl"
t = Timer Set c = fsoFilesColl(TEST_PATH & TEST_FILE, a) If Not c Is Nothing Then For i = 1 To c.Count Cells(i, 7) = c(i) #If GETATTR_TEST Then On Error Resume Next Cells(i, 8) = GetAttrW(TEST_PATH & c(i)) And Not vbArchive On Error GoTo 0 #End If Next End If Debug.Print Timer - t, "fsoFilesColl"
End Sub
(白茶) 2023/11/14(火) 22:44:39
暇な時に、お勉強も兼ねて32ビット用に書き換えてみます。←そんな能力はないかも( ̄▽ ̄)
m(__)m
(隠居Z) 2023/11/15(水) 08:10:42
いえ、32bit環境で書きました。(むしろ64bit上で動作確認してないデスね)
(白茶) 2023/11/15(水) 08:45:25
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.