Visual Basic は内部的には Unicode を使用していますが、文字列を C や C++ に渡すときは Unicode 文字を等価な ANSI 文字に変換し、C や C++ から文字列を受け取るときは ANSI 文字を等価な Unicode 文字に変換します。
http://support.microsoft.com/kb/145727/ja
という仕様がやっかい。この ANSI変換を回避してUNICODEで直接引数を受け渡しする方法がないものか。
いくつかの方法は、上掲の記事に書かれている。
以下は、文字列を直接リターンする関数のとりあえずの実現方法。
DLL の作成
// cConcat.c #include <windows.h> __declspec(dllexport) wchar_t* __stdcall cConcat( wchar_t* str1, wchar_t* str2 ) { // wcslenやwcscmpは、引数としてNULLを与えるとクラッシュするため、NULLなら、空文字列に置換. wchar_t* s1 = str1 ? str1 : L""; wchar_t* s2 = str2 ? str2 : L""; int len1 = wcslen(s1); int len2 = wcslen(s2); wchar_t* wstr = (wchar_t *) malloc( len1 + len2 + 2 ); wcscpy (wstr, s1); wcscpy (wstr + len1, s2); BSTR bstr = SysAllocString( wstr ); free (wstr); return bstr; }
※BSTRに対する wcslen の扱いには注意が必要。次の記事を参照。
http://www.artonx.org/collabo/backyard/?BasicString
VB側 (Excel を使用した例)
コンパイルされた cConcat.dll と Book.xls を同一フォルダに置く。
Book.xlsには以下、ふたつのモジュールを記述する。
Option Explicit '同一フォルダにあるDLLファイルのロード、アンロード Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleW" (ByVal lpModuleName As Long) As Long Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryW" (ByVal lpLibFileName As Long) As Long Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long ' Private Property Get LibraryPath() As String LibraryPath = ThisWorkbook.Path & "\cConcat.dll" End Property Private Sub Auto_Open() LoadDLL End Sub Private Sub Auto_Close() FreeDLL End Sub Private Sub LoadDLL() If 0 = GetModuleHandle(StrPtr(LibraryPath)) Then LoadLibrary StrPtr(LibraryPath) End If End Sub Private Sub FreeDLL() Dim hLib As Long hLib = GetModuleHandle(StrPtr(LibraryPath)) If hLib <> 0 Then FreeLibrary hLib End If End Sub
'DLL関数の呼出しコード Option Explicit Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long) Public Declare Function cConcat Lib "cConcat.dll" Alias "cConcat@8" (ByVal x As Long, ByVal y As Long) As Long ' Sub TestConcat() Dim lngPtr As Long Dim str1 As String Dim str2 As String Dim str3 As String str1 = "abcあ" str2 = "かぎ" lngPtr = cConcat(StrPtr(str1), StrPtr(str2)) CopyMemory VarPtr(str3), VarPtr(lngPtr), 4 Debug.Print str3 End Sub Sub TestConcat2() Debug.Print Concat("ab あいうc", "def") Debug.Print Concat("ab あいうc", vbNullString) Debug.Print Concat(vbNullString, vbNullString) End Sub Function Concat(s1 As String, s2 As String) As String Concat = PtrToString(cConcat(StrPtr(s1), StrPtr(s2))) End Function Private Function PtrToString(pBSTR As Long) As String CopyMemory VarPtr(PtrToString), VarPtr(pBSTR), Len(pBSTR) End Function
ポイント
・DLL関数は Declare 文で引数を ByVal x As Long とし、戻り値を Long として宣言する。
・文字列は StrPtr 関数で得られたポインタを引数に渡す。
・戻り値(ポインタ)を CopyMemory(RtlMoveMemory)関数で文字列変数に上書きする。
VBの文字列変数については、次の記事も参考になる。
Lightning Strings
http://msdn.microsoft.com/en-us/library/office/aa140182(v=office.10).aspx