質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.45%
Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

DLL

DLL(Dynamic Link Library)とは、他のモジュールからも使用する事が出来る、関数とデータが格納されているモジュールのことです。

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Q&A

4回答

34288閲覧

Windows10環境にてExcel VBAのDataObjectを使用すると文字化けする

yamashita_yuich

総合スコア316

Windows 10

Windows 10は、マイクロソフト社がリリースしたOSです。Modern UIを標準画面にした8.1から、10では再びデスクトップ主体に戻され、UIも変更されています。PCやスマホ、タブレットなど様々なデバイスに幅広く対応していることが特徴です。

DLL

DLL(Dynamic Link Library)とは、他のモジュールからも使用する事が出来る、関数とデータが格納されているモジュールのことです。

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

0グッド

1クリップ

投稿2019/05/23 09:15

概要

現在以下の環境にて、Excelセルのデータを他アプリケーションに貼り付ける処理を実装しています。

  • Windows10 (64bit)
  • MS Excel2016 (32bit)

処理としては、DataObjectを使用してセルデータをクリップボードにコピーし、
他アプリケーションに貼り付ける処理を実装しています。
また、「Microsoft Forms 2.0 Object Library」を使用しています。

知りたいこと
上記事象が発生する「原因」をご教示頂きたいです。


プログラム(抜粋)

VBA

1Public Sub SampleCode 2 3 ' 変数宣言 4 Dim dataObj as DataObject 5 6 ' オブジェクト取得 7 Set dataObj = new DataObject 8 9 ' セルの値をクリップボードに設定 10 With dataObj 11 .SetText(Sheet1.Range("A1").Text) 12 .PutInClipBoard 13 End With 14 15 ' 正しく値が取得できる 16 Debug.Print dataObj.GetText 17 18 ' 2秒待機 19 Application.Wait Now() + TimeValue("00:00:02") 20 21 ' ************************************************** 22 ' 正しく値が取得できない 23 ' ************************************************** 24 Debug.Print dataObj.GetText 25 26End Sub

補足情報として、当該事象は発生する端末と発生しない端末があります。
これらの端末間で、
「Microsoft Forms 2.0 Object Library」にて使用している「FM20.dll」ファイルの
差分はありませんでした。

また、本事象については、回避方法を検討済みです。
セルの値をクリップボートに設定する処理について、
DataObject を使用せず、「Sheet1.Range("A1").Copy」などで直接クリップボードに
コピーすると事象を回避できるようです。

冒頭にも述べた通り、本事象が発生する原因をご教示頂きたいです。

以上、なにとぞお助け頂けますと幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

atata0319

2019/05/23 16:58

手元にすぐに使える Office 2016 の環境がなかったので Office 2010 環境で試してみましたが再現しませんでした。OS は Windows10 Pro (64bit) になります。Office 2016 環境で試してみようとは思いますが、環境依存の問題であれば端末ごとの差を調べるしかなさそうな気がしますね。
yamashita_yuich

2019/05/23 23:26

atata0319さん コメントありがとうございます。 Citrix環境にて、初期状態(配布されている資材のまま)で実行して結果差異がある状況です。 ただまずは本日Cドライブ配下で差分がある可能性が高いところからDiffしてみることにします。
atata0319

2019/05/25 05:15 編集

Office 2016 ではないですが、Office 365 ProPlus で再現しました。ステップ実行時とステップ実行なしで試したところ動作が異なります。ステップ実行では確実にクリップボードの内容が破壊されます。ステップ実行では時間をかけて実行すると初回のタイミングでもクリップボードの内容が破壊されます。非ステップ実行では比較的長い間(2~4秒)破壊されません。 上の実験結果を受けての推測ですが、Excel 自体のスレッドモデルの変更が影響していると推測しています。Excel のオートメーションモデルはかなりの時間をかけてマルチスレッドに対応してきています。現時点では VBE (VBA の実行環境)もマルチスレッド版を使用していると推測されます。 Excel の UI 上でコピー操作を実施すると Excel はクリップボードの遅延レンダリングを実施するために OleSetClipboard で Excel 内部の DataObject(IDataObject を実装したクラス) を設定していると考えられています。この DataObject はマルチスレッド(正確にはマルチアパートメント)に当然対応している(と思われる)ので、Excel マクロ自体でクリップボードを操作する場合、クリップボードの破壊は発生しません。一方、Forms 2.0 の DataObject はマルチスレッド(もしくはマルチアパートメント)に対応できておらず実行エンジンのアパートメントが切り替わるタイミングで破壊されてしまうのではないかと思います。 この推測を検証するためには自分で DataObject 相当のオブジェクトを作成して、実験してみることですが、手間がかかるので今はパスですね。そのうち実験してみて検証できたら回答に記載しておきます。
yamashita_yuich

