> For the complete documentation index, see [llms.txt](https://docs.proudnet.com/proudnet.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.proudnet.com/proudnet.cn/proudnet/using_pn/pidl/using_pidl.md).

# 如何使用PIDL

## 使用Custom Build Rules

> <mark style="color:orange;">Display Name</mark> : 這是將在自訂建置規則中顯示的名稱。 例如) PIDL 規則\ <mark style="color:orange;">File Name</mark> : 建立規則檔名。 例如）PIDL\_Custom\_Build\_Rule\ <mark style="color:orange;">Directory</mark> : 指定規則檔案的儲存位置。 例如) C:\XXX\YYY

<figure><img src="/files/7l8imOJlfAtIIWKEQ0kp" alt=""><figcaption><p>Custom Build Rule 設置</p></figcaption></figure>

各項設定的詳細資訊如下。

```
Additional Dependencies : ..\..\..\util\PIDL.exe
Batching Separator :
Command Line : ..\..\..\util\PIDL.exe "$(InputPath)" -outdir .\
Display Name : PIDL
Execution Description : Compiling $(InputName).pidl ...
File Extensions : *.pidl
Name : PIDL Custom Build Rule
Outputs : $(InputDir)\$(InputName)_common.cpp;$(InputDir)\$(InputName)_common.h;$(InputDir)\$(InputName)_proxy.cpp;$(InputDir)\$(InputName)_proxy.h;$(InputDir)\$(InputName)_stub.cpp;$(InputDir)\$(InputName)_stub.h
```

完成設定後，在自訂建置規則中新增一條PIDL建置規則，如下所示。

<figure><img src="/files/CKjFYMzqr81Rn0kiailm" alt=""><figcaption><p>Custom Build Rules</p></figcaption></figure>

如果您檢查新建立的規則文件並在專案中建立文件，您將看到 PIDL 被自動選擇為建置工具。

## 使用命令提示字元(Command Prompt, cmd.exe)&#x20;

如果 PIDL AddOn 或自訂建置規則不可用，您可以透過 Windows 作業系統上的命令提示字元進行編譯。

<figure><img src="/files/n3yrP4lhzREeEAhib0ok" alt=""><figcaption></figcaption></figure>

對於cs，可以透過新增PIDL -cs命令來編譯。

<figure><img src="/files/0HHaYjVozdEmT0EYskG4" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}

* <mark style="color:orange;">PIDL.exe</mark>位於ProudNet安裝路徑的<mark style="color:orange;">util</mark>資料夾中。
* Debug, Release必須設定相同。
* 不要依照範例寫<mark style="color:orange;">PIDL.exe</mark>的路徑，而是設定為ProudNet安裝路徑的PIDL。
  {% endhint %}

{% hint style="warning" %}

* 在此範例中，建立了 Common - PIDL 資料夾並將其指定為 PIDL 編譯路徑 (outdir)。
* 請不要遵循包含路徑，而是指定您正在使用的項目所需的路徑。
  {% endhint %}

<figure><img src="/files/OpS6J3Umep0N5iLdXi2j" alt=""><figcaption><p>Common - 使用 PIDL 設定 outdir 後，將其設定為附加包含目錄。</p></figcaption></figure>

您可以透過檢查產生的來源檔案（將它們包含在 Visual Studio 專案屬性視窗中，然後建置）來使用它。

## 使用Customizations

{% hint style="info" %}
**Visual Stuido 2005, 2008 版本**

