ほっとひといき給湯室

ほっとひといき給湯室の掲示板です。お気軽にどうぞ!
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
PC研究室2nd
投稿日時: 11/10/21 07:55:51
投稿者: kumatti
投稿者のウェブサイトに移動

※ PCに関する事なら、何でもどうぞ。
前スレ
http://www.moug.net/faq/viewtopic.php?t=60115

投稿日時: 11/10/21 07:56:36
投稿者: kumatti
投稿者のウェブサイトに移動

yayadonさん、回答ありがとうございます。
(勉強になります)
 
Abyssさん、
> 64bitでの話ですか?
http://www.ka-net.org/office/of32.html
そうですね。なので、

CreateObject ("JScript")

その先をって話です。
 
# タッチの差でした。

投稿日時: 11/10/21 08:35:10
投稿者: kumatti
投稿者のウェブサイトに移動

前スレの
> タイプライブラリの定義間違いが、どこだか分からない。
これは、
 
個別に変数を宣言すれば、どのCOMインターフェースがコケてるか分かるので、

    Dim std_excepinfo As stdole.EXCEPINFO

↑構造体ですけど。
それで、何でもなかったようです。
 
よく分かりませんけども、モジュールのエクスポート、インポートで
解決しました。
(*.tlbを何度も作りなおしたからかな)
 
# IActiveScriptSiteの実装を勉強ってトコです。

回答
投稿日時: 11/10/21 11:30:46
投稿者: Abyss
メールを送信

>それで、何でもなかったようです。
  
意味がよく分かりませんけど。
  
でも、32bitでは問題ないことで、安心です。
一応、自分のところでは問題ないし。
 
ありがとうございます。

回答
投稿日時: 11/10/21 12:43:48
投稿者: 月
投稿者のウェブサイトに移動

yayadon さんの引用:
' 同一セルに対して,別の参照経由で設定&取得
rng(0).Value = i
lngValue = rng(1).Value

rng(0).ID = i
lngValue = rng(1).ID

とすると同時参照数が影響しませんでした。
メンバによっても違うようです。
 
それにしても、よく考えられたコードだなと思いました。
 
yayadon さんの引用:
Private Sub Test34()

試行の跡も見て取れますしね。

回答
投稿日時: 11/10/22 00:40:19
投稿者: yayadon

月 さんの引用:
yayadon さんの引用:
' 同一セルに対して,別の参照経由で設定&取得
rng(0).Value = i
lngValue = rng(1).Value

rng(0).ID = i
lngValue = rng(1).ID

とすると同時参照数が影響しませんでした。
メンバによっても違うようです。

 
時間がとれないのと,面倒だったので,他のプロパティは調査しなかったのですが,
どのプロパティも影響してしまうと,
他の原因で遅くなっている可能性も捨てきれなくなるので,
参照数が増えても影響しないプロパティがあることを確認できてよかったです。
 
影響しないプロパティの値は,
・Rangeオブジェクトのインスタンス内でキャッシュしていない値
もしくは
・キャッシュで持ってはいるが,同期していない/同期する必要のない値
のどちらかである可能性が高いと言えるんじゃないかと思います。
同期している(別参照経由でも値が反映される)かどうかも含めて,
一つずつ確認してみると面白いかもしれません。
 
 
-----
いろいろと検証したのですが,
同時に参照するRangeオブジェクトの参照を,わざわざ別のオブジェクト参照にしていますが,
すべて ActiveCell にしても同じ結果になります。
 
ですが,念のため,
「すべて同じセルを参照しているから,負荷がかかっているんじゃないの?」
や,逆に
「すべて同じセルを参照しているから,負荷がかからないんじゃないの?」
という仮定を排除するために,別にしたコードをアップしてみました。
 
あとは,調査するプロパティと受け取る変数の型をどれにするか迷いましたが,
Rangeオブジェクトの扱いのノウハウに詳しくないので,
すべてのパターンで調査するという王道(笑)にでたのですが,
ひとつ示すとしたらということで,
(自分の知識内で)一番負荷がニュートラルそうなものを選びました。
 
COM仕様での値の受け渡しですが,
受け取りは,
 変数のアドレス値を渡す ---(A)
ことで受け取り可能になり,
値の受け渡しは,
 そのまま値を渡す
場合と,
 変数のアドレス値を渡して,受け渡された側が逆参照して値を得る ---(B)
やり方の場合があります。
 
なので,
(A) のときと (B) のとき,
引数の型とデータ値の型が違う場合,引数の型にあわせるために一時変数が作成されて,
繰り返しが多いと,一時変数の影響によるものの可能性も排除できなくなります。
文字列が絡んでくるとさらにややこしくなります。
 
なので,
ひとつのパターンで調査しても,
他のパターンでも同じ傾向が表れないと,
ある程度正しいということが言えないので,
いろいろ調査する必要が出てくるわけです。
 
 
-----
Excelエキスパートの人は,
Rangeオブジェクトは,基本中の基本なので,
ちゃんと調査しておくと,いいことがあるかもしれません。
 

回答
投稿日時: 11/10/22 01:46:13
投稿者: yayadon

[補足]
 

yayadon さんの引用:
引数の型とデータ値の型が違う場合,引数の型にあわせるために一時変数が作成されて,

プロパティや関数の引数の型が,データ値の型と異なる場合,
一時変数が作成されて,その変数のアドレス値が渡されます。
 
これは,純粋な関数呼び出しのときだけです。
 
VBA の関数の中には,実際は,
 
 予約名(reserved name)
 
が,実際のエンティティ(関数やもろもろ)として振る舞うものがあります。
 
 Len や LenB や CLng や CInt等
 
です。それらは,実際には純粋な関数呼び出しではないこともあって,
一時変数は作成されません。
 
 
一方,
VBA.Len や VBA.CLng などは,純粋な関数呼び出しになります。
 
例えば,
VBA.Len では,Long型の変数を渡しても,
VBA.Len に実際に渡す引数が Variant型 の一時変数になってしまうため,
 
 内部処理形式の長整数値を文字列に型強制(coercion)してからの評価
 
になったりするので,
渡すデータ値の型によっては動作が異なったり,
VARIANT型は,オートメーション仕様にそった型を扱うものだったりする関係で,
構造体は,エラーになったりします。
 
 
他の関数も含めて,
引数が Variant型 の参照渡し (C++的には VARIANT* )になっているものは,
データ値の 型 が Variant型 でない場合,
上で見たように,Variant型の一時変数が作成されるので注意が必要です。
(COMオブジェクトのメソッドやプロパティの引数ではなくても,
例えば,MsgBox など,C++的には VARIANT* なものはたくさんあります)
 
数値の場合,Variant型での演算が遅いので,途中までは数値型がよいのですが,
String型の場合,つまり,BSTR の場合,
参照カウント方式を採用していない関係で常に深いコピーになる仕様になっているため,
一時変数への最後の深いコピーで
 String型 での演算による貯金をまず使い切ってしまうので,
注意が必要です。
 
引数が Variant型 の参照渡し (C++的に VARIANT* のところ)が最終的に絡んでくる場合は,
Variant型 になるところで自然にVariant型に移行させるか,
ほとんど演算をしないのならば,最初から Variant型 で用意するのが,
一時変数への最後の深いコピーを避けることができて無難な気がします。
 

回答
投稿日時: 11/10/22 02:08:45
投稿者: yayadon

[補足の補足]
 

yayadon さんの引用:
数値の場合,Variant型での演算が遅い

この理由を
 
 Variant型 のサイズが Long型 や Integer型 に比べて大きいから
 
と説明している時を見かける時がありますが,Variant型のサイズが問題なのではなく,
Variant型どうしの演算が特別なために遅くなることによって遅くなっています。
 
数値の場合,変わり具合に対して,
文字列の演算が,Variant型 と String型 とでは,大して変わらないのは,
String型であっても,BSTR文字列の演算がもともと特殊なためです。
 
 
いずれにしても,引数にVariant型が絡んでくるとややこしくなるということです。
 

回答
投稿日時: 11/10/22 05:36:15
投稿者: yayadon

Letステートメントについて書き直してみました。
 
 
-----
■ Letステートメント
  
 [Let] 左辺式 = 式
  
 ( [Let] l-expression = expression )
  
の形をとります。[ ] は省略可能を示す。
 
Letステートメントは単純データ値だけを扱うと決めた関係で,
代入する値を決める右側の 式 だけでなく 左辺式 も
  
 単純データ値への評価 (Evaluation to a simple value)
  
が必要になります。
 
例えば,
 

Application.ActiveCell = 0

のような場合の単純データ値への評価を考えてみます。
 
 
ActiveCell は,Range型 の a class type になります。(汎用Object型では無いということ)
 
固有クラス型(ヘルプでは 固有オブジェクト型 と表現)を単純データ値へ評価するには,
まず,デフォルトのプロパティを評価しようとします。
 
Rangeクラスのデフォルトのプロパティについて考えてみます。
 
ここでは,OLE/COM Object Viewer というもので,
タイプ ライブラリ上での Rangeクラスの dispinterface の定義を見てみます。
 
デフォルトのプロパティやデフォルトのメソッドですが,
ディスパッチID (DISPID) の値が 0 のものを
デフォルトのプロパティと決めています。
 
DISPID というのは,メソッドを識別する値です。
メソッドの識別の方法は,
DISPID によるものと,
インターフェース内での先頭からの相対位置によるものがあります。
 
が,ここでは,デフォルトのプロパティを知りたいので,
DISPID が 0 のものを探すため dispinterface の定義を見たということです。
 
 id という属性は,メンバーID と呼ばれている属性で,
要するに DISPID のことです。
 
探してみると,_Default という名前の
 
        [id(00000000), propget, helpcontext(0x00010000)]
        VARIANT _Default(
                        [in, optional] VARIANT RowIndex, 
                        [in, optional] VARIANT ColumnIndex);


 
        [id(00000000), propput, helpcontext(0x00010000)]
        void _Default(
                        [in, optional] VARIANT RowIndex, 
                        [in, optional] VARIANT ColumnIndex, 
                        [in] VARIANT rhs);

のふたつの定義があることがわかります。
 
propget 属性が付いている方が,Getプロパティ(取得プロパティ)で,
propput 属性が付いている方が,Letプロパティ(設定プロパティ)です。
 
コードを再び見てみます。
 
Application.ActiveCell = 0


Application.ActiveCell は,
Letステートメント
 
 [Let] l-expression = expression
 
の左側に来ています。
なので,評価の結果は,
値が代入できる左辺式(l-expression)である必要があります。
 
上のふたつの内,
設定できるプロパティは,Letプロパティの方になります。
 
(実際は,Getプロパティでも参照が返ってくれば,さらに評価を進めることによって
左辺値になる可能性があるのですが,ここでは,Letプロパティが 見つかったので,
そこには立ち入らないことにします)
 
 
ということで,
アクセス可能な デフォルトの Letプロパティ が存在しています。
下に抜き出しました。
 
        [id(00000000), propput, helpcontext(0x00010000)]
        void _Default(
                        [in, optional] VARIANT RowIndex, 
                        [in, optional] VARIANT ColumnIndex, 
                        [in] VARIANT rhs);

デフォルトの Letプロパティ を引数無しで呼んで,
右側の整数値 0 を設定できる単純データ値なのか?を確認します。
 
RowIndex と ColumnIndex は,
optional の属性が設定されています。
この意味は,
 
 「引数を省略しましたよ」ということを示す VARIANT型 の値を渡す決まり
 
になっています。
具体的には,
 
 内部処理形式が VT_ERROR 型 で,
 値が DISP_E_PARAMNOTFOUND (0x80020004L) のVARIANT型のデータ値
 
を渡すことで,「引数を省略しましたよ」ということを示めすことになっています。
 
親切なことに,VBA では,背後で代わりにやってくれます。
なので,VBAユーザーは,省略するだけでよいことになっています。
 
ということで,
RowIndex と ColumnIndex は,省略可能です。
残った rhs は,optional属性が付いていないので,省略可能ではないのですが,
これは,右側の整数値 0 を渡す引数です。
これは,省略してしまっては意味がないので別扱いです。
 
 
 rhs は,Variant型 なので,いろいろな型を扱えるのですが,
設定する側なので,
0 を入れれば,内部処理形式 Integer型 の Variant型になるため
Variant型を単純データ値として扱えます。
 
ということで,
Application.ActiveCell の単純データ値への評価が成功しました。
 
 
一方,
右側の 0 は,最初から単純データ値なので,
単純データ値への評価の中身は, の決定だけになります。
Integer型 と評価されます。
 
代入される側の型が Long型 や Double型 などの場合,
Integer型 からそれらの型へ暗黙的な型の変換(Let-coercion)が起こります。
 
今回は,代入される側の型は,Variant型なので,
Integer型の単純データ値の場合は,そのまま値のコピーを代入するだけです。
(内部処理形式の型を示す値も設定しないといけないので,
Variant型に対する値の設定の仕方がありますが,VBAが背後でやってくれます)
 
 
 
-----
今回は,右側が 0 のような単純データ値でしたが,
そうでない場合,例えば,右側も
 
Application.ActiveCell = Application.ActiveCell

のように固有クラス型だった場合は,
右側の Application.ActiveCell も単純データ値への評価をします。
 
今度は,
デフォルトのプロパティのところは,
 
        [id(00000000), propget, helpcontext(0x00010000)]
        VARIANT _Default(
                        [in, optional] VARIANT RowIndex, 
                        [in, optional] VARIANT ColumnIndex);

こちら側になります。
 
RowIndex と ColumnIndex は,optional属性が付いていて省略可能です。
省略して呼び出すと,VARIANT型のデータ値が返って来ます。
 
ということで,下記の Letステートメント
 
Application.ActiveCell = Application.ActiveCell

は,結局,
 
VARIANT型引数rhs = VARIANT型のデータ値

のようになります。
 
注意:
デフォルトのプロパティがそもそも無いもの や 評価に失敗する場合,
例えば,
 
Application.ActiveWorkbook = Application.ActiveWorkbook

などは,Letステートメントはエラーになります。
 
 
今まで,説明しませんでしたが,
評価には,静的な評価と実行時の評価があります。
 
VBA では,実際の型が,実行時までわからないことがあります。
VARIANT型もそのひとつで,
静的な評価では,VARIANT型の値は,とりあえず 単純データ値 として扱います。
 
上の右辺値の VARIANT型のデータ値 は,
実行時にならないと,内部的に,単純データ値 かどうか?がわからないということです。
で,実行時に,
該当のセルに,数値の 0 が入っていたとすると,
VARIANT型のデータ値に入っているデータ値は,
Variant型の内部処理形式の型は,Double型になっているハズで,
Double型は,単純データ値なので,
右辺の Application.ActiveCell の単純データ値への評価は成功します。
 
そして代入という形になります。
 

投稿日時: 11/10/22 08:30:49
投稿者: kumatti
投稿者のウェブサイトに移動

# Abyssさんの意図としては、個人のブログでないのだから、一般的な認識・常識に基づいて
  コメントされてくださいと言うことなのでしょう。
  (Office2010も規定でインストールするのは32ビット版ですし)
 
> 32bitでは問題ないことで、
 
WOW64とx86が完全互換とされない事例にScript Controlがあります。
http://page.freett.com/futabe/tps/sc/ascript.html
 
 
> 意味がよく分かりませんけど。
 
32bit版のタイプライブラリを64bit版に直してる過程でこちらのエラーが出て、

関数またはインターフェイスが予約されているか、または Visual Basic でサポートされていないオートメーション タイプが関数で使用されています。

ところが、定義自体にそれとおぼしき箇所はない。
 しばらく後に、モジュールのエクスポート・インポートで解決をみたということです。
(Excel側でマクロ無効で保存でも、同じ効果かもしれませんけど)

回答
投稿日時: 11/10/22 09:55:48
投稿者: ろひ

前回スレの大分前の発言で恐縮なのですが、気になったので。

kumatti さんの引用:
のちのちVBAにとってうれしいニュースとなるんじゃないかと思うんですが, Metro 用の Native API である Windows Runtime のクラスですが,(.NET のクラスと違って) クラスのインスタンス管理に参照カウント方式が採用されました。
 
(略)
 
.NET API は参照カウント方式が無い関係で,VBA とはどうも相性がよくなかったんですが,VBAも今度は相性よく対応できるでしょう。

当該の方面に詳しくないので、表現が適切じゃないかもしれませんが…
 
