2025年2月17日月曜日

32bit DLLを64bitアプリから使う(決定版)

32bit DLLを64bitアプリからcallすることはできません(ちなみに逆もできません)。需要はあるけど面倒なこの課題に取り組む。

gRPCを使って、やってやった。たぶんもっとも簡単!
これみてちょ。

Go言語入門(12)

RPC serverを作る。彼は32bit Windows実行ファイルであり、32bit Windows DLLを使って何か計算する。RPC clientを作る。彼はRPC serverを起動してそこから結果を取得できる。ちょっと前まで64bit Windows実行ファイルだったけど64bit DLLになろうとしている。



もはやGo言語そっちのけ感があるけど、やりたかったことの終着点が来た。それは過去に取り組んだこちら(32bit DLLを64bitアプリから使う)をsocketに代えてRPCでやるって取り組みだったわけだよ、、、で具体的な構造はこんな感じ(以前と同じだけど32bitと64bitの間がsocketからgRPCになってる)
で、client以外は前回(Go言語入門(11))と全くおんなじ。
PATH="$PATH:$HOME/go/bin"
mkdir grpctry4
cd grpctry4
mkdir proto
mkdir server
mkdir client
go mod init grpc/grpctry4

proto/grpctry4.proto
  1. syntax = "proto3";
  2. package grpctry4;
  3. option go_package = "./proto";
  4. service MyAESEncrypter {
  5.   rpc AESEncrypt (Plain_and_Key) returns (Cipher) {}
  6. }
  7.  
  8. message Plain_and_Key {
  9.   bytes plain=1;
  10.   bytes key=2;
  11. }
  12.  
  13. message Cipher {
  14.   bytes result=1;
  15. }
一旦、
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/grpctry4.proto
go get google.golang.org/grpc
(後回しにするとよく忘れちゃうから)

server/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "net"
  9.     "syscall"
  10.     "unsafe"
  11.  
  12.     pb "grpc/grpctry4/proto"
  13.  
  14.     "google.golang.org/grpc"
  15. )
  16.  
  17. var (
  18.     hdll, _ = syscall.LoadLibrary("rijndael.dll")
  19.     aes128encrypt, _ = syscall.GetProcAddress(hdll, "AES128Encrypt")
  20.     aes128decrypt, _ = syscall.GetProcAddress(hdll, "AES128Decrypt")
  21. )
  22.  
  23. func AES128Encrypt(plain *uint8, key *uint8, crypted *uint8) (r uint32) {
  24.     var nargs uintptr = 3
  25.     ret, _, _ := syscall.Syscall9(uintptr(aes128encrypt),
  26.         nargs,
  27.         uintptr(unsafe.Pointer(plain)),
  28.         uintptr(unsafe.Pointer(key)),
  29.         uintptr(unsafe.Pointer(crypted)),
  30.         0, 0, 0, 0, 0, 0)
  31.     r = uint32(ret)
  32.     return
  33. }
  34. func AES128Decrypt(crypted *uint8, key *uint8, plain *uint8) (r uint32) {
  35.     var nargs uintptr = 3
  36.     ret, _, _ := syscall.Syscall9(uintptr(aes128decrypt),
  37.         nargs,
  38.         uintptr(unsafe.Pointer(crypted)),
  39.         uintptr(unsafe.Pointer(key)),
  40.         uintptr(unsafe.Pointer(plain)),
  41.         0, 0, 0, 0, 0, 0)
  42.     r = uint32(ret)
  43.     return
  44. }
  45.  
  46. var (
  47.     port = flag.Int("port", 50051, "The server port")
  48. )
  49.  
  50. type server struct {
  51.     pb.UnimplementedMyAESEncrypterServer
  52. }
  53.  
  54. func (s *server) AESEncrypt(_ context.Context, in *pb.PlainAnd_Key) (*pb.Cipher, error) {
  55.     var d1 []byte = in.GetPlain()
  56.     var d2 []byte = in.GetKey()
  57.     var d1arr [16]byte = *(*[16]byte)(d1[:16])
  58.     var d2arr [16]byte = *(*[16]byte)(d2[:16])
  59.     var d3arr [16]byte
  60.     //defer syscall.FreeLibrary(hdll)
  61.     AES128Encrypt(&d1arr[0], &d2arr[0], &d3arr[0])
  62.     return &pb.Cipher{Result: d3arr[:]}, nil
  63. }
  64. func main() {
  65.     flag.Parse()
  66.     lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
  67.     if err != nil {
  68.         log.Fatalf("failed to listen: %v", err)
  69.     }
  70.     s := grpc.NewServer()
  71.     pb.RegisterMyAESEncrypterServer(s, &server{})
  72.     log.Printf("server listening at %v", lis.Addr())
  73.     if err := s.Serve(lis); err != nil {
  74.         log.Fatalf("failed to serve: %v", err)
  75.     }
  76.     defer syscall.FreeLibrary(hdll)
  77. }

cd server
GOOS=windows GOARCH=386 go build -o ../grpctry4_server.exe main.go
cd ..

