<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cgo on chai2010 的博客</title>
    <link>https://chai2010.cn/tags/cgo/</link>
    <description>Recent content in Cgo on chai2010 的博客</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Wed, 26 Oct 2022 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://chai2010.cn/tags/cgo/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Go 语言 CGO 用户深度定制 SQLite 代码</title>
      <link>https://chai2010.cn/post/2022/making-a-change-to-sqlite-source-code/</link>
      <pubDate>Wed, 26 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2022/making-a-change-to-sqlite-source-code/</guid>
      
        <description>&lt;p&gt;本文是 BRUNO CALZA 记录的关于如何改变SQLite源代码，使记录行更新时可用于 Go 的更新钩子函数的过程。原文通过深度定制 C 语言的 API 函数达成目的，这几乎是所有 CGO 深度用户必然经历的过程（关于 CGO 的基本用法可以参考译者的《Go高级编程》第2章），是一个非常有借鉴意义的技术文章。&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;作者：&lt;a href=&#34;https://brunocalza.me/author/brunocalza&#34;&gt;BRUNO CALZA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;译者：柴树杉，&lt;a href=&#34;https://github.com/wa-lang/wa&#34;&gt;凹语言&lt;/a&gt; 作者、Go语言贡献者、多本Go语言图书作者，目前在蚂蚁从事 &lt;a href=&#34;https://github.com/kusionStack/kusion&#34;&gt;KusionStack&lt;/a&gt; 和 KCL 开发。&lt;/li&gt;
&lt;li&gt;原文：&lt;a href=&#34;https://brunocalza.me/making-a-change-to-sqlite-source-code/&#34;&gt;https://brunocalza.me/making-a-change-to-sqlite-source-code/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;1-背景&#34;&gt;1. 背景&lt;/h2&gt;

&lt;p&gt;有一天，我正在考虑如何在 SQLite 中获取最近插入或更新的行记录的数据。这样做的动机是我想创建该行的 hash，本质上是为了在插入或更新行时能够构建相应表的 &lt;a href=&#34;https://en.wikipedia.org/wiki/Merkle_tree&#34;&gt;Merkle 树&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;SQLite 提供的最符合的 API 可能是 &lt;a href=&#34;https://www.sqlite.org/c3ref/update_hook.html&#34;&gt;&lt;code&gt;sqlite3_update_hook&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;sqlite3_update_hook()&lt;/code&gt; 函数为数据库连接注册一个回调函数，该数据库连接由第一个参数标识，在 rowwid 表中更新、插入或删除行时调用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个 API 的问题是它只返回行的 &lt;a href=&#34;https://sqlite.org/lang_createtable.html#rowid&#34;&gt;rowid&lt;/a&gt;。这意味着还需要为列内的行获取所有列。即使使用这种方法，我仍然无法获得行记录的原始数据。只能得到那一行的驱动信息。&lt;/p&gt;

&lt;p&gt;关于如何构建这样的树可能有很多方法，但就我而言 SQLite API 并没有提供真正想要的东西。因此，我决定趁此机会更深入地挖掘下源代码，同时看看内部实现的细节。不仅如此，我希望可以对它进行一些修改和测试，看看能否满足需求。&lt;/p&gt;

&lt;p&gt;因为对 C 语言的畏惧，开始我只是想假装看下几个源文件就跑路。没想到这次真的有惊喜。&lt;/p&gt;

&lt;h2 id=&#34;2-看看-sqlite-的代码结构&#34;&gt;2. 看看 SQLite 的代码结构&lt;/h2&gt;

&lt;p&gt;首先使用 &lt;code&gt;fossil&lt;/code&gt; 工具克隆了 &lt;a href=&#34;https://sqlite.org/src/doc/trunk/README.md&#34;&gt;SQLite源代码&lt;/a&gt;，下面是文件。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://chai2010.cn/images/2022/making-a-change-to-sqlite-source-code/01.png&#34; alt=&#34;SQLite 代码目录&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如果你对数据库比较熟悉，或许可以猜测出一些文件对应的操作。因此，我决定直接跳到 &lt;code&gt;insert.c&lt;/code&gt; 文件，看看能不能找到一些有趣的东西。&lt;/p&gt;

&lt;p&gt;遍历函数名列表，路过 &lt;a href=&#34;https://github.com/sqlite/sqlite/blob/version-3.39.4/src/insert.c#L671&#34;&gt;&lt;code&gt;sqlite3Insert&lt;/code&gt;&lt;/a&gt; 函数，看到以下注释：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;** This routine is called to handle SQL of the following forms:
**
**    insert into TABLE (IDLIST) values(EXPRLIST),(EXPRLIST),...
**    insert into TABLE (IDLIST) select
**    insert into TABLE (IDLIST) default values
**
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也许在这个函数中有一些可鼓捣的地方。我能够对其中发生的情况进行一些猜测，但引起我注意的是对名称类似于 &lt;code&gt;sqlite3vdbeXXX&lt;/code&gt; 的函数的函数调用的数量。&lt;/p&gt;