Windows8(Metro)上においては、VBAからの.NET FlameworkのAPI利用が安定しそうだ
 
…という解釈であってますか?
 
更には、既存の環境(WindowsやOffice)においても、Windows8(Metro)対応の.NET Flameworkが導入された環境であれば、安定しそうだ(=相性の改善が見込める可能性がある)、という様になりうるのでしょうか?
 
既存の環境においても、(例えば、新しいWindows Runtimeへ勝手に(ユーザーが意識なしに)更新される機会があるなどして、)VBAからの.NET Flameworkが盛んになりえるなら嬉しいのですが、そこまでの可能性もありえそうでしょうか?

回答
投稿日時: 11/10/23 02:39:08
投稿者: yayadon

[訂正]
 
上の Let ステートメント の説明の中で,
 
 Getプロパティ
 Letプロパティ
 
と表現していましたが,
VBAの仕様書通りに 英語のまま かつ 太文字 で
 
 Property Get
 Property Let
 
とするか,全部を日本語として表現する
 
 取得プロパティ
 設定プロパティ
 
のように変更することにしました。
(片方だけ日本語に訳すのは,ある意味では,誤訳な気がしたためです)
 
 
一般的に,取得する方を getter といい,設定する方を setter といいますが,
VBA仕様書(ver 1.0)では,その用語が出てこないので,
Let ステートメントを説明する時など,正式な説明では,使わない方が無難です。
 
 
また,一般的なVBAの解説では使われない用語ですが
 
 データ値 (data value)
 単純データ値 (simple data value)
 単純データ演算子 (simple data operator)
 Let-型強制 (Let-coercion)
 
等は,VBA仕様書(ver 1.0)に出てくる正式な用語/表現を使っています。
安心して使ってください。
 
 
 

引用:
propget 属性が付いている方が,Getプロパティ(取得プロパティ)で,
propput 属性が付いている方が,Letプロパティ(設定プロパティ)です。
     ↓
propget 属性が付いている方が,Property Get (取得プロパティ)で,
propput 属性が付いている方が,Property Let (設定プロパティ)です。
 
 
引用:
上のふたつの内,
設定できるプロパティは,Letプロパティの方になります。
     ↓
上のふたつの内,
設定できるプロパティは,Property Let の方になります。
 
もしくは,
 
上のふたつの内,
設定できるプロパティは,設定プロパティ の方になります。 (← ちょっと変ですが)
 
 
※ 他の箇所の訂正も同様
 

回答
投稿日時: 11/10/23 04:47:36
投稿者: yayadon

ろひ さんの引用:
kumatti さんの引用:
のちのちVBAにとってうれしいニュースとなるんじゃないかと思うんですが, Metro 用の Native API である Windows Runtime のクラスですが,(.NET のクラスと違って) クラスのインスタンス管理に参照カウント方式が採用されました。
 
(略)
 
.NET API は参照カウント方式が無い関係で,VBA とはどうも相性がよくなかったんですが,VBAも今度は相性よく対応できるでしょう。

kumattiさんでなくて,たぶん,私ですね。
 
引用:
VBAも今度は相性よく対応できるでしょう。

の意味は,
 
VBAは,.NET Framework (のクラス群)にはうまく対応できませんでしたが,
今度の Windows Runtime のクラス群には相性よく対応できるでしょう。
 
です。
 
 
ろひ さんの引用:
Windows8(Metro)上においては、VBAからの.NET FlameworkのAPI利用が安定しそうだ

.NET Framework のクラス群は,
C# や 現世代のVB 言語向けに対してのみのクラス群でした。
 
 Windows 8 以降では,
 各言語に対して,もっとニュートラルなクラス群を提供して行こう
 
というのが,あたらしいAPI設計のベースにあります。
 
具体的には,
 
 ニュートラルなクラス群で提供する機能と重複する .NET Framework の機能は,
 Metro向けの .NET Framework の中から削除される
 
 ニュートラルなクラス群で提供する機能は,
 言語の種類によらず,どの言語からでも直に利用できる
 
というような形になります。(あくまでも現地点での予定です)
 
すでに,Windows 7 において,
Windows API (Win32 API) の抽象化が行われ始めていて,
本当に基本的な機能を提供するものとその上位の機能を提供するものとを整理し始めています。
(機能のレベルが違うものが相互依存しないように整理し始めているということです)
 
最終的には,
その基本的な機能を提供するAPIだけを使ったニュートラルなクラス群
を提供するのが目的でしょう。
 
 
ろひ さんの引用:
更には、既存の環境(WindowsやOffice)においても、Windows8(Metro)対応の.NET Flameworkが導入された環境であれば、安定しそうだ(=相性の改善が見込める可能性がある)、という様になりうるのでしょうか?

上で見たように,
今後提供するクラス群は,C++ からでも C# からでも,どちらからでも,
それらクラス群が,
それら言語にとってネイティブな形で利用できるようになるということです。
(VBA はC++側です)
 
非常にセンシティブな話題なので,まともにとらないで欲しいのですが,
.NET無しでの高次元でのプログラミング という世界に移行し始めたということです。
 
今後提供するクラス群は,機能が被るものは,
Metro用の .NET Framework から省かれます。
 
C++ は,
新しいクラス群が提供する機能は,それらクラスのものを利用し,
新しいクラス群が提供しない機能は,Windows API (Win32 API) を利用する。
 
C# は,
新しいクラス群が提供する機能は,それらクラスのものを利用し,
新しいクラス群が提供しない機能は,.NET Framework のクラスが提供する機能を利用する。
それでもやれないことは,Windows API (Win32 API) を利用する。
 
 
ろひ さんの引用:
VBAからの.NET Flameworkが盛んになりえるなら嬉しいのですが、そこまでの可能性もありえそうでしょうか?

上で見たように,Windows 自体がネイティブでクラス群を提供するようになります。
各言語は,参照設定するだけで,それらクラス群を使えるようになります。
 
VBA が,参照設定すれば,ADO(のクラス群)等を使えるようになるのと同じ感じです。
 
但し,現在,VBA が参照設定できるのは,タイプ ライブラリー と呼ばれているものだけです。
 
ご存知だと思いますが,
タイプ ライブラリー とは,提供するクラス群や定数等が説明されているもので,
COMクラスを説明するのに使われてきました。
 
で,今回は,
提供するクラス群や定数等を説明するのに,
その タイプ ライブラリー は使わずに,
メタファイル と呼ばれているものを使います。
 
メタファイルは,.NET系のクラス群を説明するために使われてきたものです。
 
残念なことに,現在のVBAでは,メタファイル内のクラス群を参照設定できません。
どうなるのか?は,次期Officeの発表までは,なんとも言えません。
 
参照設定ができるようになれば,C++ や C# 等と,少なくとも,同じ土俵には上がれます。
参照設定できるようになっても,Metro UI は使えません。
 

回答
投稿日時: 11/10/23 10:43:08
投稿者: ろひ

yayadonさん、丁寧な解説ありがとうございます。
(あちこち飛んで見てたらお名前が間違ってましてkumattiさんにも失礼いたしました。)

yayadon さんの引用:
すでに,Windows 7 において,Windows API (Win32 API) の抽象化が行われ始めていて,
本当に基本的な機能を提供するものとその上位の機能を提供するものとを整理し始めています。
(機能のレベルが違うものが相互依存しないように整理し始めているということです)
 
Windows 8 以降では,各言語に対して,もっとニュートラルなクラス群を提供して行こうというのが,あたらしいAPI設計のベースにあります。
 
最終的には,その基本的な機能を提供するAPIだけを使ったニュートラルなクラス群を提供するのが目的でしょう。

まず、Windowsの背景・動向がこうだったのですね。MacOSが9からXに以降した後の黎明期(9のAPI群であるCarbonが抽象化されてCocoa環境に統合)を思い出しました。Windowsについても今後の整理・統合による発展を期待してしまいます。
 
 
yayadon さんの引用:
VBAは,.NET Framework (のクラス群)にはうまく対応できませんでしたが,今度の Windows Runtime のクラス群には相性よく対応できるでしょう。

yayadon さんの引用:
今回は,提供するクラス群や定数等を説明するのに,その タイプ ライブラリー は使わずに,メタファイル と呼ばれているものを使います。
 
メタファイルは,.NET系のクラス群を説明するために使われてきたものです。
 
残念なことに,現在のVBAでは,メタファイル内のクラス群を参照設定できません。
どうなるのか?は,次期Officeの発表までは,なんとも言えません。
 
参照設定ができるようになれば,C++ や C# 等と,少なくとも,同じ土俵には上がれます。

こちらも前記のながれにおいて次期Officeに期待ですが、VBAがより様々で高度なクラス群を便利に活用できるようになりうるかの焦点は「メタファイル内のクラス群を参照設定できるか否か」がポイントだと理解しました。
 
 
yayadon さんの引用:
上で見たように,今後提供するクラス群は,C++ からでも C# からでも,どちらからでも,それらクラス群が,それら言語にとってネイティブな形で利用できるようになるということです。(VBA はC++側です)
 
非常にセンシティブな話題なので,まともにとらないで欲しいのですが,
.NET無しでの高次元でのプログラミング という世界に移行し始めたということです。
 
今後提供するクラス群は,機能が被るものは,Metro用の .NET Framework から省かれます。
 
C++ は,新しいクラス群が提供する機能は,それらクラスのものを利用し,新しいクラス群が提供しない機能は,Windows API (Win32 API) を利用する。
 
C# は,新しいクラス群が提供する機能は,それらクラスのものを利用し,新しいクラス群が提供しない機能は,.NET Framework のクラスが提供する機能を利用する。それでもやれないことは,Windows API (Win32 API) を利用する。

C++とC#の存在理由はなんとなく理解していましたが、互いを見比べたとき、「どこへ向かおうとしているのか」が外からはわかりませんでしたので、謳う側の思想としては「.NET無しでの高次元でのプログラミング という世界に移行し始めた」なのだと理解しました。
 
◇[HOWTO] Microsoft Office で Visual Basic for Applications から Visual Basic .NET クラス ライブラリを呼び出す方法 - Microsoftサポート
http://msdn.microsoft.com/ja-jp/library/dd313957.aspx
 
上記のようなドキュメントもあるので、いったん期待するものの…
 
◇Microsoft Office と .NET の相互運用性 - MSDN
http://msdn.microsoft.com/ja-jp/library/dd313957.aspx
 
「Office VBA から .NET Framework クラス ライブラリを呼び出す」の項で、「このアプローチにはいくつかの重要な制限事項があります。 それは、COM (Office VBA が基づいている) が .NET Framework クラス ライブラリの以下のものを認識しないためです。」(以下、その具体的事由が記述)
 
…とされていることからも、Windows Runtimeが提供する(であろう)共通クラス(とWindows API(Win32 API))の動向や関係に着目していきたいと思います。
 
全体が俯瞰しやすくなる解説をいただき、ありがとうございました。
 
 
≪補足≫
◇VBAで.NET FrameworkのArrayListクラスとRandomクラスを使う - トキドキドキンドットコム
http://tokidokidokin.com/2011/03/vba%E3%81%A7-net-framework%E3%81%AEarraylist%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%A8random%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86/
(※VBA(VBS)からの.NET Frameworkクラスの利用がわかりやすくまとまっているので掲示。)

回答
投稿日時: 11/10/23 20:37:33
投稿者: yayadon

そういえば,タイプライブラリが用意されていましたね。
 

回答
投稿日時: 11/10/24 00:58:24
投稿者: yayadon

# 知っている方は,スルーの方向で。
 
 
C++ や VB6 や VBA などで作るオブジェクトと
C# などの .NET系の言語で作るオブジェクトでは,
プログラミング要素群の内,基本的な要素
 
 ・スレッド
 ・メモリ管理
 ・セキュリティ
 
について,やり方が異なっています。
 
 
-----
セキュリティ
 
個人的な考えかもしれませんが,
.NETで作るオブジェクトの利点の内の一つは,
セキュリティ であったと思います。
 
どのように実現したかの説明は省略します。
 
.NETが出てきたころと,現在のセキュリティについて,
Windows という OS において,一番状況が異なるのは,
Vista 以降に,
整合性レベル(integrity level)という概念と仕組みが導入されたことです。
 
この仕組みを使って,
ネイティブ環境で動作するオブジェクトを
ボックス化(外部への呼び出し等を自由にさせないこと)した環境に
閉じ込めることが可能になりました。
 
 
-----
メモリ管理
 
メモリ管理について大きく異なる点は,
ヒープ メモリの使い方にあります。
 
.NETのヒープ メモリの主な使い方の特徴のひとつに
次に利用するメモリの確保の仕方にあります。
以下のようなコイン ロッカー風に見える
 
 □□□□□□□□□□□□□□□□
 
□であらわされたメモリを確保したとします。
(注意: □ が 1バイト を表すという意味ではありません。
   4バイト や 8バイト などもっとまとまった範囲を表します。)
 
次に利用する場所を↑で示すと,初期状態は,
 
 □□□□□□□□□□□□□□□□
 ↑
 
になります。
 
使用するオブジェクトは三つほど使うとします。 (■ は使用済み を表す)
すると,
 
 ■■■□□□□□□□□□□□□□
    ↑
 
のように次に利用する場所は移動します。
さらに,もう一つ同じオブジェクトを使うとすると
 
 ■■■■■■□□□□□□□□□□
       ↑
 
のように次に利用する場所は移動します。
さらに,もう一つ同じオブジェクトを使うとすると
 
 ■■■■■■■■■□□□□□□□
          ↑
 
のように次に利用する場所は移動します。
最初の3つの■は,実は,もう使用されていないとします。
しかし,GC が起きるまで,回収されないので,
さらに,もう一つ同じオブジェクトを使うとすると
 
 ■■■■■■■■■■■■□□□□
             ↑
 
のように次に利用する場所は移動します。
最初の3つの■に続いて,次の3つの■も,実は,もう使用されていないとします。
ここで,GC が起きると,最初からの6つは回収されて,
 
 □□□□□□■■■■■■□□□□
             ↑
 
のように空きができます。そして,
使用中のメモリを左側に移動させます。次に使う場所を指す矢印も移動します。
 
 ■■■■■■□□□□□□□□□□
       ↑
 
のような感じになります。
要するに使い方が決まっています。
 
次に使う場所が,あらかじめ決まっているので,
新規オブジェクトの作成に入るまでが速いという特徴があります。
 
 
一方,C++ 側の仕組みは,
 
初期状態は,
 
 □□□□□□□□□□□□□□□□
 
になります。次に利用する場所は特に決まっていませんし,
また,使い方も決まっていません。
 
例えば,4つずつ使おうと決めたとします。
すると,以下のようになります。
 
 □□□□ □□□□ □□□□ □□□□
 ↑    ↑    ↑    ↑
 
↑ は,次に使う場所の候補です。
 
使用するオブジェクトは三つほど使うとします (■ は使用済み を表す)
とりあえず最初のものを使うとします。
 
 ■■■□ □□□□ □□□□ □□□□
      ↑    ↑    ↑
 
↑ は,次に使う場所の候補です。
さらに,もう一つ同じオブジェクトを使うと
 
 ■■■□ ■■■□ □□□□ □□□□
           ↑    ↑
 
のようになり,次に利用する場所の候補は上のようになります。
ここで,最初の3つが使用されていないことになったとします。
 
(注: 実は,
  この「使用されていないことになった」ということを確定する仕組みが必要になります。
  ・オブジェクトをそもそも共有しないことにする
  ・オブジェクトを渡す時は,常に所有権も渡す
  ・強参照と弱参照の仕組みを利用する
  ・オブジェクトを参照カウント方式で管理する etc.)
 
すると,(注のような何らかの仕組みで) 確定的な回収が行われて,
 
 □□□□ ■■■□ □□□□ □□□□
 ↑         ↑    ↑
 
のような状態になります。↑ は,次に使う場所の候補です。
さらに,もう一つ同じオブジェクトを使おうとするとき
最初の矢印のものを選んだとします
すると,
 
 ■■■□ ■■■□ □□□□ □□□□
           ↑    ↑
 
のようになります。↑ は,次に使う場所の候補です。
ここで,2つ目のの3つが使用されていないことが確定され,回収されました。
すると,
 
 ■■■□ □□□□ □□□□ □□□□
      ↑    ↑    ↑
 
のようになります。↑ は,次に使う場所の候補です。
 
