Saturday, March 29, 2025

Designing an Event-Driven System in C++

Event-driven architecture is a fundamental paradigm in software design where components communicate through events rather than direct method calls. This article breaks down a professional implementation of an event system in C++ using two key files:

1.    event_system.h: Core event management classes.

2.    domain.cpp: Domain-specific events and usage examples.

The source codes can be found in github. Here I just want to highlight a few points that are good to me:

·         Modular Design: Components communicate without tight coupling.

·         Modern C++ Features: Templates, RAII, and lambda expressions.

·         Professional Patterns: Type erasure, publisher-subscriber.

·         Code Flow Visualization:

 [EventsManager]

   |--- stores --> [vector<unique_ptr<ISubscription>>]

                         |

                         |--- contains --> [SubscriptionWrapper<Event<int>>]

                         |                [SubscriptionWrapper<Event<string>>]

                         |                [SubscriptionWrapper<Event<CustomType>>]

                         |

                         |--- On destruction -->

                               [~ISubscription()] --> [~SubscriptionWrapper()] --> [~Event<T>::Subscription()]

 

The rest of this article is generated by deepseek:

Part 1: Core Concepts (event_system.h)

Part 2: Domain Implementation (domain.cpp)

Part 3: Type Erasure Explained: The Power of ISubscription with a Single Virtual Destructor

This design elegantly balances flexibility and type safety, demonstrating how minimal interfaces can enable powerful abstractions!

Part 1: Core Concepts (event_system.h)

1.1 The Event Template Class

Purpose: Create type-safe, reusable events that can carry data.

template <typename... Args>

class Event { /*...*/ };

  • Template ParametersArgs... allow events to carry any data type.
  • Key Features:
    • subscribe(): Registers callbacks and returns a Subscription.
    • trigger(): Broadcasts events to all subscribers.
    • Automatic unsubscription via RAII.

1.2 The Subscription Class

