VB6, VBA の文字列 String を返す DLLを作成する

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

コンパイル

gcc -shared -o cConcat.dll cConcat.c -loleaut32

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