あらかじめ,上の例のように,いくつか使う場所を確保しておけば,
その範囲内で済むのならば,非常に効率のよい使い方ができます。
 
個人的な考えですが,
C++側の人たちが,.NETオブジェクトに興味が無いように見える原因の内の一つは,
このあたりにあるんじゃないかと思います。
 
さらに,
.NET が使用するマネージ スレッドの制限からくる違いもあるのですが,
時間が取れるまたの機会にします。
 

回答
投稿日時: 11/10/24 02:53:22
投稿者: yayadon

話は戻りますが,マコさんからの疑問が入っているので。
 

ふるふる さんの引用:
varptr(Application.ActiveCell),varptr(Application.ActiveCell) 

発言者の意図はわからないので,置いておいて,
自分が意図したのは,この場合,比べる値は,VarPtr でなく ObjPtr です。
 
ObjPtr() は,オブジェクト参照のデータ値を返す関数です。
VBA のオリジナル実装(MSの実装)では,仮想メモリのアドレス値を使っています。
 
ということで,
ActiveCell と ActiveWorkbook の ObjPtr関数の値を比べてみます。
 
ActiveCell は,Rangeインターフェースのオブジェクト参照で,
ActiveWorkbook は,Workbookインターフェースのオブジェクト参照です。
 
Rangeインターフェースのオブジェクト参照のデータ値と
Workbookインターフェースのオブジェクト参照のデータ値を比べることになります。
 
MsgBox ObjPtr(Application.ActiveCell) = ObjPtr(Application.ActiveCell)
MsgBox ObjPtr(Application.ActiveWorkbook) = ObjPtr(Application.ActiveWorkbook)

上は,False になって,下は True になります。
 
ですが,自分が意図したのは,そうことではなくて,
下が True になっているのは,たまたまです。
 
たまたまではなく,確実に断定するには,
COM仕様的には,以下のコードのように
 IUnknownインターフェースのオブジェクト参照のデータ値を比べて,
それらが同じ値かどうか?を確認することで,断定できます。
 
というのは,
IUnknownインターフェースのオブジェクト参照のデータ値同じ値の時に,
同じインスタンスだと保障されるように,
オブジェクトの実装者は,実装する約束になっているからです。
 
また,
COMオブジェクトは,IUnknownインターフェースを実装しないといけないことになっています。
 
なので,
Rangeオブジェクトは,Rangeインターフェースの他に,
IUnknownインターフェースも実装しています。
Workbookオブジェクトは,Workbookインターフェースの他に,
IUnknownインターフェースも実装しています。
 
 
ObjPtr() を使って,インスタンスが同じか?を調べるには,
以下のように,一度,stdole.IUnknown へキャストというか,
実際は,Rangeオブジェクトに対して,
IUnknownインターフェースのオブジェクト参照を渡してくださいと,
QueryInterface します。
 
それらは,VBA が背後でやってくれます。
VBA の仕様的には,Set-型強制 (Set-Coercion) といいます。
 
'' IUnknownインターフェース用のオブジェクト変数
Dim unk1 As stdole.IUnknown
Dim unk2 As stdole.IUnknown


'' ActiveCellの場合
Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Application.ActiveCell
Set unk1 = rng1    ' Set-型強制 (Set-coercion) つまり QueryInterface
Set rng2 = Application.ActiveCell
Set unk2 = rng2    ' Set-型強制 (Set-coercion) つまり QueryInterface

MsgBox ObjPtr(unk1) = ObjPtr(unk2)     ' (A)


'' ActiveWorkbookの場合
Dim wb1 As Workbook
Dim wb2 As Workbook

Set wb1 = Application.ActiveWorkbook
Set unk1 = wb1    ' Set-型強制 (Set-coercion) つまり QueryInterface
Set wb2 = Application.ActiveWorkbook
Set unk2 = wb2    ' Set-型強制 (Set-coercion) つまり QueryInterface

MsgBox ObjPtr(unk1) = ObjPtr(unk2)     ' (B)

(A) は,False になり,(B) は True になります。
先ほどと結果は同じですが,こちらが確実ということです。
 
注: 一度,Range型 や Workbook型 のオブジェクト変数に入れていますが,
 直接 Set unk1 = Application.ActiveCell のようにしても同じです。
 
 
上のようなコードは,面倒なのと効率が良くない筈なので,
Is演算子を使って,
(A) と (B) は,
 
MsgBox Application.ActiveCell Is Application.ActiveCell           ' (a)
MsgBox Application.ActiveWorkbook Is Application.ActiveWorkbook   ' (b)

のように一発で済むという話です。
該当レスのすぐ下の Abyssさんのレスで,
QueryInterface の話が出てきているのは,そういう意味です。
 
 
繰り返しになりますが,
Nothing は,オブジェクト参照用のリテラル
(VBAのオリジナル実装(MSの実装)では,アドレス値 0 を使用して,
オブジェクト参照のデータ値が何も指していないことを表すもの)
なのと,
Is演算子は,オブジェクト参照を扱う演算子です。
 
なので,Nothing は,
 
MsgBox Nothing Is Application.ActiveCell
MsgBox Nothing Is Nothing

のようにも,使えます。
2つ目は意味はないですが,エラーにならず,しかも,True になります。
 
 
 

投稿日時: 11/10/24 11:14:06
投稿者: kumatti
投稿者のウェブサイトに移動

yayadonさん、フォロー頂きましてありがとうございました。m(_ _)m

回答
投稿日時: 11/10/25 12:58:34
投稿者: ふるふる
投稿者のウェブサイトに移動

>発言者の意図はわからないので,置いておいて,
>自分が意図したのは,この場合,比べる値は,VarPtr でなく ObjPtr です。
 
すいません、objPtrの間違いです。何か違っている気がしていたのですが思い出せませんでした。
 
ここではなされている内容は、かいつまんで言うと、オブジェクトつまりクラスのインスタンスはある一定のメモリ領域を占有し、二つの変数がそれぞれ同一のメモリ領域を示しているならば、同一のインスタンスである、ということですよね。
それは正しいのですが、あるクラスの二つのインスタンスが同じである、という意味はクラスのメンバーが全て同じ値である、という場合もあるかと思うのです。
この場合、二つのインスタンスは同一ではない(片方のインスタンスのメンバーの値を変えると同一性が失われる)けど、内容は同じ。 こういう場合のための比較方法というのは、今のところ自分でメソッドを作らないといけないといけないでしょうけど、一般的ではないのでしょうかね?
 
 

回答
投稿日時: 11/10/25 14:05:02
投稿者: 月
投稿者のウェブサイトに移動

今までの話の流れと無関係です。すみません。
 
CELLの値とリアルタイムに表示が連動するUserForm用コントロール
http://www.moug.net/faq/viewtopic.php?t=60582
 
クラスについて。
 
既存のクラスを参考にするとよいと思います。
Range、FileSystemObject、Collection、Dictionary、すべてクラスです。
 
・どのようなメンバ(メソッド、プロパティ、イベントのこと)があるか
・何を1個のクラスとしているか
 
参考になると思います。

回答
投稿日時: 11/10/25 15:20:23
投稿者: yayadon

ふるふる さんの引用:
二つのインスタンスは同一ではない(片方のインスタンスのメンバーの値を変えると同一性が失われる)けど、内容は同じ。 こういう場合のための比較方法というのは、今のところ自分でメソッドを作らないといけないといけないでしょうけど、一般的ではないのでしょうかね?

IsEqual という名前になっているのは,そっち系でしょうね。
 

投稿日時: 11/10/27 16:38:27
投稿者: kumatti
投稿者のウェブサイトに移動

・Excel 2010 x64でのメソッドのフック例
 
初回のMsgBox

---------------------------
Microsoft Excel
---------------------------
エラー 438
オブジェクトは、このプロパティまたはメソッドをサポートしていません。
---------------------------
OK   
---------------------------

次のMsgBox
---------------------------
Microsoft Excel
---------------------------
エラー 438
オブジェクトは、このプロパティまたはメソッドをサポートしていません。
---------------------------
OK   
---------------------------

実行結果
test.xlsm のIDispatch::Invoke (DispId=&HAF) が呼ばれました
test.xlsm のIDispatch::Invoke (DispId=&HAF) が呼ばれました
test.xlsm のIDispatch::Invoke (DispId=&HAF) が呼ばれました
↑実行時バインディング時はInvokeが2度呼ばれので。

コードの中身がよく分からない方は、実行されないでください。
※ 過去ログからshiraさんのコードをx64用に変更。
Option Explicit
Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (ByVal lpAddress As Any, _
        ByVal dwSize As LongLong, _
        ByVal flNewProtect As Long, _
        lpflOldProtect As Long) As Long
Const PAGE_EXECUTE_READWRITE = &H40
Private Declare PtrSafe Sub CopyLongPtr Lib "kernel32" Alias "RtlMoveMemory" _
        (Destination As Any, Source As Any, _
        Optional ByVal Length As LongLong = 8)
Private Declare PtrSafe Function DispCallFunc Lib "oleaut32" _
        (ByVal pvInstance As LongPtr _
        , ByVal oVft As Long, _
        ByVal cc As Long, ByVal vtReturn As Integer, _
        ByVal cActuals As Long, prgvt As Integer, _
        prgpvarg As LongPtr, _
        pvargResult As Variant) As Long
Const CC_STDCALL = 4
Const DISP_E_MEMBERNOTFOUND = &H80020003
Const S_OK = &H0&
Const DISPID_SAVECOPYAS = &HAF&
 
Private m_pProc As LongPtr ' フックプロシージャのアドレス
Private m_PrevProc As LongPtr ' 以前の関数ポインタ
Private m_pVtbl As LongPtr ' 関数ポインタをセットしたアドレス
 
 ' フックプロシージャ(DISPID or 実行時バインド用)
Private Function InvokeProc( _
        ByVal This As Object, _
        ByVal dispIdMember As Long, _
        ByVal riid As Long, ByVal lcid As Long, _
        ByVal wFlags As Integer, ByVal pDispParams As LongPtr, _
        ByVal pVarResult As LongPtr, _
        ByVal pExcepInfo As LongPtr, _
        ByVal puArgErr As LongPtr) As Long
On Error Resume Next

    Const PARAMCOUNT = 8
    Dim Args() As Variant
    Dim pArgs(0 To PARAMCOUNT - 1) As LongPtr
    Dim vt(0 To PARAMCOUNT - 1) As Integer
    Dim vntResult As Variant
    Dim pObj As LongPtr
    Dim i As Long
    Dim hr As Long
    Dim wb As Workbook
 
     If dispIdMember = DISPID_SAVECOPYAS Then
        Set wb = This
        Debug.Print wb.Name; " の";
        Set wb = Nothing
        Debug.Print "IDispatch::Invoke (DispId=&H" & _
                 Hex$(dispIdMember) & ") が呼ばれました"
 
         ' 戻り値で実行時エラーを発生させる
        ' (エラーにしたくなければ S_OK に変更)
        InvokeProc = DISP_E_MEMBERNOTFOUND
        Exit Function
    End If
 
     ' フック対象以外なら引き続き 本来のInvokeメソッドを呼び出す
 
     Args = VBA.Array(dispIdMember, riid, lcid, wFlags, pDispParams, _
                     pVarResult, pExcepInfo, puArgErr)
    For i = 0 To PARAMCOUNT - 1
        vt(i) = vbLongLong
        pArgs(i) = VarPtr(Args(i))
    Next
    'vt(3) = vbInteger
    pObj = ObjPtr(This)
 
     ' 再帰呼び出しの発生に注意
    EndInvokeHook
    hr = DispCallFunc(pObj, 8 * 6, CC_STDCALL, vbLongLong, _
                      PARAMCOUNT, vt(0), pArgs(0), vntResult)
    StartInvokeHook
    If hr < 0 Then
        InvokeProc = hr
    Else
        InvokeProc = vntResult
    End If
 
End Function

' メモリ上の指定されたアドレスに64ビット値を書き込み
Private Function ForceCopyLongPtr(ByVal Address As LongPtr, _
                           ByVal Value As LongPtr) As Boolean
    Dim lngOld As Long
    If VirtualProtect(Address, 8, _
                      PAGE_EXECUTE_READWRITE, lngOld) = 0 Then
        Exit Function
    End If
    CopyLongPtr ByVal Address, Value
    VirtualProtect Address, 8, lngOld, lngOld
    ForceCopyLongPtr = True

End Function
 
' フック開始 (DISPID or 実行時バインド用)
Sub StartInvokeHook()

    Dim unk As stdole.IUnknown
    Dim wb As Workbook
    Dim obj As Object
    Dim pObj As LongPtr
    Dim pVtbl As LongPtr
    If m_pVtbl Then Exit Sub ' すでに開始している
 
     ' 内側にあるWorkbookオブジェクトの参照を取得
    Set unk = ThisWorkbook
    Set wb = unk
    Set obj = wb ' 念のため
    CopyLongPtr pObj, obj 'pObj=ObjPtr(obj)
    'If pObj = 0 Then Exit Sub
 
     CopyLongPtr pVtbl, ByVal pObj
    pVtbl = pVtbl + 8 * 6 ' Invokeメソッドのオフセット

    CopyLongPtr m_PrevProc, ByVal pVtbl ' 以前のポインタを退避
    m_pProc = VBA.CLngPtr(AddressOf InvokeProc)

    If Not ForceCopyLongPtr(pVtbl, m_pProc) Then Exit Sub
    m_pVtbl = pVtbl
 
End Sub

' フック終了
Sub EndInvokeHook()

    Dim p As LongPtr
    If m_pVtbl = 0 Then Exit Sub
    CopyLongPtr p, ByVal m_pVtbl
    If p = m_pProc Then ' 念のためのチェック
        ForceCopyLongPtr m_pVtbl, m_PrevProc
    End If
    m_pVtbl = 0
 
End Sub
 

Sub test()
 
    Dim wb As Workbook
    Dim obj As Object
    Set obj = ThisWorkbook
    Set wb = obj
 
    StartInvokeHook
    On Error GoTo Err_Handler
    obj.SaveCopyAs "C:\テスト02.xls" ' 実行時バインド
    wb.SaveCopyAs "C:\テスト01.xls" ' 事前バインド
    EndInvokeHook
 
    'obj.SaveCopyAs "C:\これは保存される.xls"
    Exit Sub
Err_Handler:
    MsgBox "エラー " & Err.Number & vbLf & Err.Description, vbCritical
    Resume Next
 
End Sub

回答
投稿日時: 11/10/28 06:21:16
投稿者: yayadon

DispCallFunc の宣言のところの ByVal oVft As Long
も LongPtr なのでは?

投稿日時: 11/10/28 08:19:57
投稿者: kumatti
投稿者のウェブサイトに移動

> ↑実行時バインディング時はInvokeが2度呼ばれので。
これはExcelの話なので。
  
http://msdn.microsoft.com/en-us/library/ms221473%28v=vs.85%29.aspx

HRESULT DispCallFunc(
  void *pvInstance,
  ULONG_PTR oVft,
  CALLCONV cc,
  VARTYPE vtReturn,
  UINT cActuals,
  VARTYPE *prgvt,
  VARIANTARG **prgpvarg,
  VARIANT *pvargResult
);
> ByVal oVft As Long
も LongPtr なのでは?
yayadonさん、ご指摘ありがとうございます。
確かにそうですね。
  
・権限昇格のコード
CreateObject("Shell.Application").ShellExecute "excel.exe", """" & ThisWorkbook.FullName & """", vbNullString, "runas", 1
ThisWorkbook.Saved = True
Application.Quit

投稿日時: 11/10/31 08:12:24
投稿者: kumatti
投稿者のウェブサイトに移動

・LongLong型だと無理なお話

---------------------------
Microsoft Visual Basic for Applications
---------------------------
コンパイル エラー:

