可変引数は、マーシャリングできるのか!?

ということで、今回は、C/C++で作成されたDLLの可変引数関数は、C#からマーシャリングできるのかを議題にやってみようと思う。

まず、C/C++のDLLのコード。
これは単純に、コンソールに出力するだけの簡単な関数にしてみました。(つまり、printf関数です)
宣言のヘッダーは、至って普通の宣言です。

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifdef PRINT_EXPORTS
#define PRINT_API __declspec(dllexport)
#else // PRINT_EXPORTS
#define PRINT_API __declspec(dllimport)
#endif // PRINT_EXPORTS

#ifdef __cplusplus 
extern "C"
{
#endif // __cplusplus

void PRINT_API WINAPI PrintStringW(LPCWSTR lpszFormat, ...);
void PRINT_API WINAPI PrintStringA(LPCSTR lpszFormat, ...);

#ifdef __cplusplus 
}
#endif // __cplusplus

実装は、普通にprintf関数を呼び出しているだけです。

BOOL APIENTRY DllMain( HMODULE hModule,
                    DWORD  ul_reason_for_call,
                    LPVOID lpReserved
                     )
{
    UNREFERENCED_PARAMETER(hModule);
    UNREFERENCED_PARAMETER(ul_reason_for_call);
    UNREFERENCED_PARAMETER(lpReserved);
    return TRUE;
}

void PRINT_API WINAPI PrintStringW(LPCWSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);

    vwprintf(lpszFormat, args);

    va_end(args);
}
 
void PRINT_API WINAPI PrintStringA(LPCSTR lpszFormat, ...)
{
    va_list args;
    va_start(args, lpszFormat);

    vprintf(lpszFormat, args);

    va_end(args);
}

では、マーシャリングにトライです。

関数の定義をそのまま、マーシャリングの定義に置き換えると次のようになります。

[DllImport("print.dll")]
extern static void PrintString(string format, params object[] args);

で、次にのように呼び出すと・・・

PrintString("Print %d\n", 1);

1と表示されません。

マーシャリングはFrameworkが対応の型にあわせて、値を変換し、関数を呼び出してくれているためで、
利用側が使用する型を不明の場合(可変引数など)、どのように変換して良いのか不明のため、
うまく変換できずに、値を渡しているため、おかしな値になっています。
なので、呼び出しにintを渡すのであれば、定義を次のようにすると正しく、表示されるようになります。

[DllImport("print.dll")]
extern static void PrintString(string format, int num);

結論、利用する引数の数だけ正しく宣言すれば、動作可能。

おまけ。
呼び出しがprintf関数の場合、次のようにして、文字列にしてしまえば容易です。

[DllImport("print.dll")]
extern static void PrintString(string str);

static void PrintStringWrapper(string format, params object[] args)
{
    PrintString(string.Format(format, args));
}

PrintStringWrapper("Print {0}\n", 1);

フォーマット文字列はC#書式なので注意(笑)