如果您想了解 pack/unpack,這裡有一個 Perl 的教學,同樣適用於理解 PHP 的 pack/unpack。
https://perldoc.dev.org.tw/perlpacktut.html
(PHP 4, PHP 5, PHP 7, PHP 8)
pack — 將資料打包成二進位字串
根據 format
將給定的參數打包成二進位字串。
此函式的概念源自 Perl,所有格式化程式碼都與 Perl 中的相同。然而,有一些格式化程式碼遺失了,例如 Perl 的「u」格式程式碼。
請注意,帶符號值和不帶符號值之間的區別僅影響函式 unpack(),而函式 pack() 對於帶符號和不帶符號格式程式碼給出相同的結果。
format
格式字串 format
由格式碼以及後接的選用重複參數所組成。重複參數可以是一個整數值,或是 *
表示重複到輸入資料的結尾。對於 a、A、h、H 來說,重複計數指定從一個資料參數中取用多少個字元;對於 @ 來說,它是放置下一個資料的絕對位置;對於其他所有格式碼,重複計數指定有多少個資料參數被取用並打包到產生的二進位字串中。
目前已實作的格式如下:
代碼 | 說明 |
---|---|
a | 以 NUL 填充的字串 |
A | 以空白填充的字串 |
h | 十六進位字串,低位元組在前 |
H | 十六進位字串,高位元組在前 |
c | 有號字元 (signed char) |
C | 無號字元 (unsigned char) |
s | 有號 short(永遠是 16 位元,機器位元組順序) |
S | 無號 short(永遠是 16 位元,機器位元組順序) |
n | 無號 short(永遠是 16 位元,大端序) |
v | 無號 short(永遠是 16 位元,小端序) |
i | 有號整數(大小和位元組順序取決於機器) |
I | 無號整數(大小和位元組順序取決於機器) |
l | 有號 long(永遠是 32 位元,機器位元組順序) |
L | 無號 long(永遠是 32 位元,機器位元組順序) |
N | 無號 long(永遠是 32 位元,大端序) |
V | 無號 long(永遠是 32 位元,小端序) |
q | 有號 long long(永遠是 64 位元,機器位元組順序) |
Q | 無號 long long(永遠是 64 位元,機器位元組順序) |
J | 無號 long long(永遠是 64 位元,大端序) |
P | 無號 long long(永遠是 64 位元,小端序) |
f | 浮點數(大小和表示法取決於機器) |
g | 浮點數(大小取決於機器,小端序) |
G | 浮點數(大小取決於機器,大端序) |
d | 雙精度浮點數(大小和表示法取決於機器) |
e | 雙精度浮點數(大小取決於機器,小端序) |
E | 雙精度浮點數(大小取決於機器,大端序) |
x | NUL 位元組 |
X | 往回一個位元組 |
Z | 以 NUL 填充的字串 |
@ | 以 NUL 填充至絕對位置 |
values (值)
傳回包含資料的二進位字串。
版本 | 說明 |
---|---|
8.0.0 | 此函式在失敗時不再傳回 false 。 |
7.2.0 | float 和 double 類型支援大端序和小端序。 |
7.0.15, 7.1.1 | 新增了「e」、「E」、「g」和「G」代碼,以支援浮點數和雙精度浮點數的位元組順序。 |
範例 #1 pack() 範例
<?php
$binarydata = pack("nvc*", 0x1234, 0x5678, 65, 66);
?>
產生的二進位字串長度為 6 個位元組,包含的位元組序列為 0x12、0x34、0x78、0x56、0x41、0x42。
請注意,PHP 內部將 int 值儲存為機器相關大小的有符號值(C 類型 long
)。超出 int 類型範圍的整數字面量和運算結果將儲存為 float。將這些浮點數打包為整數時,會先將它們轉換為整數類型。這可能會也可能不會產生所需的位元組模式。
最相關的情況是打包無符號數,如果 int 類型是無符號的,則可以用 int 類型表示。在 int 類型大小為 32 位元的系統中,轉換通常會產生與 int 為無符號時相同的位元組模式(儘管這依賴於 C 標準中規定的與實現相關的無符號到有符號的轉換)。在 int 類型大小為 64 位元的系統中,float 很可能沒有足夠大的尾數來容納該值而不損失精度。如果這些系統也具有原生 64 位元 C 類型 int
(大多數類 Unix 系統沒有),則在上範圍內使用 I
打包格式的唯一方法是建立與所需無符號值具有相同位元組表示形式的負 int 值。
如果您想了解 pack/unpack,這裡有一個 Perl 的教學,同樣適用於理解 PHP 的 pack/unpack。
https://perldoc.dev.org.tw/perlpacktut.html
一個輔助類別,用於在整數和二進位字串之間進行轉換。適用於從檔案或通訊端讀寫整數。
<?php
class int_helper
{
public static function int8($i) {
return is_int($i) ? pack("c", $i) : unpack("c", $i)[1];
}
public static function uInt8($i) {
return is_int($i) ? pack("C", $i) : unpack("C", $i)[1];
}
public static function int16($i) {
return is_int($i) ? pack("s", $i) : unpack("s", $i)[1];
}
public static function uInt16($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("n", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("v", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("S", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int32($i) {
return is_int($i) ? pack("l", $i) : unpack("l", $i)[1];
}
public static function uInt32($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("N", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("V", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("L", $i);
}
return is_array($i) ? $i[1] : $i;
}
public static function int64($i) {
return is_int($i) ? pack("q", $i) : unpack("q", $i)[1];
}
public static function uInt64($i, $endianness=false) {
$f = is_int($i) ? "pack" : "unpack";
if ($endianness === true) { // big-endian
$i = $f("J", $i);
}
else if ($endianness === false) { // little-endian
$i = $f("P", $i);
}
else if ($endianness === null) { // machine byte order
$i = $f("Q", $i);
}
return is_array($i) ? $i[1] : $i;
}
}
?>
使用範例
<?php
header("Content-Type: text/plain");
include("int_helper.php");
echo int_helper::uInt8(0x6b) . PHP_EOL; // k
echo int_helper::uInt8(107) . PHP_EOL; // k
echo int_helper::uInt8("\x6b") . PHP_EOL . PHP_EOL; // 107
echo int_helper::uInt16(4101) . PHP_EOL; // \x05\x10
echo int_helper::uInt16("\x05\x10") . PHP_EOL; // 4101
echo int_helper::uInt16("\x05\x10", true) . PHP_EOL . PHP_EOL; // 1296
echo int_helper::uInt32(2147483647) . PHP_EOL; // \xff\xff\xff\x7f
echo int_helper::uInt32("\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 2147483647
// 注意:使用 64 位元建置的 PHP 測試此程式碼
echo int_helper::uInt64(9223372036854775807) . PHP_EOL; // \xff\xff\xff\xff\xff\xff\xff\x7f
echo int_helper::uInt64("\xff\xff\xff\xff\xff\xff\xff\x7f") . PHP_EOL . PHP_EOL; // 9223372036854775807
?>
請注意,在 Perl 中,上面的指令看起來像這樣
$binarydata = pack ("n v c*", 0x1234, 0x5678, 65, 66);
在 PHP 中,第一個參數似乎不允許空格。因此,如果您想將 pack 指令從 Perl 轉換為 PHP,請不要忘記刪除空格!
如果您需要特別從大端序或小端序解壓縮一個有號 short,而不是使用機器位元組順序,您只需要將其解壓縮為無號形式,然後如果結果 >= 2^15,則從中減去 2^16。
例如:
<?php
$foo = unpack("n", $signedbigendianshort);
$foo = $foo[1];
if($foo >= pow(2, 15)) $foo -= pow(2, 16);
?>
/* 將浮點數從主機位元組序轉換為網路位元組序 */
function FToN( $val )
{
$a = unpack("I",pack( "f",$val ));
return pack("N",$a[1] );
}
/* 將浮點數從網路位元組序轉換為主機位元組序 */
function NToF($val )
{
$a = unpack("N",$val);
$b = unpack("f",pack( "I",$a[1]));
return $b[1];
}
請注意,格式代碼 H 總是會在右側以 0 填充,以便位元組對齊(針對奇數個半位元組)。
因此,pack("H", "7") 的結果是 0x70(ASCII 字元 'p'),而不是 0x07(BEL 字元)
同樣地,pack("H*", "347") 的結果是 0x34 ('4') 和 0x70 ('p'),而不是 0x03 和 0x47。
使用以下程式碼,您將獲得相同的效果:
<?php
function _readInt($fp)
{
return unpack('V', fread($fp, 4));
}
?>
或者使用 unpack('N', ...) 來處理大端序。
即使在 64 位元架構中,intval(6123456789) = 6123456789,且 sprintf('%b', 5000000000) = 100101010000001011111001000000000
pack 不會將傳遞給它的任何內容視為 64 位元。如果您想要打包 64 位元整數
<?php
$big = 5000000000;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($big & $left) >>32;
$r = $big & $right;
$good = pack('NN', $l, $r);
$urlsafe = str_replace(array('+','/'), array('-','_'), base64_encode($good));
// 完成!
// 重建:
$unurl = str_replace(array('-','_'), array('+','/'), $urlsafe);
$binary = base64_decode($unurl);
$set = unpack('N2', $tmp);
print_r($set);
$original = $set[1] << 32 | $set[2];
echo $original, "\\r\\n";
?>
結果為
陣列
(
[1] => 1
[2] => 705032704
)
5000000000
但**僅限於**在啟用 64 位元的機器和 PHP 發行版上。
使用 pack 將阿拉伯字元寫入檔案。
<?php
$text = "㔆㘆㘆";
$text = mb_convert_encoding($text, "UCS-2BE", "HTML-ENTITIES");
$len = mb_strlen($text);
$bom = mb_convert_encoding("", "unicode", "HTML-ENTITIES");
$fp = fopen('text.txt', 'w');
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack("a{$len}", $text));
fwrite($fp, pack('a2', $bom));
fwrite($fp, pack('a2', "\n"));
fclose($fp);
?>