C#からDLL関数をCallしたい。LoadLibraryを使って動的にCallするほうが好きなんだけど、とりあえず静的にCallしてみる。Callするのは、自作のaes128です。自作といってもそいつがやっているのはWindowsのBcryptを単にCallしているだけ。DLL内の関数は
int __stdcall AES128Encrypt(PBYTE plain,PBYTE key,PBYTE ciphered);
int __stdcall AES128Decrypt(PBYTE ciphered,PBYTE key,PBYTE plain);
です。DLLは32bitで作成しています。
では、
32bitの黒い画面を起動。
フォルダを作って、プロジェクトを作って、VSCodeを起動。
右下にわけわからんものが出るので、薦められるがままにYesを押す。今回、関係ないと思うけど。
コードはこうなっている。が、全面的に書き換える。(ひな型コードを見て、C#ってnamespaceとかclassとか絶対書かんといかんのかと思っていたけど無い場合もあるんやーってちょっとびっくり。)
// See https://aka.ms/new-console-template for more information //Console.WriteLine("Hello, World!"); using System.Runtime.InteropServices; namespace dllcall_study1 { class Program { [DllImport("aes128_32.dll", EntryPoint="AES128Encrypt")] public static extern int AES128Encrypt(IntPtr plain,IntPtr key,IntPtr ciphered); [DllImport("aes128_32.dll", EntryPoint="AES128Decrypt")] public static extern int AES128Decrypt(IntPtr ciphered,IntPtr key,IntPtr plain); static void Main(string[] args) { int ret; byte[] plain={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}; byte[] key={0xDA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA}; byte[] encrypted={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; string text = ""; string tmp = ""; int plain_size = Marshal.SizeOf(plain[0]) * plain.Length; IntPtr plain_intPtr = Marshal.AllocHGlobal(plain_size); int key_size = Marshal.SizeOf(key[0]) * key.Length; IntPtr key_intPtr = Marshal.AllocHGlobal(key_size); int encrypted_size = Marshal.SizeOf(encrypted[0]) * encrypted.Length; IntPtr encrypted_intPtr = Marshal.AllocHGlobal(encrypted_size); Marshal.Copy(plain, 0, plain_intPtr, plain_size); Marshal.Copy(key, 0, key_intPtr, key_size); Marshal.Copy(encrypted, 0, encrypted_intPtr, encrypted_size); ret=AES128Encrypt(plain_intPtr,key_intPtr,encrypted_intPtr); Marshal.Copy(encrypted_intPtr, encrypted, 0, encrypted.Length); foreach(byte b in plain){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; foreach(byte b in key){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; foreach(byte b in encrypted){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; for(int i=0;i<16;i++){plain[i]=0;} Marshal.Copy(plain, 0, plain_intPtr, plain_size); ret=AES128Decrypt(encrypted_intPtr,key_intPtr,plain_intPtr); Marshal.Copy(plain_intPtr, plain, 0, plain.Length); foreach(byte b in plain){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; foreach(byte b in key){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; foreach(byte b in encrypted){ text = string.Format("{0,2:X2}", b); tmp = tmp+text; } Console.WriteLine(tmp);tmp=""; } } }
はい、相変わらずのコメントなし。
PBYTEすなわちunsigned char*はC#ではIntPtrらしい。で、unsigned charはbyte。(型の対比はこちらにまとまったものがある。感謝!)で、byteからIntPtrへの変換でMarshalなんちゃらってのを使う。.NETは中間コードを解釈して実行するがその中間コードがマネージド、cl.exe(やらgccやら)で実行ファイル(機械語)にしたものがアンマネージドで、その橋渡しがMarshalなんちゃらなんだとあまりにもざっくりと理解しておこう。
プロジェクトフォルダにdllをおいて(本当は実行ファイルと同じフォルダにあるべきなんだけど、この場合はなぜかプロジェクトのフォルダでいける)、プロジェクトを選択して、右上の再生ボタンを押す。と、ちゃんとできているようですヾ(≧∇≦*)/
ちなみに、C言語で同じことやろうとすると、
#include <windows.h> #include <stdio.h> #define MyDLL32 "aes128_32.dll" typedef int (__stdcall *AES128Encrypt_type)(unsigned char* plain,unsigned char* key,unsigned char* ciphered); typedef int (__stdcall *AES128Decrypt_type)(unsigned char* ciphered,unsigned char* key,unsigned char* plain); AES128Encrypt_type AES128Encrypt; AES128Decrypt_type AES128Decrypt; HMODULE dll; int main(int argc,char** argv){ int ret; unsigned char plain[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}; unsigned char key[]={0xDA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA}; unsigned char encrypted[16]; dll=LoadLibrary(MyDLL32); if(dll==NULL){ printf("Failed to load dll.\n"); return -1; } AES128Encrypt=(AES128Encrypt_type)GetProcAddress(dll, "AES128Encrypt"); AES128Decrypt=(AES128Decrypt_type)GetProcAddress(dll, "AES128Decrypt"); ret=AES128Encrypt(plain,key,encrypted); if(ret==0){printf("Success!\n");}else{printf("Fail at %d\n",ret);} for(int i=0;i<16;i++){printf("%02X",plain[i]);}printf("\n"); for(int i=0;i<16;i++){printf("%02X",key[i]);}printf("\n"); for(int i=0;i<16;i++){printf("%02X",encrypted[i]);}printf("\n"); for(int i=0;i<16;i++){plain[i]=0;} ret=AES128Decrypt(encrypted,key,plain); if(ret==0){printf("Success!\n");}else{printf("Fail at %d\n",ret);} for(int i=0;i<16;i++){printf("%02X",plain[i]);}printf("\n"); for(int i=0;i<16;i++){printf("%02X",key[i]);}printf("\n"); for(int i=0;i<16;i++){printf("%02X",encrypted[i]);}printf("\n"); return 0; }
初老のおじさんはやっぱりC言語のほうがしっくりくる。修行するぞ修行するぞ修行するぞ。
コメントをお書きください