で、clientだが、前回、関数化するってことまでやってたので、コメントでexportするってのとmainの中身をコメント化するってことだけ。で、
  1. package main
  2.  
  3. /*
  4. #include <stdint.h>
  5. */
  6. import "C"
  7.  
  8. import (
  9.     "context"
  10.     "flag"
  11.     "fmt"
  12.     "log"
  13.     "os/exec"
  14.     "time"
  15.     "unsafe"
  16.  
  17.     pb "grpc/grpctry4/proto"
  18.  
  19.     "google.golang.org/grpc"
  20.     "google.golang.org/grpc/credentials/insecure"
  21. )
  22.  
  23. var (
  24.     addr = flag.String("addr", "localhost:50051", "the address to connect to")
  25. )
  26. var cmd *exec.Cmd
  27.  
  28. func init() {
  29.     srv_start_cmd := "./grpctry4_server.exe"
  30.     src_start_arg := ""
  31.     cmd = exec.Command(srv_start_cmd, src_start_arg)
  32.     cmd.Start()
  33.     fmt.Println(cmd.Process.Pid)
  34. }
  35.  
  36. type arr16 struct {
  37.     v [16]C.uint8_t
  38. }
  39.  
  40. //export AES128Encrypt
  41. func AES128Encrypt(cp *C.uint8_t, ck *C.uint8_t, cc *C.uint8_t) {
  42.     acp := (*arr16)(unsafe.Pointer(cp))
  43.     ack := (*arr16)(unsafe.Pointer(ck))
  44.     acc := (*arr16)(unsafe.Pointer(cc))
  45.     ppp := []byte{}
  46.     kkk := []byte{}
  47.     ccc := []byte{}
  48.     for i := 0; i < 16; i++ {
  49.         ppp = append(ppp, (uint8)((*acp).v[i]))
  50.         kkk = append(kkk, (uint8)((*ack).v[i]))
  51.     }
  52.     defer cmd.Process.Kill()
  53.     // Set up a connection to the server.
  54.     dialOpts := []grpc.DialOption{
  55.         grpc.WithTransportCredentials(insecure.NewCredentials()),
  56.         grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
  57.     }
  58.     conn, err := grpc.NewClient(*addr, dialOpts...)
  59.     if err != nil {
  60.         log.Fatalf("did not connect: %v", err)
  61.     }
  62.     defer conn.Close()
  63.     c := pb.NewMyAESEncrypterClient(conn)
  64.     // Contact the server and print out its response.
  65.     ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  66.     defer cancel()
  67.     r, err := c.AESEncrypt(ctx, &pb.PlainAnd_Key{Plain: ppp, Key: kkk})
  68.     if err != nil {
  69.         log.Fatalf("could not greet: %v", err)
  70.     }
  71.     ccc = r.GetResult()
  72.     for i := 0; i < 16; i++ {
  73.         (*acc).v[i] = (C.uint8_t)(ccc[i])
  74.     }
  75. }
  76. func main() {
  77.     /*
  78.      p := [...]C.uint8_t{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
  79.      k := [...]C.uint8_t{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
  80.      c := [...]C.uint8_t{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
  81.      fmt.Printf("% 02X\n", p)
  82.      fmt.Printf("% 02X\n", k)
  83.      AES128Encrypt_grpcexec(&p[0], &k[0], &c[0])
  84.      fmt.Printf("% 02X\n", c)
  85.     */
  86. }

で、-buildmode=c-sharedを付けてbuildすればいいんだけど、オプションがめっちゃ長いので、Makefileを作っちゃう。何度もやり直す羽目になった時用。

client/Makefile
  1. OPT_CC= CC=x86_64-w64-mingw32-gcc
  2. #OPT_CC= CC=i686-w64-mingw32-gcc
  3. OPT_CGO= CGO_ENABLED=1
  4. OPT_OS= GOOS=windows
  5. OPT_ARCH= GOARCH=amd64
  6. #OPT_ARCH= GOARCH=386
  7. GOFLAGS=build -buildmode=c-shared
  8. #GOFLAGS=build -ldflags "-s -w" -buildmode=c-shared
  9. GO=go
  10. #SRCS=dll64wdllmain.go dllmain.go
  11. SRCS=main.go
  12. TARGET=../grpctry4_client.dll
  13.  
  14. $(TARGET) : $(SRCS)
  15.     $(OPT_CC) $(OPT_CGO) $(OPT_OS) $(OPT_ARCH) $(GO) $(GOFLAGS) -o $(TARGET) $(SRCS)
  16.  
  17. clean :
  18.     rm $(TARGET)
