ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その2
次回予告までしておきながら、他にVBA関係の記事を書いていたこともあり、既に2ケ月以上が過ぎ、
「いつ頃までに、まとめられるかは、不明・・・」の記載通りになってしまった。
前回も書きましたけど、64bit版のVBAではどうなるかわかりませんので!!!
今回の内容は次の通り。
受け渡しするデータの型について
とりあえず必要なのは、VBAとC++のデータ型の対応。VBA独自の型は、C++に渡せないし、逆にC++にしかない型は、VBAが受け取れない。
(条件付きであれば、例外もあるけど・・・)
VBAの主な型を中心に対応を見てみると次のような感じ。
VBAの型 | C++の型 | 備考 |
---|---|---|
Byte | unsigned char BYTE |
|
Integer | short SHORT |
|
Long | int long INT LONG |
|
Single | float | |
Double | double | |
Boolean | BOOL |
C++の型 | VBAの型 | 備考 |
---|---|---|
HANDLE | Long | |
char CHAR |
Byte | 最上位ビットがOFFならばそのまま使用可 |
unsigned short WORD |
Integer | 最上位ビットがOFFならばそのまま使用可 |
unsigned int unsigned long UINT ULONG DWORD |
Long | 最上位ビットがOFFならばそのまま使用可 |
VBAにあってC++にない型(抜粋) |
---|
Currency |
Date |
Decimal |
String |
Object |
Variant |
C++にあってVBAにない型(抜粋) |
---|
WCHAR |
LONGLONG |
ULONGLONG |
VBAのString型に対応するC++の型はありません。
ちょっと特殊です。データの受け渡しにはchar型のポインタまたは、BSTR型を使用します。
これについては、次回(?)にでも・・・
処理する値を渡せるようにすること
DLLに何らかのデータ処理をしてもらうためには、必要なデータを渡してあげないと出来ません。データはVBAの関数と同様にパラメータに引数を渡します。
ただ、C++とVBAでは、構文が違うので予め覚えておく必要があります。
値渡しと参照渡しについて
- 値渡し
- 引数のアドレスをプロシージャに渡すのではなく引数の値を渡す方法。
VBAでは、ByValを指定する事により値渡しとなる。 - 参照渡し
- 引数の値をプロシージャに渡すのではなく引数のアドレスを渡す方法。
VBAでは、デフォルトで参照渡しである。明示的に指定する場合には、ByRefを指定する。
C++の場合
- 値渡し
- データ型の後ろに変数を指定する。
void doAnything(int hoge); intがデータ型で、hogeが変数 - 値渡し
- データ型の後ろに"*"を付けその後ろに変数を指定する。(ポインタですな。)
void doSomething(long* fuga); longがデータ型で、fugaが変数
処理した結果や値を返してもらえるようにすること
値を返してもらう方法は、大きく分けて2つ。一つは、関数の復帰値による方法。
これは、VBAでいえば、ファンクションプロシージャによる復帰値で結果を得る方法と同じ。
C++の場合には、関数名の前に復帰値のデータ型を指定する。
復帰値の型 関数名(パラメータリスト)
もう一つは、渡したパラメータにデータを入れてもらい、返してもらう方法。
こちらは、VBAでいえば、パラメータにByRefを指定し、返してもらう方法と同じ。
どちらを使うかは、状況に応じて使い分ければよろしいかと。
場合によっては、両方というのもありです。
数値型の場合の受け渡しの例
VBAからLongの値を渡して、2倍した結果を返してもらう「GetNumberI」という関数と、300倍した値を返してもらう「GetNumberI2」を作ってみます。
GetNumberIは、パラメータを値渡しして、復帰値で結果をもらいます。
GetNumberI2は、パラメータを参照渡しし、そのパラメータ値を変更して返してもらいます。
前回のファイルに、書き加えていきます。
まず、ヘッダファイル(AccessibleFromVBA.h)
#pragma once extern "C" { #define ACCESSIBLEFROMVBA_API __declspec(dllexport) ACCESSIBLEFROMVBA_API void WINAPI DoNothing(); ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i); ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi); }
次に、ソースファイル(AccessibleFromVBA.cpp)
#include "stdafx.h" #include "AccessibleFromVBA.h" ACCESSIBLEFROMVBA_API void WINAPI DoNothing() { return; } //int型の値を受け取って、int型の復帰値を返す(VBAのファンクションプロシージャ相当) ACCESSIBLEFROMVBA_API int WINAPI GetNumberI(int i) { return i * 2; } //int型の値を受け取って、内部で値を変更して返す(VBAでパラメータをByRef で受け渡しするイメージ) ACCESSIBLEFROMVBA_API void WINAPI GetNumberI2(int* pi) { *pi *= 300; return; }
最後に、モジュール定義ファイル(AccessibleFromVBA.def)
LIBRARY AccessibleFromVba EXPORTS DoNothing GetNumberI GetNumberI2
全て追加したら、プロジェクトをビルドします。
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========
と表示されればOK。
呼び出すExcel側は、
Private Declare Function GetNumberI Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByVal l As Long) As Long Private Declare Sub GetNumberI2 Lib "C:\Datas\MyDatas\Developer\VisualStudioComunity2017\DllForVBA\ForTest\AccessibleFromVBA.dll" (ByRef l As Long)
呼び出してみる
Public Sub DllCallTest2() Dim lValue As Long Dim lResult As Long lValue = 1000 lResult = GetNumberI(lValue) Debug.Print "GetNumberI:", lValue, lResult Debug.Print "" Debug.Print "GetNumberI2(Before):", lValue Call GetNumberI2(lValue) Debug.Print "GetNumberI2(After ):", lValue End Sub
実行結果は、
call DllCallTest2
GetNumberI: 1000 2000
GetNumberI2(Before): 1000
GetNumberI2(After ): 300000
当てにならない次回予告
とりあえず、数値の受け渡しは出来たので、次は、- 文字列
- 配列
- 構造体
の受け渡しあたりをまとめられたらいいなぁ~
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
z1000s.hatenablog.com
VBAによる祝日判定および祝日取得(改正東京五輪・パラリンピック特別措置法 対応)
2018年6月13日、参院本会議で可決、成立しました。
これに伴って、2020年の祝日が移動するものが出てきたようなので、先日公開した祝日判定処理を更新しました。
また、2020年からは「体育の日」が「スポーツの日」になるそうなので、合わせて対応済みです。
まぁ、祝日の定義の部分を変えただけなんですけどね。
処理内容は全く変えないで、元になる祝日データだけいじればいいので、祝日の追加、廃止、変更等のメンテナンスは楽ですよ。
ソース
実行例
2019年から2021年の7月から10月の祝日を出力した結果が以下の通り。
----- 2019 -----
2019/07/15 海の日
2019/08/11 山の日
2019/08/12 振替休日
2019/09/16 敬老の日
2019/09/23 秋分の日
2019/10/14 体育の日----- 2020 -----
2020/07/23 海の日
2020/07/24 スポーツの日
2020/08/10 山の日
2020/09/21 敬老の日
2020/09/22 秋分の日----- 2021 -----
2021/07/19 海の日
2021/08/11 山の日
2021/09/20 敬老の日
2021/09/23 秋分の日
2021/10/11 スポーツの日
【VBA / Excel】祝日休日の判定、取得用データを生成し、実際に判定、取得してみる
この記事では、予め用意された祝日テーブルを使用するのではなく、
クラスモジュール内で、祝日テーブルを自前で生成し、Dictionary に格納して判定処理を行っています。
祝日の定義は、クラスモジュール内に、汎用性を考慮した状態でハードコーディングしてあります。
このため、法の変更等により、祝日が変わらなければ、このまま使用できます。
別途、祝日テーブルを用意する必要はありません。
2018/9/10 追記
この記事は、祝日(振替休日等を含む)を判定の対象にしていますが、日曜日や年末年始の休み等は判定対象外です。
これらの休日も判定対象に含めたい場合には、以下の記事をあわせて参照してみて下さい。
z1000s.hatenablog.com
2018/6/14 追記
この記事には、2018/6/13の改正東京五輪・パラリンピック特別措置法が参院本会議での可決に伴って、更新された記事があります。
z1000s.hatenablog.com
2019/5/23 追記
Googleで検索して来る方が多いようですが、中にはこのページが本来の目的ではない方もいるかもしれないので、とりあえず、「こんなページもあるよ」ということで載せておきます。
z1000s.hatenablog.com
z1000s.hatenablog.com
目次みたいなもの
- 1.月、日が固定のもの
- 2.月、第N ○曜日が固定のもの
- 3.月は固定だが、日が可変のもの
- 4.月日が定まっておらず、他の祝日などに依存するもの
- 最終的なコード
- パブリックメソッド一覧
- isNationalHoliday(ByVal dtDate As Date) As Boolean
- isNationalHoliday2(ByVal dtDate As Date, ByRef sHolidayName As String) As Boolean
- getNationalHolidays(ByVal lYear As Long, ByRef dtHolidays() As Date) As Long
- getNationalHolidayName(ByVal dtHoliday As Date) As String
- reInitialize(ByVal lLastYear As Long)
- パブリックプロパティ
- 使い方と実行例
- お約束
「Excelで祝日判定をしたい」というと、「祝日データを用意して、それを・・・」とお決まりの文言が出てくるのが一般的です。
では、その祝日データはどうすれば用意できるのでしょうか?
ここのように、期間を指定して生成してくれるようなところもあるようです。
が、必要なデータが変わるたびにサイトにアクセスしてというのも面倒です。
となれば、「自分で作ってしまえ作れるようにしてしまえ」となりました。
以上
???普通ならそうはならない???
まぁ、いいじゃないですか。私がそう思ったんだから。
それに、ここで終わったら、ただの落書きで終わってしましますよ。
そろそろ本題に。
まず、処理するに当たって祝日を分類してみる。カッコ内は例。
- 月、日が固定のもの(元日:1月1日)
- 月、第N ○曜日が固定のもの(成人の日:1月第2月曜日)
- 月は固定だが、日が可変のもの(春分の日:3月19〜22日)
- 月、日が定まっておらず、他の祝日などに依存するもの(振替休日、国民の休日)
1、2項は簡単に求められる。
3項は、計算式があるのでこれも問題なし。
4項は、他の祝日に依存するため、単独では求められない。予め1〜3項の祝日がわかっている必要がある。(或いは、その都度依存する日の状態を確認するか。)
では具体的にどうすれば求められるか、簡単(?)かもしれない解説。
前提条件として、データはDate型で保持するものとします。
1.月、日が固定のもの
VBA では、DateSerial 関数を使用すると、年、月、日を指定してDate 型の値を取得できます。
DateSerial( year, month, day )
DateSerial 関数については、こちら
2.月、第N ○曜日が固定のもの
指定された月の1日の曜日がわかれば、1日以降の最初の指定された曜日(第1○曜日)へのオフセット値(何日補正すればよいか)がわかります。
それがわかれば、7の倍数を加算することで求められます。
まず、指定月の1日を求めるのは、1項で登場したDateSerial 関数。
DateSerial( year, month, 1 )
と日に「1」を指定すればOK。
次にその曜日
曜日は、Weekday 関数を使用します。
Weekday( date , [ firstdayofweek ] )
dateに、上で求めた1日の日付データを渡します。
firstdayofweekは省略可能です。今回は使用しません。
そうすると、指定した日付によって1〜7の値が返ってきます。
定数 | 値 | 説明 |
---|---|---|
vbSunday | 1 | 日曜日 |
vbMonday | 2 | 月曜日 |
vbTuesday | 3 | 火曜日 |
vbWednesday | 4 | 水曜日 |
vbThursday | 5 | 木曜日 |
vbFriday | 6 | 金曜日 |
vbSaturday | 7 | 土曜日 |
これを書いている2018年5月の場合を考えてみます。
第2週までのカレンダーは、以下の通りです。
日 | 月 | 火 | 水 | 木 | 金 | 土 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
最初の曜日までの日数は、以下のようになります。
曜日 | 指定曜日の日付 | 1日から指定曜日までの日数 |
---|---|---|
日曜日 | 6 | 5 |
月曜日 | 7 | 6 |
火曜日 | 1 | 0 |
水曜日 | 2 | 1 |
木曜日 | 3 | 2 |
金曜日 | 4 | 3 |
土曜日 | 5 | 4 |
で、曜日を使って「指定曜日までの日数」をどうやって求めるかというと
前述の曜日の定数を使って「指定曜日」 から 「1日の曜日」を引きます。
ただし、「指定曜日」が「1日の曜日」より前の場合、結果が負の値となるのでその場合には7を加えます。
5月1日は火曜日なので、vbTuesday ===> 3
例えば、第1金曜日なら、vbFriday ===> 6
なので、6 - 3 = 3 となります。
第1日曜日なら、vbSunday ===> 1
なので、1 - 3 = -2 となりますが、結果が負なので、+7 して
-2 + 7 = 5 となります。
この値が求められれば、次は1日の日付に加算を行います。
日付の加減算は、DateAdd 関数を使用します。
DateAdd(interval, number, date)
intervalには、日数の計算を行うので、"d" を指定します。
numberには、上記の計算で求めた値を指定します。
dateには、1日の日付データを指定します。
最初の曜日であれば、これで良いのですが2回め以降の曜日の場合、更に7の倍数を加算する必要があります。
第2曜日であれば、+7
第3曜日であれば、+14
第4曜日であれば、+21
第5曜日であれば、+28
となるので、第N曜日の場合
(N ー 1)*7
を加算すれば良いことになります。
最終的には、以下のようなプロシージャにより求められます。
Public Function getNthWeeksDayOfWeek(ByVal lYear As Long, _ ByVal lMonth As Long, _ ByVal lNth As Long, _ ByVal lDayOfWeek As VbDayOfWeek) As Date Dim dt1stDate As Date Dim lDayOfWeek1st As Long Dim lOffset As Long '指定年月の1日を取得 dt1stDate = DateSerial(lYear, lMonth, 1) '1日の曜日を取得 lDayOfWeek1st = Weekday(dt1stDate) '指定日へのオフセットを取得 lOffset = lDayOfWeek - lDayOfWeek1st If lDayOfWeek1st > lDayOfWeek Then lOffset = lOffset + 7 End If lOffset = lOffset + 7 * (lNth - 1) getNthWeeksDayOfWeek = DateAdd("d", lOffset, dt1stDate) End Function
ただし、N >= 5を指定した場合、日付が翌月になる場合がありますので、注意が必要です。
3.月は固定だが、日が可変のもの
これは、春分の日と秋分の日が該当。
Wikipediaによると
春分の日は、3月19~22日のいずれか。
秋分の日は、22~24日のいずれか。
となっている。
一般に、
'春分の日 Int(20.8431 + 0.242194 * (年 - 1980)) - Int((年 - 1980) / 4) '秋分の日 Int(23.2488 + 0.242194 * (年 - 1980)) - Int((年 - 1980) / 4)
という計算で求められるようです。
1980年以降に限定すれば、以下の内容でもOK。
というか、こうしたかったんだけれど、1979年以前の場合、
¥演算子の結果が、Fix 関数と同等になり、Int 関数とは結果が変わってしまうため
If で振り分ける等の必要が生じてしまうので、上記の式をそのまま使用することにしました。
'春分の日 Int(20.8431 + 0.242194 * (年 - 1980)) - (年 - 1980) \ 4 '秋分の日 Int(23.2488 + 0.242194 * (年 - 1980)) - (年 - 1980) \ 4
ちなみに、¥演算子を使用した場合は、内部で整数演算が行われるが、
/演算子の場合、整数同士の演算であっても、内部では浮動小数点数で演算が行われます。
4.月日が定まっておらず、他の祝日などに依存するもの
4.1 振替休日
1973年の国民の祝日に関する法律が改正されたことにより制定された。これにより祝日(国民の祝日)が日曜日の場合、その翌日となる月曜日が振替休日となった(ハッピーマンデー制度の法律と同じ)。1973年の天皇誕生日(4月29日)が日曜日で、同年4月30日が最初の適用日となった。
当初は祝日が2日以上連続することがなかったため、「国民の祝日の日曜日の翌日の月曜日」としていた。しかし2005年の国民の祝日に関する法律の改正(2007年施行)で、4月29日が「昭和の日」となり、「みどりの日」が4月29日から5月4日へ変更。5月3日から5月5日まで祝日が3日連続することになり、その直後の「国民の祝日の日曜日の翌日の月曜日以降の国民の祝日でない祝日の翌日」を休日とすることと改められ振替先が月曜日固定ではなくなった。
振替休日 - Wikipedia
ということなので、
祝日が日曜日であった場合、適用された年から判断し、その翌日または、それ以降で最初の祝日でない日を求めればOK。
2007年以降では、当然ながらこの日が月曜日とは限りません。
2015年5月3日の憲法記念日の振替休日は、6日の水曜日でした。
5月3日(日):憲法記念日
5月4日(月):みどりの日
5月5日(火):こどもの日
5月6日(水):5月3日の振替休日
4.2 国民の休日
前後が祝日である平日は、国民の休日となり、休日となる。
国民の休日 - Wikipedia
こちらは、翌々日まで確認が必要なので、要件は振替休日より複雑。
- 最初の祝日が月曜日から金曜日の間にあること。
- 最初の祝日が日曜日の場合、翌日は振替休日となるので、2項を満足しない。
- 最初の祝日が土曜日の場合、翌日は日曜日となるので、平日ではない。
- 最初の祝日の翌日が、平日であること。(祝日でなく、休日でもないこと。)
- 最初の祝日の翌々日が、祝日であること。
これらの条件を満たした時、最初の祝日の翌日が「国民の休日」となる。
2項、3項の祝日は、年によって日付が変動する祝日であり、「移動祝日」または「移動祝祭日」と呼ぶそうです。
最終的なコード
VBAのクラスとして作成しました。
▶クリックでソースを展開/縮小
2021/4/11
都合により、
VBAによる「祝日判定処理」を「休日判定処理」に拡張してみた - 空腹おやじのログと備忘録
と、コードを統合した上で、
こちら に移動しました。
CCompanyHoliday.clsのみ
https://github.com/Z1000R/determining-and-retrieving-holidays/blob/main/Source/CCompanyHoliday.cls
その他諸々を含めて一式
github.com
パブリックメソッド一覧
isNationalHoliday(ByVal dtDate As Date) As Boolean
指定日が国民の祝日(休日)か?
引 数 | IN/OUT | 内容 |
---|---|---|
dtDate As Date | IN | 判定したい日付 |
復帰値 | 内容 |
---|---|
True | 国民の祝日(休日)である。 |
False | 国民の祝日(休日)ではない。 |
isNationalHoliday2(ByVal dtDate As Date, ByRef sHolidayName As String) As Boolean
指定日が国民の祝日(休日)か?そうであれば、その祝日名を合わせて返す
引 数 | IN/OUT | 内容 |
---|---|---|
dtDate As Date | IN | 判定したい日付 |
sHolidayName As String | OUT | 祝日名 |
復帰値 | 内容 |
---|---|
True | 国民の祝日(休日)である。 |
False | 国民の祝日(休日)ではない。 |
getNationalHolidays(ByVal lYear As Long, ByRef dtHolidays() As Date) As Long
指定年の国民の祝日を配列に格納して返す
引 数 | IN/OUT | 内容 |
---|---|---|
lYear As Long | IN | 取得したい年 |
dtHolidays() As Date | OUT | 指定された年の国民の祝日 |
復帰値 |
---|
国民の祝日の件数 |
getNationalHolidayName(ByVal dtHoliday As Date) As String
指定日の国民の祝日名を返す
引 数 | IN/OUT | 内容 |
---|---|---|
dtHoliday As Date | IN | 日付 |
復帰値 |
---|
指定された日付の国民の祝日名 |
指定された日付が国民の祝日でない場合、復帰値は長さ0の文字列となる。
reInitialize(ByVal lLastYear As Long)
指定年までの祝日データが生成されていなければ、追加生成する。
引 数 | IN/OUT | 内容 |
---|---|---|
lLastYear As Long | IN | 生成したい最終年 |
現在の何年までのデータが生成されているかは、後述のInitializedLastYearプロパティで取得できる。
パブリックプロパティ
InitializedLastYear() As Long
何年までの国民の祝日データが生成されているか
(読み取り専用)
使い方と実行例
クラス名は、CNationalHoliday としています。
使用する前に、Newを付けて、インスタンスを生成して下さい。
(そのタイミングで、デフォルトで現在の年から5年後の年末までの祝日を内部で生成します。)
その後に、必要なメソッドを呼び出します。
日付を指定して、祝日判定をして、祝日名を取得する
Public Sub Test1() Dim cnh As New CNationalHoliday Dim dt As Date Dim sHolidayName As String dt = #8/11/2015# Debug.Print Format$(dt, "yyyy/mm/dd"), cnh.getNationalHolidayName(dt), cnh.isNationalHoliday2(dt, sHolidayName) dt = #8/11/2016# Debug.Print Format$(dt, "yyyy/mm/dd"), cnh.getNationalHolidayName(dt), cnh.isNationalHoliday2(dt, sHolidayName) dt = #8/12/2018# Debug.Print Format$(dt, "yyyy/mm/dd"), cnh.getNationalHolidayName(dt), cnh.isNationalHoliday2(dt, sHolidayName) dt = #8/12/2019# Debug.Print Format$(dt, "yyyy/mm/dd"), cnh.getNationalHolidayName(dt), cnh.isNationalHoliday2(dt, sHolidayName) dt = #2/23/2020# Debug.Print Format$(dt, "yyyy/mm/dd"), cnh.getNationalHolidayName(dt), cnh.isNationalHoliday2(dt, sHolidayName) Set cnh = Nothing End Sub
実行結果
call Test1 2015/08/11 False 2016/08/11 山の日 True 2018/08/12 False 2019/08/12 振替休日 True 2020/02/23 天皇誕生日 True
年を指定して、祝日および祝日名を取得する
Public Sub Test2(ByVal lYear As Long) Dim cnh As New CNationalHoliday Dim dt() As Date Dim i As Long Call cnh.getNationalHolidays(lYear, dt) For i = 0 To UBound(dt) Debug.Print dt(i), cnh.getNationalHolidayName(dt(i)) Next i Set cnh = Nothing End Sub
実行結果
call Test2(2018) 2018/01/01 元日 2018/01/08 成人の日 2018/02/11 建国記念の日 2018/02/12 振替休日 2018/03/21 春分の日 2018/04/29 昭和の日 2018/04/30 振替休日 2018/05/03 憲法記念日 2018/05/04 みどりの日 2018/05/05 こどもの日 2018/07/16 海の日 2018/08/11 山の日 2018/09/17 敬老の日 2018/09/23 秋分の日 2018/09/24 振替休日 2018/10/08 体育の日 2018/11/03 文化の日 2018/11/23 勤労感謝の日 2018/12/23 天皇誕生日 2018/12/24 振替休日
祝日データを、デフォルトの5年後より後の分も生成する
Public Sub Test3() Dim cnh As CNationalHoliday Dim dt As Date Dim i As Long Set cnh = New CNationalHoliday Debug.Print "Before reInitialize", cnh.InitializedLastYear cnh.reInitialize 2030 Debug.Print "After reInitialize", cnh.InitializedLastYear Set cnh = Nothing End Sub
実行結果
call Test3 Before reInitialize 2023 After reInitialize 2030
秀丸で、選択行の行頭に文字を挿入するマクロ
先日、人力検索はてなで、
q.hatena.ne.jp
に回答して、ベストアンサーを頂いたのですが
ベースになった物があって、C++のソースを編集する際に、
- 行コメントの追加
- 既存の行を改修してコメント化
するために以前作ったもの(下記)でした。
(今回の件で、ちょっとしたバグを発見して修正したけど・・・)
あちらにアップしたものは、コメント削除したものなので、わかりにくかったかも。
$comment_prefix = "//"; //範囲選択されているか if ( selecting == 1 ) { //範囲選択されていたら解除 escape; //選択開始行 #TargetLine = seltopy + 1; //選択終了行 if ( selendx == 0 ) { //選択終了行のカーソル位置が行先頭なら、その行はコメント化しない #EndLine = selendy; } else { #EndLine = selendy + 1; } //選択開始行に移動 movetolineno 1, seltopy + 1; } else { //選択開始行 #TargetLine = lineno; //選択終了行 #EndLine = lineno; } //最終行に移動 gofileend; //最終行取得 #FileEndLine = lineno; //元の行に戻る movetolineno 1, #TargetLine; while ( #TargetLine != #EndLine + 1 ) { //行先頭に移動 golinetop; //コメント化 insert $comment_prefix; //処理行をインクリメント #TargetLine = #TargetLine + 1; if ( #TargetLine > #FileEndLine ) { break; } //1行下へ移動 movetolineno 1, #TargetLine; }
LINEのバックアップがGoogleドライブに見当たらない?
スマホの機種変更をしようとしていた知人から、
「LINEのバックアップをGoogleドライブに保存したけど、ファイルが見当たらない。このまま移行作業を進めてもいいのか?」
と相談されました。
LINEの設定画面には、バックアップ日時が表示されているし、
再度バックアップすると、日時が更新されるので、
バックアップデータは、アップロードされているような雰囲気。
自分のスマホでバックアップして、Googleドライブを覗いてみても確かに見当たらない。
隠しファイルを表示させても見えない。
試しにPCのブラウザから、ドライブを確認してみても見当たらない。
で、探したら見つけました。
PCのブラウザで、Googleドライブを表示させ、「設定」-「アプリの管理」を表示させると、「非表示のアプリデータ」として保存されていました。
見えないと、本当に保存されているのか不安ですよね。
とりあえず解決ということで・・・
MS AccessのインストールされていないPCで、VBA(Excel)を使って accdbを最適化する
最近VBAネタが続く・・・
今使っているOffice 2013を買う時、Accessはあれば便利かもしれないけど、
「どうせ使う機会は無いだろう」という事で、Office Personalを買ったので
Accessは持っていない。
しかし、最近になって〇〇なデータ管理をしようかと思ったが、どうにもExcelだけでは厳しい。
ローカルで手軽に使えるデータベースが欲しいけれど、このためだけにAccessを買うというのももったいない。
(LibreOffice Calc Baseという選択肢はない。)
バックエンド用として使えれば、フロントエンドはC#とか最悪Excelでもなんとか出来る。
あれ、mdbってVBAで作れなかった?
やっぱり作れるじゃないか、accdbも!
で、このゴールデンウィークに、accdbファイル自体と、
それに、テーブルを作るためのツールを作った。
肝心のデータ管理用のフロントエンドは全く出来ていないし
データもインサートしてないのに、気が早くて「最適化ツールは絶対必要だ!!!」
となり、作り始めたはいいが、コンパイルエラー
"DBEngine"を、「変数が定義されていません」とおっしゃる。
DAOの参照設定してないじゃん。
Microsoft DAO 3.6 Object Library 参照設定して、コンパイルもOK。
いざ、実行!
実行時エラー 3343
データベースの形式 'C:\Datas\sample.accdb' を認識できません。
調べていくと
引数 options に次のいずれかの定数を使用すると、最適化されたデータベースのデータの形式のバージョンを指定できます。
DBEngine.CompactDatabase method (DAO) | Microsoft Docs
・・・
dbVersion120
最適化に Microsoft Access データベース エンジン バージョン 12.0 のファイル形式を使用するデータベースを作成します。
オプションに、dbVersion120を追記して、コンパイルしてみる。
"dbVersion120"に対して、"変数が定義されていません。"
まだ、だめですか・・・orz
さらに調べていくと、
DAO 12.0 (ACEDAO.DLL) の DBEngine.CompactDatabase メソッドを利用する。
VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板)
という記載を発見。
でも、参照設定に、DAO 12.0なんて見当たらない。
そもそも、ACEDAO.DLLなんて入ってるの?
DLLはあった。
じゃあ、どれを参照すればいいの???
オブジェクトブラウザーで「DAO」を確認すると「Microsoft office 15.0 Access database engine Object」に含まれているんですね。
ふひろ通信: Microsoft Access 2013の参照設定で「Microsoft DAO 3.6 Object Library」を有効にしようとすると警告が出る
これ(Microsoft Office 15.0 Access database engine Object Library)か?
参照設定チェックしてみると
オブジェクトブラウザに、dbVersion120の文字が表示された!
コンパイルもOK。
いざ、実行。
エラーの発生もなく、無事終了。
ダミーのデータ10万件をinsert後、全てdeleteしてから
最適化処理でファイルサイズが小さくなっているのを確認。
(拡張子".tmp"となっているのが最適化実行直後(リネーム前))
最終的なコードは、これ
'参照設定 'Microsoft Office 15.0 Access database engine Object Library Public Sub compactDB() Dim sSrcPath As String Dim sDestPath As String '最適化するファイルのパス sSrcPath = "C:\Datas\sample.accdb" '最適化後のファイル名 sDestPath = sSrcPath & ".tmp" On Error GoTo ERR_COMPACT_DB '最適化の実行 DBEngine.CompactDatabase sSrcPath, sDestPath, DAO.LanguageConstants.dbLangJapanese, DAO.DatabaseTypeEnum.dbVersion120 '冗長に書いているが、下記でもOK 'DBEngine.CompactDatabase sSrcPath, sDestPath, dbLangJapanese, dbVersion120 '最適化前のファイルを削除 Kill sSrcPath '最適化後のファイル名を最適化前のファイル名にリネーム Name sDestPath As sSrcPath ERR_COMPACT_DB: If Err.Number <> 0 Then Debug.Print "[" & CStr(Err.Number) & "]" & Err.Description Else Debug.Print "Done." End If End Sub
自分一人で使うやつなので、エラー処理は最低限。
あと、参照設定の15.0の部分は、Officeのバージョンで変わるらしい。
mdbの場合は、dbVersion120の代わりに、dbVersion40を使えばいけるのかな?(未確認)
参考サイト
DBEngine.CompactDatabase method (DAO) | Microsoft Docs
DatabaseTypeEnum enumeration (DAO) | Microsoft Docs
VBAのDictionaryのItemに動的配列を格納する
C++のmultimapのように、特定のキーに対し、複数のデータをDictionaryに格納できないか?
キー毎にデータ数が異なる場合、最大データ数を求めてから固定長配列を格納するのは無駄だし、
実際にデータがいくつ入っているか先頭から当たっていかないとわからなそう。
それは、やりたくない。
で、動的配列をItemとして格納してみたら、案外あっさりと出来ました。
ワークシートから読み込んだ地名の先頭1文字をキーとして、その地名を配列にしてItemに格納する例です。
Public Sub dicTest()
Dim dicAddress As New Dictionary
Dim sAddress() As String
Dim sKey As String
Dim lItems As Long
Dim sValue As String
Dim lRow As Long
Dim i As Long
lRow = 1
lItems = 0
ReDim sAddress(lItems)
With ThisWorkbook.Worksheets("Sheet1")
sValue = .Cells(lRow, 1).Value
Do Until (Len(sValue) = 0)
sKey = Left$(sValue, 1)
If dicAddress.Exists(sKey) = True Then
'キーありなら、格納されている配列を取得
sAddress = dicAddress.Item(sKey)
lItems = UBound(sAddress) + 1
'配列の要素数をひとつ増やす
ReDim Preserve sAddress(lItems)
Else
'キーなしなら、1個分のデータを格納できるよう、配列を初期化
lItems = 0
ReDim sAddress(lItems)
End If
sAddress(lItems) = sValue
'配列を格納
dicAddress(sKey) = sAddress
lRow = lRow + 1
sValue = .Cells(lRow, 1).Value
'Dictionaryには、コピーされた配列が格納される(ようです)。
'消さなくても試した限りでは問題はなさそうだけど、残して何度も使い回すのは気持ち悪いので消す。
Erase sAddress
Loop
End With
'格納したデータを書き出す
Debug.Print "----- い -----"
For i = 0 To UBound(dicAddress.Item("い"))
Debug.Print dicAddress.Item("い")(i)
Next i
Debug.Print "----- お -----"
For i = 0 To UBound(dicAddress.Item("お"))
Debug.Print dicAddress.Item("お")(i)
Next i
End Sub
テストデータは
---------------------------------
あきる野市
あわら市
あま市
あさぎり町
おいらせ町
いわき市
つくばみらい市
さいたま市
いすみ市
おおい町
いなべ市
いの町
いちき串木野市
---------------------------------
実行結果は、
----- い -----
いわき市
いすみ市
いなべ市
いの町
いちき串木野市
----- お -----
おいらせ町
おおい町
こんな感じ。
ポイントは、
1.キーがあった時の、データ(動的配列)の取り出し方。
2.データを追するのためのReDim PreserveとReDimの使い方。
3.データ読み出し時の指定方法dicAddress.Item("い")(i)
あたりでしょうか。
まあ、ちょっとしたネタなので、「もっといい方法がある」とか突っ込まないで下さい。
こんなのもあります。