# ProudNet 實用程式

## 使用收藏

STL、ATL等也已經提供收藏類(<mark style="color:orange;">std.map</mark>, <mark style="color:orange;">std.vector</mark>, <mark style="color:orange;">CFastArray</mark>, <mark style="color:orange;">CAtlArray</mark>, <mark style="color:orange;">CAtlMap</mark>等)。 但是ProudNet在STL和ATL都難以使用的情況下，或在要求高速性能的情況下提供有效的類

| 類別    | 函數               | 註釋                                                                    |
| ----- | ---------------- | --------------------------------------------------------------------- |
| 數組類   | Proud.CFastArray | 內部使用 [**Fast heap**](/proudnet.cn/proudnet/pn_utility.md#fast-heap) 。 |
| 鏈接列表類 | Proud.CFastList  | 內部使用Fast Heap。 與CFastArray不同，該類可以與創建者、消失者和輻射分配操作員一起使用。                |
| 地圖類   | Proud.CFastMap   | (Key, Value) 使用成對的哈希算法。 與CAtlMap的使用方法非常相似，STL.map的重複序列和一些方法可以相同地使用。   |
| 設定類   | Proud.CFastSet   | 與CFastMap不同，Key是僅有的類，其餘與CFastMap相同。                                   |

<br>

## 快速內存管理器

ProudNet內置高性能內存管理器，開發者可以利用高性能內存管理器加速應用程序的加工性能。

ProudNet支持的存儲管理器大致如下。

### -  Lookaside allocator

<mark style="color:orange;">Lookaside allocator</mark>應用了通常的memory pool技術。 如果您必須經常分配/ 釋放相同大小的內存， 推薦使用 <mark style="color:orange;">Lookaside allocator</mark> 。

主要機制如下。

> * 分配新的存儲塊時，分配新的系統內存。
> * 當解鎖存儲塊時,解鎖的塊將返回至<mark style="color:orange;">lookaside allocator</mark>。
> * 重新分配存儲塊時,返還給<mark style="color:orange;">lookaside allocator</mark>的存儲塊將被再利用。

這個過程以非常快的速度運行。 比OS環境下的內存分配速度要快得多。

但也存在缺點。

> * <mark style="color:orange;">Lookaside allocator</mark>總是隻能分配大小相同的內存。
> * 分配給 <mark style="color:orange;">Lookaside allocator</mark> 的內存塊必須在 <mark style="color:orange;">Lookaside allocator</mark> 被破壞之前全部釋放。

使用<mark style="color:orange;">Lookaside allocator</mark>的方法如下。

> * 首先使用 <mark style="color:orange;">Proud.CLookasideAllocator.New</mark> 方法創建對象。\
>   創建全局對象也可以。
> * 將存儲塊分配給 <mark style="color:orange;">Proud.CLookasideAllocator.Alloc</mark> 方法。
> * 解除設置爲 <mark style="color:orange;">Proud.CLookasideAllocator.Free</mark>。 Realloc不存在。
> * 解除所有存儲塊後，破壞 <mark style="color:orange;">Proud.CLookasideAllocator</mark> 對象。

### - Fast Heap

ProudNet的<mark style="color:orange;">Fast heap</mark>雖然比<mark style="color:orange;">Lookaside allocator</mark>稍微慢一些，但是比OS環境下的存儲器分配/解除速度更快，可以分配/解除各種大小的存儲器塊。

ProudNet 的 Fastheap 的實現類是 <mark style="color:orange;">Proud.CFastHeap</mark>。 <mark style="color:orange;">Proud.CFastHeap</mark> 還可以在所有存儲塊被破壞後移除 <mark style="color:orange;">Proud.CFastHeap</mark> 對象。

\ <mark style="color:orange;">Fast heap</mark>的使用方法如下。

> * 首先以<mark style="color:orange;">Proud.CFastHeap.New</mark>方法生成<mark style="color:orange;">Fast heap</mark>對象。 創建全局對象也可以。
> * 用 <mark style="color:orange;">Proud.CFastHeap.Alloc</mark> 方法分配內存塊 。
> * 解除爲<mark style="color:orange;">Proud.CFastHeap.Free</mark>。 可用<mark style="color:orange;">CFastHeap.Realloc</mark>重新分配內存塊。
> * 解除所有存儲塊後，破壞 <mark style="color:orange;">Proud.CFastHeap</mark> 對象。

## 指定為 C++ 類別的預設分配器

在C++類中容易接受<mark style="color:orange;">Fast heap</mark>或<mark style="color:orange;">Lookaside allocator</mark>的加速的方法是覆蓋C++的<mark style="color:orange;">operator new</mark>, <mark style="color:orange;">delete</mark>方法。

這樣，在創建和破壞C++類作爲new、delete操作符時，將使用<mark style="color:orange;">Fast heap</mark>或<mark style="color:orange;">Lookaside allocator</mark>代替系統的內存heap。

\
在以下示例中，當將類實例化到 <mark style="color:orange;">operator new</mark> 或 <mark style="color:orange;">delete</mark> 時，高性能內存管理器將允許您分配/ 釋放內存。 它們各有優缺點，請適當選擇使用。

<details>

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

```cpp
class Example
{
    static CLookasideAllocatorPtr gAlloc; // 或者用CFast Heap也OK。
public:
    void* operator new(size_t size)
    {
        return gAlloc->Alloc(size);
    }
    void operator delete(void* ptr, size_t size)
    {
        gAlloc->Free(ptr);
    }
};
 
CLookasideAllocatorPtr Example::gAlloc(CLookasideAllocator::New()); // 或者用CFast Heap也OK。
```

</details>

## 智能指示器

ProudNet擁有智能指針<mark style="color:orange;">Proud.RefCount</mark>類。

智能指針是指,只要存在參照生成客體的變數,就能起到保障該客體的存在本身的作用。 而且，只有當參照該客體的變數不再存在時，該客體纔會被破壞。

它還可以解決由於開發人員創建的錯誤（dangling）而引用已被銷毀的物件的問題或未銷毀物件的問題（leak）。

智能指針變量每次複製時客體參考計數增加1，下圖中Object Instance一直存在到參考計數爲0。

<figure><img src="/files/pDZp0nHLOm5Q5BBI3iYn" alt=""><figcaption><p>智能指針與對象之間的關係</p></figcaption></figure>

<details>

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

```cpp
class A {...};
 
void Foo()
{
    // 創建 A 對象
    Proud::RefCount<A> a(new A);
    
    // 變量b與變量a共享。 A的參考計數爲2。
    Proud::RefCount<A> b = a;
    
    // 變量 a 已解除 。 然而，A沒有被破壞，因爲參考計數仍然是1。
    a = Proud::RefCount<A>();
    
    // 變量b已解除 。 不再有變量引用A，因此A被破壞。 (即已調用 delete)
    b = Proud::RefCount<A>();
}
```

</details>

有時候想在多處參照客體的<mark style="color:orange;">智能指示器</mark>的狀態下強制破壞客體。

例如，如果<mark style="color:orange;">智能指示器</mark>參考擁有打開文件手柄的對象，則有時需要立即破壞擁有文件手柄的對象。

但如果<mark style="color:orange;">智能指示器</mark>到處參照客體,就無法知道客體被破壞的時間,因此可能會遇到難關。 在這種情況下，可以使用<mark style="color:orange;">Dispose Pattern</mark>明確執行多處參考對象的破壞。

### - Dispose Pattern

<mark style="color:orange;">Dispose Pattern</mark>是一種程序模式,即使不知道一個以上<mark style="color:orange;">智能指示器</mark>所參照的對象被破壞的時間,也能獲得明確破壞客體的效果。

如果要將<mark style="color:orange;">Dispose Pattern</mark>運用到智能類客體上,作爲客體的成員變數,具有"自我狀態",這意味着客體是否已經被破壞,無法使用。\
如果自己的狀態是"已經破壞的狀態",那麼在參考客體時就會發生錯誤,否則就要讓其正常執行。

下面是運用<mark style="color:orange;">Dispose Pattern</mark>的例子。

{% hint style="info" %} <mark style="color:orange;">Dispose Pattern</mark> 是 \
在Java或C#等<mark style="color:orange;">智能指示器</mark>和<mark style="color:orange;">Garbage Collector</mark>的程序設計語言中也有涉及。
{% endhint %}

<details>

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

```cpp
class A
{
    // 如果是true,意味着該客體處於破壞(dispose)狀態。
    bool m_disposed;
    public:
    
    A()
    {
        // 剛生成的對象處於未dispose狀態。
        m_disposed = false;
    }
 
    ~A()
    {
        Dispose();
    }
 
    void Dispose()
    {
        if(m_disposed == false)
        {
            // 進行對象破壞相關的實際執行。
            // 例如，關閉手中的文件手柄。
            ...
        
            m_disposed = true;
        }
    }
};
 
typedef Proud::RefCount<A> APtr;
 
void Foo()
{
    APtr a(new A); // 創建對象
    APtr b = a;     // 兩個智能指針變量共享一個對象
    
    a->Dispose();   // 強行破壞客體。
    
    // 現在a、b客體都處於破壞狀態。
    // 因此，不能訪問a、b所參考的對象。
    ...
}
```

</details>

## 線程實用程式

ProudNet提供了一些線程實用類。

<table><thead><tr><th width="275">類別</th><th>註釋</th></tr></thead><tbody><tr><td>Proud.Thread </td><td>線程可以很容易地生成和破壞。</td></tr><tr><td>Proud.CriticalSection</td><td>可以創建critical section。</td></tr><tr><td>Proud.CriticalSectionLock</td><td>可以lock和unlock。</td></tr></tbody></table>

## 字符串類

ProudNet使字符串類<mark style="color:orange;">Proud.String</mark>, <mark style="color:orange;">Proud.StringA</mark>使字符串像ATL或STL的字符串類一樣可以簡便地處理。

{% hint style="warning" %}
使用 .Net Framework 的程序具有名爲 System.string 的符號。\
因此.Net Framework混用時，可能需要註明System或Proud中的一個命名空間。
{% endhint %}

\
舉例如下。

```cpp
Proud::String a;  // 空字符串
a = L"123";         // 爲字符串添加值 。
puts(a);            // a本身直接提供字符串緩衝器。
a += L"abc";        // 向字符串添加另一個字符串
if(L"123abc" == a)  // 如何比較字符串中的內容
{
    a.Replace(L"123", "def");   // 字符串內容替換
}
```

### - 創建字符串功能(format)

<mark style="color:orange;">Proud.StringT</mark> 提供了創建字符串的功能， 如 <mark style="color:orange;">sprintf()</mark> 。

```cpp
Proud::String a;
a.Format(L"%d %d %s", 1, 2, L"hahaha");
// 現在a="12 hahaha"。
```

### - [Unicode-多字節相互轉換](/proudnet.cn/proudnet-note/notes.md#undefined-1)

### -  字符串處理性能

#### Copy-on-write

```cpp
// Proud.StringT 的 copy-on-write 功能只有在需要字符串時才顯示副本。 
// 在此之前，我們會互相共享字符串數據 。
 
Proud::String a = L"abc"; // a 擁有字符串'abc'
Proud::String b = a; // b共享與a相同的字符串數據
Proud::String c = b; // 現在a、b、c都共享相同的字符串數據。
c = L"bcd"; // a、b仍然共享字符串數據"abc"，但c不再共享，單獨擁有"bcd"
b = c; // b放棄與a共享"abc"，共享c擁有的"bcd"
```

#### 字符串長度測量

<mark style="color:orange;">Proud.StringT.GetLength</mark>在調用後立即返回預先測量的字符串長度。 即與<mark style="color:orange;">strlen()</mark>不同。

{% hint style="warning" %} <mark style="color:orange;">Proud.StringT</mark> 和 int 和 float 一樣， 不使用 thread safe 。\
因此，同時從多個執行緒存取同一個字串物件是不安全的（除非所有執行緒都只是讀取）。

這一點與ATL或STL的字符串類別相同。
{% endhint %}

## Timer Queue

<mark style="color:orange;">Timer Queue</mark>是在<mark style="color:orange;">線程池</mark>中執行tick event的模塊，在Windows XP，2000年以後版本的操作系統上提供名爲Windows Timer Queue的API。

每隔一段時間運行用戶指定的函數， 該函數在<mark style="color:orange;">線程池</mark>中的一個線程中運行。 如果所有線程正在運行(running state) 函數的執行將保留到線程中出現完成歷史任務線程爲止。

在<mark style="color:orange;">Timer Queue</mark>中呼叫的用戶函數是從<mark style="color:orange;">線程池</mark>中的線程中選擇一個，如果之前正在運行的用戶函數處於未運行狀態，也有無事可做的線程(idle state)，則選擇該線程並執行用戶函數。

假設有以下工作清單。

黑箭頭爲0.1s，A、B、C、D、E是每0.1s應做的工作項目。 A、D在0.1秒內結束，B在0.1秒內結束，C、E在0.1秒內無法結束。

<figure><img src="/files/3XNZxs8teQrKk7vMRAeF" alt=""><figcaption><p>每隔一段時間要運行的任務項目</p></figcaption></figure>

如果是[**服務器主環**](/proudnet.cn/proudnet-note/notes/main_loop.md#undefined-1)方法，則這些操作項目如下圖所示。 由於在一個線程中運行所有任務項目，D、E不能按時啓動。

<figure><img src="/files/wIguyEncXDWXLfUzgxDi" alt=""><figcaption><p>在一個線程中運行任務項目時</p></figcaption></figure>

然而，在<mark style="color:orange;">Timer Queue</mark>方法中，動員了另一個線程來運行D，並且E及時運行在先前完成C的線程中。

<figure><img src="/files/gZ6RzA3q7SvgATE2Odd5" alt=""><figcaption><p>在計時器隊列中運行任務項目時</p></figcaption></figure>

及時啓動必要任務，必要時進一步調動線程。

<mark style="color:orange;">Timer Queue</mark>主要在服務器程序中使用，因爲用戶函數可能同時在兩個或多個線程中運行。

實現並列性，但需承受並列性所具有的危險性，使用前請判斷該功能是否必要。

{% hint style="danger" %}
如果錯誤地使用 <mark style="color:orange;">Timer Queue</mark>\
服務器過程的線程會爆炸性地增加，對性能產生不利影響。\
如果沒有必須使用 <mark style="color:orange;">Timer Queue</mark>的原因， 請在 <mark style="color:orange;">Proud.CTimerThread</mark> 或[**服務器上使用計時器環路, RMI, 事件處理**](/proudnet.cn/proudnet/using_pn/server_client/using_server.md#rmi) 。
{% endhint %}

### - 如何使用計時器隊列

您需要存取 <mark style="color:orange;">Proud.CTimerQueue</mark> 類別。 這個類別是一個singleton。

如果將調用函數和調用週期設置爲 <mark style="color:orange;">Proud::NewTimerParam</mark> 結構，並將其作爲參數添加到 <mark style="color:orange;">Proud.CTimerQueue.NewTimer</mark> 中，則會獲得 <mark style="color:orange;">Proud.CTimerQueueTimer</mark> 對象。\
然後，在破壞 <mark style="color:orange;">Proud.CTimerQueueTimer</mark> 對象之前，指定的用戶函數每隔一段時間運行一次。

<details>

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

```cpp
VOID NTAPI UserFunction(void* context, BOOLEAN TimerOrWaitFired)
{
    int *pCallCount = static_cast<int *>(context);
 
    // 用戶函數
    std::cout << "UserFunction : " << ++(*pCallCount) << std::endl;
}
 
int _tmain(int argc, TCHAR* argv[])
{
    // 宣佈 NewTimerParam 結構域變量 。
    NewTimerParam p1;
 
    // 用於測試計數的變量聲明
    int callCount = 0;
 
    // 用戶函數設置 。
    p1.m_callback = UserFunction;
 
    // 設置要接收的參數 。
    p1.m_pCtx = &callCount;
 
    // 設定爲1秒後開始回電。
    p1.m_DueTime = 1000;
 
    // 設置爲0.1秒回撥。
    p1.m_period = 100;
 
    // 每隔一段時間,在線程池中每0.1秒調用一次用戶函數。
    Proud::CTimerQueueTimer* ret = Proud::CTimerQueue::GetSharedPtr()->NewTimer(p1);
 
    std::cout << "PRESS ANY KEY TO EXIT" << std::endl;
 
    // 等待用戶回撥
    _getch();
 
    // 破壞計時器客體。 破壞後用戶函數不再被調用。
    delete ret;
}
```

</details>

## 留下日誌

在開發網絡遊戲的過程中,必然需要留下各種執行記錄(日誌)的功能。 ProudNet爲此提供日誌留存功能。

<figure><img src="/files/D0D2mSoRKtI2n9NXPD5w" alt=""><figcaption><p>Windows也有留下事件日誌的功能。</p></figcaption></figure>

ProudNet的登錄功能以非同步方式運行，因此，在調用要留下登錄的方法後，方法立即返回。 此外，將單獨線程的實際日誌記錄在文件或數據庫中。

### - 日誌記錄到文件

<mark style="color:orange;">Proud::CLogWriter</mark> 類是允許將日誌寫入文件的類 。

<details>

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

```cpp
// 生成CLogWriter。
CAutoPtr<Proud::CLogWriter> logwriter;
logwriter.Attach(Proud::CLogWriter::New(L"log.txt"));
 
// 讓我們來寫日誌。 我們提供了兩個 WriteLine 函數。
logwriter->WriteLine( Proud::TraceID::TID_System, L"是系統日誌。 );
logwriter->WriteLine( "%是第d個日誌。", 1 );
 
// 換一個新的日誌文件。 如果創建新文件失敗， 則以 Proud::Exception 例外處理 。
logwriter->SetFileName(L"log2.txt);
```

</details>

### - 日誌記錄到數據庫

<mark style="color:orange;">Proud::CDbLogWriter</mark> 類是允許將日誌寫入DB 的類 。

爲此，您必須運行 <mark style="color:orange;">Sample/DbmsSchema</mark> 文件夾中的 <mark style="color:orange;">LogTable.sql</mark>，以預先生成 LogTable 。 DBMS的建立參考樣品數據庫建立的程序。

<details>

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

```cpp
// 要接收錯誤的函數 。
class CTestLogWriterDelegate : public ILogWriterDelegate
{
    virtual void OnLogWriterException(Proud::AdoException& Err) override
    {
        // ...
    }
};
 
CTestLogWriterDelegate g_dblogDelegate;
 
void main()
{
    // ...
    // CDbLog Parameter 填充值。
    Proud::CDbLogParameter dbparam;
    dbparam.m_dbmsConnectionString = L"Data Source=localhost;Database=Log-Test;Trusted_Connection=yes";
    dbparam.m_loggerName = L"LoggerName";
    dbparam.m_dbLogTableName = L"DbLog";
 
    // 生成CDbLogWriter。
    CAutoPtr<Proud::CDbLogWriter> dbLogWriter;
    dbLogWriter.Attach(Proud::CDbLogWriter::New(dbparam, &g_dblogDelegate));
    
    // 加入ProudNet不提供的新Field吧。
    // 注意！！用戶想要加入想要的字段時，必須在DBMS的Log-Test表上提前生成字段。
    // 在DBMS中創建TestField，將datatype稱爲int時，如下文所示生成CPropNode並放入Write Line即可。
    Proud::CProperty newnode;
    Proud::String TestField = L"TestField";
    Proud::CVariant TestValue = 123;
    newnode.Add(TestField, TestValue);
    dbLogWriter->WriteLine(L"日誌內容。", &newnode);
    
    // ...
}
```

</details>

## 延遲測量功能

ProudNet以StopWatch的形式提供延遲測量功能。

如果是可用的版本，則必須使用比現有延遲測量函數更準確的該功能，而不是服務器的<mark style="color:orange;">GetLastPing</mark>或<mark style="color:orange;">GetRecentPing</mark>。

### (1) 開始延遲測量

調用 <mark style="color:orange;">StartRoundTripLatencyTest</mark> 將開始要測量延遲的目標和 relay 。

<details>

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

```cpp
StartRoundTripLatencyTestParameter testParameter;
testParameter.testDuration = 40 * 1000; // 指定在 RoundTripLatency Test 中何時調用 StopRoundTripLatency Test 的變量。 單位是毫秒。 默認是500ms。
testParameter.pingIntervalMs = 400; // 指定在RoundTripLatency Test途中發送PING的週期的變數。 單位是毫秒。 默認是300ms。
ErrorType startError = netClient->StartRoundTripLatencyTest(HostID_Server, testParameter);
switch(startError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "HostID是自己或不是連接的Peer時" << std::endl;
    break;
}
```

</details>

### (2) 延遲測量結束

如果您想在先前指定的測試Duration之前停止測試， 請調用<mark style="color:orange;">StopRoundTripLatencyTest</mark>函數 。 如果直到testDuration結束爲止不呼叫<mark style="color:orange;">StopRoundTripLatencyTest</mark>，則將自動停止測量。

<details>

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

```cpp
ErrorType stopError = StopRoundTripLatencyTest(HostID_Server);
switch(stopError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "非連接Peer時" << std::endl;
        break;
}
```

</details>

### (3) 獲取延遲測量值

<details>

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

```cpp
RoundTripLatencyTestResult testResult;
ErrorType getError = GetRoundTripLatency(HostID_Server, testResult);
switch(getError)
{
    case ErrorType_Ok:
        std::cout << "success" << std::endl;
        std::cout << "測量期間的乒乓平均值 : " << testResult.latencyMs 
<< ", 測量期間的乒乓標準差 : " << testResult.standardDeviationMs 
<< ", 測量期間運行的乒乓次數 : " << testResult.totalTestCount << std::endl;
        break;
    case ErrorType_InvalidHostID:
        std::cout << "非連接Peer時" << std::endl;
        break;
    case ErrorType_ValueNotExist:
        std::cout << "從未使用過乒乓時" << std::endl; // 在這種情況下，RoundTripLatencyTestResult的總TestCount爲0。
        break;
}
```

</details>

## PIDL 編譯器插件

這是 Visual Studio 的附加元件，可協助您輕鬆設定 PIDL (ProudNet IDL) 檔案的自訂建置。

### - 安裝

1\.  運行 <mark style="color:orange;"><安裝路徑>\ProudNet\util\PIDL-addon.vsix</mark>。

<figure><img src="/files/67OzpmxB6vo0YiWriGFg" alt=""><figcaption></figcaption></figure>

####

2\. 選擇要安裝附加元件的 Visual Studio 並繼續安裝。

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

3\. 安裝成功完成後，會顯示如下圖所示。

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

### - 刪除

可以透過<mark style="color:orange;">Tools</mark> → <mark style="color:orange;">Extensions and Updates</mark>選單將其刪除。

{% hint style="info" %}
對於 Visual Studio 2010， <mark style="color:orange;">Tools</mark> → <mark style="color:orange;">Extension Manager</mark>
{% endhint %}

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

### - 新增PIDL檔案視窗

調用 PIDL 新增視窗。\
選擇<mark style="color:orange;">選擇項目</mark> → <mark style="color:orange;">右鍵</mark> → <mark style="color:orange;">Add new PIDL file</mark>

<figure><img src="/files/1sfRFk7GL5myehPakhi2" alt=""><figcaption></figcaption></figure>

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

> 1. 輸入 PIDL 文件名 。
> 2. 顯示添加 PIDL 文件的路徑 。
> 3. 添加外部 PIDL 文件 。
> 4. 創建新的 PIDL 文件 。
> 5. 關閉 PIDL 附加窗口 。

### - 添加 PIDL 文件

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

> 1. 輸入要添加的 PIDL 文件名
> 2. 點擊 New PIDL

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

如上圖所示，添加了PIDL文件，該文件默認設置Custom Build。 與默認設置不同時，可通過文件屬性更改功能進行更改。

### - PIDL 文件屬性窗口

調用 PIDL 屬性窗口 。

<mark style="color:orange;">選擇 PIDL 文件</mark> → <mark style="color:orange;">右鍵點擊</mark> → <mark style="color:orange;">Properties</mark> 選擇

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

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

> 1. 檢查/ 更改 PIDL 文件中的 Custom Build Tool 設置 。\
>    \ <mark style="color:orange;">PIDL Compiler Location</mark>: 設置 PIDL 編譯器位置 。\ <mark style="color:orange;">PIDL Compiler Location is Relative</mark>: 輸入路徑的絕對/相對與否值。\ <mark style="color:orange;">Output Directory (All Languages)</mark>: 設置輸出路徑 。\ <mark style="color:orange;">Output Directory is Relative (All Languages)</mark>: 輸入路徑的絕對/相對與否值。

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

> 1. 各語言設置窗口:可按各C++、C#、Java、Unreal Script語言生成。<br>
> 2. 特定於語言的設定視窗：\
>    \ <mark style="color:orange;">Generate Code:</mark> C++ 默認 Yes， 其餘語言是 False (可根據需要配置)\ <mark style="color:orange;">C++ Implementation File Extension</mark>: 僅限C++。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.proudnet.com/proudnet.cn/proudnet/pn_utility.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