2019/05/25 06:53

atata0319さん 詳細な仮説ありがとうございます。とても勉強になります。 1点気になるのは、なぜCitrix仮想環境(全員が同じ環境)で作業しているにも関わらず、 再現するユーザーと再現しないユーザーがいるのかということです。 何か見解などありませんでしょうか。 小職の技術不足によりお願いばかりとなっており大変恐縮なのですが、 お時間がありましたらご教示頂けると幸いです。
atata0319

2019/05/25 07:47

Citrixを使用されているなら、クライアントクリップボードリダイレクトが有効になっているかどうかで変わりそうな気がします。各端末での設定を確認されてはどうでしょうか? ただ、私のテスト環境はスタンドアロンなので、上記だけが影響しているとも考えられません。 参考にはなりませんが、影響があるかもしれない動作をつらつらと書いていきます。 ・OneDriveと統合しているかどうか?OneDrive上のファイルを扱う場合、Excel側で同期処理が走るのでスレッドモデルが変更されそうな気がします。SharePoint上のファイルも同様ですね。こちらの o365 環境ならではというところですが。 ・ActiveDirectory に統合している場合、クリップボードについてチェックが入り、それが影響している?ただ、クリップボードを使用する前に権限のチェックが入りそうな気がします。 ・ターミナル環境でユーザー毎にリソースの割り当てが異なるためにCPUの割り当てがスイッチするユーザーとしないユーザーが存在している。ただ、先の仮説ではあくまでもソフトウェアレベルのスレッドの話なのでCPUのスイッチが影響するとは思えないですけどね。
guest

回答4

0

はっきりとした原因はわかっていない状況ですが、Microsoft Developer Network にて issue を発見しました。

それによると、Microsoft は 2013年に問題を認識しているようですが、その後修正された旨の投稿はありません。
また、この問題に対して Microsoft は WindowsAPI を使用した処理に代替することを提案しています。

そこで、 VBA にて WindowsAPI を使用した処理に換装したところ、当該事象は発生しなくなりました。

メモリのロックやメモリアドレスなど、低レイヤーの実装を意識しなければならないため、
実装に際しては注意が必要ですが、
今回はこの方法で事象を回避しましたので共有致します。

ソースコードは以下。

VBA

