VBAでDocuWorksAPIを利用して複数のDocuWorks文書を合成して名前をつけて保存する

## はじめに
私の職場環境ではDocuWorksが標準でインストールされています。
外部とのやり取りはPDFに変換しますが、内部ネットワーク上の連絡や周知、スキャンファイルの保存などはDocuWorks文書が主流です。
このDocuWorksについて、FUJI XEROXからAPIが公開されているのですが、職場の環境でも利用可能であることが判明したので、今後活用するために記事として記録します。
今回はスキャンファイルやエクセルなどから印刷した複数のDocuWorksファイルを一つにまとめて保存する作業、これを自動化するためにDocuWorksAPIの「XDW_MergeXdwFiles」をVBAから利用します。
## 目的
複数のDocuWorks文書をまとめて、ファイル名をつけて保存したい。

## 処理フロー
１　合成用パス配列と保存用パスを取得
２　合成用パス配列の各要素をShiftJISコードへ変換
３　変換後の合成用パス配列の各要素のポインタを取得しポインタ配列を作成
４　作成したポインタでDocuWorksAPI「XDW_MergeXdwFiles」呼び出し

<details><summary>**サンプルコード**</summary><div>

```vb
Option Explicit

'DocuWorksAPI宣言部
'2.75 XDW_MergeXdwFiles 複数のDocuWorks文書を合成して1つのDocuWorks文書を生成する。
Private Declare PtrSafe Function XDW_MergeXdwFiles _
    Lib "xdwapi.dll" ( _
        ByRef lpszInputPaths As LongPtr, _
        ByVal nFiles As Long, _
        ByVal lpszOutputPath As String, _
        Optional ByVal reserved As String = vbNullString) As Long

'******************************************************************************************************
'機能　複数のDocuWorks文書を合成して1つのDocuWorks文書を生成する。
'引数　inputDwPaths　束ねるDW文書のファイルパス配列　/outputDwPath　出力パス
'戻値　XDWAPIエラーコード(Long型,16進数)
'備考　XDW_MergeXdwFilesへ文字列配列を渡すには、文字列配列の各要素へのポインタを格納した配列の先頭要素を参照渡しする
'XDW_MergeXdwFiles(const char** lpszInputPaths, int nFiles, const char* lpszOutputPath, void* reserved );
'******************************************************************************************************
Function MergeDWFiles( _
    ByRef inputDwPaths As Variant, _
    ByVal outputDwPath As String) As Long
    
Dim inputL As Long:  inputL = LBound(inputDwPaths)
Dim inputU As Long:  inputU = UBound(inputDwPaths)

'文字コード変換後の入力ファイルパスの保持用配列(String型)
Dim strAry() As String:  ReDim strAry(inputL To inputU)

'文字列配列参照用のポインタ配列(LongPtr型)
Dim ptrAry() As LongPtr: ReDim ptrAry(inputL To inputU)

'文字コードの変換とAPI用の配列の作成
Dim i As Long
For i = inputL To inputU
    strAry(i) = StrConv(CStr(inputDwPaths(i)), vbFromUnicode)
    ptrAry(i) = StrPtr(strAry(i))
Next

'ファイルの数(ポインタ配列長)
Dim fileNum As Long: fileNum = inputU - inputL + 1

'DocuWorksAPI:XDW_MergeXdwFilesの呼び出し
MergeDWFiles = XDW_MergeXdwFiles(ptrAry(inputL), fileNum, outputDwPath)
End Function
```

</div>

## 解説
ポイントは２つ。
１つ目は文字コードの変換です。
DocuWorksAPIの仕様書では、「各関数で、文字列を引数にとるとき、特に断りのない限り、日本語は__ShiftJISコード__を用いる。」と説明されており、Unicodeで扱われるVBのString型をそのままAPIに渡すとエラーとなります。
サンプルでは**StrConv関数**でUnicodeからシステム規定の文字コード（=ShiftJIS）に変換しています。

２つ目はAPIへの文字列配列の渡し方です。
XDW_MergeXdwFiles関数はAPIのヘッダーファイルの引数に「const __char**__ lpszInputPaths, int nFiles」と記載があります。
この「___char** lpszInputPaths___」で文字列配列が格納されているメモリに対するポインタ配列の先頭要素のポインタを渡し、続く「___int nFiles___」でポインタ配列の長さを渡しています。分かりにくいので図示すると下のようなイメージになります。
![図1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390397/18d8749f-e0e9-49ff-1cb1-2b137ce02c1f.jpeg)

メモリ上の配列変数は連続した領域を使用するので、メモリサイズが固定長であるLongptr型の配列の要素数を渡すことで、APIが文字列実体が保持されたメモリへの参照が可能となっています。


## 補足
実行にあたっては当然ですがDocuWorksがインストールされていることに加え、DocuWorksAPIのPathが通してある必要があります。
私の自宅環境では外付けHDD上のファイルは正常に動作しましたが、OneDrive上のフォルダへの保存は失敗しました。
ポインタの考え方について、あくまで動作結果から考察した記載です。間違ってたら誰かご指摘ください。

参考サイト
[・トキドキドキンドットコム FUJI XEROX DOCUWORKS APIをVBAから利用する（DOCUWORKSファイルのページからイメージファイルを生成する）【後編】](http://tokidokidokin.com/2010/10/fuji-xerox-docuworks-api%E3%82%92vba%E3%81%8B%E3%82%89%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%EF%BC%88docuworks%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%8B%E3%82%89-2/#.YIVkU5AzaUk)
[・言語リファレンス　文字列型 (String) (Visual Basic)](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/data-types/string-data-type)
[・引数の値渡しと参照渡し (Visual Basic)](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/procedures/passing-arguments-by-value-and-by-reference)
[・Excel VBAからDLL呼び出し](http://www.ne.jp/asahi/hishidama/home/tech/excel/dll.html)
## 最後に
今後はVBAに加えてDocuWorksAPIでも仕事効率化したい。
差し込み原稿の束ね直しとか、OCRとかDocuWorks文書に直接差し込みとか・・・

######2021/06/13修正
・StrConv関数が同一のメモリアドレスに戻り値(String型)を返す場合があるため、サンプルコードに文字コード変換後の合成ファイルパス保持用配列を追加しました。

```vb
    ptrAry(i) = StrPtr(StrConv(CStr(inputDwPaths(i)), vbFromUnicode))
```
　↓

```vb
    strAry(i) = StrConv(CStr(inputDwPaths(i)), vbFromUnicode)
    ptrAry(i) = StrPtr(strAry(i))
```

・解説を修正
・参考サイトを追加