可変引数は、マーシャリングできるのか!?
ということで、今回は、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
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);
マーシャリングは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);