Purpose: Manage callback lifetimes using RAII (Resource Acquisition Is Initialization.

class Subscription : public ISubscription { /*...*/ };

  • RAII Principle: Automatically unsubscribes when destroyed.
  • Move Semantics: Ensures safe ownership transfer (no copying allowed).

1.3 The EventsManager Class

Purpose: Centralize subscription management.

class EventsManager { /*...*/ };

  • Type Erasure: Uses ISubscription and SubscriptionWrapper to store heterogeneous subscriptions.
  • Lifetime Control: All subscriptions are destroyed when the manager is destroyed.

Part 2: Domain Implementation (domain.cpp)

2.1 Defining Event Types

struct TemperatureEvent { double value; };

struct DoorStatusEvent { bool isOpen; int sensorId; };

  • Best Practice: Use simple structs for event data to ensure immutability.

2.2 Creating Event Instances

Event<TemperatureEvent> temperatureEvent;

Event<DoorStatusEvent> doorStatusEvent;

  • Global vs. Local: These are global here for simplicity, but in larger projects, consider encapsulating them within classes.

2.3 Publisher-Subscriber Pattern

Example: Temperature Sensor (Publisher)

void update(double temp) {

    temperatureEvent.trigger(TemperatureEvent{temp});

}

Example: Climate Controller (Subscriber)

em.subscribe(temperatureEvent, [this](const TemperatureEvent& e) {

    adjustSystem(e.value);

});


Key Design Principles

1. Decoupling Components

  • Publishers (e.g., TemperatureSensor) don’t know about subscribers (e.g., ClimateController).
  • All communication happens through events.

2. RAII for Resource Management

  • Subscriptions automatically clean up when they go out of scope:

~Subscription() { if(event_) event_->unsubscribe(id_); }

3. Type Safety

  • Templates ensure compile-time checks:

// Compiler enforces correct event data types:

em.subscribe(temperatureEvent, [](const DoorStatusEvent& e) { /*...*/ }); // Error!

4. Scalability

  • Add new events without modifying existing code:

Event<NetworkPacket> networkEvent;  // New event type

 

PART 3: Type Erasure Explained: The Power of ISubscription with a Single Virtual Destructor

What is Type Erasure?

Type erasure is a design pattern that allows you to work with heterogeneous types through a uniform interface, while hiding ("erasing") their specific type information. It’s like putting different objects into a black box—the outside code interacts with the box, not the objects inside.

Key Characteristics:

  • Abstraction: Treat different types as if they were the same.
  • Encapsulation: Hide type-specific details behind a common interface.
  • Runtime Polymorphism: Resolve behavior at runtime (via virtual functions).

The ISubscription Class: Minimalist Type Erasure

In the event system code, ISubscription is the cornerstone of type erasure. Let’s break down its design:

Class Definition:

class ISubscription {

public:

    virtual ~ISubscription() = default;  // Single virtual destructor

};

Why This Works:

  1. Common Interface:
    All subscription types (e.g., Event<int>::Subscription, Event<string>::Subscription) inherit from ISubscription. This allows them to be stored in a single container:

std::vector<std::unique_ptr<ISubscription>> subscriptions_;

  1. Virtual Destructor:
    • Ensures that when a unique_ptr<ISubscription> is destroyed, the correct derived class destructor is called.
    • Triggers the destructor of the actual Event<T>::Subscription, which unsubscribes the callback.

How Type Erasure is Achieved

Step 1: Type-Specific Wrapper (SubscriptionWrapper)

For each event type Event<T>, we create a templated wrapper:

template <typename EventType>

struct SubscriptionWrapper : public ISubscription {

    typename EventType::Subscription sub;  // Concrete subscription

    SubscriptionWrapper(typename EventType::Subscription&& s)

        : sub(std::move(s)) {}

};

Step 2: Erase the Type

When storing subscriptions:

void EventsManager::subscribe(EventType& event, Callback&& cb) {

    auto sub = event.subscribe(std::forward<Callback>(cb));

    subscriptions_.emplace_back(

        std::make_unique<SubscriptionWrapper<EventType>>(std::move(sub))

    );

}

  • The SubscriptionWrapper<EventType> is created for a specific event type (e.g., Event<int>).
  • It is then upcast to ISubscription* and stored in the subscriptions_ vector.

Step 3: Type Recovery at Destruction

When subscriptions_ is cleared:

  1. The unique_ptr<ISubscription> calls the virtual destructor of ISubscription.
  2. Since SubscriptionWrapper inherits from ISubscription, its destructor is invoked.
  3. This destroys the wrapped EventType::Subscription, which in turn unsubscribes the callback.

Why Only a Destructor?

The ISubscription interface needs only a virtual destructor because:

  1. Minimal Contract: The only required operation is proper destruction.
  2. No Other Operations: The EventsManager doesn’t need to interact with subscriptions—it just needs to manage their lifetimes.
  3. Zero Overhead: No virtual function calls except during destruction.

Analogy: Library Book Returns

Imagine a library (EventsManager) that lends books (subscriptions) of various genres (event types). The librarian doesn’t care what genre a book is—they just need to know:

  1. How to shelve it (store in a vector<ISubscription*>).
  2. How to dispose of it (call the virtual destructor).

The genre-specific return process (unsubscribing) is handled automatically when the book is destroyed!


Key Takeaways

  1. Type Erasure Pattern:
    • Use a base class (ISubscription) to unify heterogeneous types.
    • Leverage virtual destructors for type-specific cleanup.
  2. Advantages:
    • Decoupling: The EventsManager doesn’t know about specific event types.
    • Scalability: Add new event types without modifying the manager.
    • Resource Safety: RAII ensures no leaked subscriptions.
  3. When to Use:
    • Managing objects with heterogeneous types but common lifecycle needs.
    • Hiding implementation details from client code.

 

Saturday, March 15, 2025

Type Misalignment Mirage

Some time ago, I developed a small Windows tool to measure how long a thread took to test an external device. The project intrigued me due to a particularly tricky bug that took me over an hour to resolve. During that time, I consulted ChatGPT for assistance, but it failed to provide useful solutions.

Today, I decided to try DeepSeek instead. I copied the same question I had asked ChatGPT, and amusingly, DeepSeek delivered the correct answer after just 137 seconds of processing. From now on, I’ll be making DeepSeek my first go-to resource for troubleshooting.

In this article, I’ll share the problem I encountered, how I ultimately solved it, and—of course—the first time and correct answer provided by DeepSeek.

Question (to ChatGPT and DeepSeek):

This is Visual C++ Win32 MFC application, why following function got GUI update with "Multicast stopped: 57730ms, sent 0 frames":  

void threadFunction()

{

    // Record the start time using high-resolution clock

    auto startTime = std::chrono::high_resolution_clock::now();

    int nSentCount = 0;

    // Simulate some work by making the thread sleep for 2 seconds

    // thread running ...,  std::this_thread::sleep_for(std::chrono::seconds(2));

    // nSentCount++;  // Increment the sent count

    // Record the end time

    auto endTime = std::chrono::high_resolution_clock::now();

    // Calculate the duration in milliseconds

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();

    CString info;

    if (nSentCount > 1) info.Format(_T("Multicast stopped: %ldms, sent %ld frames"), duration, nSentCount);

    else info.Format(_T("Multicast stopped"));

    pView->UpdateScreenInfo(EVENT_INFO, info);

}

 

Troubleshooting

Yes, I was shocked by the result—I couldn’t believe what I was seeing. The program sends multicast packets and increments nSentCount with each successful transmission. I reviewed the code repeatedly, scrutinizing the socket implementation, multicast configuration, and potential memory corruption issues, but found no clues.

Frustrated, I turned to ChatGPT. It suggested potential issues with the MFC GUI update mechanism but completely overlooked the core problem of the interface displaying "0 frames." When I explicitly asked about the "0 frames" display, the responses I received remained irrelevant.

1.  Initialize nSentCount Appropriately: …

2.  Review Modifications to nSentCount: …

3.  Implement Logging: …

Eventually, I narrowed my focus to the CString implementation. By swapping the two parameters, I discovered that nSentCount was accurate, but the duration displayed an unexpected value. To verify this, I tested the behavior using the following source code:

int main()

{

    long long duration = 1234567890;

    int nSentCount = 321;

    printf("test printf with ld: %ldms, sent %ld frames\n", duration, nSentCount);

    printf("test printf with lld: %lldms, sent %ld frames\n", duration, nSentCount);

    return 0;

}

Compile and run:

test printf with ld: 2123683780ms, sent 1234567890 frames   --- wrong

test printf with lld: 1234567890ms, sent 321 frames         --- correct

 

DeepSeek’s Answer

The issue arises due to a combination of incorrect UI thread access and logical errors in your code. Here's the breakdown and solution:

UI Update from Worker Thread:

Problem: MFC requires GUI updates to occur on the main UI thread. Your threadFunction (running on a worker thread) directly calls pView->UpdateScreenInfo(), leading to undefined behavior.

Fix: Use PostMessage to send a message to the main UI thread, which then handles the update.

Incorrect Format String Condition:

Problem: The message shows details even when nSentCount is 0. Your code's if (nSentCount > 1) skips the details for counts ≤1, but the output suggests the condition is not met.

Fix: Check if nSentCount > 0 instead to include single-frame sends.

Uncommented Work Simulation:

Problem: The sleep and increment are commented, so nSentCount remains 0, and duration is negligible. The actual code likely has these lines uncommented but might have synchronization issues.

Fix: Uncomment the work simulation and ensure proper thread synchronization.

Corrected Code:

// In your worker thread function

void threadFunction()

{

    auto startTime = std::chrono::high_resolution_clock::now();

    int nSentCount = 0;

    // Simulate work (uncommented)

    std::this_thread::sleep_for(std::chrono::seconds(2));

    nSentCount++;  // Increment sent count

    auto endTime = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();

    // Post message to UI thread

    CString info;

    if (nSentCount > 0)

        info.Format(_T("Multicast stopped: %lldms, sent %d frames"), duration, nSentCount);

    else

        info = _T("Multicast stopped");

    // Assuming pView is a valid pointer and you have a custom message handler

    ::PostMessage(pView->GetSafeHwnd(), WM_UPDATE_UI, (WPARAM)EVENT_INFO, (LPARAM)new CString(info));

}

 

// In your view class header (e.g., CMyView.h)

#define WM_UPDATE_UI (WM_USER + 1)

afx_msg LRESULT OnUpdateUI(WPARAM wParam, LPARAM lParam);

// In your view class implementation (e.g., CMyView.cpp)

BEGIN_MESSAGE_MAP(CMyView, CView)

    ON_MESSAGE(WM_UPDATE_UI, OnUpdateUI)

END_MESSAGE_MAP()

LRESULT CMyView::OnUpdateUI(WPARAM wParam, LPARAM lParam)

{

    EventType type = static_cast<EventType>(wParam);

    CString* pInfo = reinterpret_cast<CString*>(lParam);

    UpdateScreenInfo(type, *pInfo);

    delete pInfo; // Clean up the allocated string

    return 0;

}

 

Key Changes:

UI Thread Safety: Uses PostMessage to marshal data to the UI thread.

Condition Check: Updates the condition to nSentCount > 0 to handle single frames.

Correct Format Specifiers: Uses %lld for duration (64-bit integer) and %d for nSentCount.

Memory Management: Allocates CString dynamically to prevent stack corruption.

Additional Checks:

Ensure pView is valid and not destroyed before the message is processed.

Handle thread synchronization if threadFunction runs multiple times or in a loop.

 

What lesson we learned (by DeepSeek)

Your experience is a microcosm of software engineering: even "trivial" code can harbor insidious bugs, and rigor (in testing, tooling, and humility) is the antidote. Celebrate the solve – it’s these battles that keep the craft alive and rewarding. 🛠

你的这段经历是软件工程的缩影:即便是看似简单的代码,也可能暗藏隐蔽的缺陷,而唯有通过严谨(测试、工具链和谦逊的态度)才能将其化解。庆祝这次问题的解决吧——正是这些战斗,让这门技艺永葆活力并充满回报。

Sunday, March 2, 2025

An overengineering example to set a packet frame

In this article, I want to share an overengineering design that not necessary for a simple use case. However, it would be a good design for a complicated system if such packet is used for many interfaces.

Target: to set a packet frame with some header, some payload, in the following format:

Packet Header:

Source      : DeviceA

Destination : DeviceB

Sequence    : 42

Checksum    : 13

Message Type: 1

Full frame (28 bytes):

01 2a 44 65 76 69 63 65 41 00 00 00 44 65 76 69 63 65 42 00 00 00 aa 12 34 56 ff 0d

Let's see how simple a C-style will be:

int setPacketFrame(unsigned char* acData, unsigned char ucType)

{

    acData[0] = ucType;

    acData[1] = getSequenceNum();  //

    snprintf(acData+2, 10, "%s", SOURCE_DEV);

    snprintf(acData+12, 10, "%s", DEST_DEV);

    acData[22] = getReq();  //payload starts here

    acData[23] = getResult();

    acData[24] = getReason();

    acData[25] = getSomething1();

    acData[26] = getSomething2();

    acData[27] = calcBcc(acData+1, 12);

    return 28;

}

What do you think of this function? Look forward to your opinions on it.

Let's check the C++ complicated design, to hit the nut directly, see the flowchart as follows.

 


And see all the classes:

 

Simply speaking, this design is structured to build and finalize a network packet composed of a fixed header, a variable payload, and a checksum appended at the end. It uses a combination of low-level byte manipulation (via the PacketBuffer class) and higher-level abstractions (via the NetworkPacket interface and its implementation in ContiguousPacket) to offer a fluent API for constructing packets. 

Some highlights:

  • Abstraction Layers:
    Low-level memory manipulation is abstracted away using PacketBuffer, while higher-level packet-building logic is encapsulated in NetworkPacket and ContiguousPacket.
  • Fluent Interface:
    Chained method calls allow for a compact and readable way to set header fields and payloads.
  • Extensibility:
    The use of an abstract base class and cloning via CRTP facilitates the extension of the packet system to support different types of packets if needed.
  • You may find the source code from https://github.com/happytong/PacketWrapper/blob/main/packet_wrapper.cpp.


    Saturday, March 30, 2024

    How to draw countless lines without rendering delay in Android Phone

    If you just call the drawing API to draw lines on the screen, you may be comfortable using it when the line number is small, you will see the drawing is flowing smoothly. 

    public class DrawView extends View {

        Paint paint = new Paint();

        @Override

        public void onDraw(Canvas canvas) {

            switch (OperationCtrl.nState)

            {

                case CREATE_PATTERN:

                    DrawPattern(canvas, alTouchPoints);

                    break;

            }

        }

       private void DrawPattern(Canvas canvas, ArrayList<CustomPoint> points)

        {

                ...

            for (int i = 0; i < points.size()-1; i++) 

            {

                lines[lineIndex++] = points.get(i).x;

                lines[lineIndex++] = points.get(i).y;

                lines[lineIndex++] = points.get(i+1).x;

                lines[lineIndex++] = points.get(i+1).y;

            }

            paint.setColor(color);

            paint.setStrokeWidth(thickness);

            canvas.drawLines(lines, paint);

        }

    }

    I called this method the direct drawing method.

    However, when you create hundreds or thousands of lines, the drawing will be so laggy that your app is unable to be enjoyed.

    The solution is to use a buffer memory (bitmap) to do all the rendering jobs, then show up once to the user. 

    Put thousands of words into one sentence, a simple method can solve the rendering issue.  I called this method the bitmap methodAn additional improvement is every rendering only re-draws new lines compared with the previous bitmap. 

    public class DrawView extends View {

        private static Bitmap bufferBitmap = null;

        private static Canvas bufferCanvas = null;

        public static void createBitmap()

        {

            if (bufferBitmap != null) return;

            bufferBitmap = Bitmap.createBitmap(MainActivity.ScreenSize.x, MainActivity.ScreenSize.y, Bitmap.Config.ARGB_8888);

            bufferCanvas = new Canvas(bufferBitmap);

        }

        @Override

        public void onDraw(Canvas canvas) {

            switch (OperationCtrl.nState)

            {

                case CREATE_PATTERN:

                    DrawNewTouchPoints(canvas);

                    break;

            }

        }

    private void DrawNewTouchPoints(Canvas canvas)

        {

            //only draw new lines

           createBitmap();

            if (alTouchPoints.size() <= nLastDrawPoint+1)

            {

                canvas.drawBitmap(bufferBitmap, 0, 0, null);

                return;

            }

            while (alTouchPoints.size() > nLastDrawPoint+1) {

                nLastDrawPoint++;

                int thickness = alDrawThickness.get(nLastDrawPoint);

                int color = alDrawColor.get(nLastDrawPoint);

                int lineIndex = 0;

                float[] lines = new float[4];

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint-1).x;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint-1).y;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint).x;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint).y;

                paint.setColor(color);

                paint.setStrokeWidth(thickness);

                bufferCanvas.drawLines(lines, paint);

            }

            canvas.drawBitmap(bufferBitmap, 0, 0, null);

        }

    }

    With this implementation, the playback is satisfied as expected. For example, drawing 1100 lines with the same speed, the direct drawing method took 55 seconds, while the bitmap method took 18 seconds as designed.

    My app DoodleStory using the bitmap method was published in early 2024: https://play.google.com/store/apps/details?id=com.tongs.doodlestory



    You can try it out to see the performance. The source codes can be found in GitHub.

    My other Android Apps (still available as of today):

    FunPatternWifi: Users in the same WiFi network can play this game, one creates a pattern, and then others can observe similar patterns and guess the right one. Source codes are available in GitHub.

    Pattern Creation and Guess: this is the single-user version of FunPattern. The user creates one pattern, and then the app generates a few and shows animation on the screen for the user to guess.

    Location Alarm and Measure: this app will monitor the current location, and give an alarm when entering or leaving a certain range of the preset location. It can also easily measure the distance between locations. Source codes are available in GitHub.

    Hit and Solve Maths: this app shows a block on the screen randomly, the user can try to hit and do a maths calculation for fun.




    Saturday, December 9, 2023

    《诗经》一句通

    52 《诗经》一句通,经典30秒: 心乎爱矣,遐不谓矣?中心藏之,何日忘之!

    爱恋充满心怀,何不向他表白?爱意深埋在心底,何时才能把他忘记!


     51《诗经》一句通: 天生烝民,其命匪谌。靡不有初,鲜克有终。

    上天生养广大百姓,政令却谎言连篇。人们不是没有良好的初心,却很少人有良好的结局。


    50《诗经》一句通: 匪我言耄,尔用忧谑。多将熇熇,不可救药。

    不是我年迈说昏话,是你荒唐可笑。坏事干尽还气焰嚣张,真是不可救药!


    49《诗经》一句通: 匪兕匪虎,率彼旷野。哀我征夫,朝夕不暇。

    既非野牛也非虎,却像牛虎走旷野。可悲我等出征人,没日没夜忙不停。


    48《诗经》一句通: 苕之华,其叶青青。知我如此,不如无生。

    凌霄开了花,叶子青又青。知道我这样连吃的都没有,不如别生我出来!


    47《诗经》一句通: 或湛乐饮酒,或惨惨畏咎。或出入风议,或靡事不为。

    有的人饮酒作乐,有的人谨小慎微。有的人溜达闲聊,有的人忙里忙外。


    46《诗经》一句通: 溥天之下,莫非王土;率土之滨,莫非王臣。

    普天之下的地,没有哪不是王的地;四海之内的人,没有谁不是王的臣。


    45《诗经》一句通: 维南有箕,不可以簸扬。维北有斗,不可以挹酒浆。

    南天箕星闪光芒,这箕不能用来簸米糠。北天有那北斗星,这斗不能用来舀酒浆。


    44《诗经》一句通: 骄人好好,劳人草草。苍天苍天,视彼骄人,矜此劳人。

    捣鬼的人总得意,受害的人受折腾。苍天苍天应有眼,管管那些害人精,可怜可怜老实人。


    43《诗经》一句通: 蛇蛇硕言,出自口矣。巧言如簧,颜之厚矣。

    夸夸其谈的大话,都是馋人信口出。巧言动听如鼓簧,厚颜无耻脸皮厚。


    42《诗经》一句通: 秩秩大猷,圣人莫之。他人有心,予忖度之。

    典章制度有条理,圣人制订的。他人有心想谗毁,我预料得到。


    41《诗经》一句通: 萋兮斐兮,成是贝锦。彼谮人者,亦已大甚!

    文采斑斓丝线新,织成华丽贝纹锦。那个造谣的坏家伙,冤枉好人太过分!


    40《诗经》一句通: 乐彼之园,爰有树檀,其下维榖。它山之石,可以攻玉。

    我喜欢那座花园,里面有紫檀树,楮树长在它下面。他方山上的顽石,可以用来琢磨玉器。


    39《诗经》一句通: 鹤鸣于九皋,声闻于天。鱼在于渚,或潜在渊。

    幽幽沼泽仙鹤鸣,叫声响亮上云天。浅浅渚滩游鱼浮,时而潜入到深潭。


    38《诗经》一句通: 维此哲人,谓我劬劳。维彼愚人,谓我宣骄。

    只有明理人,知道我辛苦。那些蠢家伙,说我在逞强。


    37《诗经》一句通: 行道迟迟,载渴载饥。我心伤悲,莫知我哀!

    道路泥泞行走慢,饥渴交迫苦难当。我的心里好凄凉,没人知道我哀伤!


    36《诗经》一句通: 昔我往矣,杨柳依依。今我来思,雨雪霏霏。

    回想当初出征日,杨柳飘飘春风吹。如今匆匆把家还,大雪纷纷满天飞。


    35《诗经》一句通: 呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。

    鹿儿呼伴呦呦叫,同在野地吃蒿草。我有满座好宾客,弹瑟吹笙齐欢笑。


    34《诗经》一句通: 蜉蝣之羽,衣裳楚楚。心之忧矣,于我归处。

    短命蜉蝣呀,你的翅膀如同漂亮的衣裳。我心忧愁,不知将来是何下场。


    33《诗经》一句通: 子之荡兮,宛丘之上兮。洵有情兮,而无望兮。

    你热情奔放的舞姿,飘荡在宛丘山坡上。我满怀的爱恋之心,注定是有缘无分的。


    32《诗经》一句通: 岂曰无衣,与子同袍。王于兴师,修我戈矛。与子同仇。

    谁说没有衣裳?与你同穿那长袍。国君发兵交战,修好戈与矛,你我杀向同一个仇敌。


    31《诗经》一句通: 溯洄从之,道阻且长。溯游从之,宛在水中央。

    逆流而上去找她,道路险阻又漫长。顺流而下去寻她,仿佛她在水中央。


    30《诗经》一句通: 蒹葭苍苍,白露为霜。所谓伊人,在水一方。

    河边芦苇青苍苍,晶莹露水冻成霜。我的意中人呀,就在河水那一方。


    29 《诗经》一句通: 言念君子,温其如玉。在其板屋,乱我心曲。

    我思念你呀夫君!思念你美玉般的温润。你从军住在木板屋,我想你呀我心乱如麻!


    28 《诗经》一句通: 自伯之东,首如飞蓬。岂无膏沐?谁适为容!

    自从丈夫去了东方,我的头发乱如飞蓬。润发乳膏哪样少?可是打扮好了给谁看!


    27 《诗经》一句通: 采苓采苓,首阳之巅。人之为言,苟亦无信。

    采黄药啊采黄药,在那首阳山顶找。有人专爱造谣言,切莫轻信那一套。


    26 《诗经》一句通: 考槃在陆,硕人之轴。独寐寤宿,永矢弗告。

    在高原架起木屋,隐士悠哉心自高。独睡独醒独说话,此中乐趣不能言。


    25 《诗经》一句通: 江有沱,之子归,不我过。不我过,其啸也歌。

    大江也有支流,这个男人回来家。他不告别就离去。你抛弃了我,你将来再哭又有何用!


    24 《诗经》一句通: 百尔君子,不知德行。不忮不求,何用不臧。

    所有贵族都一个样,不知修行没德行。如果你不去害人又不贪,走到哪里都顺当。


    23《诗经》一句通: 谁谓河广?一苇杭之。谁谓宋远?跂予望之。

    谁说黄河宽又广?一支苇筏就可渡过。谁说宋国太遥远?踮起脚跟就看得见。 


    22《诗经》一句通: 之子于归,远送于野。瞻望弗及,泣涕如雨。

    妹子今日远嫁,相送郊野路旁。瞻望不见人影,泪流纷如雨下。


    21《诗经》一句通: 予美亡此,谁与独旦。夏之日,冬之夜,百岁之后,归于其居。

    我的爱人葬于此,谁来陪我到天亮。夏日炎炎,冬夜漫漫,百年之后,你我墓中再相见!


    20《诗经》一句通: 绸缪束薪,三星在天。今夕何夕,见此良人?

    一把柴火捆得紧,天上三星亮晶晶。今夜究竟是什么夜?见此好人太开心。


    19《诗经》一句通: 今我不乐,日月其除。无以大康,职思其居。

    及时行乐趁今日,不然时光白跑掉。行乐也别太过度,自己职责要做好。


    18《诗经》一句通: 三岁贯女,莫我肯顾。逝将去女,适彼乐土。

    我长年辛勤侍候你,你从不照顾我。我发誓要摆脱你,去那乐土找幸福。


    17《诗经》一句通: 心之忧矣,我歌且谣。不知我者,谓我士也骄。

    心里忧伤,姑且放声歌唱。不了解我的人说我这个人傲慢轻狂。


    16《诗经》一句通: 何斯违斯?莫或遑处!振振君子,归哉归哉!

    为什么此时还没有消息?我真不敢一个人住着!勤奋有为的老公呀,归来吧,归来回家中!


    15《诗经》一句通: 汉之广矣,不可泳思。江之永矣,不可方思。

    浩浩汉江多宽广,不能泅渡空惆怅。滚滚长江多漫长,不能摆渡空忧伤。


    14《诗经》一句通: 自牧归荑,洵美且异。匪女之为美,美人之贻。

    从牧场送来的荑草,真是美丽又奇特。倒不是荑草有多美,美人相赠情意浓。


    13《诗经》一句通: 青青子衿,悠悠我心。纵我不往,子宁不嗣音?

    青青是你的衣领,悠悠是我的心境。纵然我不曾去会你,难道你就如此断音讯?


    12《诗经》一句通: 风雨如晦,鸡鸣不已。既见君子,云胡不喜!

    风雨交加,鸡鸣声声。此时见你,怎不欣喜!


    11《诗经》一句通: 宜言饮酒,与之偕老。琴瑟在御,莫不静好。

    佳肴做成共饮酒,白头偕老永相爱。女弹琴来男鼓瑟,和谐美满在一块。


    10《诗经》一句通: 知我者谓我心忧,不知我者谓我何求。悠悠苍天,此何人哉。

    理解我的人,说我是心中忧愁。不理解我的人,问我在寻求什么。悠悠苍天啊,我究竟是什么样的人啊! 


    9《诗经》一句通: 投我以木桃,报之以琼瑶。匪报也,永以为好也。

    你将木桃来赠我,我拿美玉作回报。不是为了答谢你,只因你我情意深。


    8《诗经》一句通: 总角之宴,言笑晏晏。信誓旦旦,不思其反。

    小时候我们多么快乐,如今你忘了曾经的信誓旦旦,与我反目成仇。


    7 《诗经》一句通: 齿如瓠犀,螓首蛾眉。巧笑倩兮,美目盼兮。

    齿若瓠齐,娥眉修长。嫣然一笑动人心,秋波一转摄人魂。


    6《诗经》一句通: 大夫君子,无我有尤。百尔所思,不如我所之。

    大夫君子们别再怪我了,你们考虑千百次,不如我亲自跑一趟。


    5《诗经》一句通: 死生契阔,与子成说。执子之手,与子偕老。 

    我们早已立誓言,一同生死不分离。让我握住你的手,同生共死上战场。


    4《诗经》一句通: 我心匪石,不可转也。我心匪席,不可卷也。

    我心并非卵石圆,不能任意来滚转。我心也无草席软,不能随便来翻卷。 


    3《诗经》一句通: 野有死麕,白茅包之。有女怀春,吉士诱之。

    到野外打死一只野鹿,美美地打包成礼物。帅哥用它诱惑怀春的少女。


    2《诗经》一句通: 桃之夭夭,灼灼其华。之子于归,宜其室家。

    茂盛桃树,鲜艳红花。这位姑娘就要出嫁,新的家庭将和和睦睦。 


    1《诗经》一句通: 关关雎鸠,在河之洲。窈窕淑女,君子好逑。

    在河中的沙洲上,有对雎鸠鸟相互唱和。美丽善良的女子,是男子渴求的好对象。

    How to Design a General I2C Detection Tool in QNX

    When developing embedded systems on QNX 6.6, I2C communication is often a critical component for interfacing with sensors, GPIO expanders, a...