で、
cd client
make
うまくいくはず。
cd ..
でclientのDLLを呼び出す実行ファイルを作成する。これはC言語でいかせてください。こんなかんじ。
grpctry4_test_64.c
  1. #include <stdio.h>
  2. #include <stdint.h>
  3. #include <windows.h>
  4. typedef int (__stdcall *AES128Encrypt_type)(uint8_t* a,uint8_t* b,uint8_t* c);
  5. HMODULE dll;
  6. AES128Encrypt_type AES128Encrypt;
  7.   
  8. int main(int argc,char** argv){
  9.     int r,i;
  10.     uint8_t aa[16]={0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
  11.     uint8_t bb[16]={0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
  12.     uint8_t cc[16]={0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  13.     dll=LoadLibrary("grpctry4_client.dll");
  14.     AES128Encrypt=(AES128Encrypt_type)GetProcAddress(dll,"AES128Encrypt");
  15.     for(i=0;i<16;i++){
  16.         printf("%02x",aa[i]);
  17.     }
  18.     printf("\n");
  19.     for(i=0;i<16;i++){
  20.         printf("%02x",bb[i]);
  21.     }
  22.     printf("\n");
  23.     r=AES128Encrypt(aa,bb,cc);
  24.     for(i=0;i<16;i++){
  25.         printf("%02x",cc[i]);
  26.     }
  27.     printf("\n");
  28.     return 0;
  29. }
で、コンパイルする。
x86_64-w64-mingw32-gcc grpctry4_test_64.c
で、こうなる。
  • .
  • ├── a.exe
  • ├── client
  • │ ├── Makefile
  • │ ├── dllmain.h
  • │ └── main.go
  • ├── go.mod
  • ├── go.sum
  • ├── grpctry4_client.dll
  • ├── grpctry4_client.h
  • ├── grpctry4_server.exe
  • ├── grpctry4_test_64.c
  • ├── proto
  • │ ├── grpctry4.pb.go
  • │ ├── grpctry4.proto
  • │ └── grpctry4_grpc.pb.go
  • ├── rijndael.dll
  • └── server
  •     └── main.go

で、実行。
./a.exe
すごいぞ。64bitアプリから32bitDLLを呼び出せているように見える。Go言語使うってことを除けばsocketよりも全然簡単。C++でもC#でもできるんだろうけどGo言語の方がたぶん簡単。

2025年2月16日日曜日

Go言語入門(11)

Serverは32bit Windows実行ファイルで32bit Windows DLLを使って何か計算する。Clientは64bit Windows実行ファイルで自分が起動されたときにServerを自動的に起動して解放されるときにServerを閉じるってしたい。



Go言語入門(10)に間違いがあったのでその訂正も含めて全コードを載せちゃう。うざくてゴメン。
で、今回はclientを変更していくんだけど、以下のことをやる。
init関数(実行時に最初に勝手に実行される関数)を作って、そこでserverを起動するようにする。<--まぁコード見りゃわかるでしょ。
server起動を待ってRPC通信するようにする。
↑これは資料が少なすぎ、、、DialOption配列にgrpc.WithDefaultCallOptions(grpc.WaitForReady(true))を追加して、context.WithTimeoutのタイムアウトを少し長くするってした。
clientの実際の動作をmainから取り出して別関数にする。<--まぁコード見りゃわかるでしょ。
上記別関数の引数をC言語の型にする。<--これまでの取り組みを応用。とはいえ、もっといい方法があるかも、、、
、、、今回変化点多すぎ。

PATH="$PATH:$HOME/go/bin"
mkdir grpctry3
cd grpctry3
mkdir proto
mkdir server
mkdir client
go mod init grpc/grpctry3

proto/grpctry3.proto
  1. syntax = "proto3";
  2. package grpctry3;
  3. option go_package = "./proto";
  4. service MyAESEncrypter {
  5.   rpc AESEncrypt (Plain_and_Key) returns (Cipher) {}
  6. }
  7.  
  8. message Plain_and_Key {
  9.   bytes plain=1;
  10.   bytes key=2;
  11. }
  12.  
  13. message Cipher {
  14.   bytes result=1;
  15. }

server/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "net"
  9.     "syscall"
  10.     "unsafe"
  11.  
  12.     pb "grpc/grpctry3/proto"
  13.  
  14.     "google.golang.org/grpc"
  15. )
  16.  
  17. var (
  18.     hdll, _ = syscall.LoadLibrary("rijndael.dll")
  19.     aes128encrypt, _ = syscall.GetProcAddress(hdll, "AES128Encrypt")
  20.     aes128decrypt, _ = syscall.GetProcAddress(hdll, "AES128Decrypt")
  21. )
  22.  
  23. func AES128Encrypt(plain *uint8, key *uint8, crypted *uint8) (r uint32) {
  24.     var nargs uintptr = 3
  25.     ret, _, _ := syscall.Syscall9(uintptr(aes128encrypt),
  26.         nargs,
  27.         uintptr(unsafe.Pointer(plain)),
  28.         uintptr(unsafe.Pointer(key)),
  29.         uintptr(unsafe.Pointer(crypted)),
  30.         0, 0, 0, 0, 0, 0)
  31.     r = uint32(ret)
  32.     return
  33. }
  34. func AES128Decrypt(crypted *uint8, key *uint8, plain *uint8) (r uint32) {
  35.     var nargs uintptr = 3
  36.     ret, _, _ := syscall.Syscall9(uintptr(aes128decrypt),
  37.         nargs,
  38.         uintptr(unsafe.Pointer(crypted)),
  39.         uintptr(unsafe.Pointer(key)),
  40.         uintptr(unsafe.Pointer(plain)),
  41.         0, 0, 0, 0, 0, 0)
  42.     r = uint32(ret)
  43.     return
  44. }
  45.  
  46. var (
  47.     port = flag.Int("port", 50051, "The server port")
  48. )
  49.  
  50. type server struct {
  51.     pb.UnimplementedMyAESEncrypterServer
  52. }
  53.  
  54. func (s *server) AESEncrypt(_ context.Context, in *pb.PlainAnd_Key) (*pb.Cipher, error) {
  55.     var d1 []byte = in.GetPlain()
  56.     var d2 []byte = in.GetKey()
  57.     var d1arr [16]byte = *(*[16]byte)(d1[:16])
  58.     var d2arr [16]byte = *(*[16]byte)(d2[:16])
  59.     var d3arr [16]byte
  60.     //defer syscall.FreeLibrary(hdll)
  61.     AES128Encrypt(&d1arr[0], &d2arr[0], &d3arr[0])
  62.     return &pb.Cipher{Result: d3arr[:]}, nil
  63. }
  64. func main() {
  65.     flag.Parse()
  66.     lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
  67.     if err != nil {
  68.         log.Fatalf("failed to listen: %v", err)
  69.     }
  70.     s := grpc.NewServer()
  71.     pb.RegisterMyAESEncrypterServer(s, &server{})
  72.     log.Printf("server listening at %v", lis.Addr())
  73.     if err := s.Serve(lis); err != nil {
  74.         log.Fatalf("failed to serve: %v", err)
  75.     }
  76.     defer syscall.FreeLibrary(hdll)
  77. }

clinet/main.go
  1. package main
  2.  
  3. /*
  4. #include <stdint.h>
  5. */
  6. import "C"
  7.  
  8. import (
  9.     "context"
  10.     "flag"
  11.     "fmt"
  12.     "log"
  13.     "os/exec"
  14.     "time"
  15.     "unsafe"
  16.  
  17.     pb "grpc/grpctry3/proto"
  18.  
  19.     "google.golang.org/grpc"
  20.     "google.golang.org/grpc/credentials/insecure"
  21. )
  22.  
  23. var (
  24.     addr = flag.String("addr", "localhost:50051", "the address to connect to")
  25. )
  26. var cmd *exec.Cmd
  27.  
  28. func init() {
  29.     srv_start_cmd := "./grpctry3_server.exe"
  30.     src_start_arg := ""
  31.     cmd = exec.Command(srv_start_cmd, src_start_arg)
  32.     cmd.Start()
  33.     fmt.Println(cmd.Process.Pid)
  34. }
  35.  
  36. type arr16 struct {
  37.     v [16]C.uint8_t
  38. }
  39.  
  40. func AES128Encrypt_grpcexec(cp *C.uint8_t, ck *C.uint8_t, cc *C.uint8_t) {
  41.     acp := (*arr16)(unsafe.Pointer(cp))
  42.     ack := (*arr16)(unsafe.Pointer(ck))
  43.     acc := (*arr16)(unsafe.Pointer(cc))
  44.     ppp := []byte{}
  45.     kkk := []byte{}
  46.     ccc := []byte{}
  47.     for i := 0; i < 16; i++ {
  48.         ppp = append(ppp, (uint8)((*acp).v[i]))
  49.         kkk = append(kkk, (uint8)((*ack).v[i]))
  50.     }
  51.     defer cmd.Process.Kill()
  52.  
  53.     // Set up a connection to the server.
  54.     dialOpts := []grpc.DialOption{
  55.         grpc.WithTransportCredentials(insecure.NewCredentials()),
  56.         grpc.WithDefaultCallOptions(grpc.WaitForReady(true)),
  57.     }
  58.  
  59.     conn, err := grpc.NewClient(*addr, dialOpts...)
  60.     if err != nil {
  61.         log.Fatalf("did not connect: %v", err)
  62.     }
  63.     defer conn.Close()
  64.     c := pb.NewMyAESEncrypterClient(conn)
  65.  
  66.     // Contact the server and print out its response.
  67.     ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  68.     defer cancel()
  69.     r, err := c.AESEncrypt(ctx, &pb.PlainAnd_Key{Plain: ppp, Key: kkk})
  70.     if err != nil {
  71.         log.Fatalf("could not greet: %v", err)
  72.     }
  73.     ccc = r.GetResult()
  74.     for i := 0; i < 16; i++ {
  75.         (*acc).v[i] = (C.uint8_t)(ccc[i])
  76.     }
  77. }
  78. func main() {
  79.     p := [...]C.uint8_t{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
  80.     k := [...]C.uint8_t{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
  81.     c := [...]C.uint8_t{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
  82.     fmt.Printf("% 02X\n", p)
  83.     fmt.Printf("% 02X\n", k)
  84.     AES128Encrypt_grpcexec(&p[0], &k[0], &c[0])
  85.     fmt.Printf("% 02X\n", c)
  86. }

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/grpctry3.proto
go get google.golang.org/grpc
cd server
GOOS=windows GOARCH=386 go build -o ../grpctry3_server.exe main.go
cd ..
cd client
CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o ../grpctry3_client.exe main.go
cd ..
で、こうなってる。
  • .
  • ├── client
  • │ └── main.go
  • ├── go.mod
  • ├── go.sum
  • ├── grpctry3_client.exe
  • ├── grpctry3_server.exe
  • ├── proto
  • │ ├── grpctry3.pb.go
  • │ ├── grpctry3.proto
  • │ └── grpctry3_grpc.pb.go
  • ├── rijndael.dll
  • └── server
  •   └── main.go
で、実行ファイルたちはこんな感じ。

で、実行させるとこんな感じ。

うまくいっている。serverがRPCを受け入れられる状態になっていない状態でRPCするとclientでエラーになるってんで、どうやって回避するのかってのを調べるのがしんどかった。この辺の情報が少ないんよねー、、、てか、やりたいことが特殊なのかな、、、

2025年2月12日水曜日

AVR128DBxxのPROGMEMについて

AVR128DxxxのArduinoでPROGMEMを安易に使うためのメモ。AVR128DxxxってめっちゃFlash領域あるのでデータ領域として有効活用したい。

普通Arduinoでは、PROGMEMしてpgm_read_byteとかで読み込んで使ってたけど、AVR128Dxxxではどうもそうはいかないらしい。リンカスクリプトをいじるとかいろんな検討をした投稿があるけど、難しい。安易にやりたいわけよ。で、AVR128DxxxでArduinoするってことはボードマネージャーでSpence Konde氏のDxCore(https://github.com/SpenceKonde/DxCore)を入れているわけで、そのライブラリ(https://github.com/SpenceKonde/DxCore/blob/master/megaavr/libraries/Flash/README.md)を使う。今回は読み出しだけってことで使う関数は
uint8_t Flash.readByte(uint32_t address);
が、ポインタが16bitなんだろうことが原因で、正しいuint32_tのアドレスを知るすべがない。いや、わかってはいるんだけど、、、で、こういう設定
であることを前提にすると、
PROGMEM_SECTION1は0x00008000から
PROGMEM_SECTION2は0x00010000から
PROGMEM_SECTION3は0x00018000から
なので、これを信じてこんなコードを書く。
  1. #include <Flash.h>
  2.  
  3. const uint8_t test1[] PROGMEM_SECTION1={
  4.     0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
  5. };
  6.  
  7. const uint8_t test2[] PROGMEM_SECTION2={
  8.     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
  9. };
  10.  
  11. const uint8_t test3[] PROGMEM_SECTION3={
  12.     0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30, 0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A
  13. };
  14.  
  15. uint8_t v[16];
  16.  
  17. void setup(){
  18.     uint8_t i;
  19.     uint32_t adr1,adr2,adr3;
  20.     adr1=0x00008000;
  21.     adr2=0x00010000;
  22.     adr3=0x00018000;
  23.     char buf[80];
  24.     Serial2.begin(115200);
  25.     while (!Serial2 && millis() < 5000);
  26.     Serial2.println((uint32_t)(&test1[0]),HEX);
  27.     Serial2.println((uint32_t)(&test2[0]),HEX);
  28.     Serial2.println((uint32_t)(&test3[0]),HEX);
  29.     for(i=0;i<16;i++){
  30.         v[i]=Flash.readByte(adr1+i);
  31.     }
  32.     sprintf(buf,"%08lX : ",adr1);Serial2.print(buf);
  33.     for(i=0;i<16;i++){
  34.         sprintf(buf,"%02X ",v[i]);Serial2.print(buf);
  35.     }
  36.     Serial2.println("");
  37.  
  38.     for(i=0;i<16;i++){
  39.         v[i]=Flash.readByte(adr2+i);
  40.     }
  41.     sprintf(buf,"%08lX : ",adr2);Serial2.print(buf);
  42.     for(i=0;i<16;i++){
  43.         sprintf(buf,"%02X ",v[i]);Serial2.print(buf);
  44.     }
  45.     Serial2.println("");
  46.  
  47.     for(i=0;i<16;i++){
  48.         v[i]=Flash.readByte(adr3+i);
  49.     }
  50.     sprintf(buf,"%08lX : ",adr3);Serial2.print(buf);
  51.     for(i=0;i<16;i++){
  52.         sprintf(buf,"%02X ",v[i]);Serial2.print(buf);
  53.     }
  54.     Serial2.println("");
  55.  
  56. }
  57. void loop(){
  58.      
  59. }
で、.vscode/arduino.jsonにoutputパスを追加しておく。
  1. {
  2.     "configuration": "chip=avr128db28,clock=16crystal,bodvoltage=1v9,bodmode=disabled,resetpin=reset,entrycond=default,millis=tcb2,printf=default,attach=allenabled,mvioopti=enabled,startuptime=64,wiremode=mors,bootloaderusart=ser2,WDTtimeout=disabled,WDTwindow=disabled,flmap=lockdefault",
  3.     "output": "./build",
  4.     "board": "DxCore:megaavr:avrdbopti",
  5.     "sketch": "upperpgmtry1.ino",
  6.     "port": "/dev/ttyUSB0"
  7. }
まぁこんな感じ。
一旦コンパイルする。
で、buildフォルダになんじゃかんじゃできるので、.mapファイルを開いて、test1, test2, test3を探すと、
って感じで、確かに思った通りになっている。
で、実行してみると、
こんな感じで期待通りに値を取得できている。いくつかの配列を確保してアドレスがわからない場合や、mapファイルが期待と違っていた場合は、それに合わせてコードのadr?を変えて、もう一度確認して、ってことを一致するまでやればいいんじゃないかな、、、決して正しいやり方とは思わないけど、安易にやることを目標にしています。ちなみに、Serialに出力したポインタの値はやっぱりおかしい。たぶんSECTION1は(uint32_t)(test1)x0000FFFF, SECTION2は(uint32_t)(test2)x0000FFFF+0x00010000, SECTION3は(uint32_t)(test3)x0000FFFF+0x00010000なんだろうけど、実行中のコードがこれを判別することは難しい。プログラマわかっていることなので記述しとけばいいんだけど、だったらmapファイル見てやっちまうのも巨悪ではないよね。

2025年2月8日土曜日

Go言語入門(10)

Serverを32bit Windows実行ファイルにして、Clientを64bit Windows実行ファイルにして、アーキテクチャによらずに通信できることを確認する。さらに、Serverは32bit Windows DLLから戻り値を取得するようにしてみる。



ようやくだんだんと本題に近づいてきた気がする。では、Go言語入門(9)でやったのと同じようにすすめていく。
mkdir grpctry2
cd grpctry2
mkdir proto
mkdir server
mkdir client
go mod init grpc/grpctry2
そんでもって、protoを書く。こんな感じ。ま~たそれかい的なやつ。

proto/grpctry2.proto
  1. syntax = "proto3";
  2. package grpctry2;
  3. option go_package = "./proto";
  4. service MyAESEncrypter {
  5.   rpc AESEncrypt (Plain_and_Key) returns (Cipher) {}
  6. }
  7.  
  8. message Plain_and_Key {
  9.   bytes plain=1;
  10.   bytes key=2;
  11. }
  12.  
  13. message Cipher {
  14.   bytes result=1;
  15. }
そういえば、gRPCの環境構築の際にprotoc-gen-goとprotoc-gen-go-grpcのPATHをその場限りな表現で追加していた。なので、毎回PATHを確認して通ってなかったらPATHを通すってやらんといかん。~/.profileとか~/.bashrcとかに書いておけばいいんだけど、まぁその場限りの関係ってのでもいいんじゃないかと思う。
echo $PATH | grep go
ってすれば、PATHが通っていればPATH全体が表示されるし、通ってなければ何も表示されない。
PATHが通ってなければ、
PATH="$PATH:$HOME/go/bin"
ってする。
で、
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/grpctry2.proto
例によって、無言で2つの.pb.goファイルができる。
で、まずはClientを作る。これはGo言語入門(9)のClientのコードをそのまんま持ってきて、.pb.goを見ながら修正していくってかんじ。

client/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "time"
  9.  
  10.     pb "grpc/grpctry2/proto"
  11.  
  12.     "google.golang.org/grpc"
  13.     "google.golang.org/grpc/credentials/insecure"
  14. )
  15.  
  16. var (
  17.     addr = flag.String("addr", "localhost:50051", "the address to connect to")
  18. )
  19.  
  20. func main() {
  21.     p := []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
  22.     k := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
  23.     flag.Parse()
  24.     // Set up a connection to the server.
  25.     conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  26.     if err != nil {
  27.         log.Fatalf("did not connect: %v", err)
  28.     }
  29.     defer conn.Close()
  30.     c := pb.NewMyAESEncrypterClient(conn)
  31.  
  32.     // Contact the server and print out its response.
  33.     ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  34.     defer cancel()
  35.     r, err := c.AESEncrypt(ctx, &pb.PlainAnd_Key{Plain: p, Key: k})
  36.     if err != nil {
  37.         log.Fatalf("could not greet: %v", err)
  38.     }
  39.     var res []byte = r.GetResult()
  40.     fmt.Printf("% 02X\n", res)
  41. }
で、実行ファイルを作る。
go get google.golang.org/grpc
cd client
GOOS=windows GOARCH=amd64 go build -o ../grpctry2_client.exe main.go
cd ..
(実行ファイルがプロジェクトルートに出力されるようにした。)
で、とりあえずServer側もGo言語入門(9)のServerのコードをそのまんま持ってきて.pb.goを見ながら修正していく。

server/main.go
  1. package main
  2. import (
  3.     "context"
  4.     "flag"
  5.     "fmt"
  6.     "log"
  7.     "net"
  8.  
  9.     pb "grpc/grpctry2/proto"
  10.  
  11.     "google.golang.org/grpc"
  12. )
  13.  
  14. var (
  15.     port = flag.Int("port", 50051, "The server port")
  16. )
  17.  
  18. // server is used to implement helloworld.GreeterServer.
  19. type server struct {
  20.     pb.UnimplementedMyAESEncrypterServer
  21. }
  22.  
  23. // SayHello implements helloworld.GreeterServer
  24. func (s *server) AESEncrypt(_ context.Context, in *pb.PlainAnd_Key) (*pb.Cipher, error) {
  25.     var d1 []byte = in.GetPlain()
  26.     var d2 []byte = in.GetKey()
  27.     d3 := []byte{}
  28.     for i := 0; i < 16; i++ {
  29.         d3 = append(d3, d1[i]^d2[i])
  30.     }
  31.  
  32.     return &pb.Cipher{Result: d3}, nil
  33. }
  34. func main() {
  35.     flag.Parse()
  36.     lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
  37.     if err != nil {
  38.         log.Fatalf("failed to listen: %v", err)
  39.     }
  40.     s := grpc.NewServer()
  41.     pb.RegisterMyAESEncrypterServer(s, &server{})
  42.     log.Printf("server listening at %v", lis.Addr())
  43.     if err := s.Serve(lis); err != nil {
  44.         log.Fatalf("failed to serve: %v", err)
  45.     }
  46. }
こんなかんじ。で、32bit Windows実行ファイルにする。
cd server
GOOS=windows GOARCH=386 go build -o ../grpctry2_server.exe main.go
cd ..
で、実行してみる。
こんな感じで、gPRCを通すことで64bit Windowsアプリから32bit Windowsアプリの機能を実行させることができる。
では、Go言語入門(5)を参考にrijndael.dllを使ってAES暗号化するってのを実装してみる。

server/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "net"
  9.     "syscall"
  10.     "unsafe"
  11.  
  12.     pb "grpc/grpctry2/proto"
  13.  
  14.     "google.golang.org/grpc"
  15. )
  16.  
  17. var (
  18.     hdll, _ = syscall.LoadLibrary("rijndael.dll")
  19.     aes128encrypt, _ = syscall.GetProcAddress(hdll, "AES128Encrypt")
  20.     aes128decrypt, _ = syscall.GetProcAddress(hdll, "AES128Decrypt")
  21. )
  22.  
  23. func AES128Encrypt(plain *uint8, key *uint8, crypted *uint8) (r uint32) {
  24.     var nargs uintptr = 3
  25.     ret, _, _ := syscall.Syscall9(uintptr(aes128encrypt),
  26.         nargs,
  27.         uintptr(unsafe.Pointer(plain)),
  28.         uintptr(unsafe.Pointer(key)),
  29.         uintptr(unsafe.Pointer(crypted)),
  30.         0, 0, 0, 0, 0, 0)
  31.     r = uint32(ret)
  32.     return
  33. }
  34. func AES128Decrypt(crypted *uint8, key *uint8, plain *uint8) (r uint32) {
  35.     var nargs uintptr = 3
  36.     ret, _, _ := syscall.Syscall9(uintptr(aes128decrypt),
  37.         nargs,
  38.         uintptr(unsafe.Pointer(crypted)),
  39.         uintptr(unsafe.Pointer(key)),
  40.         uintptr(unsafe.Pointer(plain)),
  41.         0, 0, 0, 0, 0, 0)
  42.     r = uint32(ret)
  43.     return
  44. }
  45.  
  46. var (
  47.     port = flag.Int("port", 50051, "The server port")
  48. )
  49.  
  50. type server struct {
  51.     pb.UnimplementedMyAESEncrypterServer
  52. }
  53.  
  54. func (s *server) AESEncrypt(_ context.Context, in *pb.PlainAnd_Key) (*pb.Cipher, error) {
  55.     var d1 []byte = in.GetPlain()
  56.     var d2 []byte = in.GetKey()
  57.     var d1arr [16]byte = *(*[16]byte)(d1[:16])
  58.     var d2arr [16]byte = *(*[16]byte)(d2[:16])
  59.     var d3arr [16]byte
  60.     defer syscall.FreeLibrary(hdll)
  61.     AES128Encrypt(&d1arr[0], &d2arr[0], &d3arr[0])
  62.     return &pb.Cipher{Result: d3arr[:]}, nil
  63. }
  64. func main() {
  65.     flag.Parse()
  66.     lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
  67.     if err != nil {
  68.         log.Fatalf("failed to listen: %v", err)
  69.     }
  70.     s := grpc.NewServer()
  71.     pb.RegisterMyAESEncrypterServer(s, &server{})
  72.     log.Printf("server listening at %v", lis.Addr())
  73.     if err := s.Serve(lis); err != nil {
  74.         log.Fatalf("failed to serve: %v", err)
  75.     }
  76. }
でけた。sliceを配列に変換してdllをcallして、戻りの配列をsliceに変換するってワン(ツー?)クッションが必要になる。ちなみに、実行ファイルとDLLを同じフォルダに入れることを前提にしている(LoadLibraryのとこ)。で、serverをコンパイルしてっと。んで、実行してみる。
こんな感じで、64bit Windows実行ファイルから32bit DLLの実行結果を得ることができる。イイゾ!

フリーアドレスって本当は生産性低下を招いている。って思っている人はどのくらいいるんだろう。固定席なら自然にわかってたことが、あえてヒアリングしないとわからなくなってる。今さら座席を増やす土地やカネがないって目先のことにとらわれてるのならキズが深くなる前に見直してほしい。

2025年2月6日木曜日

Go言語入門(9)

興味本位でGo言語に触れてみようと思う。WSLでGoでgRPCをやってみる。バイト配列(ていうかスライスって位置づけ?)を入力して、バイト配列を受け取るっていうRPC。64bit Windows実行ファイルを作って動作を確認することろまで。



exampleを少し抜け出して、自分なりのものを作ってみる。とはいってもサーバーやらクライアントやらの基本的な記述はGreeterそのまんま。実際に処理をする関数だけを自分発のものにする。で、exampleを単純にコピペして進めたらはまることがあって、これって先にやっておくべきものなので、先にやっておく。こんなのgRPCのQuick startにもBasics turotialにも書いてない。Go言語の仕様からくるものなので説明がないのかもしれないけど、手っ取り早く動くものを作りたいって人にとってはこういうのが挫折の原因になると思うので、改善してほしい。まじで。で、
mkdir grpctry1
cd grpctry1
mkdir proto
mkdir server
mkdir client
go mod init grpc/grpctry1
最後のやつでgo.modなるものができている。これやらんとはまる。よくよく思い出すとそんなんやったなー最初のころ。
では、各フォルダに必要なものを作っていく。

proto/grpctry1.proto
  1. syntax = "proto3";
  2. package grpctry1;
  3. option go_package = "./proto";
  4. service MyConnector {
  5.   rpc Connect (ConnectRequest) returns (ConnectReply) {}
  6. }
  7.  
  8. message ConnectRequest {
  9.   bytes data_in1=1;
  10.   bytes data_in2=2;
  11. }
  12.  
  13. message ConnectReply {
  14.   bytes data_out=1;
  15. }
protoでbytesを含むメッセージを定義するんだけど、これって配列じゃなくて、sliceってのになるらしい。grpctry1.pb.goを見るとわかる。Go言語で配列とsliceは似ているけど違うもので、一発で代入できたりはしなくて、さもなくば、[16]byteを[]byteに代入できるわけないやろこのゴミカスがって怒られる。

server/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "net"
  9.  
  10.     pb "grpc/grpctry1/proto"
  11.  
  12.     "google.golang.org/grpc"
  13. )
  14.  
  15. var (
  16.     port = flag.Int("port", 50051, "The server port")
  17. )
  18.  
  19. // server is used to implement helloworld.GreeterServer.
  20. type server struct {
  21.     pb.UnimplementedMyConnectorServer
  22. }
  23.  
  24. // SayHello implements helloworld.GreeterServer
  25. func (s *server) Connect(_ context.Context, in *pb.ConnectRequest) (*pb.ConnectReply, error) {
  26.     var d1 []byte = in.GetDataIn1()
  27.     var d2 []byte = in.GetDataIn2()
  28.     d3 := []byte{}
  29.     for i := 0; i < 16; i++ {
  30.         d3 = append(d3, d1[i])
  31.     }
  32.     log.Println("")
  33.     for i := 0; i < 16; i++ {
  34.         d3 = append(d3, d2[i])
  35.     }
  36.  
  37.     return &pb.ConnectReply{DataOut: d3}, nil
  38. }
  39. func main() {
  40.     flag.Parse()
  41.     lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
  42.     if err != nil {
  43.         log.Fatalf("failed to listen: %v", err)
  44.     }
  45.     s := grpc.NewServer()
  46.     pb.RegisterMyConnectorServer(s, &server{})
  47.     log.Printf("server listening at %v", lis.Addr())
  48.     if err := s.Serve(lis); err != nil {
  49.         log.Fatalf("failed to serve: %v", err)
  50.     }
  51. }
sliceにsliceを連結させるのって一発でできそうだけどあえてループで1つづつ要素を追加ってやり方にしている。

client/main.go
  1. package main
  2.  
  3. import (
  4.     "context"
  5.     "flag"
  6.     "fmt"
  7.     "log"
  8.     "time"
  9.  
  10.     pb "grpc/grpctry1/proto"
  11.  
  12.     "google.golang.org/grpc"
  13.     "google.golang.org/grpc/credentials/insecure"
  14. )
  15.  
  16. var (
  17.     addr = flag.String("addr", "localhost:50051", "the address to connect to")
  18. )
  19.  
  20. func main() {
  21.     d1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
  22.     d2 := []byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}
  23.     flag.Parse()
  24.     // Set up a connection to the server.
  25.     conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  26.     if err != nil {
  27.         log.Fatalf("did not connect: %v", err)
  28.     }
  29.     defer conn.Close()
  30.     c := pb.NewMyConnectorClient(conn)
  31.  
  32.     // Contact the server and print out its response.
  33.     ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  34.     defer cancel()
  35.     r, err := c.Connect(ctx, &pb.ConnectRequest{DataIn1: d1, DataIn2: d2})
  36.     if err != nil {
  37.         log.Fatalf("could not greet: %v", err)
  38.     }
  39.     var d3 []byte = r.GetDataOut()
  40.     fmt.Printf("% 02X\n", d3)
  41. }

以上。
proto/grpctry1.ptoroの
option go_package = "./proto";
とか、
server/main.goの
pb "grpc/grpctry1/proto"
とか、 client/main.goの
pb "grpc/grpctry1/proto"
とか、まじでなぞ。まじで呪文。
で、実行してみる。
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/grpctry1.proto
go run server/main.go
そすっと、
まぁ、こんな感じで怒られるので凹む。
go get google.golang.org/grpc
ってやればいいっぽいので、それやって、改めて実行してみる。
とりあえず、動くにはうごいたらしい。
ctrl+cでいったん止めて、わかりやすい感じでserverとclientを実行してみる。こうなる。
はい。よくできました。
protocで生成されるpb.goファイルを見る(理解する必要はない)ってところが実は重要だと思った。
ではWindows実行ファイルを作る。
cd server
GOOS=windows GOARCH=amd64 go build -o grpctry1_server.exe main.go
cd ../client
GOOS=windows GOARCH=amd64 go build -o grpctry1_client.exe main.go
cd ..
で、各フォルダに実行ファイルができたので、エクスプローラーでアドレスバーに\\wsl$っていれてWSLのファイルシステムを掘っていって実行ファイルをWindows側に取り出して、例によって実行許可を与えて、で、実行。
ちなみにserverを起動すると、こういうのがでる。許可するしかない。
で、こうなる。
いいぞ。、、、てか、ファイルサイズがマジクソデカくないか?

フリーアドレスをやめる<--翌日のリスタートが早くなるので業務効率が上がる。
組織改編は4月とする<--1~3月の無駄な作業がなくなるので業務効率が上がる。
エレベーターの横のボタンは押さない※<--その階で扉が開くのが遅くなることの防止となるので業務効率が上がる。
(※それが必要な方がそれをすることは正当な行為であり妨げない)
まずは簡単にできるところからやってくれんかな。