目次: C言語とlibc
組み込みソフトなどでバイナリデータをC言語の配列として記述したいときがあります。毎回手で変換するのは面倒ですし、スクリプトや実行バイナリだと移植が面倒(特にWindows)で、意外と悩ましいです。
もしcmakeだけで実装できれば移植の心配はなくなる(cmakeが対応しているプラットフォームに限る)のでは?と思い、試しに作ってみました。変換の本体は下記の関数です。
function(convert_bin2c input_file output_file c_var_name)
file(READ ${input_file} BIN_HEX HEX)
file(SIZE ${input_file} BIN_LEN)
# Wrap lines per 16bytes
string(REPEAT ".." 16 REGEX_PAT)
string(REGEX REPLACE "(${REGEX_PAT})" "\\1\n" BIN_HEX_WRAP ${BIN_HEX})
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " BIN_ARRAY ${BIN_HEX_WRAP})
# C format
set(C_H_INC "#include <stdint.h>")
set(C_H_LEN "const size_t ${c_var_name}_len = ${BIN_LEN};")
set(C_H_DAT "const uint8_t ${c_var_name}_dat[] = {\n${BIN_ARRAY}\n};")
# Generate header
file(WRITE ${output_file} "${C_H_INC}\n\n${C_H_LEN}\n${C_H_DAT}\n")
endfunction()
処理の概要は下記のとおりです。cmakeは文法にクセがありすぎて見づらいし書きづらいし最悪ですが、やっていることは難しくありません。
なおfile(SIZE) はcmake 3.14、string(REPEAT) はcmake 3.15で追加された機能なので、古すぎるcmakeだと動かないと思います。詳細はcmakeのドキュメントをご確認ください。
例えば下記のように使います。
convert_bin2c(${CMAKE_SOURCE_DIR}/test.bin ${CMAKE_BINARY_DIR}/include/bin.h array_binary)
ソースコードディレクトリ直下のtest.binをビルドディレクトリのinclude/bin.hに変換し、array_binary_dat(配列本体)という名前にします。
これだけだと合っているか間違っているか確認しづらいので、動作確認用に簡単なソースコード一式を作りましょう。
cmake_minimum_required(VERSION 3.16)
project(test_bin2c)
enable_language(C)
add_executable(test_bin2c)
target_sources(test_bin2c PRIVATE main.c)
target_include_directories(test_bin2c PRIVATE
${CMAKE_BINARY_DIR}/include
)
function(convert_bin2c input_file output_file c_var_name)
file(READ ${input_file} BIN_HEX HEX)
file(SIZE ${input_file} BIN_LEN)
# Wrap lines per 16bytes
string(REPEAT ".." 16 REGEX_PAT)
string(REGEX REPLACE "(${REGEX_PAT})" "\\1\n" BIN_HEX_WRAP ${BIN_HEX})
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " BIN_ARRAY ${BIN_HEX_WRAP})
# C format
set(C_H_INC "#include <stdint.h>")
set(C_H_LEN "const size_t ${c_var_name}_len = ${BIN_LEN};")
set(C_H_DAT "const uint8_t ${c_var_name}_dat[] = {\n${BIN_ARRAY}\n};")
# Generate header
file(WRITE ${output_file} "${C_H_INC}\n\n${C_H_LEN}\n${C_H_DAT}\n")
endfunction()
convert_bin2c(${CMAKE_SOURCE_DIR}/test.bin ${CMAKE_BINARY_DIR}/include/bin.h array_binary)
実行ファイル名はtest_bin2cでソースコードはmain.cです。変換対象のバイナリはtest.binとしました。変換後のC言語ソースコードはビルドディレクトリの下のinclude/bin.hとしました。
動作確認用のmain.cとtest.binは下記の通りです。
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <bin.h>
static void dump(const uint8_t *dat, size_t len)
{
printf("00000000: ");
for (size_t i = 0; i < len; i++) {
printf("%02x ", dat[i]);
if (i % 16 == 7) {
printf(" ");
}
if (i % 16 == 15) {
printf("\n%08x: ", (int)(i + 1));
}
}
printf("\n");
}
int main(int argc, char *argv[])
{
dump(array_binary_dat, array_binary_len);
return 0;
}
$ cat test.bin aaaa bbbb cccc DDDDD EEEEE FFFFF
あえて説明するほどでもないですが、main.cは配列を16バイトずつダンプするだけのプログラムです。test.binはこのデータのままでも、お好きなデータに置き換えて試しても良いです。
これでビルドできるはずですので、確認します。ビルドツールをNinjaにしたのは私の単なる好みなので、何を使っても良いです。動くはず。
$ cmake -B build -G Ninja ./ -- The C compiler identification is GNU 12.2.0 -- The CXX compiler identification is GNU 12.2.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/lib/ccache/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/lib/ccache/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/katsuhiro/share/projects/c/test-cmake-bin2c/build $ ninja -C build ninja: Entering directory `build' [2/2] Linking C executable test_bin2c
生成されたヘッダファイルを確認します。コンパイルは通っているので文法的には間違っていないと思いますが、一応ご紹介ということで。
$ cat build/include/bin.h #include <stdint.h> const size_t array_binary_len = 33; const uint8_t array_binary_dat[] = { 0x61, 0x61, 0x61, 0x61, 0x0a, 0x62, 0x62, 0x62, 0x62, 0x0a, 0x63, 0x63, 0x63, 0x63, 0x0a, 0x44, 0x44, 0x44, 0x44, 0x44, 0x0a, 0x45, 0x45, 0x45, 0x45, 0x45, 0x0a, 0x46, 0x46, 0x46, 0x46, 0x46, 0x0a, };
変換結果は特に問題なさそうなので、動作確認します。正しく変換できているかどうか、hexdumpの出力と比較します。
$ ./build/test_bin2c 00000000: 61 61 61 61 0a 62 62 62 62 0a 63 63 63 63 0a 44 00000010: 44 44 44 44 0a 45 45 45 45 45 0a 46 46 46 46 46 00000020: 0a $ hexdump -C test.bin 00000000 61 61 61 61 0a 62 62 62 62 0a 63 63 63 63 0a 44 |aaaa.bbbb.cccc.D| 00000010 44 44 44 44 0a 45 45 45 45 45 0a 46 46 46 46 46 |DDDD.EEEEE.FFFFF| 00000020 0a |.| 00000021
動きました。結果も間違ってなさそうです。
思いつく欠点としては、巨大なファイルを扱うとcmakeが遅かったりメモリ食いすぎて死んじゃう気がします。が、cmakeが扱いきれないレベルの巨大配列をソースコードに書くこと自体、設計が間違っていると思うので、処理速度や巨大ファイルの扱いはあまり気にしないことにします。
< | 2023 | > | ||||
<< | < | 06 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)