* 可以使用[**Custom Build Rules**](#custom-build-rules)建立規則檔案。
* 1 個副檔名為 <mark style="color:orange;">.rules</mark> 的規則文件
  {% endhint %}

{% hint style="info" %}
**Visual Studio 2010 或更高版本**

* 可以使用/不能使用[**Custom Build Rules**](#custom-build-rules)創建
* 需要三個檔案：<mark style="color:orange;">.props</mark>, <mark style="color:orange;">.targets</mark>, <mark style="color:orange;">.xml</mark>。 先前版本中建立的<mark style="color:orange;">.rules</mark> 檔案不能按原樣使用。
  {% endhint %}

> **2種使用customizations的方法**
>
> 1\. 在 Visual Studio 2005 或 2008 中建立專案後，建立 <mark style="color:orange;">.rules</mark> 檔案並將其設定為使用，然後將專案轉換為 2010 或更高版本的方法\
> : 轉換專案時，<mark style="color:orange;">.rules</mark> 檔案會自動轉換為 <mark style="color:orange;">.props</mark>, <mark style="color:orange;">.targets</mark>, <mark style="color:orange;">.xml</mark>。<br>
>
> 2\. 生成<mark style="color:orange;">.props</mark>, <mark style="color:orange;">.targets</mark>, <mark style="color:orange;">.xml</mark>文件後直接編寫xml代碼使用的方法

Visual Studio 2005、2008和Visual Studio 2010之後的版本具有不同的定義宏觀。\
如果該規則未以Visual Studio 2010後版本中使用的宏觀編寫，則轉換前應將其轉換爲Visual Studio 2010後版本中使用的宏觀，或轉換後分別修改<mark style="color:orange;">.props</mark>, <mark style="color:orange;">.targets</mark>, <mark style="color:orange;">.xml</mark>。

## 在 PIDL 內容中使用include或import

開發程序時，有時想要在<mark style="color:orange;">.pidl</mark>文件中加入include或import語句。

```
#include "a/b/c.h"
 
class MyStub // PIDL 編譯輸出
{
    ...
}
```

為此，請在 PIDL 內容中使用以下內容：

{% tabs %}
{% tab title="C++" %}

```cpp
#include "a/b/c.h" 
// for C++ language. semicolon is mandatory!
```

{% endtab %}

{% tab title="Java" %}

```java
import com.mycompany.mygame; // for Java
```

{% endtab %}
{% endtabs %}

## 編組

{% tabs %}
{% tab title="C++" %}

#### 在 RMI 中使用自訂類別類型

```cpp
// MyType.h
 
namespace Proud
{
    // 被呼叫的RMI函數的內容被轉換為字串並輸出。
    // 建立日誌時很有用。
    void AppendTextOut(String &a,const MyType &b);
 
    // 從訊息緩衝區讀取自訂類型的內容。
    CMessage& operator>>(CMessage &a, MyType &b);
 
    // 將自訂類型內容插入訊息緩衝區。
    CMessage& operator<<(CMessage &a, const MyType &b);
}
```

在 ProudNet 的 RMI 功能中，我們使用 <mark style="color:orange;">Proud.CMessage</mark>。 RMI 參數是透過重載上述函數來編組的。 這裡使用的是<mark style="color:orange;">Proud.CMessage</mark>。 <mark style="color:orange;">Proud.CMessage</mark>包含ProudNet中用於將RMI轉換為訊息或從訊息中讀取參數的訊息數據，並用作流物件。\ <br>

在封送處理函數中實現流的範例如下：

```cpp
namespace Proud
{
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        a>>b.x,b.y>>b.z>>b.w;
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        // 不要使用a.UseInternalBuffer()！
        a<<b.x,b.y<<b.z<<b.w;
        return a;
    }
    void AppendTextOut(String &a,const MyType &b)
    {
        String f;
        f.Format(L"{x=%f,y=%f,z=%f,w=%f}",b.x,b.y,b.z,b.w);
        a+=f;
    }
}
```

最後，在包含在 PIDL 編譯器中建立的代理程式和存根檔案之前，必須先包含宣告上述重載方法的頭檔。

```cpp
// 例1
#include "MyType.h"
#include "MyPIDL_proxy.h"
 
// 例2
#include "MyType.h"
#include "MyPIDL_stub.h"
```

實際實現的範例請參考<mark style="color:orange;"><安裝資料夾>/sample/CustomTypeMarshal</mark> 或<mark style="color:orange;">\<Sample/CasualGame/GCServer/FarmCommon.h></mark>。 若要檢查您自己的編組函數是否有效，請使用 <mark style="color:orange;">Proud.TestMarshal()</mark>。
{% endtab %}

{% tab title="C#" %}
如果您從 C++ 端序列化並傳送 <mark style="color:orange;">CFastArray</mark>，C# 端會接收它、反序列化它，並在步驟 5 中使用它。\
在此範例中，當從 C# 用戶端呼叫名為 Ping 的 RMI 時，接收它的 C++ 伺服器呼叫 StudentList RMI 並將 C++ 的 <mark style="color:orange;">CFastArray</mark> 傳送到 C#，而 C# 將其作為 List 接收。

1. [編寫](#1-c-student) [C# Student 類](#1-c-student)
2. [編寫](#2-c++-cstudent) [C++ CStudent 類](#2-c++-cstudent)
3. [編寫](#3-pidl) [PIDL ](#3-pidl)
4. [編寫](#4-c++) [C++ 編組程式碼](#4-c++)
5. [編寫](#5-c) [C# 編組程式碼 ](#5-c)

#### (1) 編寫 C# Student 類

```csharp
namespace CsClient
{
    class Student
    {
        public string Name;
        public int ID;
        public int Kor;
        public int Eng;
        public int Mat;
        public override string ToString()
        {
            return string.Format("Name: {0}({1}) K: {2}, E: {3}, M: {4}", Name, ID, Kor, Eng, Mat);
        }
    }
} 
```

#### (2) 編寫 C++ CStudent 類

```cpp
class CStudent
{
    public:
    Proud::String Name;
    int ID;
    int Kor;
    int Eng;
    int Mat;
}; 
```

#### (3) 編寫 PIDL

#### S2C.PIDL

<pre data-line-numbers><code><strong>rename cs(Proud::CFastArray&#x3C;CStudent>, System.Collections.Generic.List&#x3C;CsClient.Student>);
</strong><strong>[marshaler(cs)=CsClient.MyMarshaler]
</strong>global S2C 3000
{
<strong>    StudentList([in] Proud::CFastArray&#x3C;CStudent> students);
</strong>} 
</code></pre>

{% hint style="success" %} <mark style="color:orange;">Line1</mark>: rename關鍵字用於不同語言之間的PIDL通訊。 這個名稱必須包含命名空間。

<mark style="color:orange;">Line2</mark>: marshaler(cs) 關鍵字指定在 C:: 端使用的封送拆收器。 這個名稱必須包含命名空間。

<mark style="color:orange;">Line5</mark>: 建立一個 StudentList PIDL。 基本上，您可以像在 C++ 中使用它一樣編寫它。
{% endhint %}

**C2S.PIDL**

```
[marshaler(cs)=CsClient.MyMarshaler]
global C2S 4000
{
    Ping([in] int value);
} 
```

此範例 PIDL 程式碼是當客戶端發送 ping 時發送 StudentList 的 PIDL 程式碼。建立檔案以建立 <mark style="color:orange;">.h</mark>, <mark style="color:orange;">.cpp</mark>, <mark style="color:orange;">.cs</mark> 檔案。

&#x20;

#### (4) 編寫 C++ 編組程式碼

此程式碼封送作為範例建立的 CStudent 類別。

```cpp
namespace Proud
{
    CMessage& operator >> (CMessage& msg, CStudent student)
    {
        msg >> student.Name >> student.ID >> student.Kor >> student.Eng >> student.Mat;
        return msg;
    }
    CMessage& operator << (CMessage& msg, const CStudent& student)
    {
        msg << student.Name << student.ID << student.Kor << student.Eng << student.Mat;
        return msg;
    }
} 
```

當 C# 客戶端發送 ping 時，此程式碼會傳送 StudentList。

```
class C2SStub : public C2S::Stub
{
    public:
    DECRMI_C2S_Ping;
};
 
DEFRMI_C2S_Ping(C2SStub)
{
    {
        CriticalSectionLock(g_lock, true);
        g_S2CProxy.StudentList(remote, RmiContext::ReliableSend, g_Students);
    }
    return true;
}
C2SStub g_C2SStub; 
```

&#x20;

#### (5) 編寫 C# 編組程式碼

C#中的Marshaling是透過繼承並實作<mark style="color:orange;">Nettention.Proud.Marshaler</mark>然後替換它來開發的，它透過PIDL中聲明的<mark style="color:orange;">marshaler(cs)=CsClient.MyMarshaler</mark>關鍵字進行操作，無需您自己進行操作。

```csharp
namespace CsClient
{
    class MyMarshaler : Nettention.Proud.Marshaler
    {
        public static bool Read(Message msg, out List<Student> students)
        {
            students = null;
            if (!msg.ReadScalar(out var size))
            {
                return false;
            }
            students = new List<Student>();
            for (int i = 0; i < size; ++i)
            {
                Student s = new Student();
                if (!msg.Read(out s.Name)) { return false;
                }
                if (!msg.Read(out s.ID))
                {
                    return false;
                }
                if (!msg.Read(out s.Kor))
                {
                    return false;
                }
                if (!msg.Read(out s.Eng))
                {
                    return false;
                }
                if (!msg.Read(out s.Mat))
                {
                    return false;
                }
                students.Add(s);
            }
            return true;
        }
 
        public static void Write(Message msg, List<Student> students)
        {
            msg.WriteScalar(students.Count);
            for (int i = 0; i < students.Count; ++i)
            {
                msg.Write(students[i].Name);
                msg.Write(students[i].ID);
                msg.Write(students[i].Kor);
                msg.Write(students[i].Eng);
                msg.Write(students[i].Mat);
            }
        }
    }
} 
```

```csharp
g_S2CStub.StudentList = (remote, rmiContext, students) =>
{
    lock(g_lock)
    {
        // 您可以使用傳遞給 List<Student> 的學生。
        foreach (var s in students)
        {
            Console.WriteLine(s.ToString());
        }
    }
    return true;
};
```

{% endtab %}
{% endtabs %}

### - 基於條件的編組方法

有一種方法可以在 RMI 參數中編組字元訊息，每個欄位在不同的字元類型之間有效或無效。 您可以使用 switch/case 語句或物件多態性來實現各種封送處理。

<details>

<summary> <span data-gb-custom-inline data-tag="emoji" data-code="27a1">➡️</span> 使用 switch/case 的範例 </summary>

```cpp
namespace Proud
{
    enum UnitType
    {
        Zergling,     // 星際爭霸的雜技（地面型通商攻擊單元）
        Queen,        // 星際爭霸之王（飛行型特殊技術使用單元）
        Broodling    // 女王使用育雛技能創造的生存時間短的攻擊單位。
    };
 
    struct Unit
    {
        UnitType m_type;          // 單位類型
        Vector2D m_position;    // 單位所在地
        int m_energy;            // 單位的能量（或法力）->僅對皇后有效
        float m_lifeTime;        // 單位存活時間限制->僅對育雛有效
        int m_attackPower;        // 單位攻擊力 -> 只對雜技有效
    };
 
    CMessage& operator<<(CMessage& msg,const Unit& unit)
    {
        msg<<unit.m_type<<unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg<<unit.m_attackPower;
            break;
        case Queen:
            msg<<unit.m_energy;
            break;
        case Broodling:
            msg<<unit.m_lifeTime;
            break;
        }
        return msg;
    }
 
    CMessage& operator>>(CMessage& msg,Unit& unit)
    {
        msg>>unit.m_type>>unit.m_position;
        switch(unit.m_type)
        {
        case Zergling:
            msg>>unit.m_attackPower;
            break;
        case Queen:
            msg>>unit.m_energy;
            break;
        case Broodling:
            msg>>unit.m_lifeTime;
            break;
        }
        return msg;
    }
}
```

</details>

### - 以位元為單位編組數據

若要減少訊息中儲存的資料量，您可以逐位封送資料。\ <mark style="color:orange;">Proud.CMessage</mark> 有以下方法以位元為單位儲存資料。

<table><thead><tr><th width="351">方法</th><th>註釋</th></tr></thead><tbody><tr><td>Proud.CMessage.ReadBits</td><td>以位元爲單位讀取</td></tr><tr><td>Proud.CMessage.WriteBits</td><td>以位元為單位寫入</td></tr></tbody></table>

<details>

<summary><span data-gb-custom-inline data-tag="emoji" data-code="27a1">➡️</span> 例子</summary>

```cpp
namespace Proud
{
    struct MyType
    {
        int x,y,z;
    };
    CMessage& operator>>(CMessage &a, MyType &b)
    {
        // 6位元用於讀取x值
        a.ReadBits(b.x,6);
        // 3位元用於讀取y值
        a.ReadBits(b.y,3);
        // 15 位元用於讀取 z 值。 換句話說，總共使用了6+3+15=24bit（3位元組）。
        a.ReadBits(b.z,15);
        return a;
    }
    CMessage& operator<<(CMessage &a, const MyType &b)
    {
        a.WriteBits(b.x,6); // 以位元為單位儲存 x、y 和 z 值。
        a.WriteBits(b.y,3);
        a.WriteBits(b.z,15);
        return a;
    }
}
```

</details>

{% hint style="warning" %}
**位元級資料編組的注意事項**&#x20;

您要記錄的位數不得超出您要記錄的實際值的範圍。\
例如，如果您嘗試記錄一個 int，但 int 內部的值實際上是負數，則第一位是 1。 在這種情況下，如果嘗試記錄少於31位元以減少位數，則第一位的值將被省略，因此在逐位讀取/寫入時要小心。
{% endhint %}

### - 編組enum類型

若要封送enum類型，請實作下列函數。

```cpp
enum type { ... } ;
 
namespace Proud
{
    inline CMessage& operator<<(CMessage& a,type b)
    {
        a<<(int)b;
        return a;
    }
    inline CMessage& operator>>(CMessage& a,type& b)
    {
        int x;
        a>>x;
        b=(type)x;
        return a;
    }
    inline void AppendTextOut(String &a,type b)
    {
        String txt;
        txt.Format(L"%d",(int)b);
        a+=txt;
    }
}
```

透過使用ProudNet中已定義的巨集<mark style="color:orange;">PROUDNET\_SERIALIZE\_ENUM</mark>，可以如下輕鬆完成上述實作。

```
PROUDNET_SERIALIZE_ENUM(type)
```

### - 編組集合（陣列等）

ProudNet 已準備好編組幾種基本集合（陣列等）類型。 支援 <mark style="color:orange;">Proud.CFastArray</mark>, <mark style="color:orange;">Proud.CFastMap</mark>, <mark style="color:orange;">std.vector</mark> , <mark style="color:orange;">CAtlArray</mark>。

{% hint style="info" %}
參考<mark style="color:orange;">marshaler.h</mark>中的operator>>, operator<<超載
{% endhint %}

```
// 下面是在 PIDL 中聲明數組類型的範例。
Foo([in] Proud::CFastArray<MyType> a, [in] std::vector<MyType> b);
```

如果需要 <mark style="color:orange;">marshaler.h</mark> 中預設定義的集合類型以外的集合類型，則需要實現與所需集合相對應的重載。為此，請參閱[**使用**](#rmi) [**RMI 的自訂類別類型**](#rmi)或 <mark style="color:orange;">marshaler.h</mark> 以了解已實現的範例。

\
當您想要封送 ProudNet 尚未支援的集合類型時，請參閱下列例程。

<details>

<summary><span data-gb-custom-inline data-tag="emoji" data-code="27a1">➡️</span> <mark style="color:orange;">std.vector</mark> marshalling 示例</summary>

```cpp
namespace Proud
{
    // 向量中可用的序列化函數
    // for output to stream
    template<typename elem>
    inline CMessage& operator>>(CMessage &a, std::vector<elem> &b)
    {
        // 獲得大小。
        int size;
        a >> size;
 
        // 如果大小不能接受，則會產生例外。
        // 可能有人懷疑它被駭客入侵了。
        if (size<0 ||size >= CNetConfig::MessageMaxLength)
            ThrowExceptionOnReadArray(size);
 
        // 減少記憶體碎片
        b.reserve(size);
        b.resize(0);
 
        // 一項一項地讀取每個數組項。
        elem e;
        for (int i = 0;i < size;i++)
        {
            a >> e;
            b.push_back(e);
        }
        return a;
    }
 
    // 可在vector、 list等unary item elem等 
    // 使用的 serialization functions
    // for input from stream
    template<typename elem>
    inline CMessage& operator<<(CMessage &a, const std::vector<elem> &b)
    {
        // 記錄數組大小。
        int size = (int)b.size();
        a << size;
        
        // 記錄每個數組參數。
        for (std::vector<elem>::const_iterator i = b.begin();i != b.end();i++)
        {
            a << (*i);
        }
        return a;
    }
 
    template<typename elem>
    inline void AppendTextOut(String &a, std::vector<elem> &b)
    {
        a += L"<vector>";
    }
}
```

</details>

## 關鍵字列表

<table data-full-width="false"><thead><tr><th width="128.33333333333331">關鍵字名稱</th><th width="304">註釋</th><th>用法範例</th></tr></thead><tbody><tr><td>access</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 設定命名空間中代理程式和存根類別的存取權限</td><td>[access=權限名稱]</td></tr><tr><td>byval</td><td>將參數作為值傳遞</td><td>P2PChat([in] Proud::String a, [in, byval] int b);</td></tr><tr><td>global</td><td>命名空間存取權限（目前僅支援全域）</td><td>global Simple 2000 { ... }</td></tr><tr><td>in</td><td>輸入參數</td><td>Chat([in] string txt);</td></tr><tr><td>include</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 將包含插入到輸出中</td><td>#include "a/b/c.cpp"</td></tr><tr><td>marshaler</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 用於使用者定義類型的封送拆收器</td><td>[marshaler(cs) = SimpleCSharp.CMyMarshaler]</td></tr><tr><td>mutable</td><td>將參數作為可變參數傳遞（:=引用）</td><td>Chat([in, mutable] string txt);</td></tr><tr><td>private</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 設定命名空間中代理程式和存根類別的存取權限</td><td>[access=private]</td></tr><tr><td>protected</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 設定命名空間中代理程式和存根類別的存取權限</td><td>[access=protected]</td></tr><tr><td>public</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 設定命名空間中代理程式和存根類別的存取權限</td><td>[access=public]</td></tr><tr><td>rename</td><td>在特定環境下透過替換編譯</td><td>rename cs(Proud::String, System.String);</td></tr><tr><td>using</td><td><mark style="color:orange;">[<strong>C#</strong>]</mark> 將 using 關鍵字插入輸出中</td><td>using(cs) System.XXX;</td></tr></tbody></table>

## \[C#] 想要傳遞多種參數時

```
global 名稱空間名稱 2000 
{
    函數名稱([in] int 變數名);
}
```

當PIDL如上所寫時，PIDL編譯器編譯基本類型如下。

<pre><code>namespace 名稱空間名稱
{
    class Proxy : public ::Proud::IRmiProxy
    {
    public:
<strong>        virtual bool 函數名稱( ::Proud::HostID remote, ::Proud::RmiContext&#x26; rmiContext , const int&#x26; 變數名) PN_SEALED; 
</strong>        ...
    }
}
</code></pre>

想要更改傳遞方式時，請使用以下關鍵詞。

<table><thead><tr><th width="184.33333333333331">關鍵字名稱</th><th width="259">例子(PIDL)</th><th>例子(輸出結果)</th></tr></thead><tbody><tr><td>無</td><td>Chat([in] int val);</td><td>...Chat(..., const int&#x26; val)...</td></tr><tr><td>byval</td><td>Chat([in, byval] int val);</td><td>...Chat(..., const int val)...</td></tr><tr><td>mutable</td><td>Chat([in, mutable] int val);</td><td>...Chat(..., int&#x26; val)...</td></tr></tbody></table>

***

## :arrow\_left: [返回](/proudnet.cn/proudnet/using_pn/pidl.md)