1Option Explicit 2 3' WindowsAPI宣言 4' 各APIの詳細は (http://wisdom.sakura.ne.jp/system/winapi/win32/win90.html) を参照のこと 5Private Declare Function OpenClipboard Lib "user32.dll" (ByVal hWnd As Long) As Long 6Private Declare Function EmptyClipboard Lib "user32.dll" () As Long 7Private Declare Function CloseClipboard Lib "user32.dll" () As Long 8Private Declare Function IsClipboardFormatAvailable Lib "user32.dll" (ByVal wFormat As Long) As Long 9Private Declare Function GetClipboardData Lib "user32.dll" (ByVal wFormat As Long) As Long 10Private Declare Function SetClipboardData Lib "user32.dll" (ByVal wFormat As Long, ByVal hMem As Long) As Long 11Private Declare Function GlobalAlloc Lib "kernel32.dll" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long 12Private Declare Function GlobalLock Lib "kernel32.dll" (ByVal hMem As Long) As Long 13Private Declare Function GlobalUnlock Lib "kernel32.dll" (ByVal hMem As Long) As Long 14Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) As Long 15Private Declare Function lstrcpy Lib "kernel32.dll" Alias "lstrcpyW" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long 16 17' WindowsAPI゛て使用する定数宣言 18Private Const GMEM_MOVEABLE As Long = &H2 19Private Const GMEM_ZEROINIT As Long = &H40 20Private Const CF_UNICODETEXT As Long = &HD 21 22 23Public Sub Test() 24 25 ' クリップボードにテキストを設定 26 Call SetClipboard("テストテキスト") 27 28 ' クリップボードのテキストを取得してイミディエイトウインドウに出力 29 Debug.Print GetClipboard 30 31End Sub 32 33' 文字列をクリップボードにコピーします 34' 本処理は DataObject を使用することで意図しない文字列がクリップボードに貼りつくことがあるため 35' DataObject を使用せずに WindowsAPI を使用した処理で実装する 36' また、処理中はメモリのロックを実施するため、本処理中で強制終了しないようにすること 37Public Sub SetClipboard(ByRef sUniText As String) 38 39 ' 変数宣言 40 Dim iStrPtr As Long 41 Dim iLen As Long 42 Dim iLock As Long 43 44 ' クリップボードを開く (開けなかった場合はエラーをスロー) 45 If OpenClipboard(0&) = 0 Then 46 ' 実は開けていた場合に備えて閉じる処理を呼び出す 47 On Error Resume Next 48 Call CloseClipboard 49 On Error GoTo 0 50 Err.Raise 1000, , "クリップボードを開けませんでした" 51 End If 52 53 ' クリップボードを空にする (失敗した場合はクリップボードを閉じてエラーをスロー) 54 If EmptyClipboard = 0 Then 55 On Error Resume Next 56 Call CloseClipboard 57 On Error GoTo 0 58 Err.Raise 1000, , "クリップボードを空にできませんでした" 59 End If 60 61 ' 確保するメモリ領域を取得 (終端文字ワイド文字列 分も確保するために2バイト多く確保) 62 iLen = LenB(sUniText) + 2& 63 64 ' ヒープから指定されたバイト数のメモリを確保 (失敗した場合はクリップボードを閉じてエラーをスロー) 65 iStrPtr = GlobalAlloc(GMEM_MOVEABLE Or GMEM_ZEROINIT, iLen) 66 If iStrPtr = Null Then 67 On Error Resume Next 68 Call CloseClipboard 69 On Error GoTo 0 70 Err.Raise 1000, , "メモリの確保に失敗しました" 71 End If 72 73 ' グローバルメモリオブジェクトをロックし、メモリブロックの先頭へのポインタを取得 74 iLock = GlobalLock(iStrPtr) 75 If iLock = Null Then 76 On Error Resume Next 77 Call CloseClipboard 78 On Error GoTo 0 79 Err.Raise 1000, , "メモリのロックに失敗しました" 80 End If 81 82 ' 指定された文字列を、メモリにコピー 83 If lstrcpy(iLock, StrPtr(sUniText)) = Null Then 84 On Error Resume Next 85 Call CloseClipboard 86 On Error GoTo 0 87 Err.Raise 1000, , "メモリのコピーに失敗しました" 88 End If 89 90 ' グローバルメモリオブジェクトのロックカウントを減らします 91 ' 0以外が返却された場合はロックが解放されなかったとしてエラーをスローします 92 If GlobalUnlock(iStrPtr) <> 0 Then 93 On Error Resume Next 94 Call CloseClipboard 95 On Error GoTo 0 96 Err.Raise 1000, , "メモリのアンロックに失敗しました" 97 End If 98 99 ' クリップボードに、指定されたデータ形式でデータを格納 100 If SetClipboardData(CF_UNICODETEXT, iStrPtr) = Null Then 101 On Error Resume Next 102 Call CloseClipboard 103 On Error GoTo 0 104 Err.Raise 1000, , "クリップボードにデータを格納できませんでした" 105 End If 106 107 ' クリップボードを閉じる 108 If CloseClipboard = 0 Then 109 Err.Raise 1000, , "クリップボードをクローズできませんでした" 110 End If 111 112End Sub 113 114' クリップボードからテキストを取得します 115' テキストを取得する処理においては DataObject を使用しても問題が発生しないことから 116' WindowsAPI を使用しない実装とします 117Public Function GetClipboard() As String 118 119 With New DataObject 120 .GetFromClipboard 121 GetClipboard = .GetText 122 End With 123 124End Function 125

投稿2019/05/25 06:12

編集2019/05/25 06:19
yamashita_yuich

総合スコア316

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

原因を特定できない場合、別の方法を試してみてはどうでしょうか。

VBAでセルに含まれる全ての数値をカウントアップするマクロを作成する - えくせるちゅんちゅん
にて「クリップボードへの格納に関しては従来のDataObjectを使った方法は安定しない…」ことが説明されており、以下サイトが紹介されていましたので参考にしてください。

[VBA]DataObjectを使ったクリップボード操作が上手くいかない場合の対処法

以下は、クリップボードへの格納を関数にした例です。

VBA

1'Microsoft Forms 2.0 Object Libraryを参照設定 2Sub CopyToClipboad2(ByVal strInput As String) 3 With CreateObject("Forms.TextBox.1") 4 .MultiLine = True 5 .Text = strInput 6 .SelStart = 0 7 .SelLength = .TextLength 8 .Copy 9 End With 10End Sub

投稿2019/05/24 22:39

編集2019/05/24 22:45
TanakaHiroaki

総合スコア1063

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamashita_yuich

2019/05/25 04:18

ご回答ありがとうございます。自己解決のところに記載しますのが、Microsoftもこの問題を認識しており、WindowsAPIを使用する代替策を推奨しております。つきまして、対処法としてはMicrosoftの推奨方法にしようと考えています。 今回の質問背景として、原因を正しく把握して根本対策をしたかったというものがありましたが、現在のところはっきりとした根本原因はわかっていない状況です。
guest

0

私は同じような経験をしています。
結論から、あきらめています。

この手のグラフィック関係は、
小生の結論から、パソコンのスペックに左右されると結論付けています。

パソコンのスペックによって、動いた入り動かなかったりしている現象を何度も見ています。

パソコンのスペックの違いで、動作検証はしましたか?
Excelの場合、グラフィックの処理スペックで結果が大きく異なります。

Excelの実体験では、パソコンのスペックで、動作結果が大きく異なるのが、グラフィック関係です。
運用を変えるか、マシンスペックを上げるかの2択ではと思います。

投稿2019/06/01 12:13

kai_keitai

総合スコア344

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamashita_yuich

2019/06/01 12:46

ご回答ありがとうございます。 小職のソースコードを見てわかる通り、DataObjectを使用してクリップボードへの設定を実施した直後は正しい値が取得できています。これはすなわち、一度は正しい値がメモリに格納されたことを意味しています。 その後、待機処理を実行したあとに同一メモリ領域から文字列を読みだそうとすると期待しない文字列が取得される (すなわちメモリが書き換えられている OR メモリを参照する位置が変わってしまっている) また、クリップボードに少ない文字列情報を読み書きする処理は軽量な処理であることから、マシンスペックやブラフィックス性能はは関係ないと考えます。 加えて、コメントでも記載した通り、Citrix環境(同一スペックの環境)で複数人が同じ処理を実行して本事象が発生する場合と発生しない場合があることからも、マシンスペックの問題ではないと考えます。 本件については、コメント欄にて atata0319 さんが記載されている内容と小職の自己解決が参考になると考えています。
guest

0

コピー直後と時間をおいた後でクリップボードの内容が違うということであれば、クリップボードを監視して書き換えるものがあるんじゃないでしょうか?

たとえば IDE とか、クリップボード履歴とか、アンチウィルスソフトとか。

もしかすると、SetText メソッド (フォーム) の format を 13 にすると改善するかもしれません。Standard Clipboard Formats

投稿2019/05/24 01:03

Zuishin

総合スコア28662

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yamashita_yuich

2019/05/25 04:21

ご回答ありがとうございます。ご回答頂いた通り、クリップボードにコピーした直後は正しく読み出しができることから、何かの別プロセス、もしくはFM20.dll、もしくはそこから呼ばれるuser32.dllの内部で処理異常が発生していることによりメモリの上書きがされていると考えています。但し現在まで正しい原因はわかっていない状況です。 SetTextで13(UNICODE)を指定した処理も試しましたが、変化はありませんでした。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.45%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問