2023年12月29日金曜日

WSLでLinuxのDLL作成

 

先日作ったstudy1.dllをLinux用のDLLすなわちstudy1.soとしてビルドする。

Makefileをちょこちょこっとへんこうして、ソースファイル中の__stdcallはLinuxではいらんので、#define文で__stdcall亡き者にする置換をして、defファイルの役割をするmapファイルを作成する。

Makefile_Lin
TARGETNAME=study1
CC=gcc
CXX=g++
LINK=gcc
CFLAGS= -fPIC -O4 -Wall -c
LFLAGS= -shared  -static-libgcc -static-libstdc++ -Wl,--version-script,$(TARGETNAME).map -o $(TARGETNAME).so
#LFLAGS= -shared -Wl,--version-script,$(TARGETNAME).map -o $(TARGETNAME).so
OBJS= $(TARGETNAME).o nijo.o tasu.o

ALL : $(TARGETNAME).so

$(TARGETNAME).so : $(OBJS)
	$(LINK) $(OBJS)  $(LFLAGS) 

.cpp{}.obj :
	$(CXX) $(CFLAGS) $<

.c{}.obj :
	$(CC) $(CFLAGS) $<

clean :
	rm $(OBJS) $(TARGETNAME).so

study1.cpp

#include <stdint.h>

#ifndef WIN32
#define __stdcall
#endif

/*Cで作成された別のファイルにある関数のプロトタイプ宣言*/
extern "C"{
    extern uint32_t nijo(uint32_t a);
    extern uint32_t tasu(uint32_t a,uint32_t b);
}

extern "C" uint32_t __stdcall keisan(uint32_t a,uint32_t b){
    return tasu(nijo(a),b);
}

study1.map

{
  global:
    keisan;
  local: *;
};

で、Makeする。

まずは、うまくいった。

次はテスト用。

study1.cpp

#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>

typedef uint32_t (*keisan_type)(uint32_t a,uint32_t b);
void* dll;
keisan_type keisan;

int main(int argc,char** argv){
    uint32_t a,b,ans;
    dll=dlopen("./study1.so",RTLD_LAZY);
    keisan=(keisan_type)dlsym(dll,"keisan");
    a=2;
    b=3;
    ans=keisan(a,b);
    printf("%d^2 + %d = %d\n",a,b,ans);
    return 0;
}

で、例によって直でコンパイルして実行する。

うまくいった。


WSLでGUIアプリ作成(monodevelop)(1)

 


WSLを布教するにはGUIアプリも作成して実行できないといけない。かもしれない。

LinuxにはGUI作成機能まで備えた統合開発環境がそれなりに存在する。C言語だとAnjuta、PythonだとTkinter。なんとなくぽっと出てきたC#開発環境のmonodevelop。今ではMACの標準的な開発環境になっている。で、そんなmonodevelopをWSL内のUbuntuで使えるようにする。そして、有料の統合開発環境を駆逐していく。あ、そんなつもりじゃ、、、

