Làm cách nào để thiết kế chương trình C ++ để cho phép nhập hàm thời gian chạy?


10

hôm nay, tôi muốn hỏi bạn một câu hỏi về khả năng của C ++ để nhận ra một kiến ​​trúc phần mềm cụ thể.

Tất nhiên, tôi đã sử dụng tìm kiếm nhưng không tìm thấy bất kỳ câu trả lời liên kết trực tiếp nào.

Về cơ bản, mục tiêu của tôi là xây dựng một chương trình cho phép người dùng mô hình hóa và mô phỏng các hệ thống vật lý được cấu tạo tùy ý, ví dụ như một chiếc xe lái. Tôi giả sử có một thư viện các mô hình vật lý (các hàm trong các lớp). Mỗi chức năng có thể có một số đầu vào và trả về một số đầu ra tùy thuộc vào mô tả vật lý cơ bản, ví dụ mô hình động cơ đốt, mô hình kéo khí động học, mô hình bánh xe, v.v.

Bây giờ, ý tưởng là cung cấp cho người dùng một khung cho phép anh ta soạn bất kỳ chức năng nào theo nhu cầu của mình, tức là để ánh xạ mọi hành vi vật lý. Khung nên cung cấp các chức năng để kết nối đầu ra và đầu vào của các chức năng khác nhau. Do đó, khung cung cấp một lớp container. Tôi gọi nó là THÀNH PHẦN, có thể chứa một hoặc nhiều đối tượng mô hình (FUNCTION). Các thùng chứa này cũng có thể chứa các thành phần khác (xem mẫu tổng hợp) cũng như các kết nối (CONNECTOR) giữa các tham số chức năng. Ngoài ra, lớp thành phần cung cấp một số chức năng số chung như bộ giải toán, v.v.

Thành phần của các chức năng nên được thực hiện trong thời gian chạy. Trong trường hợp đầu tiên, người dùng sẽ có thể thiết lập một thành phần thông qua việc nhập một XML xác định cấu trúc thành phần. Sau đó, người ta có thể nghĩ đến việc thêm GUI.

Để cho bạn hiểu rõ hơn ở đây là một ví dụ rất đơn giản:

<COMPONENT name="Main">
  <COMPONENT name="A">
    <FUNCTION name="A1" path="lib/functionA1" />
  </COMPONENT>
  <COMPONENT name="B">
    <FUNCTION name="B1" path="lib/functionB1" />
    <FUNCTION name="B2" path="lib/functionB2" />
  </COMPONENT>
  <CONNECTIONS>
    <CONNECTOR source="A1" target="B1" />
    <CONNECTOR source="B1" target="B2" />
  </CONNECTIONS>        
</COMPONENT>

Không cần thiết phải đi sâu vào các khả năng của khung vì vấn đề của tôi chung chung hơn nhiều. Khi mã / chương trình khung được biên dịch, mô tả vấn đề vật lý, cũng như các hàm do người dùng định nghĩa, không được biết. Khi người dùng chọn (thông qua XML hoặc mới hơn qua GUI) một chức năng, khung sẽ đọc thông tin chức năng, tức là sẽ lấy thông tin của các tham số đầu vào và đầu ra, để cung cấp cho người dùng tùy chọn kết nối các chức năng.

Tôi biết các nguyên tắc phản ánh và tôi biết rằng C ++ không cung cấp tính năng này. Tuy nhiên, tôi chắc chắn rằng khái niệm "xây dựng các đối tượng trong thời gian chạy" là rất thường xuyên được yêu cầu. Tôi nên thiết lập kiến ​​trúc phần mềm của mình trong C ++ như thế nào để đạt được mục tiêu? C ++ có phải là ngôn ngữ phù hợp? Tôi bỏ qua điều gì?

Cảm ơn trước!

Chúc mừng, Oliver


C ++ không có con trỏ hàm và các đối tượng hàm. Có phải tất cả các chức năng được biên dịch vào tệp thực thi, hoặc chúng có trong các thư viện động (trên nền tảng nào) không?
Caleth

1
Câu hỏi quá rộng theo nghĩa là nó thường yêu cầu bằng đại học về kỹ thuật điện / [tự động hóa thiết kế điện tử (EDA)] ( en.wikipedia.org/wiki/Electronic_design_automation ) hoặc kỹ thuật cơ khí / thiết kế hỗ trợ máy tính (CAD) . Nói một cách tương đối, việc gọi thư viện động C / C ++ rất dễ dàng, hãy xem các quy ước gọi C cho x86 . Nó có thể yêu cầu thao tác ngăn xếp (thông qua con trỏ ngăn xếp CPU) và các giá trị thanh ghi CPU.
rwong