型が一致しません。
---------------------------
OK   ヘルプ   
---------------------------

    Dim tmp1^(0 To 5103^) 'NG
    Dim tmp2^(0 To 5103@) 'OK
    Dim tmp3^(0 To 5103#) 'OK

配列の要素数の限界はいくつ?
http://hpcgi1.nifty.com/MADIA/VBBBS/wwwlng.cgi?print+200306/03060015.txt
 
# 添字の型はLong型だと今更知る。

回答
投稿日時: 11/10/31 22:20:50
投稿者: simple

割込み、失礼します。
 
コード作成関係のネタですが、こんな話はどうでしょうか。
http://en.wikipedia.org/wiki/Hofstadter_sequence
に Hofstadter Female and Male sequences というのが定義されています。
 
F(0) = 1
M(0) = 0
F(n) = n - M(F(n-1)) (n>0のとき)
M(n) = n - F(M(n-1)) (n>0のとき)
上記で二つの系列 F, M を定めます。
 
M(200)を単純な再帰で求めようとすると
結構時間がかかります。40秒くらい。
このとき、M(10000)を求めてください。
# すぐに回答が出てしまうかもしれません。

回答
投稿日時: 11/11/01 12:42:16
投稿者: 月
投稿者のウェブサイトに移動

simple さんの引用:
このとき、M(10000)を求めてください。

求めてみましたw自信はありませんw
他の人のことも考えて、答えはあえて載せません。
M(200)でもM(10000)でも一瞬で求まります( ̄ω ̄;)

回答
投稿日時: 11/11/01 12:57:02
投稿者: 月
投稿者のウェブサイトに移動

私からもクイズ。
 
「プログラマーはハロウィンとクリスマスを区別できない。なぜだろう」
 
Twitterで流れてきました。
答えもそこに書いてあるので検索すればすぐわかりますが、面白かったので。

回答
投稿日時: 11/11/01 14:28:52
投稿者: ふるふる
投稿者のウェブサイトに移動

>このとき、M(10000)を求めてください。
 
単純な再帰プログラムだと、スタックエラーになるかも。M(200)でも1分以上かかったので。なのでこれは答えではないです。

Option Explicit

Function Female(n)
    If n = 0 Then
        Female = 1
    Else
        Female = n - Male(Female(n - 1))
    End If
End Function

Function Male(n)
    If n = 0 Then
        Male = 0
    Else
        Male = n - Female(Male(n - 1))
    End If
End Function

Sub Calc()
    Dim num As Integer
    num = InputBox("Input M(Integer)?")
    MsgBox Male(num)
End Sub

ソースコードはきれいなんですけどね。
 
>「プログラマーはハロウィンとクリスマスを区別できない。なぜだろう」
「エキスパートCプログラミング」で読んだネタだったかな。
 

回答
投稿日時: 11/11/01 14:36:13
投稿者: 月
投稿者のウェブサイトに移動

ふるふる さんの引用:
単純な再帰プログラムだと、スタックエラーになるかも。M(200)でも1分以上かかったので。なのでこれは答えではないです。

なるほど〜。再帰はそうやって書くんですね。
私は再帰で書いてません。
 
ふるふる さんの引用:
>「プログラマーはハロウィンとクリスマスを区別できない。なぜだろう」
「エキスパートCプログラミング」で読んだネタだったかな。

本当ですね。
 
エキスパートCプログラミング
http://www.amazon.co.jp/gp/product/toc/4756116396

回答
投稿日時: 11/11/01 20:47:40
投稿者: simple

月さん、ふるふるさん、コメントありがとうございました。
 
この場合、小さい方から確定していけばよいのは、
ご指摘のとおりです。
 
実は関数型プログラミング言語の本で、なかなかおもしろいと
思ったので、以下紹介します。
テーマの趣旨を説明することで、責めを果たしたいと思います。
 
(1)
相互再帰の形を保ったままで、性能を上げることを考えます。
こうした場合によく使われるのが、
メモ化(memoization)という手法です。
http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96
 
これは、一度計算した結果はdictionaryあたりに保存しておいて、
次に呼ばれた時は計算せずに返すという方法です。
 
male と female それぞれの頭で、dicに n が存在していたら、
値を返して、exitします。
逆に、それぞれの最後では、dicに登録します。
 
これで、相当スピードはあがります。
(2)
しかし、これだけだと、ふるふるさんから指摘がありましたように、
n = 4000 近くで、stack overflowになると思います。
 
それで、これを回避するには、dicによるキャッシュが
頭から計算されるようにすることです。
 
具体的には、小さいnから計算していくことになります。
コードは示しませんが、こんな考え方で、再帰の形で実行が可能です。
 
ちなみに m(10000)は 6180 です。
 
------------------------
以下は余談です。スキップいただいて結構です。
 
(3)
memoizationはコードのなかでロジックを組み込むことが普通でしょうけど、
ある種の関数型言語(以下、clojureの例です)では、
ロジックを書かなくても ふるふるさんと同様な機能を持つ関数 man に対し、
  (def man (memoize man) )
のようにすることで、関数 man にメモ化機能を組み込むことが可能です。
 
また、遅延評価の機能を持つ言語では、
  (iterate inc 0) で 0から始まる無限数列 を定義しておいて、
その自然数列に対して、無限数列を返すメソッド man-seq を
  (def man-seq (map man (iterate inc 0)) )
のように定義することができます。
 
この数列の100 番目を取り出したいときは、
  (nth man-seq 100)
を実行すると、この時点で初めて100番目までが実行されます。
101番目以降は実際に呼ばれるまでは実行されません。
(こうした仕組みが、遅延評価と呼ばれているようです。)
 
読みかじり状態で、本当に大胆だと思いますが、簡単な紹介まで。

回答
投稿日時: 11/11/01 21:31:20
投稿者: 月
投稿者のウェブサイトに移動

simple さんの引用:
m(10000)は 6180 です。

わーい、当たった。
https://gist.github.com/c38be47a990a9653e214
 
 
月 さんの引用:
「プログラマーはハロウィンとクリスマスを区別できない。なぜだろう」

答え
 
「OCT 31 = DEC 25だからだ」
 
https://twitter.com/_k18/status/130667610206515201
 
解説
 
OCT 31は8進数の31と読めます。
私たちが普段使っているのは10進数で、9の次は10です。つまり9の次に桁が上がります。
8進数では7の次が10です。つまり7の次に桁が上がります。
8進数の31を10進数に変換すると25になります。
DEC 25は10進数の25と読めます。
よって「OCT 31 = DEC 25」 となります。

回答
投稿日時: 11/11/02 07:30:54
投稿者: simple

どちらでもいいことに属しますが、前の発言で、
> 無限数列を返すメソッド man-seq
と書いたのはデタラメで、man-seq は単なるsequenceの書き間違いでした。
訂正させていただきます。
 
「OCT 31 = DEC 25」 は私も閲覧中のtwitterで目にしました、なるほど、と。
書籍にもネタとして使われていた由緒あるものとは知りませんでした。
# でも仏教徒の私には縁遠い風習で・・・。 Wink

投稿日時: 11/11/08 08:43:34
投稿者: kumatti
投稿者のウェブサイトに移動

質問でなくて報告なので、こちらで。
 
Abyssさんお勧めのSetWindowSubclassのマシン語による使用結果です。
http://www.moug.net/faq/viewtopic.php?t=60887
A ↓エクセルがコケます。

	cmp edx, 20Ah  ;WM_MOUSEWHEEL
	jnz label
	xor rax, rax
	ret
label:
	mov r10, 1111111111111111h ;DefSubclassProc
	sub rsp, 32
	call r10
	add rsp, 40
	ret

B ↓ リボン等のスクロールは可、右端の_、□、☓ボタンが効かない。
	sub rsp, 56
	cmp edx, 20Ah  ;WM_MOUSEWHEEL
	jnz label
	xor rax, rax
	add rsp, 56
	ret
label:
	mov r10, 1111111111111111h ;DefSubclassProc
	call r10
	add rsp, 56
	ret
    
VBA
Option Explicit

Private Declare PtrSafe Function SetWindowSubclass Lib "ComCtl32" (ByVal hWnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr, ByVal dwRefData As LongPtr) As Long
Private Declare PtrSafe Function RemoveWindowSubclass Lib "ComCtl32" (ByVal hWnd As LongPtr, ByVal pfnSubclass As LongPtr, ByVal uIdSubclass As LongPtr) As Long
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal length As LongPtr)
Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal lpAddress As Any, ByVal dwSize As LongPtr, ByVal flAllocationType As Long, ByVal flProtect As Long) As LongPtr
Private Declare PtrSafe Function VirtualFree Lib "kernel32" (ByVal lpAddress As Any, ByVal dwSize As LongPtr, ByVal dwFreeType As Long) As Long
Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, ByVal lpProcName As String) As LongPtr
Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleW" (ByVal lpModuleName As LongPtr) As LongPtr
Const MEM_TOP_DOWN = &H100000
Const MEM_RELEASE = &H8000&
Const PAGE_EXECUTE_READWRITE = &H40&
Const MEM_RESERVE = &H2000&
Const MEM_COMMIT = &H1000&

Private vp As LongPtr '実行可能コードへのポインタ
Const n1 = 22& 'DefSubclassProc