&lt;p&gt;这让我想起 SQLite 底层使用了一个名为 &lt;a href=&#34;https://www.sqlite.org/opcode.html&#34;&gt;vdbe&lt;/a&gt; 的虚拟机。这意味着所有SQL语句都首先被翻译成该虚拟机的语言。然后，执行引擎执行虚拟机代码。让我们看一个简单的 &lt;code&gt;INSERT&lt;/code&gt; 语句如何被翻译成字节码:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sqlite&amp;gt; create table a (a int, b text);
sqlite&amp;gt; explain INSERT INTO a VALUES (1, &#39;Hello&#39;);
addr  opcode         p1    p2    p3    p4             p5  comment      
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     8     0                    0   Start at 8
1     OpenWrite      0     2     0     2              0   root=2 iDb=0; a
2     Integer        1     2     0                    0   r[2]=1
3     String8        0     3     0     Hello          0   r[3]=&#39;Hello&#39;
4     NewRowid       0     1     0                    0   r[1]=rowid
5     MakeRecord     2     2     4     DB             0   r[4]=mkrec(r[2..3])
6     Insert         0     4     1     a              57  intkey=r[1] data=r[4]
7     Halt           0     0     0                    0   
8     Transaction    0     1     1     0              1   usesStmtJournal=0
9     Goto           0     1     0                    0   
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我得出的结论是 &lt;a href=&#34;https://github.com/sqlite/sqlite/blob/version-3.39.4/src/insert.c#L671&#34;&gt;&lt;code&gt;sqlite3Insert&lt;/code&gt;&lt;/a&gt; 实际上是根据SQLite插入规则，将解析后的 &lt;code&gt;INSERT&lt;/code&gt; 语句转换为一系列虚拟机字节码指令。&lt;/p&gt;

&lt;p&gt;因此这并不是我要找的地方。我真正需要的是在插入之前创建记录的位置。我猜测那只能是执行虚拟机代码的地方，可能是执行 &lt;code&gt;Insert (OP_INSERT)&lt;/code&gt; 操作码的地方。&lt;/p&gt;

&lt;p&gt;根据上图我直接找到了 &lt;code&gt;vdbe.c&lt;/code&gt; 文件的位置，直奔主题。&lt;/p&gt;

&lt;p&gt;我发现有一个有 8000行代码的 &lt;code&gt;switch( pOp-&amp;gt;opcode )&lt;/code&gt; 语句，通过 &lt;code&gt;OP_INSERT&lt;/code&gt; 关键字找到插入操作对应的代码位置。&lt;/p&gt;

&lt;p&gt;在对应分支的第一行中，总算找到了相关的线索:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt; Mem *pData;       /* MEM cell holding data for the record to be inserted */
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以 &lt;code&gt;pData&lt;/code&gt; 指向要插入的记录数据。您可以在 &lt;code&gt;L5402&lt;/code&gt; 中看到&lt;code&gt;pData = &amp;amp;aMem[pOp-&amp;gt;p2];&lt;/code&gt;，它是如何将 &lt;code&gt;pData&lt;/code&gt; 值设置为虚拟机内存 &lt;code&gt;aMem&lt;/code&gt; 地址的，该地址位于虚拟机寄存器 &lt;code&gt;p2&lt;/code&gt; 所指向的位置。&lt;/p&gt;

&lt;p&gt;快速回顾一下: 首先在 &lt;code&gt;insert.c&lt;/code&gt; 文件我们了解到 &lt;code&gt;INSERT&lt;/code&gt; 语句被翻译成一堆虚拟机指令。然后通过 &lt;code&gt;INSERT&lt;/code&gt; 的数据通过这些&lt;code&gt;sqlite3vdbeXXX&lt;/code&gt; 调用到达虚拟机。我假设将 &lt;code&gt;OP_INSERT&lt;/code&gt; 操作码和数据注册到虚拟机是在第2593行:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, aRegIdx[i], regNewData);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面 &lt;code&gt;regNewData&lt;/code&gt; 的一个更详细的说明:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;** The regNewData parameter is the first register in a range that contains
** the data to be inserted or the data after the update.  There will be
** pTab-&amp;gt;nCol+1 registers in this range.  The first register (the one
** that regNewData points to) will contain the new rowid, or NULL in the
** case of a WITHOUT ROWID table.  The second register in the range will
** contain the content of the first table column.  The third register will
** contain the content of the second table column.  And so forth.
**
** The regOldData parameter is similar to regNewData except that it contains
** the data prior to an UPDATE rather than afterwards.  regOldData is zero
** for an INSERT.  This routine can distinguish between UPDATE and INSERT by
** checking regOldData for zero.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以，在这一点上，我们正在用数据执行机器代码。代码向下滚动一点，让我们看看如何使用 &lt;code&gt;pData&lt;/code&gt;。在 &lt;code&gt;L5448-L5449&lt;/code&gt; 处可以看到:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;  x.pData = pData-&amp;gt;z;
  x.nData = pData-&amp;gt;n;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;x&lt;/code&gt; 的定义如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt; BtreePayload x;   /* Payload to be inserted */
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完美。再向下滚动一点，我们看到:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;  rc = sqlite3BtreeInsert(pC-&amp;gt;uc.pCursor, &amp;amp;x,
      (pOp-&amp;gt;p5 &amp;amp; (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), 
      seekResult
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们终于找到了插入原始数据的位置。但是，我们怎么知道它的格式和这里记录的一样呢? 如果仔细查看示例 &lt;code&gt;INSERT&lt;/code&gt; 中的虚拟机代码，在&lt;code&gt;INSERT&lt;/code&gt; 操作码之前有一个 &lt;code&gt;MakeRecord&lt;/code&gt; 操作码，它负责构建记录。&lt;/p&gt;

&lt;p&gt;你可以在 &lt;code&gt;vdb.c&lt;/code&gt; 文件中查看 &lt;code&gt;OP_MakeRecord&lt;/code&gt; 实现，并看到以下注释:&lt;/p&gt;

&lt;p&gt;You can check the OP_MakeRecord implementation at vdbe.c file and see the following comment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;将 &lt;code&gt;P1&lt;/code&gt; 开头的 &lt;code&gt;P2&lt;/code&gt; 寄存器转换为记录格式，用作数据库表中的数据记录或索引中的键。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 &lt;code&gt;case&lt;/code&gt; 语句的最后几行看到了关键部分:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;  /* Invoke the update-hook if required. */
  if( rc ) goto abort_due_to_error;
  if( pTab ){
    assert( db-&amp;gt;xUpdateCallback!=0 );
    assert( pTab-&amp;gt;aCol!=0 );
    db-&amp;gt;xUpdateCallback(db-&amp;gt;pUpdateArg,
           (pOp-&amp;gt;p5 &amp;amp; OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT,
           zDb, pTab-&amp;gt;zName, x.nKey);
  }
  break;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看来我需要的东西都在这里了。更新钩子钩子和原始数据。只需要更新时传递给回调函数即可。&lt;/p&gt;

&lt;h2 id=&#34;3-开始定制-sqlite&#34;&gt;3. 开始定制 SQLite&lt;/h2&gt;

&lt;p&gt;这就是我期望的 API：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;db-&amp;gt;xUpdateCallback(db-&amp;gt;pUpdateArg,
	(pOp-&amp;gt;p5 &amp;amp; OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT,
	zDb, pTab-&amp;gt;zName, x.nKey, pData-&amp;gt;z, pData-&amp;gt;n);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;传递的是数据（&lt;code&gt;pData-&amp;gt;z&lt;/code&gt;）和其大小(&lt;code&gt;pData-&amp;gt;n&lt;/code&gt;)。&lt;/p&gt;

&lt;p&gt;为了解释函数签名的变化，还需要在多个地方进行相应的修改。&lt;/p&gt;

&lt;p&gt;以下是 &lt;code&gt;fossil&lt;/code&gt; 工具提示的变化的源文件：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;EDITED     src/main.c
EDITED     src/sqlite.h.in
EDITED     src/sqlite3ext.h
EDITED     src/sqliteInt.h
EDITED     src/tclsqlite.c
EDITED     src/vdbe.c
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还有一些针对编译提示的修改。&lt;/p&gt;

&lt;h2 id=&#34;4-克隆一份-go-sqlite-驱动&#34;&gt;4. 克隆一份 Go SQLite 驱动&lt;/h2&gt;

&lt;p&gt;现在是时候在一个 Go 程序中创建一个简单的测试了。我比较熟悉与 SQLite 交互的 &lt;code&gt;mattn/go-sqlite3&lt;/code&gt; 驱动程序。该项目通过导入SQLite合并文件并通过CGO绑定工作。&lt;/p&gt;

&lt;p&gt;因此还需要再克隆下 Go SQLite 驱动，更新被我修改的文件。并在Go API中进行了必要的更新以访问新值。&lt;/p&gt;

&lt;p&gt;主要是对 &lt;code&gt;updateHookTrampoline&lt;/code&gt; 的更改，现在接收记录为 &lt;code&gt;*C.Char&lt;/code&gt; 和 &lt;code&gt;int&lt;/code&gt; 类型的数据大小，转型为字节 Slice 并将其传递给回调函数:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func updateHookTrampoline(handle unsafe.Pointer, op int, db *C.char, table *C.char, rowid int64, data *C.char, size int) {
	callback := lookupHandle(handle).(func(int, string, string, int64, []byte))
	callback(op, C.GoString(db), C.GoString(table), rowid, C.GoBytes(unsafe.Pointer(data), C.int(size)))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;RegisterUpdateHook&lt;/code&gt; 函数也需要做同样的调整。&lt;/p&gt;

&lt;h2 id=&#34;5-改动后的效果&#34;&gt;5. 改动后的效果&lt;/h2&gt;

&lt;p&gt;现在已经准备好了测试的所有东西。让我们运行一个简单的例子，灵感来自 &lt;a href=&#34;https://fly.io/blog/sqlite-internals-btree/&#34;&gt;SQLite Internals: Pages &amp;amp; B-trees&lt;/a&gt; 博客文章。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import (
	&amp;quot;database/sql&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;log&amp;quot;
	&amp;quot;os&amp;quot;

	&amp;quot;github.com/mattn/go-sqlite3&amp;quot;
)

func main() {
	sqlite3conn := []*sqlite3.SQLiteConn{}
	sql.Register(&amp;quot;sqlite3_with_hook_example&amp;quot;,
		&amp;amp;sqlite3.SQLiteDriver{
			ConnectHook: func(conn *sqlite3.SQLiteConn) error {
				sqlite3conn = append(sqlite3conn, conn)
				conn.RegisterUpdateHook(func(op int, db string, table string, rowid int64, data []byte) {
					switch op {
					case sqlite3.SQLITE_INSERT:
						fmt.Printf(&amp;quot;%x\n&amp;quot;, data)
					}
				})
				return nil
			},
		})
	os.Remove(&amp;quot;./foo.db&amp;quot;)

	srcDb, err := sql.Open(&amp;quot;sqlite3_with_hook_example&amp;quot;, &amp;quot;./foo.db&amp;quot;)
	if err != nil {
		log.Fatal(err)
	}
	defer srcDb.Close()
	srcDb.Ping()

	_, err = srcDb.Exec(`CREATE TABLE sandwiches (
		id INTEGER PRIMARY KEY,
		name TEXT,
		length REAL,
		count INTEGER
	);`)
	if err != nil {
		log.Fatal(err)
	}
	_, err = srcDb.Exec(&amp;quot;INSERT INTO sandwiches (name, length, count) VALUES (&#39;Italian&#39;, 7.5, 2);&amp;quot;)
	if err != nil {
		log.Fatal(err)
	}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;不要忘记添加更新 &lt;code&gt;go.mod&lt;/code&gt; 文件 &lt;code&gt;replace github.com/mattn/go-sqlite3 =&amp;gt; github.com/brunocalza/go-sqlite3 v0.0.0-20220926005737-36475033d841&lt;/code&gt;，重新定向驱动。&lt;/p&gt;

&lt;p&gt;运行后应该得到以下的结果：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;05001b07014974616c69616e401e00000000000002
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这正是 &lt;code&gt;(&#39;Italian&#39;, 7.5, 2)&lt;/code&gt; 数据的 Efficient Sandwich 编码的结果，不包含主键和记录的长度(前两个字节)。&lt;/p&gt;

&lt;p&gt;看到输出结果我才发现能够理解SQLite源代码的部分内容真的很有趣，尽管我不理解它的大部分。但是我做了一些更改并看到这些更改，并通过 Go 的驱动程序看到结果的变化。&lt;/p&gt;

&lt;p&gt;老实说这种更改数据库源代码的方法风险太大。与新版本保持同步也是一个太大的问题，但这是一个值得记录的有趣经历。&lt;/p&gt;</description>
      
    </item>
    
    <item>
      <title>使用Go语言开发iOS应用(Swift版)</title>
      <link>https://chai2010.cn/post/golang/go-ios-app/</link>
      <pubDate>Wed, 25 May 2016 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/golang/go-ios-app/</guid>
      
        <description>

&lt;h1 id=&#34;使用go语言开发ios应用-swift版&#34;&gt;使用Go语言开发iOS应用(Swift版)&lt;/h1&gt;

&lt;p&gt;本文加上读者对Go语言和Swift语言都有一定了解, 但是对二者混合使用不了解的同学.&lt;/p&gt;

&lt;p&gt;本教程是基于一个真实上架的iOS应用做的简单的总结。&lt;/p&gt;

&lt;p&gt;我们先看看运行效果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://chai2010.cn/images/go-ios-yjyy-01.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;

&lt;p&gt;Go语言是Google公司于2010年开源的一个面向网络服务和多并发环境的编程语言，特点是简单。
但是因为简单，也就只能实现90%的性能，这是Go语言的最大优点，因为 少即是多 的道理不是每个人都能领悟的。&lt;/p&gt;

&lt;p&gt;Swift是Apple公司于2014年发布的用来替代ObjectiveC的语言，主要面向iOS和OS X上的界面程序开发。
当然用swift来开发服务器也是大家关注的一个领域，作者看好在不远的将来Swift将逐步替代C++和Rust语言。&lt;/p&gt;

&lt;p&gt;Go语言和Swift语言本来是风马牛不相及的两个语言，为何非一定要整到一起呢？
原因很简单，因为作者是一个Go粉，同时也算是半个Swift粉；想试水iOS开发，但是实在是受不了ObjectiveC的裹脚布语法。&lt;/p&gt;

&lt;p&gt;补充下：本人虽然不喜欢ObjectiveC的语法，但是觉得ObjectiveC的runtime还是很强悍的。
理论上，基于ObjectiveC的runtime，可以用任何流行的编程语言来开发iOS应用，RubyMotion就是一个例子。&lt;/p&gt;

&lt;p&gt;其实，现在流行的绝大部分语言都有一个交集，就是c语言兼容的二进制接口。
所以说，C++流行并不是C++多厉害，而是它选择几本无缝兼容了C语言的规范。&lt;/p&gt;

&lt;p&gt;但是，完全兼容C语言的规范也有缺点，就是语言本身无法自由地发展，因为很多地方会受到C语言编程模型的限制。
C++和ObjectiveC是两个比较有代表的例子。&lt;/p&gt;

&lt;p&gt;所以说，Swift一出世就兼容C语言的二进制接口规范，同时抱紧了ObjectiveC的runtime大腿，而去自己确实有很大优秀的特性。&lt;/p&gt;

&lt;p&gt;但是，我们这里暂时不关心Swift和ObjectiveC的混合编程，我们只关注作为ObjectiveC子集的C语言如何与Swift混合编程。&lt;/p&gt;

&lt;h2 id=&#34;swift调用c函数&#34;&gt;Swift调用C函数&lt;/h2&gt;

&lt;p&gt;Swift调用C函数的方法有多种：通过ObjectiveC桥接调用和直接调用。其实两者的原理是一样的，我个人跟喜欢选择最直接也最暴力的直接调用C函数的方式。&lt;/p&gt;

&lt;p&gt;比如有一个C函数:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void getInput(int *output) {
    scanf(&amp;quot;%i&amp;quot;, output);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;生成一个桥接的头文件&lt;code&gt;xxx-Bridging-Header.h&lt;/code&gt;，里面包含c函数规格说明：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;void getInput(int *output);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;swift就可以直接使用了:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import Foundation

var output: CInt = 0
getInput(&amp;amp;output)

println(output)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果不用桥接文件，可以在swift中声明一个Swift函数，对应C函数:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@_silgen_name(&amp;quot;getInput&amp;quot;) func getInput_swift(query:UnsafePointer&amp;lt;CInt&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了明确区分C函数和swift函数，我们将&lt;code&gt;getInput&lt;/code&gt;重新声明为&lt;code&gt;getInput_swift&lt;/code&gt;，使用方法和前面一样：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import Foundation

var output: CInt = 0
getInput_swift(&amp;amp;output)

println(output)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;swift中如何管理c返回的内存&#34;&gt;Swift中如何管理c返回的内存&lt;/h2&gt;

&lt;p&gt;Swift语言本身是自带ARC的，用户很少直接关注内存问题。但是C函数如果返回内存到Swift空间，
Swift的ARC是无效的，需要手工释放C内存。&lt;/p&gt;

&lt;p&gt;假设我们自己用C语言实现了一个字符串克隆的函数:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;char* MyStrDup(char* s) {
    return strdup(s);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在swift中可以这样使用：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@_silgen_name(&amp;quot;MyStrDup&amp;quot;)
func MyStrDup_swift(query:UnsafePointer&amp;lt;CChar&amp;gt;) -&amp;gt; UnsafeMutablePointer&amp;lt;CChar&amp;gt;

let p = MyStrDup_swift(&amp;quot;hello swift-c!&amp;quot;)
let s = String.fromCString(p)!
p.dealloc(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用&lt;code&gt;String.fromCString(p)!&lt;/code&gt;从C字符串构建一个swift字符串，然后手工调用&lt;code&gt;p.dealloc(1)&lt;/code&gt;释放c字符串内存空间。&lt;/p&gt;

&lt;p&gt;函数调用和内存管理是跨语言编程中最重要的两个基础问题，目前已久初步可以工作了。&lt;/p&gt;

&lt;h2 id=&#34;go语言导出c静态库&#34;&gt;Go语言导出C静态库&lt;/h2&gt;

&lt;p&gt;Go语言提供了一个cgo的工具，用于Go语言和C语言交互。这是Go语言使用C语言的一个例子:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package main

//#include &amp;lt;stdio.h&amp;gt;
import &amp;quot;C&amp;quot;

func main() {
    C.puts(C.CString(&amp;quot;abc&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;既然要交互，自然会涉及到C语言回调Go语言函数的情形。为此，cgo提供了一个&lt;code&gt;export&lt;/code&gt;注释命令，
用于生成Go语言函数对应的C语言函数:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;//export MyStrDup
func MyStrDup(s *C.char) *C.char {
    return C.strdup(s)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;MyStrDup&lt;/code&gt;指定的名字必须和Go函数名字一致，函数的参数最后是C语言支持的类型。&lt;/p&gt;

&lt;p&gt;现在，我们就得到了用Go语言实现的&lt;code&gt;MyStrDup&lt;/code&gt;函数，使用方法和前面的C语言实现的&lt;code&gt;MyStrDup&lt;/code&gt;是一样的。&lt;/p&gt;

&lt;p&gt;和引用C语言函数库遇到的问题一样，我们如何在工程中引用这些C代码或Go代码实现的函数呢？&lt;/p&gt;

&lt;p&gt;答案还是来自C语言：将代码构建为C静态库或者C动态库，然后将静态库或动态库导入Swift工程。&lt;/p&gt;

&lt;p&gt;但是，对于iOS来说，构建C静态库或者C动态库的过程要麻烦（使用xcode也只是隐藏了构建的具体步骤）。&lt;/p&gt;

&lt;p&gt;因为，iOS涉及到多种CPU架构：模拟器的x86、4s的32位arm、5s以后的64位arm，64位arm中还有不同当版本&amp;hellip;&lt;/p&gt;

&lt;p&gt;这是C静态库或者C动态库构建始终都要面对的问题。&lt;/p&gt;

&lt;h2 id=&#34;交叉构建的参数&#34;&gt;交叉构建的参数&lt;/h2&gt;

&lt;p&gt;Go1.6之后增加了构建C静态库的支持，交叉编译也非常简单，只需要设置好&lt;code&gt;GOARCH&lt;/code&gt;和&lt;code&gt;GOOS&lt;/code&gt;就行。&lt;/p&gt;

&lt;p&gt;因为，iOS的&lt;code&gt;GOOS&lt;/code&gt;只有&lt;code&gt;Darwin&lt;/code&gt;一种类型，我们只需要设置&lt;code&gt;GOARCH&lt;/code&gt;就可以了。&lt;/p&gt;

&lt;p&gt;要构建C静态库，我们需要将上面的&lt;code&gt;MyStrDup&lt;/code&gt;实现放到一个&lt;code&gt;main&lt;/code&gt;包中:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package main

//#include &amp;lt;string.h&amp;gt;
import &amp;quot;C&amp;quot;

func main() {
    //
}

//export MyStrDup
func MyStrDup(s *C.char) *C.char {
    return C.strdup(s)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;main&lt;/code&gt;包中的&lt;code&gt;main&lt;/code&gt;函数不会被执行，但是&lt;code&gt;init&lt;/code&gt;函数依然有效。&lt;/p&gt;

&lt;p&gt;使用下面的命令就可以构建当前系统的c静态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go build -buildmode=c-archive
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;要交叉编译iOS可用的c静态库，我们需要先设置&lt;code&gt;GOARCH&lt;/code&gt;，同时打开cgo特性（交叉编译时，cgo默认是关闭的）。&lt;/p&gt;

&lt;p&gt;下面是构建针对模拟器的x86/amd64类型的C静态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export CGO_ENABLED=1
export GOARCH=amd64

go build -buildmode=c-archive -o libmystrdup_amd64.a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们使用&lt;code&gt;-o&lt;/code&gt;参数指定了输出的静态库文件名。构建命令同时还会生成一个头文件（可能叫&lt;code&gt;libmystrdup_386.h&lt;/code&gt;），
我们没有用到这个头文件，直接删除掉就可以。&lt;/p&gt;

&lt;p&gt;下面是构建针对模拟器的x86/386类型的C静态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export CGO_ENABLED=1
export GOARCH=386

go build -buildmode=c-archive -o libmystrdup_386.a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在构建x86/386类型的C静态库时可能会有一些link错误，我们暂时先用以下方法回避。&lt;/p&gt;

&lt;p&gt;创建一个&lt;code&gt;patch_386.go&lt;/code&gt;文件：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// Copyright 2016 &amp;lt;chaishushan{AT}gmail.com&amp;gt;. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// 针对iOS模拟器link时缺少的函数
// 属于临时解决方案

package main

/*
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;time.h&amp;gt;

size_t fwrite$UNIX2003(const void* a, size_t b, size_t c, FILE* d) {
    return fwrite(a, b, c, d);
}

char* strerror$UNIX2003(int errnum) {
    return strerror(errnum);
}

time_t mktime$UNIX2003(struct tm * a) {
    return mktime(a);
}
double strtod$UNIX2003(const char * a, char ** b) {
    return strtod(a, b);
}

int setenv$UNIX2003(const char* envname, const char* envval, int overwrite) {
    return setenv(envname, envval, overwrite);
}
int unsetenv$UNIX2003(const char* name) {
    return unsetenv(name);
}

*/
import &amp;quot;C&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当然，还是会有一些警告出现，暂时忽略它们。&lt;/p&gt;

&lt;h2 id=&#34;构建多cpu类型的静态库&#34;&gt;构建多cpu类型的静态库&lt;/h2&gt;

&lt;p&gt;然后，将C静态库加入到ios的xcode工程文件就可以了。&lt;/p&gt;

&lt;p&gt;x86构建是比较简单的，因为我们可以默认使用本地的构建命令。
但是，如果要构建arm的静态库，则需要先配置好构建环境。&lt;/p&gt;

&lt;p&gt;我从Go代码中扣出了一个&lt;code&gt;clangwrap.sh&lt;/code&gt;脚本（好像是在&lt;code&gt;$GOROOT/misci/ios&lt;/code&gt;目录）:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#!/bin/sh
# This uses the latest available iOS SDK, which is recommended.
# To select a specific SDK, run &#39;xcodebuild -showsdks&#39;
# to see the available SDKs and replace iphoneos with one of them.
SDK=iphoneos
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
export IPHONEOS_DEPLOYMENT_TARGET=7.0
# cmd/cgo doesn&#39;t support llvm-gcc-4.2, so we have to use clang.
CLANG=`xcrun --sdk $SDK --find clang`

if [ &amp;quot;$GOARCH&amp;quot; == &amp;quot;arm&amp;quot; ]; then
    CLANGARCH=&amp;quot;armv7&amp;quot;
elif [ &amp;quot;$GOARCH&amp;quot; == &amp;quot;arm64&amp;quot; ]; then
    CLANGARCH=&amp;quot;arm64&amp;quot;
else
    echo &amp;quot;unknown GOARCH=$GOARCH&amp;quot; &amp;gt;&amp;amp;2
    exit 1
fi

exec $CLANG -arch $CLANGARCH -isysroot $SDK_PATH &amp;quot;$@&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;里面比较重要的是&lt;code&gt;IPHONEOS_DEPLOYMENT_TARGET&lt;/code&gt;环境变量，这里意思是目标最低支持ios7.0系统。&lt;/p&gt;

&lt;p&gt;构建arm64环境的静态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export CGO_ENABLED=1
export GOARCH=arm64
export CC=$PWD/clangwrap.sh
export CXX=$PWD/clangwrap.sh

go build -buildmode=c-archive -o libmystrdup_arm64.a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;构建armv7环境的静态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export CGO_ENABLED=1
export GOARCH=arm
export GOARM=7
export CC=$PWD/clangwrap.sh
export CXX=$PWD/clangwrap.sh

go build -buildmode=c-archive -o libmystrdup_armv7.a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后我们用&lt;code&gt;lipo&lt;/code&gt;命令将以上这些不同的静态库打包到一个静态库中：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;lipo libmystrdup_386.a libmystrdup_adm64.a libmystrdup_arm64.a libmystrdup_armv7.a -create -output libmystrdup.a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样的话，只要引入一个静态库就可以支持不同cpu类型的目标了。&lt;/p&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;毛主席教导我们：要在战争中学习战争。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://appsto.re/cn/QH8ocb.i&#34;&gt;野鸡医院&lt;/a&gt;&lt;/strong&gt; 这个app是作者第一个iOS应用，这篇教程也是在iOS开发过程逐步学习总结的结果。&lt;/p&gt;

&lt;p&gt;完整的例子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AppStore安装: &lt;a href=&#34;https://appsto.re/cn/QH8ocb.i&#34;&gt;https://appsto.re/cn/QH8ocb.i&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Swift工程: &lt;a href=&#34;https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift&#34;&gt;https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Go静态库工程: &lt;a href=&#34;https://github.com/chai2010/ptyy/tree/master/cmd/yjyy&#34;&gt;https://github.com/chai2010/ptyy/tree/master/cmd/yjyy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;静态库构建脚本: &lt;a href=&#34;https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift/vendor/gopkg&#34;&gt;https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift/vendor/gopkg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所有的代码均可以免费获取(BSD协议): &lt;a href=&#34;https://github.com/chai2010/ptyy&#34;&gt;https://github.com/chai2010/ptyy&lt;/a&gt;&lt;/p&gt;
</description>
      
    </item>
    
    <item>
      <title>Go和C如何共享内存资源</title>
      <link>https://chai2010.cn/post/golang/go13-cgo/</link>
      <pubDate>Sun, 13 Oct 2013 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/golang/go13-cgo/</guid>
      
        <description>

&lt;p&gt;Go语言作为一个现代化的编程语言以及支持垃圾内存的自动回收特性(GC).
我们现在关注的是C语言返回的内存资源的自动回收技术.&lt;/p&gt;

&lt;h2 id=&#34;cgo初步&#34;&gt;CGO初步&lt;/h2&gt;

&lt;p&gt;Go语言的cgo技术允许在Go代码中方便的使用C语言代码. 基本的用法如下:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package rand

/*
#include &amp;lt;stdlib.h&amp;gt;
*/
import &amp;quot;C&amp;quot;

func Random() int {
    return int(C.random())
}

func Seed(i int) {
    C.srandom(C.uint(i))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中&lt;code&gt;&amp;quot;C&amp;quot;&lt;/code&gt;是导入一个虚拟的包, 用于引用C语言的符号.&lt;/p&gt;

&lt;p&gt;Go语言和C语言通讯交互主要是通过传递参数和返回值. 其中参数和返回值除了基本的
数据类型外, 最重要的是如何相互&lt;strong&gt;传递/共享&lt;/strong&gt;二进制的内存块.&lt;/p&gt;

&lt;h2 id=&#34;go向c语言传递内存块&#34;&gt;Go向C语言传递内存块&lt;/h2&gt;

&lt;p&gt;这个最简单, 有很多现成的例子:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package print

// #include &amp;lt;stdio.h&amp;gt;
// #include &amp;lt;stdlib.h&amp;gt;
import &amp;quot;C&amp;quot;
import &amp;quot;unsafe&amp;quot;

func Print(s string) {
    cs := C.CString(s)
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.free(unsafe.Pointer(cs))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为C语言的字符串结尾有&lt;code&gt;\0&lt;/code&gt;, Go语言字符串没有&lt;code&gt;\0&lt;/code&gt;, 因此需要重新构造一个C字符串.
其中 &lt;code&gt;C.CString(s)&lt;/code&gt; 是构造一个C的字符串, 然后复制字符串并传入 &lt;code&gt;C.fputs&lt;/code&gt;.
用完之后不要忘记调用&lt;code&gt;C.free&lt;/code&gt;释放新创建的C字符串(可以用&lt;code&gt;defer&lt;/code&gt;释放).&lt;/p&gt;

&lt;p&gt;如果是普通的内存块, 可以直接传递给C函数:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package main

// #include &amp;lt;stdlib.h&amp;gt;
import &amp;quot;C&amp;quot;
import &amp;quot;unsafe&amp;quot;

func Copy(dst, src []byte, size int) {
    C.memcpy(unsafe.Pointer(&amp;amp;dst[0]), unsafe.Pointer(&amp;amp;src[0]), C.size_t(size)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个代码并没有涉及内存的创建/复制/删除等额外的操作, 是比较理想的集成方式.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意:&lt;/strong&gt; 在C语言使用该资源期间要防止Go语言的GC提前释放被C语言使用的Go内存!&lt;/p&gt;

&lt;h2 id=&#34;c向go语言返回内存块&#34;&gt;C向Go语言返回内存块&lt;/h2&gt;

&lt;p&gt;如果是C语言向Go返回内存块, 一般是先创建一个对应的Go的切片. 有现成的函数&lt;code&gt;C.GoBytes()&lt;/code&gt;可以基于C的内存块构造切片.&lt;/p&gt;

&lt;p&gt;比如获取C返回的内存块数据:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package main

// #include &amp;lt;stdlib.h&amp;gt;
import &amp;quot;C&amp;quot;
import &amp;quot;unsafe&amp;quot;

func GetData() []byte {
    p := C.malloc(1024)
    defer C.free(p)
    return C.GoBytes(p, 1024)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;代码并不复杂. 但是效率并不理想: 其中需要新创建一个Go的切片, 并进行一次冗余的复制操作.&lt;/p&gt;

&lt;p&gt;如果想去掉冗余的复制操作, 就需要基于C的内存块构造切片. 这个需要依赖Go语言的反射技术.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package main

// #include &amp;lt;stdlib.h&amp;gt;
import &amp;quot;C&amp;quot;
import &amp;quot;unsafe&amp;quot;
import &amp;quot;reflect&amp;quot;

func GetData() []byte {
    p := C.malloc(1024)
    var s []byte
    h := (*reflect.SliceHeader)((unsafe.Pointer(&amp;amp;s)))
    h.Cap = 1024
    h.Len = 1024
    h.Data = uintptr(p)
    return s
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回的&lt;code&gt;s&lt;/code&gt;是基于C语言内存块构造的切片. 没有冗余的内存复制操作.&lt;/p&gt;

&lt;p&gt;但是, 上面的代码却有内存泄漏的问题. Go语言的GC并不会自动释放&lt;code&gt;C.malloc&lt;/code&gt;释放的内存.&lt;/p&gt;

&lt;p&gt;如果需要Go语言的GC自动管理C语言返回的内存, 需要基于之前讲过的 &amp;ldquo;&lt;a href=&#34;http://my.oschina.net/chai2010/blog/161797&#34;&gt;Go语言资源自动回收技术[OSC源创会主题补充3]&lt;/a&gt;&amp;rdquo; .&lt;/p&gt;

&lt;p&gt;简而言之, 就是要将C语言的内存块绑定到一个Go语言的内存资源, 然后依靠&lt;code&gt;runtime.SetFinalizer&lt;/code&gt;的技术管理C语言的内存块.&lt;/p&gt;

&lt;p&gt;核心代码如下:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;type Slice struct {
    Data []byte
    data *c_slice_t
}

type c_slice_t struct {
    p unsafe.Pointer
    n int
}

func newSlice(p unsafe.Pointer, n int) *Slice {
    data := &amp;amp;c_slice_t{p, n}
    runtime.SetFinalizer(data, func(data *c_slice_t) {
        C.free(data.p)
    })
    s := &amp;amp;Slice{data: data}
    h := (*reflect.SliceHeader)((unsafe.Pointer(&amp;amp;s.Data)))
    h.Cap = n
    h.Len = n
    h.Data = uintptr(p)
    return s
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中 &lt;code&gt;newSlice&lt;/code&gt; 基于C语言的内存块构造 &lt;code&gt;Slice&lt;/code&gt; 结构体.
如果 &lt;code&gt;Slice.data&lt;/code&gt; 资源没有被引用, 则会自动触发C语言的内存释放函数.&lt;/p&gt;

&lt;h2 id=&#34;完整的测试代码&#34;&gt;完整的测试代码&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;package main

/*
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;

void print(char* s) {
    printf(&amp;quot;print: %s\n&amp;quot;, s);
}
*/
import &amp;quot;C&amp;quot;
import (
    &amp;quot;fmt&amp;quot;
    &amp;quot;reflect&amp;quot;
    &amp;quot;runtime&amp;quot;
    &amp;quot;time&amp;quot;
    &amp;quot;unsafe&amp;quot;
)

type Slice struct {
    Data []byte
    data *c_slice_t
}

type c_slice_t struct {
    p unsafe.Pointer
    n int
}

func newSlice(p unsafe.Pointer, n int) *Slice {
    data := &amp;amp;c_slice_t{p, n}
    runtime.SetFinalizer(data, func(data *c_slice_t) {
        println(&amp;quot;gc:&amp;quot;, data.p)
        C.free(data.p)
    })
    s := &amp;amp;Slice{data: data}
    h := (*reflect.SliceHeader)((unsafe.Pointer(&amp;amp;s.Data)))
    h.Cap = n
    h.Len = n
    h.Data = uintptr(p)
    return s
}

func testSlice() {
    msg := &amp;quot;hello world!&amp;quot;
    p := C.calloc((C.size_t)(len(msg) + 1), 1)
    println(&amp;quot;malloc:&amp;quot;, p)

    s := newSlice(p, len(msg)+1)
    copy(s.Data, []byte(msg))

    fmt.Printf(&amp;quot;fmt.Printf: %s\n&amp;quot;, string(s.Data))
    C.print((*C.char)(p))
}

func main() {
    testSlice()

    runtime.GC()
    runtime.Gosched()
    time.Sleep(1e9)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;测试程序的输出:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;D:&amp;gt;go run hello.go
malloc: 0x6f7f50
fmt.Printf: hello world!
print: hello world!
gc: 0x6f7f50
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;注: Go1.3之前有效, Go1.4之后改了移动栈.&lt;/em&gt;&lt;/p&gt;
</description>
      
    </item>
    
    <item>
      <title>C？Go？Cgo！[翻译]</title>
      <link>https://chai2010.cn/post/golang/cgo2013/</link>
      <pubDate>Tue, 02 Apr 2013 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/golang/cgo2013/</guid>
      
        <description>

&lt;ul&gt;
&lt;li&gt;原文： &lt;a href=&#34;http://golang.org/doc/articles/c_go_cgo.html&#34;&gt;http://golang.org/doc/articles/c_go_cgo.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;中文： &lt;a href=&#34;http://zh-golang.appsp0t.com/doc/articles/c_go_cgo.html&#34;&gt;http://zh-golang.appsp0t.com/doc/articles/c_go_cgo.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cgo允许在Go包中调用C代码。如果Go代码含有特殊的cgo语法，可以通过cgo生成相应的Go和C文件，它们可以被编译到一个Go包中。&lt;/p&gt;

&lt;p&gt;以一个例子开始，下面的Go包提供了 &lt;code&gt;Random&lt;/code&gt; 和 &lt;code&gt;Seed&lt;/code&gt; 两个函数，它们是基于C语言的 &lt;code&gt;random&lt;/code&gt; 和 &lt;code&gt;srandom&lt;/code&gt; 函数的实现。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package rand

/*
#include &amp;lt;stdlib.h&amp;gt;
*/
import &amp;quot;C&amp;quot;

func Random() int {
    return int(C.random())
}

func Seed(i int) {
    C.srandom(C.uint(i))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们从 &lt;code&gt;import&lt;/code&gt; 语句开始，讲解相关的代码。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rand&lt;/code&gt; 包导入了一个 &lt;code&gt;&amp;quot;C&amp;quot;&lt;/code&gt; 包，但是这个包并不是由Go标准库提供。因为 C 包是Cgo工具生成的一个虚拟包，它映射到C语言的名字空间。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rand&lt;/code&gt; 包中有几个地方使用了 C 包： &lt;code&gt;C.random&lt;/code&gt;、&lt;code&gt;C.srandom&lt;/code&gt;、&lt;code&gt;C.uint(i)&lt;/code&gt; 和 &lt;code&gt;import&lt;/code&gt; 语句。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Random&lt;/code&gt; 函数调用C语言标准库中的 &lt;code&gt;random&lt;/code&gt; 函数，然后返回结果。 在C语言中，&lt;code&gt;random&lt;/code&gt; 返回的结果为 &lt;code&gt;long&lt;/code&gt; 类型，对应cgo生成的 &lt;code&gt;C.long&lt;/code&gt;。在函数返回前我们必须将C类型转换为Go类型。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func Random() int {
    return int(C.random())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面是一个等价的实现，为了更好说明类型转换的使用，这里使用了一个临时变量。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func Random() int {
    var r C.long = C.random()
    return int(r)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Seed 函数进行相反的类型转换。它将传入的Go的 &lt;code&gt;int&lt;/code&gt; 类型变量转换为C语言的 &lt;code&gt;unsigned int&lt;/code&gt;，然后传入C语言的 &lt;code&gt;srandom&lt;/code&gt; 函数。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func Seed(i int) {
    C.srandom(C.uint(i))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Cgo能够知道 &lt;code&gt;unsigned int&lt;/code&gt; 对应 &lt;code&gt;C.uint&lt;/code&gt; 类型。关于数值类型的详细说明可以参考&lt;a href=&#34;http://golang.org/cmd/cgo&#34;&gt;Cgo文档&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;到此为止，只有 &lt;code&gt;import&lt;/code&gt; 语句的注释还没有解释。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/*
#include &amp;lt;stdlib.h&amp;gt;
*/
import &amp;quot;C&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Cgo可以识别这个注释。注释中，任意以 &lt;code&gt;#cgo&lt;/code&gt; 开头的行会被忽略，它们是cgo的扩展命令。 剩余的行在编译包的C代码时时，将被当作头文件处理。在这个例子中，虽然只有一个 &lt;code&gt;#include&lt;/code&gt; 语句，但是可以包含任意的C语言代码。在构建包中C代码时，&lt;code&gt;#cgo&lt;/code&gt; 规则可以指定用于编译和连接的选项。&lt;/p&gt;

&lt;p&gt;有一点要注意：如果使用了 &lt;code&gt;//export&lt;/code&gt; 规则，那么注释中的C代码将只能包含对应函数的声明（&lt;code&gt;extern int f();&lt;/code&gt;）， 而不能是对应函数的定义（&lt;code&gt;int f() { return 1; }&lt;/code&gt;）。使用 &lt;code&gt;//export&lt;/code&gt; 规则，可以使Go函数被C语言函数调用。&lt;/p&gt;

&lt;p&gt;关于 &lt;code&gt;#cgo&lt;/code&gt; 和 &lt;code&gt;//export&lt;/code&gt; 的用法在 &lt;a href=&#34;http://golang.org/cmd/cgo/&#34;&gt;cgo文档&lt;/a&gt; 中有详细说明。&lt;/p&gt;

&lt;h2 id=&#34;字符串相关&#34;&gt;字符串相关&lt;/h2&gt;

&lt;p&gt;和Go语言不同，C语言没有明确的字符串类型。在C语言中，字符串表现为以 &lt;code&gt;NULL&lt;/code&gt; 结尾的 &lt;code&gt;char&lt;/code&gt;数组。&lt;/p&gt;

&lt;p&gt;Go语言和C语言字符串之间的转换由以下函数完成：&lt;code&gt;C.CString&lt;/code&gt;、&lt;code&gt;C.GoString&lt;/code&gt; 和 &lt;code&gt;C.GoStringN&lt;/code&gt;。这些函数函数在转换时均构造了一个字符串的副本。&lt;/p&gt;

&lt;p&gt;这里是 Print 函数的另一个实现，它通过C语言的 &lt;code&gt;stdio&lt;/code&gt; 标准库函数 &lt;code&gt;fputs&lt;/code&gt; 将字符串写到标准输出：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;package print

// #include &amp;lt;stdio.h&amp;gt;
// #include &amp;lt;stdlib.h&amp;gt;
import &amp;quot;C&amp;quot;
import &amp;quot;unsafe&amp;quot;

func Print(s string) {
    cs := C.CString(s)
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.free(unsafe.Pointer(cs))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Go语言的GC并不能管理C语言函数分配的内存。当使用 &lt;code&gt;C.CString&lt;/code&gt;（使用C函数分配了内存）返回C字符串时， 必须要记得在完成后用 &lt;code&gt;C.free&lt;/code&gt; 释放对应的内存。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;C.CString&lt;/code&gt; 返回C语言字符串起始地址，因此在函数返回时将它转换为 &lt;code&gt;unsafe.Pointer&lt;/code&gt; 类型，然后用 C.free释放对应内存空间。cgo中的一个常用习惯是在创建新内存后使用 &lt;code&gt;defer&lt;/code&gt; 释放对应的内存 （特别是在后面代码很复杂时），下面是重写的 &lt;code&gt;Print&lt;/code&gt; 函数：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;func Print(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;构建cgo包&#34;&gt;构建cgo包&lt;/h2&gt;

&lt;p&gt;要构建cgo包，只要直接简单执行 &lt;a href=&#34;http://golang.org/cmd/go/#Compile_packages_and_dependencies&#34;&gt;&amp;ldquo;go build&amp;rdquo;&lt;/a&gt; 或 &lt;a href=&#34;http://golang.org/cmd/go/#Compile_and_install_packages_and_dependencies&#34;&gt;&amp;ldquo;go install&amp;rdquo;&lt;/a&gt; 命令。 go命令可以识别 &lt;code&gt;&amp;quot;C&amp;quot;&lt;/code&gt; 虚拟包的语法，并且可以自动调用cgo生成相应的中间代码文件。&lt;/p&gt;

&lt;h2 id=&#34;更多的cgo资源&#34;&gt;更多的Cgo资源&lt;/h2&gt;

&lt;p&gt;在 &lt;a href=&#34;http://localhost:6060/cmd/cgo/&#34;&gt;cgo命令&lt;/a&gt; 文档中有C包的更多的细节说明和构建的详细流程。 在Go目录树中的 &lt;a href=&#34;http://golang.org/misc/cgo/&#34;&gt;cgo例子&lt;/a&gt; 演示了更全面的用法。&lt;/p&gt;

&lt;p&gt;如果是简单的Cgo例子，可以参考 &lt;a href=&#34;http://research.swtch.com/&#34;&gt;Russ Cox&lt;/a&gt; 的 &lt;a href=&#34;http://code.google.com/p/gosqlite/source/browse/sqlite/sqlite.go&#34;&gt;gosqlite&lt;/a&gt; 项目。 在 &lt;a href=&#34;https://godashboard.appspot.com/project?tag=cgo&#34;&gt;Go Project Dashboard&lt;/a&gt; 列表中有很多基于cgo的项目。&lt;/p&gt;

&lt;p&gt;最后，如果想了解Cgo的工作原理，可以查看runtime包中的 &lt;a href=&#34;http://golang.org/src/pkg/runtime/cgocall.c&#34;&gt;cgocall.c&lt;/a&gt; 代码。&lt;/p&gt;
</description>
      
    </item>
    
  </channel>
</rss>