1
Các chức năng tải động không được hỗ trợ bởi ngôn ngữ C ++. Bạn sẽ phải xem xét một cái gì đó cụ thể nền tảng. Ví dụ, trình biên dịch C ++ trên Windows sẽ hỗ trợ Windows DLL, hỗ trợ một dạng phản chiếu.
Simon B

Trong C ++, thật khó để gọi một hàm có chữ ký (kiểu đối số và kiểu trả về) không được biết đến tại thời điểm biên dịch. Để làm như vậy, bạn cần biết cách các hàm gọi hoạt động ở cấp độ lắp ráp của nền tảng bạn đã chọn.
Bart van Ingen Schenau

2
Cách tôi giải quyết điều này là biên dịch mã c ++ để tạo trình thông dịch cho bất kỳ ngôn ngữ nào hỗ trợ lệnh eval. Vấn đề Bang được giải quyết bằng c ++. : P Hãy suy nghĩ về lý do tại sao điều đó không đủ tốt và cập nhật câu hỏi. Nó giúp khi các yêu cầu thực sự rõ ràng.
candied_orange

Câu trả lời:


13

Trong C ++ tiêu chuẩn thuần túy, bạn không thể "cho phép nhập hàm thời gian chạy"; theo tiêu chuẩn, tập hợp các hàm C ++ được biết đến tĩnh tại thời gian xây dựng (trong thực tế, thời gian liên kết) do được cố định từ liên kết của tất cả các đơn vị dịch thuật soạn thảo chương trình của bạn.

Trong thực tế, hầu hết thời gian (không bao gồm các hệ thống nhúng) chương trình C ++ của bạn chạy trên một số hệ điều hành . Đọc hệ điều hành: Ba phần dễ dàng để có cái nhìn tổng quan.

Một số hệ điều hành hiện đại cho phép tải các plugin động . POSIX đáng chú ý chỉ định dlopen& dlsym. Windows có một cái gì đó khác biệt LoadLibrary(và một mô hình liên kết kém hơn; bạn cần chú thích rõ ràng các chức năng liên quan, được cung cấp hoặc sử dụng bởi các plugin). BTW trên Linux thực tế bạn có thể có dlopenrất nhiều plugin (xem manydl.cchương trình của tôi , với đủ kiên nhẫn, nó có thể tạo ra sau đó tải gần một triệu plugin). Vì vậy, điều XML của bạn có thể thúc đẩy việc tải các plugin. Mô tả đa thành phần / đa kết nối của bạn nhắc nhở tôi về các tín hiệu và khe cắm Qt ( yêu cầu mocbộ xử lý trước ; bạn cũng có thể cần một cái gì đó tương tự).

Hầu hết các triển khai C ++ sử dụng xáo trộn tên . Do đó, bạn sẽ khai báo tốt hơn là extern "C"các hàm liên quan đến plugin (và được xác định trong chúng và được truy cập bởi dlsymchương trình chính). Đọc C ++ dlopen mini HowTo (ít nhất là cho Linux).

BTW, QtPOCO là các khung C ++ cung cấp một số cách tiếp cận di động và cấp cao hơn cho các plugin. Và libffi cho phép bạn gọi các hàm có chữ ký chỉ được biết khi chạy.

Một khả năng khác là nhúng một số trình thông dịch, như Lua hoặc Guile , vào chương trình của bạn (hoặc viết một trình thông dịch của riêng bạn, như Emacs đã làm). Đây là một quyết định thiết kế kiến ​​trúc mạnh mẽ. Bạn có thể muốn đọc Lisp trong phần nhỏngôn ngữ lập trình thực dụng để biết thêm.

Có những biến thể hoặc hỗn hợp của những cách tiếp cận đó. Bạn có thể sử dụng một số thư viện biên dịch JIT (như libgccjit hoặc asmjit). Bạn có thể tạo trong thời gian chạy một số mã C và C ++ trong một tệp tạm thời, biên dịch nó thành một plugin tạm thời và tải động plugin đó (tôi đã sử dụng một cách tiếp cận như vậy trong GCC MELT ).