こちら(https://www.monodevelop.com/download/#fndtn-download-lin)を見てインストールする。wslのUbuntuは現在22.04.03LTSらしく、

monodevelopのwebページに22.04用のインストールの説明はないけど、18.04のやつでいけるらしい。Wikipedia(https://ja.wikipedia.org/wiki/MonoDevelop)にそう書いてある。

ではやってみる。

sudo apt install apt-transport-https dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/ubuntu vs-bionic main" | sudo tee /etc/apt/sources.list.d/mono-official-vs.list
sudo apt update

ここまで、言われたとおりにやってみた。

で、この後
sudo apt install monodevelop
ってすると、めっちゃインストールされる。

で、
monodevelop
ってすると、GUIが起動する。

くっそーーーちょー簡単。

ところで、WSLからGUIアプリを起動すると、画面が乱れることがある。

こんなん。
こういう場合は一度WSL関連をすべて閉じて、Windowsのコマンドプロンプトで
wsl --shutdown
ってしてから、再度WSLを起動すると、一時的によくなる。何がきっかけでおかしくなるのかわからんので、とりあえず対処療法。

さて、インストールだけで終わったらちょっと間抜けなので、なんか作ってみる。どういうのを作るか、、、。




2023年12月27日水曜日

WSLでWindows実行ファイルを作るまでの地味な道のり(2)

WSLでMinGWを使って、C言語で32bitおよび64bitのWindows実行ファイルを作るっていう、もうあっちこっちで復習が繰り返されているテーマを敢えて復習してみる。

前回作成したdllをテストするための実行ファイルを作ります。

test.c
#include <stdio.h>
#include <stdint.h>
#include <windows.h>

typedef uint32_t (__stdcall *keisan_type)(uint32_t a,uint32_t b);
HMODULE dll;
keisan_type keisan;

int main(int argc,char** argv){
    uint32_t a,b,ans;
    dll=LoadLibrary("study1.dll");
    keisan=(keisan_type)GetProcAddress(dll,"keisan");
    a=2;
    b=3;
    ans=keisan(a,b);
    printf("%d^2 + %d = %d\n",a,b,ans);
    return 0;
}
で、これをコンパイルするルールもMakefileに書くって手もあるんだけど、そんなそんな頻繁にいじらないので、直でコンパイルする。
/usr/bin/i686-w64-mingw32-gcc -o test.exe test.c
そして、実行
./test.exe

うまくいったわい。

では、64bitのDLLを作成してみる。いじるのはMakefileだけ。

Makefile_64
TARGETNAME=study1
CC=/usr/bin/x86_64-w64-mingw32-gcc
CXX=/usr/bin/x86_64-w64-mingw32-g++
LINK=/usr/bin/x86_64-w64-mingw32-gcc
CFLAGS= -Wall -c
LFLAGS= -o $(TARGETNAME)_64.dll -shared -static-libgcc -static-libstdc++
OBJS= $(TARGETNAME).o nijo.o tasu.o

ALL : $(TARGETNAME)_64.dll

$(TARGETNAME)_64.dll : $(OBJS)
	$(LINK) $(LFLAGS) $(OBJS) $(TARGETNAME).def

.cpp{}.obj :
	$(CXX) $(CFLAGS) $<

.c{}.obj :
	$(CC) $(CFLAGS) $<

clean :
	rm $(OBJS) $(TARGETNAME)_64.dll
で、Makeするわけだが、-fオプションでMakefile_64を指定するんだけど、一度cleanをしておく。32bitでコンパイルしたオブジェクトを消したいので。
make -f Makefile_64 clean
make -f Makefile_64
で、study1_64.dllができる。

では、これもテストしてみます。

test_64.c
#include <stdio.h>
#include <stdint.h>
#include <windows.h>

typedef uint32_t (__stdcall *keisan_type)(uint32_t a,uint32_t b);
HMODULE dll;
keisan_type keisan;

int main(int argc,char** argv){
    uint32_t a,b,ans;
    dll=LoadLibrary("study1_64.dll");
    keisan=(keisan_type)GetProcAddress(dll,"keisan");
    a=2;
    b=3;
    ans=keisan(a,b);
    printf("%d^2 + %d = %d\n",a,b,ans);
    return 0;
}

dllのファイル名を変えただけ。で、直でコンパイルする。
/usr/bin/x86_64-w64-mingw32-gcc -o test_64.exe test_64.c
そして、実行
./test_64.exe

うまくいった。

昔はcygwinでdll作っていたので、cygwin1.dllなしでも動くようにしたかったり、いろいろ勝手がわからなくてあれやこれややってみてたけど、mingwのおかげでだいぶ良くなった。そしてwslのおかげでMSYSが要らなくなったのでそれもだいぶ良くなった。

Visual Studioのライセンス条項がゆるくなってくれると、いわゆる会社でも今よりさらにコンピューティングが盛んになって、生産性が上がるのに、、、じゃぁ会社で買えよってスタンスか、いやプロフェッショナルプログラマーじゃない人もプログラミングする時代なのでなんとかならんもんか。まぁ拙者はマウスポチポチよりも黒い画面に向き合うのが好きなのでwslで満足してますです。

しかし、この手の指南をしているブログとかwebページって最後に「いかがでしたか」とかひどいやつだと「いかがだったでしょうか」とか書いてあるのが多いんだけど、なんでやろ。なんかいいことあんのかな。かすかにイラっとする。なんで、ささいな内容で誰かのためになっているって自信たっぷりなのか。(、、、自分も書いてたりして。)

あんたみたいなの、ろくな老人になれんよって、また、たしなめられる。


WSLでWindows実行ファイルを作るまでの地味な道のり(1)

 

WSLでMinGWを使って、C言語で32bitおよび64bitのWindows実行ファイルを作るっていう、もうあっちこっちで復習が繰り返されているテーマを敢えて復習してみる。

WSLはここhttps://learn.microsoft.com/ja-jp/windows/wsl/installに書いてある通り、管理者権限のコマンドプロンプトからwsl --installってするだけでディストリビューションまでインストールされる。
ディストリビューションを選びたい場合はこちらhttps://learn.microsoft.com/ja-jp/windows/wsl/install-manualに従う。
まぁ一度wsl --installってして、wsl --unregister Ubuntuでディストリビューションをアンインストールて、その後、好みのディストリビューションをStoreからインストールするって手もあるんじゃない?
で、Ubuntuがインストールされているとして進める。

まずはproxy内にあるPCでWSLを使う場合、インストールしたディストリビューションにproxyを教えないといけない(これをしないとsudo apt *もできないので)。
[apt用のproxy設定]
/etc/apt/apt.conf
ってファイルを作る。内容は、
Acquire::http::Proxy "http://サーバー名:ポート番号";
Acquire::https::Proxy "http://サーバー名:ポート番号";
とする。
なお、このファイルを作るにはroot権限(WindowsでいうところのAdministrator権限)が必要なので、
viエディタを使う場合には
sudo vi /etc/apt/apt.conf
ってする。Linuxになじみのない人はnanoを使う方がいいかも。
sudo nano /etc/apt/apt.conf

[全般的なproxy設定(環境変数)とgit用の設定]
~/.bashrc
の最後に
export ftp_proxy=http://サーバー名:ポート番号
export http_proxy=http://サーバー名:ポート番号
export https_proxy=http://サーバー名:ポート番号
export FTP_PROXY=http://サーバー名:ポート番号
export HTTP_PROXY=http://サーバー名:ポート番号
export HTTPS_PROXY=http://サーバー名:ポート番号
git config --global http.proxy ${HTTP_PROXY}
git config --global https.proxy ${HTTPS_PROXY}
git config --global url."https://".insteadOf git://
を追加する。ちなみに最後の行が必要なのかどうかはわからん。
"~"は自分のホームフォルダのこと。
~/.bashrcは自分の所有物なので、sudoは不要。
で、これを有効にするために
source ~/.bashrc
ってするか、一旦ログアウト
logout
する。

ここまできたら、aptのデータベース更新とインストール済みアプリの更新を行う。
sudo apt update
sudo apt -y upgrade
以降のアプリのインストールにもaptコマンドを使うのが通なんだけど、Windowsにかぶれてしまっている人はsynapticを使う。
sudo apt -y install synaptic
で、完了したら
sudo synaptic
ってすると、こういうのが出る。

何書いているかわからんが"Close"しか押せないので"Close"を押す。
こうなる。
Searchを押す。
build-essential
と入力して
"Search"を押す。
こうなる。
build-essentialの左にあるボックスをクリックする。
こうなるので、"Mark for Installation"を押す。
こういうのが出てくる。関連するアプリ等を全部選択してくれる。ので、"Mark"を押す。
次は"mingw"を検索する。で、出てきた結果から、"mingw-w64"を探して同じくMarkする。
こうなる。上のほうにスクロールすると、gccとg++も選択されていることがわかる。
で、Applyを押す。
すると、こういうのが出るので、やはりApplyを押す。
そして、ひたすら待つ。
意外とすぐに終わる。

で、Close。
で、×おしてsynapticを終了する。

wslのシェル(windowsでいうコマンドプロンプト画面)で作業用のフォルダを作成して、そこに移動します。
mkdir -p work/gcc/dllstudy1
cd work/gcc/dllstudy1

で、VSCodeを起動します。
code .
WindowsにVSCodeがインストールされていればwslでVSCodeを起動することができます。
こうなる。
当然"Yes ..."を押す。
VSCodeの拡張機能をインストールする。この辺https://learn.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-vscodeを参考に。
うちの場合はwslに関連しそうなのはこいつらが入っている。

うちの場合は、sshでがちLinuxに入るって使い方もしているから、wslだけってなら要らんものも入っているかも。
VSCodeでは使っている間にその場面に応じて使えそうな拡張機能を教えてくれるので基本的にはそれに従えばいい。
こんなかんじで右下になにか出る。


で、今回のdllの機能は、、、例題なので、
2つの引数a,bから、a^2+bを戻すものとする。引数も戻り値も32bit符号なし整数とする。
で、2乗するコードを不必要に分割して、例題としてほんの少し現実のプロジェクトに近づける。あまりにもシンプルな例題だと応用までの距離が遠すぎてしんどい、と、世の中の例題を見ていて思っていたので。ていうか、Windows実行ファイルといいながら、いきなりdllを作成するってのがそもそもマニアック。
で、以下のファイルを作成する
Makefile
study1.cpp
tasu.c
nijo.c
study1.def
まぁ内容はたいしたことにはならないはず。
では、じゃんじゃんやっていきまっす。

ちなみにVSCodeでファイルを作るにはこれを押す。


Makefile
TARGETNAME=study1
CC=/usr/bin/i686-w64-mingw32-gcc
CXX=/usr/bin/i686-w64-mingw32-g++
LINK=/usr/bin/i686-w64-mingw32-gcc
CFLAGS= -Wall -c
LFLAGS= -o $(TARGETNAME).dll -shared -static-libgcc -static-libstdc++
OBJS= $(TARGETNAME).o nijo.o tasu.o

ALL : $(TARGETNAME).dll

$(TARGETNAME).dll : $(OBJS)
	$(LINK) $(LFLAGS) $(OBJS) $(TARGETNAME).def

.cpp{}.obj :
	$(CXX) $(CFLAGS) $<

.c{}.obj :
	$(CC) $(CFLAGS) $<

clean :
	rm $(OBJS) $(TARGETNAME).dll


study1.cpp
#include <stdint.h>

/*Cで作成された別のファイルにある関数のプロトタイプ宣言*/
extern "C"{
    extern uint32_t nijo(uint32_t a);
    extern uint32_t tasu(uint32_t a,uint32_t b);
}

extern "C" uint32_t __stdcall keisan(uint32_t a,uint32_t b){
    return tasu(nijo(a),b);
}


tasu.c
#include <stdint.h>

uint32_t tasu(uint32_t a,uint32_t b){
    return a+b;
}


nijo.c
#include <stdint.h>

uint32_t nijo(uint32_t a){
    return a*a;
}


study1.def
LIBRARY study1
EXPORTS
    keisan

では、Makeしてみる。

VSCodeでTerminalを表示させる。

でMake

そして

できた。

テストする前に、dependencies(https://github.com/lucasg/Dependencies)でkeisanって関数がちゃんとエクスポートされているか見てみる。
wsl内のファイルには普通にエクスプローラでアクセスできるので、ここからDependenciesGUIの画面にドロップすればいい。


とりあえず、ここまではうまくいっている。(つづく)

Google-code-prettifyで行番号が表示されなくなってるー。過去の記事でも表示されなくなっているからGoogle側の問題だろう。いつか解決すると信じてそうっとしておく。

2023年12月24日日曜日

ArduinoたちでI2C

 

Arduino同士でI2C通信してもらいます。
R4がMaster、R3がSlaveでR3はI2Cメモリーとして働いてもらいます。R4のI/Oが5Vなので、R3とレベル変換なしに通信できるのがいい。さすがルネ。

I2Cの接続はとても簡単です。SCL同士、SDA同士を接続するだけ。

wire_master_study1.ino

#include <stdint.h>
#include <Wire.h>

uint8_t slave_address = 0x55;
uint16_t slave_memadr[] = {0x0000, 0x0010, 0x0020, 0x0130};

void setup(void)
{
    Wire.begin();
    Serial.begin(115200);
}
uint8_t cnt = 0;
void loop(void)
{
    uint8_t txbuf[16];
    uint8_t rxbuf[16];
    uint8_t i;
    char uart_txbuf[128];
    for (i = 0; i < 16; i++){
        txbuf[i] = cnt + i;
    }
    Serial.println("start ");
    sprintf(
        uart_txbuf,
        "%04X : %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
        slave_memadr[0], txbuf[0], txbuf[1], txbuf[2], txbuf[3], txbuf[4], txbuf[5], txbuf[6], txbuf[7],
        txbuf[8], txbuf[9], txbuf[10], txbuf[11], txbuf[12], txbuf[13], txbuf[14], txbuf[15]);
    Serial.println(uart_txbuf);
    Wire.beginTransmission(slave_address);
    Wire.write((uint8_t)((slave_memadr[0] >> 8) & 0x00FF));
    Wire.write((uint8_t)((slave_memadr[0] >> 0) & 0x00FF));
    for (i = 0; i < 16; i++){
        Wire.write(txbuf[i]);
    }
    Wire.endTransmission();
    delay(10);

    Wire.beginTransmission(slave_address);
    Wire.write((uint8_t)((slave_memadr[0] >> 8) & 0x00FF));
    Wire.write((uint8_t)((slave_memadr[0] >> 0) & 0x00FF));
    //Wire.endTransmission();
    Wire.requestFrom(slave_address, 16);
    for (i = 0; i < 16; i++){
        rxbuf[i] = Wire.read();
    }
    Wire.endTransmission();
    sprintf(
        uart_txbuf,
        "       %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
        rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3], rxbuf[4], rxbuf[5], rxbuf[6], rxbuf[7],
        rxbuf[8], rxbuf[9], rxbuf[10], rxbuf[11], rxbuf[12], rxbuf[13], rxbuf[14], rxbuf[15]);
    Serial.println(uart_txbuf);
    cnt = ((cnt + 1) & 0x3F);
    delay(1000);
}

wire_slave_study1.ino

#include <Wire.h>

uint8_t slave_address = 0x55;
uint8_t slave_memory[0x0500];
uint16_t adr;
uint8_t recv_flag;

void requestEvent(){
    uint8_t n;
    uint16_t i = 0;
    for (n = 0, i = 0; n < 64; n++, i++){
        Wire.write(slave_memory[adr + i]);
    }
}
void receiveEvent(int n){
    uint16_t i = 0;
    if (Wire.available()){
        adr = (uint16_t)(Wire.read()) * 256;
    }
    if (Wire.available()){
        adr = adr + (uint16_t)(Wire.read());
    }
    while (Wire.available()){
        slave_memory[adr + i] = Wire.read();
        i = i + 1;
    }
    recv_flag = 1;
}

void setup(void)
{
    pinMode(A5, INPUT_PULLUP);
    pinMode(A4, INPUT_PULLUP);
    Wire.begin(slave_address);
    Serial.begin(115200);
    Wire.onReceive(receiveEvent);
    Wire.onRequest(requestEvent);
    recv_flag = 0;
}

void loop(void)
{
    char uart_txbuf[16];
    uint16_t i;
    if (recv_flag == 1){
        recv_flag = 0;
        sprintf(uart_txbuf, "%04X : ", adr);
        Serial.print(uart_txbuf);
        for (i = 0; i < 16; i++){
            sprintf(uart_txbuf, "%02X", slave_memory[adr + i]);
            Serial.print(uart_txbuf);
        }
        Serial.println("");
    }
}
はい。いつも通りのいきなりな感じです。
デバイスアドレスは0x55で1バイト目がメモリ(のふり)のアドレスのHighバイトで、2バイト目がLowバイトを指示する。Slave側はこれを記憶しておく。続けてデータが来れば、それを保存していき、リクエストが来ればWire.writeでデータを返送する。
通信の全体像はこんな感じ。

Masterが送っている(書き込んでいる(つもり))部分はこんな感じ。

Masterが要求してSlaveが返答している部分はこんな感じ。

いい感じです。

いやーついでといえばついでで久しぶりに更新しましたー。
ついでがなければ、冬休みまで放置するところやったわ。ここ1,2ヶ月でも世の中がどんどん混沌としてきて、どうなっちゃうんやろ。
供給を人質にして、(自らが作りこんだ)問題の対応にコストアップを要求してくるメーカーがあったり、まさに苦難の行軍。