32bit DLLを64bitアプリからcallすることはできません。今、何かの32bit DLLを持っているとして、これの64bit版が必ず入手できるとは限らない。そして、世の中は64bit化していって64bit DLLしか存在しないって状況もあり得る。ということは、64bitアプリから64bitDLLだけでなく32bitDLLを読み出さないといけないっていう需要は必ずある。
ちなみにMicrosoftはこう言っている。「アウトプロセス COM サーバーでラップ」しろ、、、と。なんとも難しいことを言う。
日曜技術者としてはsocketでやることにしたってことで。
で、目指すところは、こうです。
64bit dllはいらんように見える(64bit appからsocketを使ってやりきれなくもない)けど、64bit appのコードは、32bit appで32bit dllと直接やり取りしていたのとほとんど同じコードにできるってところを目指しているからです。すなわち64bit dll + 32bit appで32bit dllをラッピングして、あたかも直接32bit dllを呼び出せているように見せるってこと。
でわ、
32bit DLLを64bitアプリから使う(3)で作業したフォルダから、
MyDLL32.dll
MyDLLServer32.exe
MyDLLClientApp64a.c
を持ってくる。
MyDLLClientApp64a.cはMyDLL64.cppにリネームする。
32bit DLLを64bitアプリから使う(1)で作業したフォルダから、
MyDLL32.def
testMyDLL32.c
Makefile
をもってきて、MyDLL32.defとtestMyDLL32.cをそれぞれ
MyDLL64.def
testMyDLL64.c
にリネームする。
まぁこうなるわ。
Makefile
CC="C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\cl.exe" LINK="C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\link.exe" test64 : MyDLL64.dll testMyDLL64.exe .\testMyDLL64.exe MyDLL64.dll : MyDLL64.obj $(LINK) /DLL /out:MyDLL64.dll /DEF:MyDLL64.def MyDLL64.obj MyDLL64.obj : MyDLL64.cpp $(CC) /c MyDLL64.cpp testMyDLL64.exe : testMyDLL64.C $(CC) testMyDLL64.c clean: del MyDLL64.dll testMyDLL64.exe MyDLL64.obj MyDLL64.lib MyDLL64.exp testMyDLL64.obj
コンパイラとリンカを64bit版に変更。
ファイル名たちの32を64に変更。
MyDLL64.def
LIBRARY MyDLL64 EXPORTS tasu hiku
32を64に変えただけ。
MyDLL64.cpp
#include <stdio.h> #include <stdint.h> //#include <conio.h> #include <WinSock2.h> #include <WS2tcpip.h> #pragma comment(lib, "ws2_32.lib") #define DIRECTION_TASU 0x01 #define DIRECTION_HIKU 0x02 #define DIRECTION_HALT 0xFF typedef union si4_consisted_of_ui1{ int32_t si4; uint8_t ui1[4]; }SI4_C_UI1; WSADATA wsaData; SOCKET sockTalk; struct sockaddr_in addr; STARTUPINFO si; PROCESS_INFORMATION pi; int32_t sendbytes(int8_t* sendbuf, int32_t nsend){ int32_t nsent; printf("(C)send:"); for(int i=0;i<nsend;i++){printf("%02X",sendbuf[i]);} printf("\n"); nsent=send(sockTalk, (const char*)sendbuf, nsend, 0); if (nsent < 0){ perror("(C)send"); } return nsent; } extern "C" int32_t __stdcall tasu(int32_t arg1,int32_t arg2){ int8_t sendbuf[256]; int8_t buf[256]; int32_t recvsize; SI4_C_UI1 st_arg1; SI4_C_UI1 st_arg2; SI4_C_UI1 st_wa; st_arg1.si4=arg1; st_arg2.si4=arg2; sendbuf[0]=DIRECTION_TASU; memcpy(&(sendbuf[1]),&(st_arg1.ui1[0]),4); memcpy(&(sendbuf[5]),&(st_arg2.ui1[0]),4); sendbuf[9]='\0'; sendbytes(sendbuf,9); recvsize = recv(sockTalk, (char*)buf, sizeof(buf), 0); if (recvsize > 0){ printf("(C)recv:"); for(int i=0;i<recvsize;i++){printf("%02X", buf[i]);} printf("\n"); } memcpy(&(st_wa.ui1[0]),&(buf[0]),4); return st_wa.si4; } extern "C" int32_t __stdcall hiku(int32_t arg1,int32_t arg2){ int8_t sendbuf[256]; int8_t buf[256]; int32_t recvsize; SI4_C_UI1 st_arg1; SI4_C_UI1 st_arg2; SI4_C_UI1 st_wa; st_arg1.si4=arg1; st_arg2.si4=arg2; sendbuf[0]=DIRECTION_HIKU; memcpy(&(sendbuf[1]),&(st_arg1.ui1[0]),4); memcpy(&(sendbuf[5]),&(st_arg2.ui1[0]),4); sendbuf[9]='\0'; sendbytes(sendbuf,9); recvsize = recv(sockTalk, (char*)buf, sizeof(buf), 0); if (recvsize > 0){ printf("(C)recv:"); for(int i=0;i<recvsize;i++){printf("%02X", buf[i]);} printf("\n"); } memcpy(&(st_wa.ui1[0]),&(buf[0]),4); return st_wa.si4; } void halt(){ int8_t sendbuf[256]; sendbuf[0]=DIRECTION_HALT; sendbuf[1]='\0'; sendbytes(sendbuf,2); } int32_t initialize(){ ZeroMemory(&si,sizeof(si)); si.cb=sizeof(si); ZeroMemory(&pi,sizeof(pi)); if(!CreateProcess(NULL,"MyDLLServer32.exe",NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)){ printf( "(C)CreateProcess failed (%d).\n", GetLastError() ); return -1; } WSAStartup(WINSOCK_VERSION, &wsaData); memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockTalk = socket(addr.sin_family, SOCK_STREAM, 0); if (sockTalk == INVALID_SOCKET){ printf("(C)socket failed\n"); return -1; } if (connect(sockTalk, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0){ printf("(C)connect failed\n"); return -1; } return 0; } void finalize(){ halt(); closesocket(sockTalk); WSACleanup(); // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); } BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved ){ switch( fdwReason ) { case DLL_PROCESS_ATTACH: printf("DLL_PROCESS_ATTACH\n"); initialize(); break; case DLL_THREAD_ATTACH: printf("DLL_THREAD_ATTACH\n"); break; case DLL_THREAD_DETACH: printf("DLL_THREAD_DETACH\n"); break; case DLL_PROCESS_DETACH: printf("DLL_PROCESS_DETACH_1\n"); if (lpvReserved != nullptr){ break; // do not do cleanup if process termination scenario } printf("DLL_PROCESS_DETACH_2\n"); finalize(); break; } return TRUE; } #if 0 int main(){ int32_t arg1,arg2,wa,sa; initialize(); arg1=10; arg2=3; wa=tasu(arg1,arg2); printf("(C)%d + %d = %d\n",arg1,arg2,wa); sa=hiku(arg1,arg2); printf("(C)%d - %d = %d\n",arg1,arg2,sa); getch();//process状態を確認するため一旦止める finalize(); return 0; } #endif
もともとmain()にあったCreateProcess関連とsocket初期化関連をまとめてInitialize()関数として、socket閉じ関連とCloseHandle関連をまとめてFinalize()関数とした。
main()のローカル変数で、(Process関連とsocket関連で)必要なものはグローバル変数にした。
変数のグローバル化に伴い各関数において引数化が不要なものは削除。それに伴い呼び出し系も調整。
32bit DLLを64bitアプリから使う(5)からDllMain()をもってきて、DLL_PROCESS_ATTACHでInitialize()を実行、DLL_PROCESS_DETACHでFinalize()を実行するようにした。
sendとrecvの引数のうちバッファのポインタについて、なぜか明確な型キャストを求められたので追加。
tasu()とhiku()の呼び出し規約を変更(「extern "C"」、「__stdcall」追加)。
testMyDLL64.c
#include <windows.h> #include <stdio.h> #include <conio.h> #define MyDLL64 "MyDLL64.dll" typedef int (__stdcall *tasu_type)(int arg1,int arg2); typedef int (__stdcall *hiku_type)(int arg1,int arg2); //typedef int (__stdcall *pi_type)(void); HMODULE dll; tasu_type ptasu; hiku_type phiku; //pi_type ppi; int main(int argc,char** argv){ int arg1=10; int arg2=3; int wa,sa; dll=LoadLibrary(MyDLL64); if(dll==NULL){ printf("Failed to load dll.\n"); return -1; } ptasu=(tasu_type)GetProcAddress(dll, "tasu"); phiku=(hiku_type)GetProcAddress(dll, "hiku"); //ppi=(pi_type)GetProcAddress(dll, "pi"); wa=ptasu(arg1,arg2); printf("%d + %d = %d\n",arg1,arg2,wa); sa=phiku(arg1,arg2); printf("%d - %d = %d\n",arg1,arg2,sa); //printf("pi = %f\n",ppi()); getch(); FreeLibrary(dll); return 0; }
32を64に変えた。getch()を入れた。getch()を入れるために#include <conio.h>を追加。すなわちほぼ変更なし。これが目指すところなので。
で、
フォルダ移動
cd /d C:\Users\hoge\Documents\mscpp-vscode\projects\dll32-study\dll32wrap
Makefile使って実行
nmake test64
32bitのサーバーアプリが起動されています。
(Microsoft Program Maintenance Utilityはnmakeのことらしい)
そしてキーボードを押すと、
THREAD_ATTACHとTHREAD_DETACHが出現するようになったのはなぜなのか、、、まぁ今日は気にしない。
32bitサーバーアプリも含めて、終了されていて、期待通りです。よしきた!
32bit DLLを64bitアプリから呼び出せるようにするって課題に対して、平易で具体的な例を示せたことに個人的には意味ある活動だったと自己満足しています。ただ、socketではなく、いつかCOMサーバーを使ったRPCでもやってみたい。
Python(64bit)でも使えるのか、ハードウェアを制御する(ハードウェアメーカーが配布している)DLLでもラッピングできるのかとか、自動生成とかまだやってみたいことがある。が、初老のおじさんは体力的に問題があるのでやるかどうかはわからない。
、、、参照渡しはどうすんだ?って疑問がわいている。
コメントをお書きください