Trong tất cả các cách tiếp cận này, quản lý bộ nhớ là một mối quan tâm đáng kể (nó là một thuộc tính "toàn bộ chương trình" và thực sự "phong bì" của chương trình của bạn là "thay đổi"). Bạn sẽ cần ít nhất một số văn hóa về thu gom rác . Đọc cẩm nang của GC về thuật ngữ. Trong nhiều trường hợp ( tham chiếu vòng tròn tùy ý trong đó con trỏ yếu không dự đoán được), sơ đồ đếm tham chiếu thân yêu với con trỏ thông minh C ++ có thể không đủ. Xem thêm này .

Đọc thêm về cập nhật phần mềm động .

Lưu ý rằng một số ngôn ngữ lập trình, đáng chú ý là Common Lisp (và Smalltalk ), thân thiện hơn với ý tưởng về các hàm nhập thời gian chạy. SBCL là một phần mềm miễn phí của Common Lisp, và biên dịch thành mã máy ở mọi tương tác REPL (và thậm chí có thể thu thập mã máy và có thể lưu toàn bộ tệp hình ảnh lõi mà sau đó có thể dễ dàng khởi động lại).


3

Rõ ràng bạn đang cố gắng tạo ra kiểu phần mềm Simulink hoặc LabVIEW của riêng bạn, nhưng với một thành phần XML không linh hoạt.

Ở mức cơ bản nhất, bạn đang tìm kiếm một cấu trúc dữ liệu hướng đồ thị. Các mô hình vật lý của bạn được xây dựng từ các nút (bạn gọi chúng là các thành phần) và các cạnh (trình kết nối trong cách đặt tên của bạn).

Không có cơ chế thực thi ngôn ngữ để thực hiện việc này, ngay cả với sự phản chiếu, vì vậy thay vào đó, bạn sẽ phải tạo API và bất kỳ thành phần nào muốn chơi sẽ phải thực hiện một số chức năng và tuân thủ các quy tắc do API của bạn quy định.

Mỗi thành phần sẽ cần triển khai một tập hợp các hàm để thực hiện các công cụ như:

  • Lấy tên của thành phần hoặc các chi tiết khác về nó
  • Lấy số lượng bao nhiêu đầu vào hoặc đầu ra mà thành phần lộ ra
  • Hỏi một thành phần về một đầu vào cụ thể đầu ra của chúng tôi
  • Kết nối đầu vào và đầu ra với nhau
  • và những người khác

Và đó chỉ là để thiết lập biểu đồ của bạn. Bạn sẽ cần các chức năng bổ sung được xác định để tổ chức cách mô hình của bạn thực sự được thực thi. Mỗi chức năng sẽ có một tên cụ thể và tất cả các thành phần phải có các chức năng đó. Mọi thứ cụ thể cho một thành phần phải có thể truy cập được thông qua API đó, theo cách thức giống hệt nhau từ thành phần này sang thành phần khác.

Chương trình của bạn không nên cố gắng gọi những 'chức năng do người dùng xác định' này. Thay vào đó, nó nên gọi một hàm 'tính toán' cho mục đích chung hoặc một số thứ tương tự trên mỗi thành phần, và chính thành phần đó đảm nhiệm việc gọi hàm đó và chuyển đổi đầu vào của nó thành đầu ra của nó. Các kết nối đầu vào và đầu ra là sự trừu tượng cho chức năng đó, đó là điều duy nhất mà chương trình sẽ thấy.

Nói tóm lại, rất ít trong số này thực sự cụ thể đối với C ++, nhưng bạn sẽ phải triển khai một loại thông tin loại thời gian chạy, phù hợp với miền vấn đề cụ thể của bạn. Với mỗi hàm được xác định bởi API, bạn sẽ biết tên hàm nào sẽ được gọi trong thời gian chạy và bạn sẽ biết các kiểu dữ liệu của từng cuộc gọi đó và bạn chỉ cần sử dụng tải thư viện động cũ thông thường để thực hiện. Điều này sẽ đi kèm với một số lượng lớn nồi hơi, nhưng đó chỉ là một phần của cuộc sống.

Một khía cạnh cụ thể của C ++ mà bạn muốn ghi nhớ mặc dù vậy, tốt nhất là để API của bạn là API C để bạn có thể sử dụng các trình biên dịch khác nhau cho các mô-đun khác nhau, nếu người dùng đang cung cấp các mô-đun của riêng họ.

DirectShow là một API thực hiện mọi thứ tôi mô tả và có thể là một ví dụ tốt để xem xét.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.