Sub starteHook()

    Const code$ = "020AFA8138EC834848C03148087500001111BA49C338C483FF411111111111119090C338C48348D2"
    Dim hWnd As LongPtr, WndPtr As LongPtr, funcPtr As LongPtr
    Dim i&, j&, UB&, length&, slen&
    Dim lnglngCode^()

    vp = 0^  '初期化

    hWnd = Application.hWnd
    slen = Len(code)
    UB = slen \ 16
    length = slen + 1
    ReDim lnglngCode(0 To UB - 1)
    
    For i = 1 To slen Step 16
        lnglngCode(j) = "&H" & Mid$(code, i, 16)
        j = j + 1
    Next

    '実行可能属性を持った領域を確保。
    vp = VirtualAlloc(0&, length, MEM_RESERVE Or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
    CopyMemory ByVal vp, lnglngCode(0), length

    funcPtr = GetProcAddress(GetModuleHandle(StrPtr("comctl32")), "DefSubclassProc")
    CopyMemory ByVal vp + n1, funcPtr, 8

    'サブクラス化開始
    SetWindowSubclass hWnd, vp, hWnd, 0

    
End Sub


Sub endHook()

    Dim hWnd As LongPtr
    hWnd = Application.hWnd
    
    'サブクラス化終了
    RemoveWindowSubclass hWnd, vp, hWnd
    VirtualFree vp, 0, MEM_RELEASE

End Sub

回答
投稿日時: 11/11/08 15:30:34
投稿者: Abyss
メールを送信

SetWindowSubclassとDefSubclassProc関数は最初の4パラメーターが
一致しますね。であれば、x64環境では jmp命令でいい気がします。
 

	cmp dx, 20Ah  ;WM_MOUSEWHEEL
	jnz label
	xor rax, rax
	ret
label:
	mov r10, qword 0h ;DefSubclassProc
	jmp r10

 
const code$ = "480275020afa8166000000ba49c3c031e2ff410000000000"
const n1 = 13^
 

回答
投稿日時: 11/11/09 07:40:25
投稿者: simple

こんにちは。
もうひとつこんな問題はいかがでしょう。
 
"X10Y1","X10Y10","X10Y2","X1Y1","X1Y10","X1Y2","X2Y1","X2Y10","X2Y2"
という配列を
"X1Y1", "X1Y2", "X1Y10", "X2Y1", "X2Y2", "X2Y10", "X10Y1", "X10Y2", "X10Y10"
のように、
文字部分と数字部分に分けた場合の順序に従ってソートしてください。
 
上記は(数字以外の)文字部分と数字部分のペアが2つですが、
n個でも対応できるようなものを考えて下さい。
正規表現ということになるのでしょうか。
なお、
数字が無い場合もあります。この場合は、例えば、
z1A, z1B, z12, z13, zAB という順序になります。
# もちろん出典はありますが、あえて書きません。

投稿日時: 11/11/09 08:07:40
投稿者: kumatti
投稿者のウェブサイトに移動

Abyssさん、クロスアセンブルまでして頂きまして、
ありがとうございました。
(先のAと変わらず、Excelにメッセージが飛んだ瞬間、コケます)
 
ーーー
こちらを読みまして、即値の指定の仕方を思いついたのですが、
http://www.nasm.us/doc/nasmdo11.html
> 1111111111111111h
と同じ事の様で。
 
結果、上記のBと変わらず。

	sub rsp, 56
	cmp dword edx, 20Ah  ;WM_MOUSEWHEEL
	jnz label
	xor rax, rax
	add rsp, 56
	ret
label:
	mov qword r10, qword 0  ;DefSubclassProc
	call qword r10
	add rsp, 56
	ret

元々、天才プログラマーの方のコードの模倣(解析)から
始まったので、凡人(私の事です)には
越えられない壁なのかなと既にあきらめています。

回答
投稿日時: 11/11/09 12:02:06
投稿者: Abyss
メールを送信

ダメでしたかぁ。
 
64ビットOSでもMicrosoft側がOfficeインストールは32ビットを推薦する
(規定では32ビットInstallらしいですね)理由がTypeLibraryやActiveXの互換性の
問題が存在するに加わって、もし今回の検証方法が正しければこれも
もう一つの問題点ですかね。今のところ、64ビット環境に魅力を感じない
私としては、成功例を見てみたいだけですね。
 
お陰様で、Assemblyの「Microsoft FASTCALL Convention」の勉強をする
きっかけになりました。ありがとうございます。

投稿日時: 11/11/09 16:30:11
投稿者: kumatti
投稿者のウェブサイトに移動

Abyssさん、コメントありがとうございました。
# 私も出来るのか、そもそも不可能なのかそれだけでも知りたいですね。

投稿日時: 11/11/10 12:37:02
投稿者: kumatti
投稿者のウェブサイトに移動

x64のMASMでも同様なのを確認しました。
(これ以上の展開もありませんので)
# simpleさん、お邪魔しました。

回答
投稿日時: 11/11/11 06:21:16
投稿者: simple

>お邪魔しました。
いえいえ、お邪魔しているのはこちらです。
 
あまり食指が動かないテーマだったでしょうか。
実は、この話は、色々な言語を使って、どれが短く、すっきり書けるか、
ネットで議論されていたものです。
「数字混じり文字列ソート」で検索するとヒットします。
 
以下では、VBAのコードは挙げていません。次回以降にします。
関係乏しい話で恐縮です。でも実用一辺倒でなくてもよろしいかと。
お急ぎのかたは、以下すべてスキップ願います Wink
-------------------------
オブジェクト指向言語Rubyを使ったものが一番短く書けるようですね。
はじめから横道に逸れて恐縮ですが、最初にそれを紹介しましょう。 
 

class Array
    def mixed_sort
        sort_by{|s| 
           s.scan(/(\d+)|([^\d]+)/).map{|a|
               a[1] || a[0].to_i
           }
        } 
    end
end

ary = ["X10Y1","X10Y10","X10Y2","X1Y1","X1Y10","X1Y2","X2Y1","X2Y10","X2Y2"]
p ary.mixed_sort
# =>  ["X1Y1", "X1Y2", "X1Y10", "X2Y1", "X2Y2", "X2Y10", "X10Y1", "X10Y2", "X10Y10"] が出力されます。

---------------------------
配列を扱うクラスは、標準的、基本的なもので、
もちろん言語仕様として提供されているのですが、
Rubyでは、ユーザーが自由にそれにメソッドを追加できます。
(上ではmixed_sortというメソッドを Arrayクラス に追加しています。)
この仕組みをオープンクラスと呼んでいます。
 
mixed_sortというメソッドの中身ですが、
   sort_by{|s| 
      s.scan(/(\d+)|([^\d]+)/).map{|a| 
          a[1] || a[0].to_i
      }
   }

   sort_by{|s|
     ・・・・
   }
の{| | ・・・ } 部分はブロックと呼ばれます。
ごくおおざっぱに言うと、sort_byというメソッドに
無名関数のようなものを渡している、と考えて良いと思います。
 
「配列の各要素 s に対して、ブロックの中で計算した結果」を基準にして、
ソートが行われます。
 
例えば、配列の一要素 "X10Y1"がブロック引数 s にセットされたあと、
 
     s.scan(/(\d+)|([^\d]+)/).map{|a| 
            a[1] || a[0].to_i
      }
は何をしているかというと、
まず、s.scan(/(\d+)|([^\d]+)/) は、
   /(\d+)|([^\d]+)/という正規表現を使って、
数字の連と、文字列を それぞれグループ として記憶し、
それを配列にしたものを返します。
 
"X10Y1"の場合、
[ [nil ,"X"] , [10, nil] ,[nil "Y"], [1,nil] ] という配列を返します。
 
さらに、それにmapというブロック付きメソッドを作用させると、
各要素にブロックを適用した結果を配列として返します。
 
a[1] || a[0].to_i は、
・a[1]つまり文字列があれば、それ以下は計算せずにその文字列 a[1]を返し、
・文字列がなければ、a[0]つまり数字を数値(integer)に変換したものを返します。
従って、[ "X", 10 , "Y", 1 ] が返ります。
 
配列の要素      ソートの判定に使われる配列
----------      --------------------------
"X10Y1"       [ "X", 10 , "Y",  1 ] 
"X10Y10"         [ "X", 10 , "Y", 10 ] 
・・・・・・・
"X2Y2"           [ "X",  2 , "Y",  2 ]  
となりますから、
sort_byはこの配列を使ったソートを実行します。
 
Rubyは結局、よく使われる機能が言語仕様としてうまく備わっていますから、
その点がすっきり書ける原動力だと思います。
 
ちなみに、sort_byというメソッドは version 1.8 で追加された機能で、
それ以前は、sortしかなく、
C言語の qsort に比較関数のポインタを渡すのと同じ感じで、
大小関係を返すブロックをsortに渡すことが行われていました。
(場合によっては、速度向上のため、schwartz 変換を施すことが
perlと同様行われていました。今はsort_byのお陰で、
その必要はありませんが。)
 
ブロックという仕組みは、
関数1つを採る高階関数を実現する仕組みと言えます。
それが結構使い易い形で実現されていて、このブロックが
Rubyの大きな利点、特徴かと思います。
関数型言語の一部機能をうまく取り入れていると思います。
(Rubyの開発者によるブロックについての解説が下記にあります。
http://itpro.nikkeibp.co.jp/article/COLUMN/20050930/221971/?ST=oss
http://itpro.nikkeibp.co.jp/article/COLUMN/20050930/221978/?ST=oss
http://itpro.nikkeibp.co.jp/article/COLUMN/20050930/221979/?ST=oss

 
----------------------------
それで、VBAではどう書くのでしょうか。
VBAで正規表現を書くのはRubyに比べると正直言って面倒です。
しかも、sortに相当するものがありませんから、大変です。
# 簡単かと思って発言してしまったのですが、
# 手間どりました。発言をいったん取り消そうかと思ったほどです。
 
もう少し時間を置こうかと思います。
 
# 一部にトンデモナイ間違いがありましたので修正しました。
# 時間感覚が麻痺していたみたいです。

回答
投稿日時: 11/11/11 22:47:36
投稿者: simple

コメントがないので、私案をあげてみます。
反則かもしれませんけど、JScriptのSortメソッドを使いました。
 
この場合、SortにSortfunctionをどうやって与えるか不明なので、
その場しのぎの方法で数字も文字列として大小比較することにしました。
もっと良い方法がありましたら、ご教示くださいまし。
 

Sub test()
    Dim ary
    Dim re As Object
    Dim matches, m
    Dim s
    Dim myDigit As String, myStr As String
    Dim k As Long
    Dim p As Long
    Dim mat() As String
    Dim sc As Object
    Dim myList, temp
    Dim x

    ary = Array("X10Y1", "X10Y10", "X10Y2", "X1Y1", _
                "X1Y10", "X1Y2", "X2Y1", "X2Y10", "X2Y2")
    Set re = CreateObject("VBScript.RegExp")
    re.Pattern = "(\d+)|(\D+)"
    re.Global = True

    Set sc = CreateObject("ScriptControl")
    sc.Language = "JScript"

    ' myList ; ary の各要素について、
    '          数字と文字に分離したものからなる配列
    '            (ただし、最後に要素そのものを追加)
    '          を要素とする配列
    Set myList = sc.eval("new Array()")
    For Each s In ary
        Set matches = re.Execute(s)
        p = 1
        Set temp = sc.eval("new Array()")
        For Each m In matches
            myDigit = m.submatches(0)
            myStr = m.submatches(1)
            p = p + 1
            If Len(myDigit) = 0 Then
                temp.push myStr
            Else
                temp.push Right(String(10, "0") & myDigit, 10)
            End If
        Next
        temp.push s
        myList.push temp
    Next

    '配列をソート
    CallByName myList, "sort", VbMethod

    '結果を配列にセット
    ReDim mat(UBound(ary))
    p = 0
    For Each x In myList
        mat(p) = CallByName(x, "pop", VbMethod)  '最後の項目を取得
        p = p + 1
    Next
    Stop
End Sub

 
自前でSort関数を書いてもいいのかもしれませんが。

回答
投稿日時: 11/11/11 23:04:22
投稿者: Abyss
メールを送信

> x64のMASMでも同様なのを確認しました。
 
それなら、Excel固有の問題ではなく、アセンブリの
組み方に問題があると私は解釈したいですけど。

回答
投稿日時: 11/11/11 23:19:22
投稿者: n

こんにちは。
>「数字混じり文字列ソート」
XP以降(?)だと Function StrCmpLogicalW Lib "SHLWAPI.DLL" で判定してSortもありでしょうか。

回答
投稿日時: 11/11/12 10:53:36
投稿者: simple

nさん ありがとうございました。
なるほど、そういうAPIがあるんですか、知りませんでした。
PHPにもそのままのメソッドがあるようでした。
 
ところで、具体的にどう書いたらいいでしょうか。
JScript側でも自由にAPIの関数が呼べるのでしょうか。
JScriptを使うという提案じゃないのかな?
ご教示いただければ幸いです。
 
Declare Function StrCmpLogicalW Lib "SHLWAPI.DLL" (ByVal lpStr1 As String, ByVal lpStr2 As String) As Long
と宣言し、
    sc.AddCode "function comp(x, y){return StrCmpLogicalW(x,y);}"
としたうえで、
    CallByName myList, "sort", VbMethod, sc.CodeObject.comp
などとしても、CallByNameのところで、
アプリケーション定義またはオブジェクト定義のエラーとなります。
JScript側でStrCmpLogicalWを使うことを宣言している訳ではないんでしょうから、
当然と言えるのでしょうけど。

投稿日時: 11/11/12 11:15:16
投稿者: kumatti
投稿者のウェブサイトに移動

simpleさんらしくないですね。

int StrCmpLogicalW(
  __in  PCWSTR psz1,
  __in  PCWSTR psz2
);

http://msdn.microsoft.com/en-us/library/windows/desktop/bb759947%28v=vs.85%29.aspx
これは、Unicode版のみのAPIですよ。
StrPtrを使わないと。

回答
投稿日時: 11/11/12 16:43:41
投稿者: n

すみません、単純にSort時の比較の話でした。

Option Explicit
Private Declare Function StrCmpLogicalW Lib "SHLWAPI.DLL" ( _
                                        ByVal lpStr1 As String, _
                                        ByVal lpStr2 As String) As Long
Sub test()
    Dim i   As Long
    Dim j   As Long
    Dim mx  As Long
    Dim tmp As String
    Dim ary

    ary = VBA.Array("X10Y1", "X10Y10", "X10Y2", "X1Y1", _
                    "X1Y10", "X1Y2", "X2Y1", "X2Y10", "X2Y2")
    mx = UBound(ary)
    For i = 0 To mx - 1
        For j = i + 1 To mx
            If StrCmpLogicalW(StrConv(ary(i), vbUnicode), _
                              StrConv(ary(j), vbUnicode)) > 0 Then
                tmp = ary(i)
                ary(i) = ary(j)
                ary(j) = tmp
            End If
        Next
    Next
    Debug.Print Join(ary, vbLf)
End Sub

#Sort前後でまとめてStrConvしたほうが良いのかもしれませんが、とりあえず。

回答
投稿日時: 11/11/12 19:15:50
投稿者: simple

# ただ今、帰宅しました。
ご指摘ありがとうございます。
StrPtr は UNICODE文字列バッファーのアドレスを返すので、
この返り値をStrCmpLogicalWの引数にする、ということですかね。
引数の話以前のJScriptとの連携に気をとられて、テキトーでした。
失礼しました。
 
nさん、コードの提供ありがとうございました。やはり直に書いたほうが簡潔ですね。
この比較の場合はこれがベストでしょうね。
 
私の元々の意図は、正規表現の利用、一般的な各種比較関数によるソートということだったのですが、
この方向では、VBAはRubyより冗長にしかならないと思います。
できるだけ簡潔な記述で利用できるようなクラスを工夫しておくと
いった対応が考えられるでしょうか。
 
終わりにもう少し、どなたかご存じでしたら教えてください。
(1)
JScriptとWinAPIとの連携というのは可能なものですか?
(2)
qsort に比較関数を与えるのと同様に、
比較関数を取り替えるようなことをするには、
一般的にはどのようにするのでしょうか。
関数ポインタのようなものを簡単に扱う方法はありますか?
 
やはり、sort本体と、比較関数を切り分け、
比較関数はクラスのメソッドとして実現し、
sortの引数にはそのクラスのインスタンスを渡す方式でしょうか。
比較関数を変える場合は、その都度implementsして
別のクラスを作成していくという方法でしょうか。
(Rubyのブロックは、そのあたりが非常に簡便にできて便利です。)

回答
投稿日時: 11/11/12 21:42:00
投稿者: simple

kumattiさんからご指摘いただいた方法を
nさんのコードで使用すると結果が異なります。
StrPtrを使用する場合は、宣言が変わってくるのかと想像します。
StrPtrを使用する場合の宣言を参考までに教えて下さい。
基本的なことですみません。

投稿日時: 11/11/13 08:15:41
投稿者: kumatti
投稿者のウェブサイトに移動

PCWSTR型を別の書き方をすると、 const WCHAR*型です。
 
なので、nさんの宣言で言えば

Private Declare Function StrCmpLogicalW Lib "SHLWAPI.DLL" ( _
                                        ByVal lpStr1 As Long, _
                                        ByVal lpStr2 As Longg) As Long

です。
半角英数字しか処理しない前提なら、文字化けは起こらないのかもしれません。
(推奨される使い方ではないでしょうけど)

回答
投稿日時: 11/11/13 09:24:55
投稿者: simple

ありがとうございました。動作確認できました。

投稿日時: 11/11/14 08:39:35
投稿者: kumatti
投稿者のウェブサイトに移動

失礼。Longgになっていました。
 > JScriptとWinAPIとの連携というのは可能なものですか?
http://kinuasa.wordpress.com/2011/04/17/vbscript%E3%81%8B%E3%82%89windows-api%E9%96%A2%E6%95%B0%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99/
このAPIがStrCmpLogicalW を指すなら、
Unicode文字列へのポインタをどうSCript側から渡すのか難しいと思います。

投稿日時: 11/11/15 08:22:48
投稿者: kumatti
投稿者のウェブサイトに移動

> SCript側から渡すのか難しい VBA上なら、関係無かったですね。

回答
投稿日時: 11/11/15 14:11:23
投稿者: 月
投稿者のウェブサイトに移動

StrCmpLogicalW関数の代わりとなる関数を書いてみました。
サンプルコードはnさんのコードを利用させていただきました。
バグがないといいのですが(汗)
https://gist.github.com/1366050

回答
投稿日時: 11/11/15 23:59:07
投稿者: simple

kumatti さん、丁寧な応答、ありがとうございました。
 
月さん どうもありがとうございました。
おかげで、APIに依存しない方法が完成したものと思います。
 
ついでに余談で、しかもRuby話で恐縮です。お急ぎのかたは、例によってスキップ下さい。
以前書いたシュワルツ変換というのはこういう話です。
ファイルをその更新時刻でソートしようという例で考えると、

files = Dir["*"]
sorted = files.sort{|a,b| File.new(a).mtime <=> File.new(b).mtime}
というのが一番素直は方法でした。
しかし、これだと比較するたびにFileオブジェクトが作られるので非効率。
そこで、
更新時刻とファイル名を連結した配列を要素に持つ一時配列を作っておいて、
それでソートを掛けて、そのあとで、2番目の要素(ファイル名)を取り出す、
files = Dir["*"].map{ |f|
   [test(?M,f),f]
}.sort.map{|f| f[1]}
などいうことをしていました。
これが発案者の名前に因む、シュワルツ変換です。(Dave Thomas氏の著作から引用)
 
今回の例も、
Set m(0) = Reg.Execute(a)
を何度も繰り返すので、節約しようなどという気にもなるのですが、
多数のソートならいざ知らず、
簡潔なコードにしておいて、全体のわかりやすさを優先するほうが良いのだと思います。
それに速度優先なら、バブルソートではなく、別のソートのほうが効率がいいのでしょう。
今回のテーマとは別ですから、余計な話かもしれません。

回答
投稿日時: 11/11/16 15:48:59
投稿者: 月
投稿者のウェブサイトに移動

simple さんの引用:
今回の例も、
Set m(0) = Reg.Execute(a)
を何度も繰り返すので、節約しようなどという気にもなるのですが、

やってみようと思いコードを書き始めたのですがうまくいきません。
 
Sub test2()
    Dim ary
    Dim Reg As RegExp
    Dim m As MatchCollection

    ary = VBA.Array("X10Y1", "X10Y10", "X10Y2", "X1Y1", _
                    "X1Y10", "X1Y2", "X2Y1", "X2Y10", "X2Y2")
    
    Set Reg = New RegExp
    Reg.Global = True
    Reg.MultiLine = True
    Reg.pattern = "^(\d+|[^\n\d]+)+$"
    Set m = Reg.Execute(Join(ary, vbLf))
    Stop
End Sub

ここでmのSubMatchesに期待したものが入らないのです。
"X10Y1"で言えば、SubMatchesに["X", "10", "Y", "1"]の4つが入ることを期待したのですが、最後の"1"しか入っていませんでした。以上、進捗報告でした。
 
ちなみに、mixedCompare関数を少し修正しました。
変更後のコードのURLは変わりませんが、変更前のコードのURLはこちらです。
https://gist.github.com/1366050/82fdfe7663f6e2cdb1c72766e0e02e9cddb99415

回答
投稿日時: 11/11/17 15:41:04
投稿者: 月
投稿者のウェブサイトに移動

月 さんの引用:
ちなみに、mixedCompare関数を少し修正しました。
変更後のコードのURLは変わりませんが、変更前のコードのURLはこちらです。
https://gist.github.com/1366050/82fdfe7663f6e2cdb1c72766e0e02e9cddb99415

どこを変更したの?と思いますよね。
変更した箇所を表示するWebサービスを作りました。
 
diff with url
http://honda0510-y4q6xj6z.dotcloud.com/?old=https://raw.github.com/gist/1366050/34ef55a79dd93b419fcca40df8a517937976d998/Module1.bas&new=https://raw.github.com/gist/1366050/411ada541319ae18b8cb98898153453adbec3463/Module1.bas
 
diff with url | 月ノート
http://blog.honda0510.dotcloud.com/?p=661

投稿日時: 11/11/26 08:45:11
投稿者: kumatti
投稿者のウェブサイトに移動

・JScriptのtypeofの例

var o = new ActiveXObject("Excel.Application");
o.visible = true;
o.Workbooks.Open("任意のパス");//\→\\
WScript.Echo(typeof(o.Application.ThisWorkbook) + '\t' + typeof(o.Application.Workbooks(1)));
WScript.Quit();

※ 内側のオブジェクトが評価されるケース。

回答
投稿日時: 11/12/03 06:29:16
投稿者: yayadon

# 古い話で恐縮なんですが...
Integerのオーバーフロー絡みの一連の話について,まとめてみました。
  
例えば,
 

引用:
(任意の整数型) / (任意の整数型) -> Double 型になります。

という表現ですが,
この背後にある,正確な意味を解ってる人や,
他の言語も知っていて,この手の一般的な仕組みをわかっている人には,
こういう表現でもいいんですが,
VBA で初めてこの手のことを習う人に対して
仕様書的に,正確に表現するのならば,
  
 任意の整数型 / 任意の整数型 -> Double / Double -> Double
  
となります。
 
各演算子ごとに,
左右のオペランドの型に対応してそれぞれに決めてある有効な値型(effective value type)に
オペランドの型が,Let-型変換(Let-coercion)されてから,演算が行われます。
  
他も同様で,
  
 Integer * Long -> Long
  

  
 Long * Integer -> Long
  
という感じではなく,仕様書的には,
  
 Integer * Long -> Long * Long -> Long
  

  
 Long * Integer -> Long * Long -> Long
  
という形になります。
  
  
あと,物理的な話も出てきていますが,
Integer がオーバーフローすること と 物理的なメモリの扱い方からくる制限
のような,物理的に説明するような下位の概念は,言語仕様的には無関係です。
 
VBA は,高水準言語/高級言語 なので,
言語の記述自体は,抽象度が高いところで行われるので,
記述の仕方から起こる現象を物理的な話で説明するのは,多くの場合,ふさわしくないです。
  
それは,
 
Dim obj As Object
   略
Set obj = Nothing

の Set obj = Nothing 説明の適否が
  
 × メモリを解放します。<--言語仕様の説明として,物理寄り過ぎる
              (しかも本当にメモリを解放するのかはオブジェクト依存)
  
 ○ オブジェクトを解放します。
  
となるのと同様です。
 
演算子の話の戻ります。
Dim a As Integer の a の型は,Integer なんですが,
変数の型のことを,仕様書では,宣言型(declared type) と呼んでいます。
中に入っている値そのものの型のことは,値型(value type)と呼んでいます。
(私が思うには,型の自動拡張があるVariant型が存在する関係で)
型の概念を,側 と 中身 の2つの型で区別しています。
 
VBA のヘルプでは,宣言型と値型の区別をしないで, と単に呼び,
Variant型の値型(中身の型のこと)のことを,内部処理形式 として区別しています。
(type) の意味が,値が取る得る範囲を制限するものという概念なのは同じです。
 
変数だけでなく,
関数(Function)にも宣言型があって,戻り値の型が宣言型になります。
 
で,同様に,
演算子にも宣言型があり,演算結果の型が宣言型になります。
一つの演算子には,扱うオペランドの型の違いによって,複数の宣言型があります。
 
変数の型(宣言型)から値がハミ出すとオーバーフロー エラーを報告するのと同様に,
演算子の宣言型から値がハミ出すとオーバーフロー エラーを報告する仕様になっています。
 
 

回答
投稿日時: 11/12/08 15:42:55
投稿者: yayadon

# 基本に興味のある方向け
 
VBA では,Variant 型のある仕組みについて,
あまり取り上げられていないような気がします。
もしくは,ただ単に意識していないだけかもしれません。
 
その仕組みに迫ってみたいと思います。
 
 
-----
ByRef 渡し と Variant型
 
 
まず初めに,以下のような,
長整数型(Long)で,値を 参照渡し(ByRef) するプロシージャを見てみます。
どんな値を渡されても,100 にしてしまうという意味のないものです。
 

Private Sub TestByRefLong(ByRef lngValue As Long)

    lngValue = 100   '何が渡ってきても,100 にする。

End Sub

 
これを利用するコードも書いてみます。
 
Private Sub Test1()

    Dim lngValue As Long
    lngValue = 1
    TestByRefLong lngValue
    MsgBox lngValue             '(3)

End Sub

(3) の MsgBox は,100 と表示します。
いわゆる参照渡しのため,値が変更されています。
 
変数のアドレス値を返す VarPtr で変数のアドレス値を探ってみると,
 
Private Sub TestByRefLong(ByRef lngValue As Long)

    MsgBox VarPtr(lngValue)    '(2) 仮引数 (formal parameter) のアドレス値
    lngValue = 100             '何が渡ってきても,100 にする。

End Sub

Private Sub Test1()

    Dim lngValue As Long
    lngValue = 1
    MsgBox VarPtr(lngValue)    '(1) 実引数 (argument) のアドレス値
    TestByRefLong lngValue
    MsgBox lngValue

End Sub

(1) の実引数(argument)側と (2) の仮引数(formal parameter)側は,
同じ値を示すハズです。
同じ変数なので,値が変更されるのも当然と言えます。
 
 
ただし,以下のように,lngValue + 1 したものを渡すと
 
Private Sub TestByRefLong(ByRef lngValue As Long)

    MsgBox VarPtr(lngValue)    '(2)
    lngValue = 100   ' 何が渡ってきても,100 にする。

End Sub

Private Sub Test2()

    Dim lngValue As Long
    lngValue = 1
    MsgBox VarPtr(lngValue)       '(1)
    TestByRefLong lngValue + 1    '<--- 演算結果を渡す
    MsgBox lngValue               '(3) <--- 当然 100 には,ならない。

End Sub

(1) の実引数(argument)側と (2) の仮引数(formal parameter)側は,
異なる値を示すハズです。
(3) の MsgBox は,100 にはならず,1 にままになります。
(1) と (2) が,異なっているため,
 
 操作している変数が別物のため,値が変わらない
 
ということになります。
 
どういうことかというと,
lngValue + 1 の結果を入れるための一時的な変数が作成されていて,
(2) の仮引数は,その一時的な変数への参照になっているわけです。
 
 
今回は,演算の結果の値でしたが,
メソッドやプロパティや関数の戻り値でも同様です。
 
素直に,参照渡しになるのは,変数を直接渡した時だけです。
 
ここまでは,経験則でわかることです。
 
 
 
-----
次は,渡す変数の型を Long 型ではなく,
Integer 型にして渡してみます。
 
Private Sub TestByRefLong(ByRef lngValue As Long)

    lngValue = 100   '何が渡ってきても,100 にする。

End Sub

Private Sub Test3()

    Dim intValue As Integer     'この型だけ Integer に変更
    intValue = 1
    TestByRefLong intValue
    MsgBox intValue

End Sub

コンパイル エラーになります。
 
 ByRef 引数の型が一致しません。
 
のエラー報告からわかるように,ByRef の時は,
Variant型,Object型,固有クラス型 以外の型の場合は,
実引数の型と一致しないといけないことになっています。
 
仮引数 と 実引数 が実質同じ変数でなければいけないということは,
型も同じでないと困るということでしょう。
これは,仕様書で決まってなくても,なんとなくわかります。
 
 
今度は,本題の Variant 型 にしてみます。
 
Private Sub TestByRefVariant(ByRef vntValue As Variant)

    MsgBox VarPtr(vntValue)    '(2)
    vntValue = 100             '何が渡ってきても,100 にする。

End Sub

Private Sub Test4()

    Dim vntValue As Variant
    vntValue = 1
    MsgBox VarPtr(vntValue)    '(1)
    TestByRefVariant vntValue
    MsgBox vntValue            '(3) 100 になる。

End Sub

(1) の実引数(argument)側と (2) の仮引数(formal parameter)側は,
同じ値を示すハズです。
(3) の値も変更されて,100 になっているハズです。
 
変数を,直接,ByRef 引数 に渡していますし,
実際に,VarPtr の値も同じなので,納得できると思います。
 
変数を直接ではなく,演算結果や戻り値を渡した場合は,
先ほど見て,考えてみたように,
一時変数ができて,その参照になるために,
値は,変更されずに 1 のままになります。
このあたりは,Variant 型でも同じです。
 
 
-----
ここまでが,前提です。
 
 
ここで,実引数の変数の型だけを Long 型にしてみます。
つまり,
 
 Long型の変数を ByRef Variant型 引数に直接渡すと,どうなるか?
 
ということを見てみます。
 
まず,前提として,
ByRef 引数の 型 が,Variant 型の場合は,
引数の型が,実引数と仮引数で異なっていても,
コンパイル エラーにならずに実行できるという仕様になっています。
 
Private Sub TestByRefVariant(ByRef vntValue As Variant)

    MsgBox VarPtr(vntValue)    '(2)
    vntValue = 100             '何が渡ってきても,100 にする。

End Sub

Private Sub Test5()

    Dim lngValue As Long
    lngValue = 1
    MsgBox VarPtr(lngValue)    '(1)
    TestByRefVariant lngValue
    MsgBox lngValue            '(3) 100 になる。

End Sub

(1) の実引数(argument)側と (2) の仮引数(formal parameter)側は,
異なる値を示すハズです。
素直に変数を渡しているのに,別物になっています。
 
そもそも,Variant/Object/固有クラス型以外の時は,
型が異なると,ByRef 渡しは成立せずエラーになっていたのは,
同じ変数を指さないと行けないからです。
 
ということは,異なる値を示したということは,
仮引数が参照しているものは,一時変数で,かつ,
型が同じもの,つまり,Variant型の一時変数 である可能性が高いことになります。
調べ方は省略しますが,実際に調べてみると,
16バイトのVariant型だということがわかります。
 
要するに,
 
 lngValue
 

 
 vntValue が指すVariant型の一時変数
 
は,別物 なわけです。
 
 
で,別物であるにもかかわらず,
(3) の値は変更されていて,100 になっているハズです。
 
 
それは,なぜだろうか?と考えると,
普段は意識しない Variant 型の側面(ある仕組み)が見えてくると思います。
 
 
 

回答
投稿日時: 11/12/09 12:03:54
投稿者: yayadon

# 時間がとれたので
# ByRef 引数 を整理してみます。
 
具体的なコードは,最後に Class1 クラスのプロパティ/メソッドとして示します。
読んでいて,よくわからない場合は,
先にコードを走らせてみるのもいいかもしれません。
 
 
◆ 型が同じ変数を直接渡された場合
 

-------------------------------
   Dim lngValue As Long      実引数
           ↑参照
 ByRef refValue As Long      仮引数
-------------------------------
  Dim vntValue As Variant   実引数
      ↑参照
 ByRef refValue As Variant  仮引数
-------------------------------
refValue は,変数を指すポインターであるが,
refValue がVBA言語内で扱われる時は,直接渡された変数と実質同じものとなる。
 
※ オブジェクト参照だけでなく,これも 参照(reference) と呼ばれていますが,
 多くの場合,「ByRef 引数」のように表現される時が多いようです。
 
 
◆ 型が同じ演算結果や戻り値が渡された場合
 
--------------------------------------
   Dim lngValue As Long        実引数
      ↓代入
  Dim tmpValue As Long      一時変数
           ↑参照
 ByRef refValue As Long        仮引数
--------------------------------------
  Dim vntValue As Variant     実引数
      ↓代入
  Dim tmpValue As Variant    一時変数
      ↑参照
 ByRef refValue As Variant     仮引数
--------------------------------------
refValue は,一時変数(tmpValue)を指すポインターであるが,
refValue がVBA言語内で扱われる時は,一時変数(tmpValue)と実質同じものとなる。
 
 
◆ 型が異なる変数が直接渡された場合
 
ByRef 引数 の型が Variant/Object/固有クラス以外の時
コンパイル エラー になる。(ByRef 引数の型が一致しません)
--------------------------------------
   Dim intValue As Integer  ← 例えば,Integer の時
  
 ByRef refValue As Long     ← 例えば,Long の時
--------------------------------------

 
ByRef 引数 の型が Variant の時
--------------------------------------
  Dim lngValue As Long     ← 例えば,Long の時
      ↑参照
  Dim tmpValue As Variant 一時変数
      ↑参照
 ByRef refValue As Variant
--------------------------------------
refValue は,一時変数(tmpValue)を指すポインターであるが,
refValue がVBA言語内で扱われる時は,一時変数(tmpValue)と実質同じものとなる。
 
tmpValue の値は,変数 lngValue を指すポインター値。VBA的には参照。
 
 
そこで,
 
  Dim lngValue As Long
      ↑参照
  Dim tmpValue As Variant
 
の部分が,
 
  Dim lngValue As Long
           ↑参照
 ByRef refValue As Long
 
と実質同じになっていれば,良さそうです。
 
 
まず,次のコードを見てください。
 
Dim lngValue As Long
Dim vntValue As Variant

lngValue = 1
vntValue = lngValue

の vntValue は,内部処理形式 が Long の Variant型になっています。
Variant 型の値は,Long型 の値 1 です。
 
Variant型の 内部処理形式 が ByRef refValue As Long のように
なれば,ポインター として,参照 として振る舞えるようになれます。
 
で,
実際に Variant型 には,内部処理形式 の値として,
参照フラグ(VT_BYREF / &H4000 / 0x4000 ) というものがあります。
 
VBA にも,
Variant型の 内部処理形式 の値を返す機能として,
VarType というものがあります。
 
しかし,Long 型 等の値は返しますが,
この参照フラグの部分の値は,返してくれません。
 
ByRef 引数 の取り扱い方からわかるように,
VBA では,参照 と 実際の変数 の取り扱い方を区別しません。
そのため,参照フラグ が立っていても,
参照フラグ が立っていないものとして処理してくれます。
 
そのため,参照フラグ は,必要ないものとして,
VarType では,&H4000 の部分の値は返さないことにしたのでしょう。
 
どういうことかというと,
Variant型の 内部処理形式 が,参照フラグ + Long型 だった時は,
あたかも Long型 だけのように振る舞ってくれるようになっています。
 
もう一度,以下を見てみます。
--------------------------------------
  Dim lngValue As Long       実引数  例えば,Long の時
      ↑
  Dim tmpValue As Variant   一時変数は,参照フラグ が立っている。
      ↑
 ByRef refValue As Variant    仮引数
--------------------------------------
仮引数 refValue は,あたかも 一時変数 tmpValue のように振る舞います。
そして,一時変数 tmpValue は,
参照フラグ が立っているために,あたかも 実引数 lngValue のように振る舞います。
その結果,
仮引数 refValue は,あたかも 実引数 lngValue のように振る舞うようになります。
 
つまり,参照 という仕組みにおいて,
参照の参照 は,参照 と同じように振る舞います。
もちろん,
そのためにVBA側で,カラクリが仕込んであります。
 
IDE上で,表示される値を見ているとき,気が付かないですが,
ByRef 引数 は,参照先変数 と実質同じものとして扱えていて,
参照先変数の値を見ています。
かつ,ByRef 引数 が Variant型 の場合,
Variant型 の参照先変数で 参照フラグ が立っている時は,
表示されている値は,参照の参照の値,つまり,渡された変数の値 になっています。
 
Variant型 の参照先変数で 参照フラグ が立っていない時は,
表示されている値は,参照の値,つまり,一時的な変数の値 になっています。
 
Private Sub TestByRefVariant(ByRef vntValue As Variant)

    MsgBox VarPtr(vntValue)    '(2)
    vntValue = 100             '何が渡ってきても,100 にする。

End Sub

Private Sub Test5()

    Dim lngValue As Long
    lngValue = 1
    MsgBox VarPtr(lngValue)            '(1)
    TestByRefVariant lngValue + 1      '演算結果を渡す
    MsgBox lngValue                    '(3) 100 になる。

End Sub

--------------------------------------------
  Dim lngValue As Long     ← 例えば,Long の時

    lngValue + 1
           ↓                ← 演算結果の代入(つまり,値のコピー)
  Dim tmpValue As Variant 一時変数は,参照フラグ が立っていない。
      ↑
 ByRef refValue As Variant
--------------------------------------

 
演算結果や戻り値は,参照フラグが立たず,値のコピーになります。
 
 
-----
 
◆ ByRef 引数 ----- プロパティの場合
 
実は,パラメータに変数を直接渡しても,参照フラグが立たない場合があります。
Letステートメントの左辺式(l-expression) にパラメータがある場合です。
 
それは,どんな場合かというと,例えば,
プロパティに値をセットするような場合で,かつ,
そのプロパティの型が,ByRef 付き もしくは 何も無し※1 で宣言されている場合です。
 
 ※1 参考書では,必ず習うので,いままで,説明しませんでしたが,
   ByRef も ByVal も付いていない時は,ByRef がついているものとみなされます。
 
ByRef String型のプロパティ
タイプライブラリの定義だと,BSTR* つまり ポインター になっているものの場合や,
ByRef Variant型 のプロパティ,
タイプライブラリの定義だと,VARIANT* つまり ポインター になっているものの場合
など,ポインタ渡しでも,
 
 オブジェクト参照.Value = lngValue
 
のようなプロパティへの代入時は,一時変数への値のコピーになります。
 
(1) Long -> ByRef Long
-------------------------------------
  Dim lngValue As Long                ← 例えば,Long の時
   オブジェクト参照.Value = lngValue
  ----------------------------------
    lngValue 
           ↓                ← 演算結果の代入(つまり,値のコピー)
  Dim tmpValue As Long    一時変数
      ↑
 ByRef refValue As Long     ← 例えば,ByRef Long の時
-------------------------------------------------------------------

(2) Variant -> ByRef Variant
  Dim vntValue As Variant              ← 例えば,Variant の時
   オブジェクト参照.Value = vntValue
   -------------------------------------
    vntValue
           ↓                ← 演算結果の代入(つまり,値のコピー)
  Dim tmpValue As Variant 一時変数は,参照フラグ が立っていない。
      ↑
 ByRef refValue As Variant     ← 例えば,ByRef Variant の時
-------------------------------------------------------------------

(3) Long -> ByRef Variant

  Dim lngValue As Long     ← 例えば,Long の時
   オブジェクト参照.Value = vntValue
  ----------------------------------
    lngValue
           ↓                ← 演算結果の代入(つまり,値のコピー)
  Dim tmpValue As Variant 一時変数は,参照フラグ が立っていない。
      ↑
 ByRef refValue As Variant
-------------------------------------------------------------------

 
 
最後に,
変数の直接渡しを,プロパティメソッド で比べてみます。
 
'' Class1 クラスモジュール
Option Explicit

'プロパティ  ByRef Long
Public Property Let PropertyLongByRef(ByRef refValue As Long)

    refValue = 100      '参照先は一時変数なので,値は反映されません。

End Property

'メソッド   ByRef Long
Public Sub MethodLongByRef(ByRef refValue As Long)

    refValue = 100      '直接渡された変数ならば,値は反映されます。

End Sub

'プロパティ   ByRef Variant
Public Property Let PropertyVariantByRef(ByRef refValue As Variant)

    refValue = 100      '参照先のVariant型一時変数には 参照フラグは立ちません。
                        'なので,値は反映しません。

End Property

'メソッド   ByRef Variant
Public Sub MethodVariantByRef(ByRef refValue As Variant)

    refValue = 100      '参照先のVariant型一時変数には 参照フラグが立ちます。
                        'なので,値は反映されます。

End Sub

Private Sub Test6()

    Dim myClass As Class1
    Dim lngValue As Long
    Dim vntValue As Long

    Set myClass = New Class1

    lngValue = 1
    myClass.PropertyLongByRef = lngValue
    MsgBox lngValue                           ' 1 のまま

    lngValue = 1
    myClass.MethodLongByRef lngValue
    MsgBox lngValue                           ' 100 になっている

    vntValue = 1
    myClass.PropertyVariantByRef = vntValue
    MsgBox vntValue                           ' 1 のまま

    vntValue = 1
    myClass.MethodVariantByRef vntValue
    MsgBox vntValue                           ' 100 になっている

End Sub

感覚的にも,
プロパティの方は,代入(Letステートメント)しているんだから,
コピーになるのは,当然のように思えますが,
仕様書を見ると,
 
MS-VBAL さんの引用:
There may be implementation-specific differences in the semantics of parameter passing during invocation of procedures imported from a library project.

may とあるので,ByVal 渡しも含めて,
パラメータへの渡し方において,セマンティクスの違いがあっても良い
ことには,なっています。
 
上で,ByRef 引数 が,一時変数への参照 であるように表現していますが,
仕様書上のセマンティクス的には,
それら2つをあわせて,
プロシージャ内のローカル変数のようなスコープの扱い※2になっています。
どのように実装するかは,決められていなく,
その変数/値が有効な範囲(スコープ/エクステント)と,
どの型であるか?もしくは,どの型を持っているとして扱うか?のことだけが定義されいます。
 
 
注2: VBA 仕様書では,スコープ のことを エクステント(extent) と呼んでいるようです。
 
注3: 直書きしているので,コードが動かない場合は,適宜変更してください。 
注4: Object型 と 固有クラス型 は,時間があれば,あとでまとめます。
 
 
 
 
 
 

回答
投稿日時: 11/12/09 15:36:04
投稿者: yayadon

補足
 

yayadon さんの引用:

上で,ByRef 引数 が,一時変数への参照 であるように表現していますが,
仕様書上のセマンティクス的には,
それら2つをあわせて,
プロシージャ内のローカル変数のようなスコープの扱い※2になっています。
どのように実装するかは,決められていなく,
その変数/値が有効な範囲(スコープ/エクステント)と,
どの型であるか?もしくは,どの型を持っているとして扱うか?のことだけが定義されいます。

 
例えば,
 
' ByRef 引数
Private Sub TestByRefLong(ByRef lngValue As Long)
    lngValue = 100   ' 何が渡ってきても,100 にする。
End Sub

Private Sub Test2()

    Dim lngValue As Long
    lngValue = 1
    TestByRefLong lngValue + 1    '<--- 演算結果(a value)を渡す

End Sub

のようなコードの場合のように,
ByRef 引数 lngValue に,
変数(a variable)ではなく
lngValue + 1 の演算結果(a value)を渡すような場合,
 
Private Sub TestByRefLong(ByRef lngValue As Long)
    lngValue = 100   ' 何が渡ってきても,100 にする。
End Sub

が,セマンティクス的には,ByVal 引数 lngValue であるかのように
 
Private Sub TestByRefLong(ByVal lngValue As Long)
    lngValue = 100   ' 何が渡ってきても,100 にする。
End Sub

 
のようなコードに解釈されて,
その ByVal 引数 lngValue に lngValue + 1 の演算結果が Let-代入 される形になります。
 
このような解釈になるのは,ByVal 引数 も含めて区分けすると
 
 ・Optional 引数 に対して,実引数が省略された時
 ・ByVal 引数 の時
 ・ByRef 引数 かつ
  渡される式が,(演算結果のこと)
         関数
         プロパティ
         unboundメンバー に分類される時
 
になります。
 
 
純粋に ByRef 引数 のまま,つまり,参照渡し(referece parameter binding) になるのは,
 
 ・ByRef 引数 かつ 渡される式が 変数 に分類される時
 
になります。
 
cf. MS-VBAL p.86
 
 
-----
先のレスも含めて,なんのことか,さっぱりわからない場合は,
 
 ・関数の ByRef 引数 に,実引数を渡す時,変数 以外の場合は,
  たとえ同じ型であっても,Let-代入 のためコピーが起きる
 
 ・ByRef 引数 の時,変数直接渡す場合,
  実引数の型が ByRef 引数と同じ場合は,参照渡しになる
 
 ・Variant 型 の ByRef 引数 の時,変数直接渡す場合,
  実引数が同じ型(Variant)の場合は,参照渡しになるが,
  それ以外の型の場合は,新規 Variant型変数 に参照が作成される
 
 ・プロパティに渡す場合は,変数を直接であっても
  Let-代入 の範疇に入るためコピーが起きる
 
の4つを覚えておけばOKです。
 
 

回答
投稿日時: 11/12/09 16:48:21
投稿者: yayadon

# さらに補足
 
 
何事にも例外はあって,
ByRef Variant 型 の引数をとる VBA の関数については,
もうひとつ知っておかないといけないことがあります。
 
 
これは,経験則でわかっている人も多いと思いますが,
関数呼び出しだと思っているものが,実は関数呼び出しでないものがあります。
例えば,
 
 Len や CLng
 
などは,VBA の関数呼び出しではなく,関数呼び出しにするには,
 
 VBA.Len や VBA.CLng
 
とする必要があります。
 
仕様書では,p.45 に
予約名special-form として,予約識別子 として定義されているものがあります。
 

MS-VBAL さんの引用:
reserved-name = “Abs” / “CBool” / “CByte” / “CCur” / “CDate” / “CDbl” / “CDec” / “CInt” / “CLng” / “CLngLng” / “CLngPtr” / “CSng” / “CStr” / “CVar” / “CVErr” / “Date” / “Debug” / “DoEvents” / “Fix” / “Int” / “Len” / “LenB” / “Me” / “PSet“ / “Scale“ / “Sgn” / “String”

MS-VBAL さんの引用:
special-form = “Array“ / “Circle” / “Input” / “InputB” / “LBound” / “Scale” / “UBound”

MS-VBAL さんの引用:
A <reserved-name> is a <reserved-identifier> that is used within expressions as if it was a normal program defined entity(2.2). A <special-form> is a <reserved-identifier> that is used in an expression as if it was a program defined procedure name but which has special syntactic rules for its argument.

日本語訳:----------------------------------------------------------------
<予約名> は,あたかも,それがふつうにプログラム内で定義されたエンティティのように
式の中で使われる <予約識別子> である。
 
<special-form> は,あたかも,それがプログラム内で定義されたプロシージャ名であり,けれども,その引数に関しては,特別な構文規則をもっているかのように,式の中で使われる <予約識別子> である。
-------------------------------------------------------------------------
 
 
エンティティが,なんのことかよくわからないかもしれませんが,
関数などのプログラムの構成要素(p.24)の総称のことです。
 
要するに上記の
 
 予約名 は,プログラムのなんらかの構成要素になっているものとして扱い,
 special-form は,関数呼び出しだが,引数の扱い方は特別な規則で扱う
 
ということです。
 
実際には,
先の規則にそって呼び出すと,
関数呼び出しだと都合が悪いことが多々あったり,
プロシージャとして呼び出す時,
先の規則のままだと都合が悪いかったりするので,
どんなエンティティなのかをぼかしているということでしょう。
 
 

回答
投稿日時: 11/12/09 18:06:25
投稿者: yayadon

訂正
 
上の方の Test6 ですが,
Dim vntValue As Long は,いらないので,
 

Private Sub Test6()

    Dim myClass As Class1
    Dim lngValue As Long

    Set myClass = New Class1

    lngValue = 1
    myClass.PropertyLongByRef = lngValue
    MsgBox lngValue                           ' 1 のまま

    lngValue = 1
    myClass.MethodLongByRef lngValue
    MsgBox lngValue                           ' 100 になっている

    lngValue = 1
    myClass.PropertyVariantByRef = lngValue
    MsgBox lngValue                           ' 1 のまま

    lngValue = 1
    myClass.MethodVariantByRef lngValue
    MsgBox lngValue                           ' 100 になっている

End Sub

です。

回答
投稿日時: 11/12/11 23:38:53
投稿者: yayadon

ByRef Variant のまとめ
 
関数の ByRef 引数 と 予約名 について見てきたところで,
Len 関数 の動作の違いを見ることで,結びとします。
 
Len 関数は,文字数 もしくは 変数に必要な バイト数 を返します。
但し,Variant型 は,文字列に型強制(coercion)したときの 文字数 を返します。
 
Long型 の変数を直接 Len と VBA.Len 関数に渡してみます。

Dim lngValue As Long

lngValue = 1234567890
MsgBox Len(lngValue)             '(1)
MsgBox VBA.Len(lngValue)         '(2)

(1) は,4 になります。4 バイト ということでしょう。
素直に Long 型で扱っています。
 
 
(2) について考えてみます。
 
VBE7.DLL の宣言を見てみると,Len 関数は,
 
 VARIANT _stdcall Len([in] VARIANT* Expression);
 
となっています。
 
VARIANT* は,VBA で表現すると ByRef Expression As Variant になるので,
VBA.Len 関数の引数の型は,ByRef Variant になります。
 
渡された変数 lngValue の型は,Long 型で,
VBA.Len 関数の引数の型は Variant 型ということは,上で見てきたことから,
 
 lngValue
  ↑(参照)
 variant型一時変数 参照フラグ + Long (C++ だと,VT_BYREF | VT_I4)
  ↑(参照)
 Expression
 
となります。
その結果,VBA.Len関数 側から見ると,
渡された Long型 の変数ではなく,Variant型 の一時(or ローカル)変数に見えます。
 
Variant型が渡された場合は,文字列に型強制した時の 文字数 だったので,
(2) の MsgBox は,10 と表示される筈です。
 
 
-----
 
文字列の場合も理屈は同じになります。
 
例えば,MsgBox 関数は ByRef Varaint 型 をとり
以下のようなコード
 
Dim strValue As String
strValue = "ABC"
MsgBox strVAlue

は,
 
 strValue
  ↑(参照)
 Variant型一時変数 参照フラグ + String (C++ だと,VT_BYREF | VT_BSTR)
  ↑(参照)
 Expression
 
の形になり,参照フラグが立つ一時変数が挟まり
一方,以下のようなコード
 
Dim vntValue As Variant
vntValue = "ABC"
MsgBox vntVAlue

は,型が,Variant型 どうしで同じなので,直に参照になり
 vntValue
  ↑(参照)
 Expression
の形になります。
 
なので,
 
Dim strValue As String
strValue = "ABC"
MsgBox strValue


 
Dim vntValue As Variant
vntValue = "ABC"
MsgBox vntValue

ならば,
String型 の方が
参照フラグ + String (VT_BYREF | VT_BSTR) の Variant型変数の作成があるため
(参照のため,文字列のコピーは起きませんが) 手数は多くなります。
 
 
文字列のコピーが起こるわけではないので,ほとんど変わりませんが,
String型変数の方が,上記理屈どおり遅くはなります。
Private Declare Function QueryPerformanceCounter Lib "Kernel32" _
                        (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "Kernel32" _
                        (lpFrequency As Currency) As Long

'' String型変数 => ByRef Variant
Private Sub TestString()

    Dim ctr1 As Currency
    Dim ctr2 As Currency
    Dim freq As Currency
    
    Dim i As Long
    Dim m As Long
    Const IterationCount As Long = 100000
    
    Dim strValue As String
    strValue = "ABC"

    Debug.Print "String型変数を直接"
    
    For m = 1 To 10
        Call QueryPerformanceCounter(ctr1)

        For i = 1 To IterationCount
            Call VBA.CStr(strValue)       'String型 変数 直接
        Next

        Call QueryPerformanceCounter(ctr2)
        Call QueryPerformanceFrequency(freq)
        Debug.Print Format((ctr2 - ctr1) / freq, "0.00000000E-"" sec.""")
    Next
    
    MsgBox "終了"
    
End Sub

'' Variant型変数 => ByRef Variant
Private Sub TestVariant()

    Dim ctr1 As Currency
    Dim ctr2 As Currency
    Dim freq As Currency
    
    Dim i As Long
    Dim m As Long
    Const IterationCount As Long = 100000
    
    Dim vntValue As Variant
    vntValue = "ABC"

    Debug.Print "Variant型変数を直接"
    
    For m = 1 To 10
        Call QueryPerformanceCounter(ctr1)

        For i = 1 To IterationCount
            Call VBA.CStr(vntValue)       'Variant型 変数 直接
        Next

        Call QueryPerformanceCounter(ctr2)
        Call QueryPerformanceFrequency(freq)
        Debug.Print Format((ctr2 - ctr1) / freq, "0.00000000E-"" sec.""")
    Next
    
    MsgBox "終了"
    
End Sub

 
 
 
-----
以下のように,演算結果 を ByRef Variant に渡すもの
 
Dim strValue As String
strValue = "ABC"
MsgBox strValue & "DEF"

Dim vntValue As Variant
vntValue = "ABC"
MsgBox vntValue & "DEF"

や 関数の戻り値 を ByRef Variant に渡すもの
 
Dim lngValue As Long
lngValue = 1234567890
MsgBox Hex$(lngValue)

Dim lngValue As Long
lngValue = 1234567890
MsgBox Hex(lngValue)

は,
変数ではなく,値の代入になるので,話が変わってきます。
 
MsgBox でなく,VBA.CStr 等にすれば計測できますが,
それに渡すまでにかかる時間が実際にはわからないので,
時間を計って確認してもあまり意味が無い気もするのでやめておきます。
 
 
-----
文字列の連結の方は,
vntValue & "DEF" は,演算時,strValue & "DEF" の演算になり,
どちらも,文字列を結合した地点では,値型は String型(BSTR) です。
なので,そこからは,同じになります。
 
文字列どうしの演算になるとは,どういうことかというと,
& 演算子は,オペランド vntValue を単純データ値として評価する必要があるため,
実装は,vntValue = 100 のように Integer が入っていれば,
文字列型の "100" に型強制をする必要があります。
 
そのため,
先に,内部処理形式(vt)のチェックが必要になるでしょうから,
Variant型の方が理屈上は遅くなるハズです。
 
-----
関数の戻り値の方は,
理屈で説明できるほど,ちょっとよくわかっていないのでやめておきます。
 
経験上は,ByRef Variant に対しては,
戻り値が Variant型 の方が速い気がいます。
 
 
 

回答
投稿日時: 11/12/17 03:27:43
投稿者: yayadon

自分も Enum について少し調べてみました。
 
 
MIDL の enum の仕様
http://msdn.microsoft.com/en-us/library/windows/desktop/aa366818%28v=VS.85%29.aspx
 

MIDL さんの引用:
Objects of type enum are int types, and their size is system-dependent. By default, objects of enum types are treated as 16-bit objects of type unsigned short when transmitted over a network. Values outside the range 0 - 32,767 cause the run-time exception RPC_X_ENUM_VALUE_OUT_OF_RANGE.

日本語訳:
enum 型のオブジェクトは int 型であり,そのサイズはシステム依存である。既定では,enum 型のオブジェクトは,ネットワーク上を伝わる際は,unsigned short 型の16ビットオブジェクトとして取り扱われる。範囲 0 - 32,767 以外の値は実行時例外 RPC_X_ENUM_VALUE_OUT_OF_RANGE を引き起こす。
-----
 
要点は,
実際のサイズは,システム依存だが,
値は,0 から 32,767 の値 つまり 0 から &H7FFF つまり,
どのようなサイズでも,
0ビット目から14ビット目までが 1 になれるという意味のようです。
 
# The maximum number of identifiers is 65,535. と
# 最上位(the most significant bit)の 15ビット目も 1 になれるようにかかれていますが,
# リンク先上のツッコミも 15ビット目以上は 0 でないとエラーになるといっています。
 
上記訳中の
ネットワーク上を伝わる際 というのは,各マシン間 だけでなく,
別プロセスとのやり取りも含まれます。
また,同一プロセス内でのやり取りでも,
別アパートメント間とのやり取りもこれに含まれます。
が,アパートメントがどういうものかわからない場合は,
意識する必要はないです。
要するに,ネットワーク上は 0 から 32,767 の値が許されるということです。
 
 
 
VBA の Enum の仕様
VBAL p.23
 
VBAL さんの引用:
There is no Enum-specific value type. Instead, Enum members are represented as Long data values.

日本語訳:
Enum に固有の値型は存在しない。代わりに,Enum のメンバーは Long データ値として表わされる。
-----
 
VBA の場合,あくまで仕様上の話ですが,ビットでみると
Long 値の
 
  3
  1                                0
 □□□□□□□□ □□□□□□□□ □■■■■■■■ ■■■■■■■■
 
の黒いところだけが,1 になれるようです。
# ネットワーク上は,ビッグエンディアン で流れるので,
# 左側(先頭側)を 31ビット目にしてあります。
 
 
実例を見てみると...
-----
タイプライブラリ ----- MsgBox の場合
(OLE/COM Object Viewer -> ファイル メニュー -> ViewTypeLib... ->
%Program Files%\Common Files\microsoft shared\VBA\VBA7\VBE7.DLL)
 
MsgBox
[entry("(null)"), helpcontext(0x000f6552)]
VbMsgBoxResult _stdcall MsgBox(
                [in] VARIANT* Prompt, 
                [in, optional, defaultvalue(0)] VbMsgBoxStyle Buttons, 
                [in, optional] VARIANT* Title, 
                [in, optional] VARIANT* HelpFile, 
                [in, optional] VARIANT* Context);

その戻り値 VbMsgBoxResult は,
typedef [uuid(ED822012-6D7F-11CF-B949-00AA004455EA), helpcontext(0x0010fdf8)]
enum {
    vbOK = 1,
    vbCancel = 2,
    vbAbort = 3,
    vbRetry = 4,
    vbIgnore = 5,
    vbYes = 6,
    vbNo = 7
} VbMsgBoxResult;

 
typedef は,型 に 別名 を付けるもので,
VBA 仕様上では固有の型が無い Enum に VbMsgBoxResult という 型名 を付けています。
 
Dim returnValue As 型名
 
とする時,例えば,MsgBox の場合は,
上記で見てきた仕様にそって実装されているハズなので,
王道?なのが VbMsgBoxResult で,
面倒なら Long で。
 
# kumattiさん と同じ意見だけど,これに限らず,
# インテリセンスが効いた方がなんとなく正統派って気がする(笑)。
# でも,自分は Integer って書いてたかも(爆)
 
 

投稿日時: 11/12/17 17:39:00
投稿者: kumatti
投稿者のウェブサイトに移動

・Application.GetOpenFilenameメソッド

        [id(0x00000433), helpcontext(0x00020816)]
        HRESULT GetOpenFilename(
                        [in, optional] VARIANT FileFilter, 
                        [in, optional] VARIANT FilterIndex, 
                        [in, optional] VARIANT Title, 
                        [in, optional] VARIANT ButtonText, 
                        [in, optional] VARIANT MultiSelect, 
                        [in, lcid] long lcid, 
                        [out, retval] VARIANT* RHS);

うーん、[out, retval]属性や内側の戻り値(HRESULT型)をどう説明すべきか。
 
# yayadonさん、どうも。

回答
投稿日時: 11/12/17 20:53:32
投稿者: yayadon

訂正:
%Program Files% -> %ProgramFiles% or %ProgramFiles(x86)%
 
 

投稿日時: 11/12/18 08:52:11
投稿者: kumatti
投稿者のウェブサイトに移動

最後の引数が[out, retval]属性で定義されてるなら、VBAでは戻り値扱いになります。
# これで、納得されるかな。

yayadon さんの引用:
訂正:
%Program Files% -> %ProgramFiles% or %ProgramFiles(x86)%
 
 

エクスプローラだと環境変数でも開けますけど、OLE/COM Object Viewerでは開けませんでした。

回答
投稿日時: 11/12/18 11:01:05
投稿者: yayadon

kumatti さんの引用:
%ProgramFiles% or %ProgramFiles(x86)%
 
エクスプローラだと環境変数でも開けますけど、OLE/COM Object Viewerでは開けませんでした。

こちら Windows 7 32bit だと開けるんですけどね。
64bitだと使えないんですね。
報告ありがとうございます。
 
 

投稿日時: 11/12/18 12:06:48
投稿者: kumatti
投稿者のウェブサイトに移動

失礼しました。

%ProgramFiles%

でいいです。
 
WOW64での環境変数
http://d.hatena.ne.jp/espresso3389/20080129/1201612560
 
# 64Bit版だと、
---------------------------
OLE/COM Object Viewer
---------------------------
LoadTypeLib( C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7\VBE7.DLL ) failed.
<No system message defined> STG_E_FILENOTFOUND ($80030002)
---------------------------
OK   
---------------------------

で読み出せなかったので、32Bit版で試して、
(こちらの環境では)
C:\Program Files (x86)\以下に該当のファイルがないので
開けなかっただけでした。

回答
投稿日時: 11/12/20 19:36:10
投稿者: yayadon

◆ 参照渡し: メソッドの戻り値([out, retval] 型* 引数)の場合
 
上で見てきたように,
参照渡しの場合,
型が同じ変数を ByRef引数 に直接渡せば,その変数の場所が渡り,
変数の中身/値ではなく,変数自体が渡ったような感じになりました。
 
しかし,参照渡しであっても,
[out, retval] 引数 による戻り値の場合の参照渡しは,
参照渡しの引数に対して,VBAユーザーが変数を直接渡したとしても,
VBA は,VBA自身が用意した一時変数の場所を使います。
その後に,ユーザーが用意した変数に,浅くコピーします。
 
 
◆ VBA 側が入れるカラクリ
具体的なメソッドの方がいいと思ったので,
引数が多くてややこしいですが,以下のような呼び出しの場合を見てみます。
cf. http://www.moug.net/tech/exvba/0060013.html
下記のコード
 

Dim myFile As Variant   'Variant 型に変更してあります。
myFile = Application.GetOpenFilename("CSVファイル(*.csv),*.csv")

は,VBA 側が変数をいくつか用意することで,
 
'引数省略時用 ----- 実は引数は省略できないため
Public Const vtMissing As Variant = 初期化(VT_ERROR で値は DISP_E_PARAMNOTFOUND)
'ロケールとソート
Public Const LOCALE_USER_DEFAULT As Long = &H400&

----------------------------------------------------------------------------
Dim myFile As Variant
Dim myFileFilter As Variant  'or String

Dim hr As Long          '実際の戻り値 HRESULT を受ける変数
Dim tmpRtn As Variant   'VBA が用意した参照渡し戻り値用変数

myFileFilter = "CSVファイル(*.csv),*.csv"
hr  = Application.GetOpenFilename( _
                         myFileFilter, _
                         vtMissing, _
                         vtMissing, _
                         vtMissing, _
                         vtMissing, _
                         LOCALE_USER_DEFAULT, _
                         tmpRtn )  '<-- 戻り値の参照渡しの箇所

If hr がエラーを示すか? Then  ' S_OK ( 0& ) と比較しているだけに見えるが...
    ' もろもろの処理
    ' ここでエラーを発生させる等。
End If

'成功時
myFile = tmpRtn    ' 実際の戻り値用変数に代入(浅いコピー※)


のような感じになります。
 
※ 浅いコピー(shallow copy)とは,
 変数の値が指す文字列や配列の実態のコピーまでは伴わないコピーのこと。
 VBA では,通常は, 文字列の実態までコピーする深いコピー(deep copy)になります。
 
 

投稿日時: 11/12/21 10:46:04
投稿者: kumatti
投稿者のウェブサイトに移動

Microsoft Windows XP との支援技術の互換性をテストする
http://msdn.microsoft.com/ja-jp/library/cc421545.aspx
Microsoft Active Accessibility 2.0 Software Development Kit Tools
http://web.archive.org/web/20021015014220/http://msdn.microsoft.com/downloads/sample.asp?url=/msdn-files/027/001/785/msdncompositedoc.xml
DeclareDPIAware.manifest
http://msdn.microsoft.com/en-us/library/windows/desktop/ee872012%28v=vs.85%29.aspx
Mt.exe
http://msdn.microsoft.com/en-us/library/windows/desktop/aa375649%28v=vs.85%29.aspx

mt.exe -manifest DeclareDPIAware.manifest -outputresource:AccExplorer32.exe;1

# 我ながらよく捜したなと。

回答
投稿日時: 11/12/22 01:59:45
投稿者: yayadon

EXCELのシートをAccessのテーブルの様に扱うには
http://www.moug.net/faq/viewtopic.php?t=61500
 
 
◆ Excelファイルのデータを処理しているのはどこか?
 
話の流れの中で,
Excelファイルのデータを処理しているのはどこか?が出てきています。
カラクリに興味がない人には,
ある意味どうでもいいことなのでこちらに書きます。
 
答えから先に書くと,JETエンジンでも,Excel でもなく
 
 Excelファイル用の ISAMドライバ
 
というものが担当しています。具体的には,
%WINDIR%\System32 フォルダ内にある
 
 msexcl40.dll
 
という DLL が担当しています。
ISAM (Indexed Sequential Access Method) というのは,
レコード処理の仕方の方法論のうちの一つです。
 
 
◆ msexcl40.dll を指定していないのに,Jetはなぜ居場所がわかるか?
 
Jet 4.0 エンジンは,
Extended Properties に設定された "Excel 8.0" について,
レジストリの
 
 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\ISAM Formats
 
ハイブ下に
 
 Excel 8.0 サブキー
 
を見つけ,その
 
 Engine エントリ
 

 
 Excel
 
という値を得て,レジストリの
 
 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines
 
ハイブ下に
 
 Excel サブキー
 
を見つけ,
 
 Win32 エントリ
 
で,Jet 4.0 OLEDB 向け Excel用 ISAM を提供する DLL の居場所
 
 C:\Windows\System32\msexcl40.dll
 
を突き止める感じになっていると思われます。
その結果,使用するDLLの名前と居場所がわかります。
 
(テキストファイルは,mstext40.dll です)
 
 
◆ 関係する DLL がロードされるタイミング
 
ちなみに,以下のようなコードがあって,初めて実行した時
 

Dim cn As ADODB.Connection                                                 '(0)
Set cn = New ADODB.Connection                                              '(1)
cn.Provider = "Microsoft.Jet.OLEDB.4.0"                                    '(2)
cn.Properties("Data Source").Value = "Excelファイルのパス"                 '(3)
cn.Properties("Extended Properties").Value = "Excel 8.0;HDR=YES;"          '(4)
cn.Open                                                                    '(5)
cn.Close                                                                   '(6)
Set cn = Nothing                                                           '(7)

次の DLL
 
A: ADO (msado15.dll)
B: JETエンジン (msjet40.dll) & JET OLEDB (msjetoledb40.dll) & OLE DB Core (oledb32.dll)
C: Excel ISAM ドライバ (msexcl40.dll)
 
がロードされるタイミングは,どの行が実行される/された時なのか?まで
わかっていたら JETエキスパート でしょう。
一つだけ,毎回アンロードされる DLL もあります。
 
カラクリ調査の三種の神器?
 OLE/COM Object Viewer
 Dependency Walker
 Process Explorer
のうち,Process Explorer で確認できます。
 
呼び出し順は,仕組み的に見ると
 
ADO → OLE DB Core Service → Jet 4.0 OLEDB Provider → Jet Engine → Excel ISAM Driver
 
となっています。なので,ロードされる順はこの順になります。
 
# 今回は,実験のために個別にプロパティを設定していますが,
# ConnectionString で一度に情報を提供してあげる方が好みです。
# また,変数で一度受けたインターフェースを
# 再度 With で受けるのは,不必要な AddRef/Release を増やすだけなので,
# 個人的にはお勧めしません。
 
 

投稿日時: 11/12/23 10:15:58
投稿者: kumatti
投稿者のウェブサイトに移動

simple さんの引用:

この場合、SortにSortfunctionをどうやって与えるか不明なので、
その場しのぎの方法で数字も文字列として大小比較することにしました。

魔界の仮面弁士さんがそのコードを載せられていますね。
http://hpcgi1.nifty.com/MADIA/VBBBS/wwwlng.cgi?print+201112/11120007.txt

回答
投稿日時: 11/12/23 15:45:17
投稿者: simple

ありがとうございます。参考になります。
マニュアルに載っていないExecuteStatementというものがあるんですね。
(evalと同じ動作でしょうか。)
以下で、確認しました。

Sub test()
    Dim sc As Object
    Set sc = CreateObject("ScriptControl")
    '「Set sc = CreateObject("MSScriptControl.ScriptControl")」でも OK 。
    sc.Language = "jscript"

    Dim objArray As Object
    Set objArray = sc.eval("a=new Array()")
    sc.ExecuteStatement "function Push(s){if(!isNaN(parseInt(s)))a.push(s)}"

    sc.Run "Push", "1"
    sc.Run "Push", "5"
    sc.Run "Push", "2"
    sc.Run "Push", "3"
    sc.ExecuteStatement "a.sort(function(x,y){return x-y})"
    'sc.eval "a.sort(function(x,y){return x-y})"

    Dim GetValues
    GetValues = CallByName(objArray, "toString", VbMethod)
    '「GetValues = objArray.toString()」でも OK 。
    '「GetValues = objArray.ToString()」だと NG 。
    Stop  '確かにソートされてます。
End Sub

 
ただ、11/11/11 22:47:36 で挙げた例では、
配列のどの要素が文字列になるか、数値になるかは動的に変化するので、
比較関数を明示的に書くのは難しいですね。
再帰的な比較関数ということになるのでしょうか。
 
かと言って、
仮に数字をevalで数値に変換して配列に代入して、
sortに任せると、
文字列と数値が混在した配列の場合では、
数値が自動的に文字列に変換されて、辞書式順序が適用されるようでした。
("10"のほうが"2"より小さい)
 
いずれにしましても、情報提供ありがとうございました。

投稿日時: 11/12/24 08:35:27
投稿者: kumatti
投稿者のウェブサイトに移動

simpleさん、コメントありがとうございました。
なかなか、難しい題材なのですね。

投稿日時: 11/12/28 09:01:03
投稿者: kumatti
投稿者のウェブサイトに移動

クリップボードへのコピーをトリガーにしたい
http://www.vbalab.net/vbaqa/c-board.cgi?cmd=ntr;tree=70753;id=excel
ビューアチェイン
http://wisdom.sakura.ne.jp/system/winapi/win32/win92.html
WM_DRAWCLIPBOARDで処理するのが正攻法でしょうけど、実際のコードを示せないので、ダンマリしてました。
 
# フックの負担を軽くするテクが要るので。

投稿日時: 11/12/29 08:40:49
投稿者: kumatti
投稿者のウェブサイトに移動

http://www.moug.net/faq/viewtopic.php?t=61597
よく考えたらWin32には(Internet Archive)
http://www.mvps.org/emorcillo/download/vb6/*
(tl_ole.zip)
がありますね。
> DLCTL_NO_SCRIPTS
mshtmdid.h
 
昨今はWebAPIが主流ですし、Firefoxのmozctl.dllなんてのもあるので
過去のものになりつつあるのかなと。

投稿日時: 11/12/29 08:49:15
投稿者: kumatti
投稿者のウェブサイトに移動

> Firefoxのmozctl.dll
GeckoエンジンでWebページを開く(Office)
http://www.ka-net.org/office/of44.html

投稿日時: 12/01/08 18:47:08
投稿者: kumatti
投稿者のウェブサイトに移動

閉じます。