Công nghệ thành phần bên ngoài (). Công nghệ thành phần bên ngoài () Công nghệ thành phần bên ngoài 8.3

OLEG FILIPOV, ANT-Inform, Phó Trưởng phòng Phát triển, [email được bảo vệ]

Mở rộng chức năng của 1C:Enterprise
Phần 1: Các thành phần COM bên ngoài

Sẽ có lúc trong cuộc đời của mỗi nhà phát triển 1C khi nhiệm vụ được giao cho anh ta vượt quá khả năng của nền tảng 1C. Hãy cùng tìm cách “vượt qua ranh giới có thể”

Các nhà phát triển 1C có kinh nghiệm có lẽ đã đoán được từ ba từ đầu tiên của tiêu đề bài viết rằng chúng ta sẽ nói về các thành phần bên ngoài dành cho 1C:Enterprise. Công nghệ này đã tồn tại từ thời nền tảng 1C:Enterprise 7.7 (hoặc cũ hơn). Về mặt lịch sử, nó chủ yếu xuất hiện để giải quyết các vấn đề tương tác với thiết bị thương mại (máy quét mã vạch, cân điện tử, máy tính tiền), trong đó nền tảng 1C cần xử lý một số sự kiện nhất định do thiết bị khởi xướng. Những sự kiện như vậy không gì khác hơn là các chuỗi byte đến trên cổng COM/LPT. Tất nhiên, việc điều chỉnh nền tảng 1C để hoạt động với các cơ chế cấp thấp như vậy sẽ không chính xác cho lắm nên chúng tôi đã nghĩ ra công nghệ các thành phần bên ngoài cho 1C: Enterprise.

Trong loạt bài này chúng ta sẽ không chỉ nói về các thành phần bên ngoài: các thành phần bên ngoài là một cơ chế khá mạnh và phức tạp nên việc sử dụng chúng để giải quyết một số vấn đề được coi là “dùng đại bác bắn chim sẻ”. Tuy nhiên, các thành phần bên ngoài là công cụ phổ biến nhất để giải quyết tất cả các loại vấn đề nằm ngoài phạm vi của nền tảng 1C:Enterprise, vì vậy chúng ta sẽ bắt đầu với chúng. Bài viết sẽ thảo luận về công nghệ lâu đời nhất, được sử dụng thường xuyên nhất và đã được thử nghiệm theo thời gian để tạo các thành phần bên ngoài - dựa trên COM.

Các thành phần bên ngoài là gì

Các thành phần bên ngoài, như đã đề cập ở trên, đã xuất hiện trong 1C:Enterprise kể từ phiên bản 7.7. Ban đầu, theo truyền thống, các nhà phát triển nền tảng 1C “không bận tâm” và các thành phần bên ngoài là một đối tượng có các thuộc tính và phương thức bắt buộc nhất định. Các thành phần vẫn tồn tại ở dạng tương tự cho đến ngày nay. Nghĩa là, các thành phần được viết cho 1C:Enterprise 7.7 (về lý thuyết) sẽ hoạt động với 1C:Enterprise 8.3.

Trong thực tế, có một số điểm tinh tế: nếu một thành phần bên ngoài tương tác tích cực với chính nền tảng, thì các chức năng của nó sẽ được chuyên biệt hóa cho một phiên bản cụ thể.

Kể từ phiên bản 8.2, 1C:Enterprise đã giới thiệu một công nghệ mới để phát triển các thành phần bên ngoài, được gọi là NativeAPI. Các thành phần được viết bằng công nghệ này không còn là đối tượng COM nữa. Một mặt, đây là một điểm cộng - các thành phần này không yêu cầu đăng ký, mặt khác, việc sử dụng chúng ở bất kỳ nơi nào khác ngoại trừ nền tảng 1C:Enterprise là không thể. Việc phát triển các thành phần bên ngoài đã trở nên phức tạp hơn một chút; chúng phải hỗ trợ số lượng giao diện cần thiết lớn hơn một chút. Nhưng chúng ta sẽ nói về nó trong bài viết tiếp theo.

Các thành phần bên ngoài và công nghệ COM

Tôi sẽ không mô tả chi tiết về công nghệ COM vì có rất nhiều tài liệu về chủ đề này. Có lẽ người ta chỉ nên nói rằng nhiều người “nhầm lẫn” việc tạo một máy chủ inproc thông thường với việc tạo các thành phần bên ngoài cho 1C:Enterprise, nhưng điều này không khác xa sự thật. Rất thường xuyên, bạn có thể thực hiện được chỉ với chức năng này. Cách tạo đối tượng COM trong Visual Studio được mô tả ở nhiều nguồn khác nhau. Đặc biệt, người này đã viết chi tiết cách thực hiện việc này, kèm theo ví dụ về cuộc gọi từ 1C:Enterprise.

Nếu bạn vẫn muốn tạo một thành phần bên ngoài COM chính thức cho 1C:Enterprise (trong số các tùy chọn tại sao điều này có thể cần thiết, tôi chỉ có thể nghĩ ra một - thành phần đó phải tương tác tích cực với hệ thống trên nền tảng 1C, thông báo cho người dùng, thay đổi dòng trạng thái, hiển thị hộp thoại, v.v.), khi đó bạn cần sử dụng trực tiếp công nghệ của các thành phần bên ngoài. Vậy hãy bắt đầu.

Bài viết này được dành để làm việc với các thành phần bên ngoài, cụ thể là kết nối chúng. Hiện tại, để mở rộng khả năng của 1C Enterprise, hai công nghệ của các thành phần bên ngoài được sử dụng:

  • 1 Sử dụng API gốc
  • 2 Sử dụng công nghệ COM
Trong bài viết này, tôi quyết định nhấn mạnh cách làm việc với các thành phần API gốc.
Vì vậy, hãy bắt đầu, từ đơn giản đến phức tạp:
Trích từ ITS

1. Giả sử VK của chúng tôi nằm trong một thư mục cụ thể trên đĩa:

Có thể được sử dụng trong "Máy khách dày (ứng dụng thông thường)";

Đây là ví dụ đơn giản nhất về cách làm việc với thành phần Gốc. Xin lưu ý rằng loại thành phần này không yêu cầu đăng ký trong hệ thống, điều này giúp đơn giản hóa rất nhiều việc quản trị.

2. Ví dụ được thảo luận ở trên hoàn toàn không thực tế. Thông thường, thành phần này được đặt trong một bố cục. Bố cục phải chứa kho lưu trữ zip với các tệp thành phần và tệp MANIFEST.xml
Tệp kê khai ví dụ:

3. Khi làm việc trên máy khách mỏng và web, hãy đảm bảo sử dụng phương pháp này.
Trích dẫn từ ITS:

Giải trình:
%APPDATA%\1C\1Cv82\ExtCompT- thư mục cài đặt các thành phần cho máy khách Dày và Mỏng.
%APPDATA%\Roaming\Mozilla\Tiện ích mở rộng- phần mở rộng thư mục (trong trường hợp của tôi) cho Mozilla FF/
Khi sử dụng phương pháp SetExternalComponent(), tùy thuộc vào client sử dụng, các tiện ích mở rộng sẽ được giải nén vào thư mục thích hợp.

Một ví dụ về quy trình cài đặt thành phần bên ngoài:

Cài đặtThành phần bên ngoài- phương thức này chỉ nên được gọi trong quá trình cài đặt thành phần ban đầu và trong trường hợp cần cập nhật phiên bản đã cài đặt của thành phần.

Trường hợp client mỏng và dày:
Chỉ cần thực hiện lại thao tác cài đặt của thành phần bên ngoài bằng phương thức là đủ Cài đặtThành phần bên ngoài().

Trong trường hợp máy khách web cập nhật một thành phần:

  • Cần gỡ bỏ plugin thông qua cơ chế làm việc với tiện ích bổ sung của trình duyệt web (Mozilla FF)
  • Sử dụng phương pháp Cài đặtThành phần bên ngoài
Để kết nối VK, bạn có thể sử dụng quy trình sau:

Nếu thành phần chưa được cài đặt, một ngoại lệ sẽ được đưa ra.

2. Có những trường hợp một thành phần cần được cài đặt từ bộ lưu trữ tạm thời (tệp được nhận từ nguồn của bên thứ ba, xử lý bên ngoài), trong trường hợp này, các tham số đầu tiên trong các phương thức Đính kèm Thành phần Bên ngoài và Cài đặt Thành phần Bên ngoài là địa chỉ của kho lưu trữ trong bộ lưu trữ tạm thời. Dưới đây là một ví dụ có thể về cách thức hoạt động của nó:

&OnClient BiếnĐịa chỉLưu trữThành phần; &Thành phần biến OnClient; &OnClient Thủ tục OnOpen(Thất bại) // địa chỉ, chứa một chuỗi (liên kết điều hướng đến dữ liệu nhị phân của kho lưu trữ zip trong // bộ lưu trữ tạm thời) ComponentArchiveAddress = GetArchiveAddressInTemporaryStorage(); EndProcedure // WhenOpen() &OnServer // các phương thức ConnectExternalComponent, SetExternalComponent có thể lấy // làm tham số đầu tiên là một chuỗi có định dạng "liên kết điều hướng" // (URL tới một thành phần bên ngoài được đóng gói trong kho lưu trữ ZIP, ở định dạng tương tự như // GetNavigationLink ). Hàm GetArchiveAddressInTemporaryStorage()ProcessingObject = FormAttributValue("ProcessingObject"); Liên kết lưu trữ = PlaceInTemporaryStorage(ProcessingObject.GetLayout("MIKO_phone_IP"), UniqueIdentifier mới); ReturnLinkToArchive; EndFunction // GetArchiveAddressInTemporaryStorage() &OnClient // Thủ tục chỉ nên được gọi một lần, nếu thành phần chưa được cài đặt // hoặc cần được cập nhật Thủ tục InstallComponent(Command) Cố gắng InstallExternalComponent(ArchiveComponentAddress); Báo cáo ngoại lệ("Không thể cài đặt thành phần bên ngoài."); EndAttempt; Kết thúc thủ tục // InstallComponent() &OnClient // thủ tục chính để khởi tạo một thành phần Thủ tục Khởi tạo(Lệnh) Cố gắng kết nối một thành phần bên ngoài(ComponentArchiveAddress,"Comp", ,ExternalComponentType.Native); Thành phần = Mới("AddIn.Comp.MIKO_phone_IP"); Báo cáo ngoại lệ ("Ngoại lệ khởi tạo. Thành phần có thể chưa được cài đặt."); EndAttempt; Kết thúcThủ tục
  • Hướng dẫn

Giới thiệu

Bài viết này đưa ra ý tưởng về cách các thành phần bên ngoài hoạt động trong hệ thống 1C: Enterprise.
Quá trình phát triển thành phần bên ngoài cho hệ thống 1C: Enterprise phiên bản 8.2, chạy trên hệ điều hành Windows với chế độ hoạt động tệp sẽ được hiển thị. Tùy chọn này được sử dụng trong hầu hết các giải pháp được thiết kế cho doanh nghiệp nhỏ. VK sẽ được triển khai bằng ngôn ngữ lập trình C++.

Các thành phần bên ngoài "1C: Enterprise"

"1C: Enterprise" là một hệ thống có thể mở rộng. Để mở rộng chức năng của hệ thống, các thành phần bên ngoài (EC) được sử dụng. Theo quan điểm của nhà phát triển, VC là một đối tượng bên ngoài có các thuộc tính và phương thức, đồng thời cũng có thể tạo ra các sự kiện để hệ thống 1C: Enterprise xử lý.
Các thành phần bên ngoài có thể được sử dụng để giải quyết một nhóm vấn đề khó hoặc thậm chí không thể thực hiện được bằng ngôn ngữ lập trình được tích hợp trong 1C: Enterprise. Đặc biệt, lớp này bao gồm các tác vụ yêu cầu tương tác ở mức độ thấp với hệ điều hành, chẳng hạn như để làm việc với thiết bị cụ thể.
Hệ thống 1C: Enterprise sử dụng hai công nghệ để tạo các thành phần bên ngoài:
  • sử dụng API gốc
  • sử dụng công nghệ COM
Với những hạn chế nhất định, sự khác biệt giữa hai công nghệ nêu trên là không đáng kể, vì vậy chúng tôi sẽ xem xét việc phát triển trò chơi điện tử bằng API gốc. Nếu cần, những phát triển đã triển khai có thể được áp dụng để phát triển phần mềm máy tính sử dụng công nghệ COM, đồng thời, với những sửa đổi nhỏ, được áp dụng để sử dụng trong hệ thống 1C: Enterprise với các tùy chọn vận hành khác ngoài chế độ vận hành tệp.
Cấu trúc VK
Thành phần bên ngoài của hệ thống 1C: Enterprise được trình bày dưới dạng thư viện DLL. Mã thư viện mô tả lớp con IComponentBase. Lớp được tạo phải xác định các phương thức chịu trách nhiệm thực hiện các chức năng của thành phần bên ngoài. Các phương pháp được ghi đè sẽ được mô tả chi tiết hơn bên dưới khi tài liệu được trình bày.

Ra mắt bản demo VK

Nhiệm vụ:
  1. Lắp ráp một thành phần bên ngoài được cung cấp cùng với đăng ký ITS và nhằm mục đích chứng minh các khả năng chính của cơ chế thành phần bên ngoài trong 1C
  2. Kết nối thành phần demo với cấu hình 1C
  3. Đảm bảo các hàm được khai báo hoạt động chính xác
biên soạn
Bản demo VK nằm trên đĩa đăng ký ITS trong thư mục “/VNCOMP82/example/NativeAPI”.
Để xây dựng VC demo, chúng tôi sẽ sử dụng Microsoft Visual Studio 2008. Các phiên bản khác của sản phẩm này không hỗ trợ định dạng dự án Visual Studio được sử dụng.


Mở dự án AddInNative. Trong cài đặt dự án, chúng tôi bao gồm thư mục chứa các tệp tiêu đề cần thiết để xây dựng dự án. Theo mặc định, chúng nằm trên đĩa ITS trong thư mục /VNCOMP82/bao gồm.
Kết quả của việc xây dựng là tập tin /bind/AddInNative.dll. Đây là thư viện được biên dịch để kết nối với cấu hình 1C.
Kết nối cấu hình VK với 1C
Hãy tạo một cấu hình 1C trống.
Dưới đây là mã cho mô-đun ứng dụng được quản lý.
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...\bind\AddInNative.dll", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.AddInNativeExtension"); Kết thúcThủ tục
Nếu không có lỗi nào được báo cáo khi khởi động cấu hình 1C thì VK đã được kết nối thành công.
Kết quả của việc thực thi đoạn mã trên là một đối tượng xuất hiện trong chế độ hiển thị chung của cấu hình DemoComp, có các thuộc tính và phương thức được xác định trong mã của thành phần bên ngoài.
Trình diễn chức năng tích hợp
Hãy kiểm tra chức năng của bản demo VK. Để thực hiện việc này, hãy thử thiết lập và đọc một số thuộc tính, gọi một số phương thức VK, đồng thời nhận và xử lý tin nhắn VK.
Tài liệu được cung cấp trên đĩa ITS nêu rõ chức năng sau của VC demo:
  1. Quản lý trạng thái đối tượng thành phần
    Phương pháp: Bật, Tắt
    Của cải: Bao gồm
  2. Quản lý hẹn giờ
    Mỗi giây thành phần sẽ gửi tin nhắn đến 1C: Hệ thống doanh nghiệp có thông số Thành phần, hẹn giờ và một dòng đếm đồng hồ hệ thống.
    Phương pháp: bộ đếm thời gian bắt đầu, Dừng hẹn giờ
    Của cải: Có đồng hồ hẹn giờ
  3. Phương pháp Hiển thị trong dòng trạng thái, hiển thị trên dòng trạng thái văn bản được truyền cho phương thức dưới dạng tham số
  4. Phương pháp Tải lênHình ảnh. Tải hình ảnh từ tệp được chỉ định và chuyển nó sang hệ thống 1C: Enterprise dưới dạng dữ liệu nhị phân.
Hãy đảm bảo các chức năng này hoạt động. Để thực hiện việc này, hãy chạy đoạn mã sau:
biến DemoComp; Quy trình khi hệ thống khởi động() ConnectExternalComponent(...); DemoComp = Mới ("AddIn.DemoVK.AddInNativeExtension"); DemoComp.Disable(); Báo cáo(DemoComp.Enabled); DemoComp.Enable(); Báo cáo(DemoComp.Enabled); DemoComp.StartTimer(); Thủ tục Kết thúc Thủ tục Báo cáo Xử lý sự kiện bên ngoài(Nguồn, Sự kiện, Dữ liệu)(Nguồn + " " + Sự kiện + " " + Dữ liệu); Kết thúcThủ tục
Kết quả chạy cấu hình hiển thị trong hình


Bảng “Tin nhắn” hiển thị kết quả của các lệnh gọi phương thức DemoComp.Disable()Demo.Comp.Enable(). Các dòng tiếp theo trong cùng bảng chứa kết quả xử lý tin nhắn nhận được từ VK - Nguồn, Sự kiệnDữ liệu tương ứng.

Tên thành phần bên ngoài tùy chỉnh

Nhiệm vụ: Thay đổi tên của thành phần bên ngoài thành tên tùy ý.
Phần trước đã sử dụng mã định danh AddInNativeExtension, ý nghĩa của nó không được giải thích. Trong trường hợp này AddInNativeExtension- đây là tên của phần mở rộng.
Mã VK xác định một phương thức Đăng kýTiện ích mở rộngNhư, trả lại tên cho hệ thống 1C: Enterprise, cần thiết cho việc đăng ký VK trong hệ thống sau này. Nên chỉ định một mã định danh ở một mức độ nào đó tiết lộ bản chất của thành phần bên ngoài.
Đây là mã hoàn chỉnh của phương pháp Đăng kýTiện ích mở rộngNhư với tên tiện ích mở rộng đã thay đổi:
bool CAddInNative::RegisterExtensionAs(WCHAR_T** wsExtensionName) ( wchar_t *wsExtension = L"SomeName"; int iActualSize = ::wcslen(wsExtension) + 1; WCHAR_T* dest = 0; if (m_iMemory) ( if(m_iMemory->AllocMemory ((void**)wsExtensionName, iActualSize * sizeof(WCHAR_T))) ::convToShortWchar(wsExtensionName, iActualSize) trả về sai;
Trong ví dụ đã cho, tên VK được đổi thành Một số tên. Sau đó, khi kết nối VK, bạn phải chỉ định tên mới:
DemoComp = Mới ("AddIn.DemoVK.SomeName");

Mở rộng danh sách thuộc tính VK

Nhiệm vụ:
  1. Nghiên cứu triển khai thuộc tính VK
  2. Thêm thuộc tính đọc/ghi của loại chuỗi
  3. Thêm thuộc tính chuỗi đọc/ghi lưu trữ kiểu dữ liệu của tập thuộc tính cuối cùng. Không có hành động nào được thực hiện khi đặt giá trị thuộc tính

Để xác định các thuộc tính của thành phần đang được tạo, nhà phát triển phải triển khai các phương thức sau trong mã thư viện AddInNative.cpp:
GetNProps
Trả về số thuộc tính của phần mở rộng này, 0 nếu không có thuộc tính
TìmProp
Trả về số sê-ri của thuộc tính có tên được truyền trong tham số
GetPropName
Trả về tên của thuộc tính theo số sê-ri và mã định danh ngôn ngữ đã truyền
GetPropVal
Trả về giá trị của thuộc tính với số thứ tự được chỉ định
SetPropVal
Đặt giá trị của thuộc tính với số thứ tự được chỉ định
IsPropCó thể đọc được
Trả về cờ dễ đọc của thuộc tính với số thứ tự đã chỉ định
IsPropCó thể ghi
Trả về cờ có thể ghi của thuộc tính với số thứ tự đã chỉ định


Hãy xem xét việc thực hiện các phương thức lớp trên CDaddInBản địa.
Trong demo VC, 2 thuộc tính được xác định: Bao gồmCó đồng hồ hẹn giờ (Được kích hoạtIsTimerHiện tại).
Trong phạm vi toàn cầu của mã thư viện, hai mảng được xác định:
wchar_t tĩnh *g_PropNames = (L"IsEnabled", L"IsTimerPresent"); wchar_t tĩnh *g_PropNamesRu = (L"Đã bật", L"Có bộ hẹn giờ");
nơi lưu trữ tên thuộc tính tiếng Nga và tiếng Anh. Trong tập tin tiêu đề AddInNative.h bảng liệt kê được xác định:
đạo cụ enum ( ePropIsEnabled = 0, ePropIsTimerPresent, ePropLast // Luôn cuối cùng);
ePropIsEnabledePropIsTimerHiện tại, tương ứng có giá trị 0 và 1, được sử dụng để thay thế số sê-ri của thuộc tính bằng mã định danh có ý nghĩa. ePropLast, có giá trị là 2, được sử dụng để lấy số lượng thuộc tính (sử dụng phương pháp GetNProps). Những tên này chỉ được sử dụng trong mã thành phần và không có sẵn từ bên ngoài.
Các phương thức FindProp và GetPropName thực hiện tìm kiếm mảng g_PropNamesg_PropNamesRu.
Để lưu trữ giá trị của các trường trong mô-đun thư viện, lớp CAddInNative có các thuộc tính lưu trữ giá trị của thuộc tính thành phần. phương pháp GetPropValSetPropVal return và đặt giá trị của các thuộc tính này cho phù hợp.
phương pháp IsPropCó thể đọc đượcIsPropCó thể ghi và quay lại ĐÚNG VẬY hoặc SAI, tùy thuộc vào số thứ tự được truyền của thuộc tính phù hợp với logic ứng dụng.
Để thêm thuộc tính tùy chỉnh, bạn cần:

  1. Thêm tên thuộc tính đang được thêm vào mảng g_PropNamesg_PropNamesRu(tài liệu AddInNative.cpp)
  2. Liệt kê đạo cụ(tài liệu AddInNative.h) trước ePropLast thêm tên xác định duy nhất thuộc tính đang được thêm
  3. Sắp xếp bộ nhớ để lưu trữ các giá trị thuộc tính (tạo các trường thành phần mô-đun lưu trữ các giá trị tương ứng)
  4. Thực hiện thay đổi phương pháp GetPropValSetPropValđể tương tác với bộ nhớ được phân bổ ở bước trước
  5. Phù hợp với logic ứng dụng, thực hiện các thay đổi đối với các phương thức IsPropCó thể đọc đượcIsPropCó thể ghi
Điểm 1, 2, 5 không cần giải thích. Chi tiết thực hiện các bước này có thể được tìm thấy bằng cách nghiên cứu phần phụ lục của bài viết.
Hãy đặt tên cho các thuộc tính thử nghiệm Bài kiểm traKiểm tra loại tương ứng. Khi đó, theo kết quả của bước 1, chúng ta có:
wchar_t tĩnh *g_PropNames = (L"IsEnabled", L"IsTimerPresent", L"Test", L"TestType"); wchar_t tĩnh *g_PropNamesRu = (L"Đã bật", L"Có bộ hẹn giờ", L"Kiểm tra", L"Kiểm tra loại");
Chuyển khoản đạo cụ sẽ trông giống như:
Đạo cụ enum ( ePropIsEnabled = 0, ePropIsTimerPresent, ePropTest1, ePropTest2, ePropLast // Luôn tồn tại cuối cùng);
Để đơn giản hóa đáng kể mã, chúng tôi sẽ sử dụng STL C++. Đặc biệt, để làm việc với chuỗi WCHAR, hãy kết nối thư viện chuỗi.
Để lưu giá trị phương thức Bài kiểm tra, chúng ta định nghĩa trong lớp CDaddInBản địa trong phạm vi của một lĩnh vực tư nhân:
kiểm tra chuỗi1;
Để truyền các tham số chuỗi giữa 1C: Enterprise và các thành phần bên ngoài, trình quản lý bộ nhớ 1C: Enterprise được sử dụng. Chúng ta hãy xem xét kỹ hơn công việc của anh ấy. Các chức năng được sử dụng để cấp phát và giải phóng bộ nhớ tương ứng Phân bổ bộ nhớGiải phóng bộ nhớđược xác định trong tập tin ImemoryManager.h. Nếu cần truyền một tham số chuỗi cho hệ thống 1C: Enterprise, thành phần bên ngoài phải cấp phát bộ nhớ cho nó bằng cách gọi hàm Phân bổ bộ nhớ. Nguyên mẫu của nó trông như thế này:
bool ảo ADDIN_API AllocMemory (void** pMemory, ulCountByte dài không dấu) = 0;
Ở đâu pBộ nhớ- địa chỉ của con trỏ mà địa chỉ của vùng bộ nhớ được phân bổ sẽ được đặt vào đó,
ulCountByte- kích thước của vùng bộ nhớ được phân bổ.
Ví dụ về cấp phát bộ nhớ cho chuỗi:
WCHAR_T *t1 = NULL, *test = L"TEST_STRING"; int iActualSize = wcslen(test1)+1; m_iMemory->AllocMemory((void**)&t1, iActualSize * sizeof(WCHAR_T)); ::convToShortWchar(&t1, test1, iActualSize);
Để thuận tiện khi làm việc với các kiểu dữ liệu chuỗi, chúng tôi sẽ mô tả hàm wstring_to_p. Nó nhận một chuỗi wstring làm tham số. Kết quả của hàm là một cấu trúc được lấp đầy tBiến thể. Mã chức năng:
bool CAddInNative::wstring_to_p(std::wstring str, tVariant* val) ( char* t1; TV_VT(val) = VTYPE_PWSTR; m_iMemory->AllocMemory((void**)&t1, (str.length()+1) * sizeof(WCHAR_T)); memcpy(t1, str.c_str(), (str.length()+1) * sizeof(WCHAR_T)); val -> pstrVal = t1; trả về đúng)
Sau đó, phần trường hợp tương ứng của câu lệnh switch của phương thức GetPropVal sẽ có dạng:
trường hợp ePropTest1: wstring_to_p(test1, pvarPropVal); phá vỡ;
Phương pháp SetPropVal:
trường hợp ePropTest1: if (TV_VT(varPropVal) != VTYPE_PWSTR) trả về sai; test1 = std::wstring((wchar_t*)(varPropVal -> pstrVal)); phá vỡ;
Để triển khai thuộc tính thứ hai, chúng tôi xác định trường lớp CaddInBản Địa
uint8_t Last_type;
trong đó chúng ta sẽ lưu loại giá trị được chuyển cuối cùng. Để thực hiện việc này, hãy thêm lệnh vào phương thức CaddInNative::SetPropVal:
Last_type = TV_VT(varPropVal);
Bây giờ, khi yêu cầu đọc giá trị của thuộc tính thứ 2, chúng ta sẽ trả về giá trị loại cuối cùng, nhiệm vụ được chỉ định yêu cầu gì.
Hãy kiểm tra chức năng của những thay đổi được thực hiện.
Để làm điều này, chúng ta hãy trình bày diện mạo của cấu hình 1C như sau:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); DemoComp.TypeCheck = 1; Báo cáo (Chuỗi (DemoComp.TypeCheck)); DemoComp.Test = "Vasya"; Báo cáo (Chuỗi (DemoComp.Test)); DemoComp.Test = "Petya"; Báo cáo (Chuỗi (DemoComp.Test)); Báo cáo (Chuỗi (DemoComp.TypeCheck)); Kết thúcThủ tục
Sau khi ra mắt, chúng tôi sẽ nhận được một chuỗi thông báo:
3
Vasya
Peter
22

Thông báo thứ hai và thứ ba là kết quả của việc đọc thuộc tính được đặt ở bước trước. Tin nhắn đầu tiên và thứ hai chứa mã loại của tập thuộc tính cuối cùng. 3 tương ứng với một giá trị số nguyên, 22 tương ứng với một giá trị chuỗi. Sự tương ứng của các loại và mã của chúng được thiết lập trong tệp loại.h, nằm trên đĩa ITS.

Mở rộng danh sách các phương pháp

Nhiệm vụ:
  1. Mở rộng chức năng của thành phần bên ngoài với chức năng sau:
  2. Khám phá các cách để triển khai các phương thức thành phần bên ngoài
  3. Thêm một phương thức hàm Chức năng1, lấy hai chuỗi (“Parameter1” và “Parameter2”) làm tham số. Kết quả là một chuỗi như: “Đang kiểm tra. Tham số1, Tham số2"
  4. Đảm bảo những thay đổi bạn thực hiện có hiệu quả.

Để xác định các phương thức của thành phần đang được tạo, nhà phát triển phải triển khai các phương thức sau trong mã thư viện AddInNative:
GetNPhương thức, Phương thức tìm kiếm, GetMethodName
Được thiết kế để thu được số lượng phương thức tương ứng, tìm kiếm số lượng và tên của phương thức. Tương tự như các phương thức tương ứng cho thuộc tính
NhậnNParams
Trả về số tham số của phương thức với số thứ tự đã chỉ định; nếu phương thức có số này vắng mặt hoặc không có tham số, trả về 0
GetParamDefValue
Trả về giá trị mặc định của tham số đã chỉ định của phương thức đã chỉ định
HasRetVal
Trả về cờ xem phương thức có giá trị trả về thứ tự được chỉ định có giá trị trả về hay không: true cho các phương thức có giá trị trả về và SAI nếu không thì
CallAsProc
SAI, lỗi thời gian chạy sẽ xảy ra và quá trình thực thi mô-đun 1C: Enterprise bị chấm dứt. Bộ nhớ dành cho mảng tham số được phân bổ và giải phóng bởi 1C: Enterprise.
GọiAsFunc
Thực thi phương thức với số thứ tự được chỉ định. Nếu phương thức trả về SAI, lỗi thời gian chạy sẽ xảy ra và quá trình thực thi mô-đun 1C: Enterprise bị chấm dứt. Bộ nhớ dành cho mảng tham số được cấp phát bởi 1C: Enterprise. Nếu giá trị trả về là chuỗi hoặc kiểu dữ liệu nhị phân, thành phần sẽ cấp phát bộ nhớ bằng hàm Phân bổ bộ nhớ trình quản lý bộ nhớ, ghi dữ liệu vào đó và lưu trữ địa chỉ này vào trường tương ứng của cấu trúc. 1C: Doanh nghiệp sẽ giải phóng bộ nhớ này bằng cách gọi Giải phóng bộ nhớ.
Mô tả đầy đủ về các phương pháp, bao gồm danh sách các tham số, được mô tả chi tiết trong tài liệu được cung cấp trên đĩa ITS.
Chúng ta hãy xem việc thực hiện các phương pháp được mô tả ở trên.
Trong mã thành phần, hai mảng được xác định:
wchar_t tĩnh *g_MethodNames = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture"); wchar_t tĩnh *g_MethodNamesRu = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadImage");
và liệt kê:
Phương thức enum ( eMethEnable = 0, eMethDisable, eMethShowInStatusLine, eMethStartTimer, eMethStopTimer, eMethLoadPicture, eMethLast // Luôn tồn tại cuối cùng);
Chúng được sử dụng trong các chức năng GetNPhương thức, Phương thức tìm kiếmGetMethodName, bằng cách tương tự với việc mô tả các thuộc tính.
phương pháp NhậnNParams, GetParamDefValue, HasRetVal triển khai switch, tùy thuộc vào các tham số được truyền và logic ứng dụng, trả về giá trị được yêu cầu. Phương pháp HasRetVal trong mã của nó, nó có một danh sách các phương thức duy nhất có thể trả về kết quả. Vì họ anh trở lại ĐÚNG VẬY. Đối với tất cả các phương pháp thép trả về SAI.
phương pháp CallAsProcGọiAsFunc chứa mã thực thi trực tiếp của phương thức.
Để thêm một phương thức chỉ có thể được gọi dưới dạng hàm, bạn cần thực hiện những thay đổi sau đối với mã nguồn của thành phần bên ngoài:
  1. Thêm tên phương thức vào mảng g_Tên phương thứcg_MethodNamesRu(tài liệu AddInNative.cpp)
  2. Thêm một mã định danh phương thức có ý nghĩa vào bảng liệt kê Phương thức (tệp AddInNative.h)
  3. Thực hiện thay đổi mã chức năng NhậnNParams theo logic chương trình
  4. Nếu cần, hãy thay đổi mã phương thức GetParamDefValue, nếu bạn muốn sử dụng các giá trị mặc định của các tham số phương thức.
  5. Thực hiện thay đổi chức năng HasRetVal
  6. Thực hiện các thay đổi về logic của hàm CallAsProc hoặc GọiAsFunc, đặt mã thực thi trực tiếp của phương thức vào đó
Hãy trình bày các mảng g_Tên phương thứcg_MethodNamesRu, cũng như liệt kê phương phápđến dạng:
wchar_t tĩnh *g_MethodNames = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture", L"Test"); wchar_t tĩnh *g_MethodNamesRu = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture", L"Test");

Phương thức Enum ( eMethEnable = 0, eMethDisable, eMethShowInStatusLine, eMethStartTimer, eMethStopTimer, eMethLoadPicture, eMethTest, eMethLast // Luôn tồn tại );
Hãy chỉnh sửa chức năng GetNPropsđể nó trả về số lượng tham số của phương thức “Test”:
dài CAddInNative::GetNParams(const long lMethodNum) ( switch(lMethodNum) ( case eMethShowInStatusLine: return 1; case eMethLoadPicture: return 1; case eMethTest: return 2; mặc định: return 0; ) return 0; )
Hãy thực hiện các thay đổi đối với chức năng:
bool CAddInNative::GetParamDefValue(const long lMethodNum, const long lParamNum, tVariant *pvarParamDefValue) ( ​​​​ TV_VT(pvarParamDefValue)= VTYPE_EMPTY; switch(lMethodNum) ( case eMethEnable: case eMethDisable: case eMethShowInStatusLine: case eMethStartTimer : case eMethStopTimer: case eMethTest : // Mặc định không có giá trị tham số break;
Cảm ơn dòng được thêm vào
trường hợp eMethTest:
nếu thiếu một hoặc nhiều đối số, các tham số tương ứng sẽ có giá trị trống ( VTYPE_EMPTY). Nếu bạn cần một giá trị mặc định cho một tham số thì nên đặt nó trong phần eMethTest câu lệnh chuyển đổi chức năng CDdInNative::GetParamDefValue.
Vì phương thức Test có thể trả về một giá trị nên bạn cần thay đổi mã hàm HasRetVal:
bool CAddInNative::HasRetVal(const long lMethodNum) ( switch(lMethodNum) ( case eMethLoadPicture: case eMethTest: return true; mặc định: return false; ) return false; )
Và thêm mã thực thi của phương thức vào hàm GọiAsFunc:
bool CAddInNative::CallAsFunc(const long lMethodNum, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray) ( ... std::wstring s1, s2; switch(lMethodNum) ( case eMethLoadPicture: ... break; case eMethTest: if (!lSizeArray || !paParams) trả về sai; s1 = (paParams) -> pwstrVal; s2 = (paParams+1) -> pwstring_to_p(std::wstring(s1+s2), pvarRetValue return ret ; ;
Hãy biên dịch thành phần và đưa mã cấu hình về dạng:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); Lane = DemoComp.Test("Xin chào," "Thế giới!"); Báo cáo(mỗi); Kết thúcThủ tục
Sau khi khởi chạy cấu hình, chúng ta sẽ nhận được thông báo: “Xin chào, Thế giới!”, Cho biết phương thức đã hoạt động thành công.

hẹn giờ

Nhiệm vụ:
  1. Nghiên cứu cách triển khai bộ đếm thời gian trong bản demo VK
  2. Sửa đổi phương thức “StartTimer” bằng cách thêm khả năng truyền tham số khoảng thời gian phản hồi của bộ hẹn giờ (tính bằng mili giây)
  3. Đảm bảo những thay đổi bạn thực hiện có hiệu quả.

Trong WinAPI, bạn có thể sử dụng tin nhắn để làm việc với thời gian WM_TIMER. Thông báo này sẽ được gửi đến chương trình của bạn theo khoảng thời gian mà bạn đã đặt khi tạo bộ hẹn giờ.
Để tạo bộ hẹn giờ, hãy sử dụng chức năng Bộ hẹn giờ:
UINT SetTimer(HWND hWnd, // bộ mô tả cửa sổ UINT nIDevent, // mã định danh bộ đếm thời gian (số) UINT nElapse, // độ trễ TIMERPROC lpTimerFunc); // con trỏ tới hàm
Hệ điều hành sẽ gửi tin nhắn WM_TIMER vào chương trình với khoảng thời gian được chỉ định trong đối số trôi qua(tính bằng mili giây). Trong tham số cuối cùng, bạn có thể chỉ định một chức năng sẽ được thực thi mỗi khi bộ hẹn giờ kích hoạt. Tiêu đề của hàm này sẽ trông như thế này (tên có thể là bất kỳ tên nào):
void __stdcall Hẹn giờProc (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
Hãy xem xét việc triển khai bộ hẹn giờ trong VC demo.
Vì chúng tôi đang xem xét quá trình phát triển một thành phần bên ngoài cho dòng hệ điều hành Windows nên chúng tôi sẽ không xem xét việc triển khai bộ hẹn giờ trong các hệ điều hành khác. Đặc biệt, đối với hệ điều hành GNU/Linux, việc triển khai sẽ khác nhau về cú pháp của hàm Bộ hẹn giờHẹn giờProc.
Mã thực thi gọi phương thức Bộ hẹn giờ, mà hàm được truyền tới MyTimerProc:
m_uiTimer = ::SetTimer(NULL,0,100,(TIMERPROC)MyTimerProc);
ID của bộ đếm thời gian đã tạo được đặt trong một biến m_uiTimerđể sau này có thể vô hiệu hóa nó.
Chức năng MyTimerProc như sau:
VOID CUỘC GỌI MyTimerProc(HWND hwnd, // tay cầm cửa sổ dành cho tin nhắn hẹn giờ UINT uMsg, // tin nhắn WM_TIMER UINT idEvent, // mã định danh bộ đếm thời gian DWORD dwTime // thời gian hệ thống hiện tại) ( if (!pAsyncEvent) return; wchar_t *who = L "ComponentNative", *what = L"Timer"; wchar_t *wstime = new wchar_t; if (wstime) ( wmemset(wstime, 0, TIME_LEN); ::_ultow(dwTime, wstime, 10); pAsyncEvent->ExternalEvent(ai , cái gì, wstime); xóa wstime;
Bản chất của hàm là phương thức này được gọi Sự kiện bên ngoài, gửi tin nhắn đến hệ thống 1C: Enterprise.
Để mở rộng chức năng của phương pháp bộ đếm thời gian bắt đầu Hãy làm như sau:
Sửa đổi mã phương thức NhậnNParams vì vậy nó dành cho phương pháp eMethStartTimer giá trị trả về 1:
trường hợp eMethStartTimer: return 1;
Đây là mã phương pháp CallAsProcđến dạng:
trường hợp eMethStartTimer: if (!lSizeArray || TV_VT(paParams) != VTYPE_I4 || TV_I4(paParams)<= 0) return false; pAsyncEvent = m_iConnect; #ifndef __linux__ m_uiTimer = ::SetTimer(NULL,0,TV_I4(paParams),(TIMERPROC)MyTimerProc); #else // код для GNU/Linux #endif break;
Bây giờ hãy kiểm tra chức năng. Để thực hiện việc này, chúng tôi sẽ viết mã trong mô-đun ứng dụng được quản lý của cấu hình:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); DemoComp.StartTimer(2000); Kết thúcThủ tục
Sau khi bắt đầu cấu hình, chương trình sẽ nhận được thông báo trong khoảng thời gian 2 giây, cho biết bộ hẹn giờ đang hoạt động chính xác.

Tương tác với 1C: Hệ thống doanh nghiệp

Để tương tác giữa thành phần bên ngoài và hệ thống 1C: Enterprise, các phương thức của lớp IAddInDefBase, được mô tả trong tệp AddInDefBase.h. Chúng tôi liệt kê những cái được sử dụng phổ biến nhất:
Tạo thông báo lỗi
bool ảo ADDIN_API AddError(wcode ngắn không dấu, nguồn const WCHAR_T*, const WCHAR_T* descr, scode dài)
mã wcode, mã hóa- mã lỗi (có thể tìm thấy danh sách mã lỗi kèm theo mô tả trên đĩa ITS)
nguồn- nguồn lỗi
mô tả- Mô tả lỗi
Gửi tin nhắn tới 1C: Hệ thống doanh nghiệp
bool ảo ADDIN_API InternalEvent(WCHAR_T* wszSource, WCHAR_T* wszMessage, WCHAR_T* wszData) = 0;
nguồn wsz- nguồn tin nhắn
wszMessage- Tin nhắn văn bản
wszData- dữ liệu được truyền đi
Việc chặn tin nhắn được thực hiện bằng thủ tục Xử lý sự kiện bên ngoài
Đăng ký thành phần bên ngoài trong 1C: Hệ thống doanh nghiệp
bool ảo ADDIN_API RegisterProfileAs(WCHAR_T* wszProfileName)
wszTên hồ sơ- Tên thành phần.
Những phương pháp này đủ để tương tác đầy đủ giữa VK và 1C. Để nhận dữ liệu bởi một thành phần bên ngoài từ hệ thống 1C: Enterprise và ngược lại, thành phần bên ngoài sẽ gửi một tin nhắn đặc biệt, tin nhắn này sẽ bị hệ thống 1C chặn và nếu cần, sẽ gọi các phương thức của thành phần bên ngoài để truyền dữ liệu trở lại .

Kiểu dữ liệu tVariant

Khi trao đổi dữ liệu giữa thành phần bên ngoài và hệ thống 1C: Enterprise, kiểu dữ liệu tVariant được sử dụng. Nó được mô tả trong tệp type.h, có thể tìm thấy trên đĩa ITS:
struct _tVariant ( _ANONYMOUS_UNION union ( int8_t i8Val; int16_t shortVal; int32_t lVal; int intVal; unsigned int uintVal; int64_t llVal; uint8_t ui8Val; uint16_t ushortVal; uint32_t ulVal; uint64_t ; t errCode; float fltVal; struct tm tmVal; void* pInterfaceVal; __VARIANT_NAME_2/*iface*/; pstrVal; uint32_t strLen ; //đếm byte ) __VARIANT_NAME_3/*str*/; _ANONYMOUS_STRUCT struct ( WCHAR_T* pwstrVal; uint32_t wstrLen; //đếm ký hiệu ) __VARIANT _NAME_1 mảng chiều trong pvarVal TYPEVAR vt);
Kiểu tBiến thể là một cấu trúc bao gồm:
  • hỗn hợp (kết hợp) dành trực tiếp cho việc lưu trữ dữ liệu
  • định danh kiểu dữ liệu
Nói chung, làm việc với các biến kiểu tBiến thể xảy ra theo thuật toán sau:
  1. Xác định loại dữ liệu hiện được lưu trữ trong một biến
  2. Truy cập trường hỗn hợp tương ứng để truy cập trực tiếp vào dữ liệu
Sử dụng loại tBiến thểđơn giản hóa đáng kể sự tương tác của 1C: Hệ thống doanh nghiệp và các thành phần bên ngoài

Ứng dụng

Thư mục “examples” chứa các ví dụ cho bài viết
ví dụ/1 - khởi chạy thành phần demo
ví dụ/2 - trình diễn việc mở rộng danh sách tài sản
ví dụ/3 - trình diễn mở rộng danh sách các phương thức
Mỗi thư mục chứa một dự án VS 2008 và cấu hình 1C được tạo sẵn.

Tiêu đề của bài viết có chứa cụm từ “dành cho người giả”. Khi nói đến ấm trà, trước hết tôi muốn nói đến chính tôi. Tất cả kiến ​​thức về C++ của tôi vẫn ở mức 3-4 năm đại học, khi tôi dấn thân vào con đường quanh co 1C. Và mọi thứ sẽ ổn, nhưng gần đây có một nhiệm vụ nảy sinh yêu cầu viết một thành phần bên ngoài. Tôi đã phải nhớ lại những ký ức của mình và phủi bụi kiến ​​thức về C++ của mình. Hóa ra mọi thứ không quá đáng sợ. Tôi muốn giới thiệu ngắn gọn cho bạn về cách viết các thành phần bên ngoài.

Các thành phần mẫu trên ITS

Đĩa ITS chứa tài liệu đầy đủ về cơ chế của các thành phần bên ngoài, được bổ sung bằng một dự án mẫu và một mẫu để bạn phát triển. Vật liệu này được gọi là “Công nghệ thành phần bên ngoài”. Tài liệu này rất hay nhưng bạn vẫn cần phải hiểu nó và thời gian, như thường lệ, rất ngắn. Thực ra chỉ có vài điểm mấu chốt đáng quan tâm, còn lại là mục nát và phù phiếm :)

Vật liệu cần thiết

Để tạo một thành phần bên ngoài, chúng ta sẽ cần:

  1. Tài liệu “Công nghệ tạo linh kiện bên ngoài” nằm trên ITS
  2. Mẫu thành phần bên ngoài trống đi kèm với tài liệu
  3. MS Visual Studio. Phiên bản Express là miễn phí và quá đủ cho nhu cầu của chúng tôi.
  4. Có kiến ​​thức cơ bản về cú pháp C++, cụ thể:
  • Khả năng phân biệt khai báo biến với vòng lặp hoặc điều kiện
  • Hiểu rằng các chuỗi ở dạng thuần túy của chúng không tồn tại trong C++, có những mảng mà bạn rõ ràng cần phải bận tâm đến bộ nhớ
  • Tất nhiên, cần có khả năng thực hiện nhiệm vụ bằng ngôn ngữ được chỉ định. Ở mức tối thiểu, khả năng gọi một số thư viện của bên thứ ba từ C++ sẽ tự thực hiện mọi thứ.

Hãy bắt đầu đào

Tài liệu về API gốc khá chi tiết. Tóm lại, nó nói như sau:

  1. Một thành phần bên ngoài cho phép bạn mở rộng ngôn ngữ tích hợp với một (hoặc một số) đối tượng mới. Những thứ kia. chúng ta sẽ tạo một lớp nhất định mà chúng ta có thể tạo bằng cách sử dụng toán tử “Mới” và gọi các phương thức của đối tượng này từ ngôn ngữ tích hợp sẵn.
  2. Để đối tượng của chúng tôi hoạt động, nền tảng sẽ “giao tiếp” với nó bằng một giao thức nhất định mà chúng tôi có nghĩa vụ phải cung cấp.
  3. Bản thân mã thành phần theo quy ước bao gồm hai phần: phần thứ nhất là việc đăng ký chính thành phần đó trong hệ thống, phần thứ hai là hoạt động của lớp mới và sự tương tác của nó với nền tảng.

Chúng tôi sẽ không đi sâu vào chi tiết cụ thể của việc triển khai, chúng tôi sắp hết thời hạn và chúng tôi không có đủ năng lực. Chúng ta cần nhanh chóng hiểu nơi chúng ta cần nhập dòng để thành phần hoạt động. Để thực hiện việc này, hãy lấy mẫu thành phần bằng ITS và mở nó trong Visual Studio. Mẫu nằm trong thư mục mẫu của kho lưu trữ đã giải nén. Hãy xem chúng ta có gì ở đây.

Chúng tôi quan tâm đến tệp AddInNative.cpp. Mọi nhận thức đều nằm trong đó. Nó chứa các mẫu cho tất cả các phương pháp cần thiết; bạn chỉ cần tùy chỉnh chúng một chút. Tuy nhiên, hóa ra việc sử dụng một mẫu trống làm cơ sở sẽ dễ dàng hơn mà xử lý một ví dụ hoạt động. Nó có một số chuông và còi hữu ích không có trong mẫu trống. Khi đã hiểu rõ, bạn sẽ cần lấy một mẫu trống và tinh chỉnh nó bằng kiến ​​thức về vấn đề. Một ví dụ về thành phần hoạt động nằm trong thư mục example\NativeAPI và một mẫu trống nằm trong thư mục mẫu.

Hãy mở dự án từ thư mục ví dụ và trong đó - tệp AddInNative.cpp

Ở phần đầu của tệp có phần khai báo các hằng và hàm phụ trợ. Chúng tôi quan tâm đến các dòng sau:

Đối tượng của chúng tôi, với tư cách là đối tượng “thực”, sẽ hỗ trợ các phương thức được viết bằng cả tiếng Nga và tiếng Anh. Vì mục đích này, tên viết của thuộc tính và phương thức được khai báo bằng hai ngôn ngữ. Khung màu xanh dành cho phòng tắm kiểu Anh, khung màu đỏ dành cho phòng tắm kiểu Nga. Hình ảnh cho thấy ví dụ này đã triển khai một số phương thức và thuộc tính. Nhiệm vụ của chúng ta là loại bỏ chúng và chèn chúng vào.

Dòng khai báo tên lớp được tô sáng bằng khung màu xanh lá cây. Thành thật mà nói, tôi không hiểu nó có nghĩa gì. Nếu bạn thay đổi nó, không có gì hoạt động. Vì ban đầu họ bảo lưu rằng tôi là một “hình nộm” nên tôi có thể được tha thứ. :)

Do đó, nếu đối tượng của chúng ta chứa phương thức “RunCalculation” và thuộc tính “Destination”, thì chúng ta cần mô tả tên này trong mảng g_MethodNamesRu và g_PropNamesRu tương ứng.

Cuộc gọi từ ngôn ngữ 1C

Vì vậy, đối tượng của chúng ta sẽ chứa một phương thức và thuộc tính đọc-ghi.

Hãy có kịch bản sử dụng sau:

OurObject = New(“AddIn. MyComponent. DataSender”); Đối tượng của chúng tôi. Đích = "somemail@server. com";
Đối tượng của chúng tôi. RunCalculation(PaymentAmount, “Dành cho tiện ích”);

Có một thuộc tính chuỗi và một phương thức có tham số dạng số và chuỗi. Để tất cả điều này hoạt động, 1C thực hiện xấp xỉ giao thức liên lạc sau với thành phần:

Nền tảng gọi các hàm được xác định trước trên đối tượng của chúng ta và nó phản hồi và thực thi các lệnh của nó. Tình huống tương tự với các phương thức, chỉ ở đó, ngoài số phương thức, số lượng tham số, sự hiện diện của giá trị trả về và sự hiện diện của các tham số tùy chọn được yêu cầu.

Hãy quay lại mã của chúng tôi. Để tránh “những con số ma thuật”, hai bảng liệt kê được khai báo trong lớp CAddInNative, chịu trách nhiệm xác định số lượng phương thức và thuộc tính. Hãy mở tệp CDdInNative.h và xem chúng ngay từ đầu:

Mẫu trống không chứa các bảng liệt kê này và cũng không có ví dụ nào để phân tách các cuộc gọi bằng tiếng Nga với các cuộc gọi không phải bằng tiếng Nga. Cách tiếp cận này là tùy chọn. Điều quan trọng là phải tôn trọng giao diện và việc có chuyển khoản hay không là tùy thuộc vào bạn.

Chuỗi Unicode

Nhiều người có thể biết rằng nền tảng này hoạt động trên các ký tự 2 byte ở định dạng Unicode. Mẫu khai báo một loại đặc biệt WCHAR_T cho mục đích này. Loại này là trình bao bọc đa nền tảng và đảm bảo cùng kích thước ký tự trên Windows và Linux. Loại wchar_t tiêu chuẩn có thể khác nhau về kích thước trên các hệ thống khác nhau. Cũng xin lưu ý rằng tất cả các chuỗi ký tự đều được khai báo bằng tiền tố là chữ L. Điều này có nghĩa là chuỗi như vậy thuộc loại wchar_t.

Có một quy tắc đơn giản: bên trong, các thành phần chuỗi được xử lý dưới dạng wchar_t (trên Linux có thể có 4 byte, trên Windows - 2), nhưng ngay khi chúng tôi chuyển chuỗi sang 1C hoặc nhận nó từ đó, chúng tôi cần WCHAR_T (nghiêm túc 2 byte trên tất cả các hệ thống).

Để chuyển đổi một loại chuỗi này sang loại khác, mẫu cung cấp các hàm phụ trợ:

Cái đầu tiên tạo thành WCHAR_T từ wchar_t tiêu chuẩn:

uint32_t convToShortWchar(WCHAR_T** Đích, const wchar_t* Nguồn, uint32_t len ​​​​= 0);

Thứ hai thì ngược lại. Biểu mẫu wchar_t từ WCHAR_T.

uint32_t convFromShortWchar(wchar_t** Dest, const WCHAR_T* Source, uint32_t len ​​​​= 0);

Khi tương tác với nền tảng, chỉ WCHAR_T luôn được sử dụng.

Loại biến thể

Một điều thú vị khác là kiểu dữ liệu Variant chung. Nó cho phép chúng ta tương tác với ngôn ngữ 1C, như bạn biết, ngôn ngữ này không được gõ và mỗi biến trong đó có thể chứa bất kỳ thứ gì. Loại này được sử dụng khi trao đổi giá trị. Chúng ta truyền hai tham số cho phương thức RunCalculation - một số và một chuỗi. Thành phần này sẽ nhận được hai giá trị Biến thể. Trách nhiệm của chúng tôi là kiểm tra loại thực tế của chúng. Không ai có thể ngăn cản bạn chuyển không phải một số cho thành phần mà là một bảng giá trị.

Mặc dù, có vẻ như tôi đã sai. Đối với tôi, có vẻ như sẽ không thể chuyển Bảng giá trị sang NativeAPI, bởi vì... nó không có trong danh sách các loại được phép, tuy nhiên, bạn có thể chuyển Ngày thay vì Chuỗi. Điều này cũng không tốt. Chúng ta phải kiểm tra loại thực của biến xuất phát từ 1C.

Loại Variant rất đơn giản. Đây là một cấu trúc có thuộc tính là các giá trị thuộc các loại khác nhau. Có các thuộc tính như DATE, wchar_t, int và các thuộc tính khác. Phần chính của Biến thể là thuộc tính “vt”, lưu trữ loại thực của biến và từ đó bạn có thể hiểu chính xác cách diễn giải Biến thể này. Ngoài ra, một số macro phụ trợ đã được khai báo để đơn giản hóa việc làm việc với kiểu Variant.

Vào vấn đề

Có vẻ như đó là tất cả với phần giới thiệu. Tôi đề xuất xem xét một ví dụ về việc triển khai một thành phần bên ngoài. TK sẽ là một ví dụ về một thành phần từ đĩa ITS. Ví dụ này mô tả các tính năng sau:

  • Hiển thị văn bản trên thanh trạng thái của cửa sổ chính;
  • Gửi một sự kiện hẹn giờ bên ngoài;
  • Truyền dữ liệu nhị phân sang 1C:Enterprise;
  • Thực hiện các tài sản;
  • Thực hiện các thủ tục;
  • Thực hiện các chức năng;

Thành phần này có API sau:

  • Của cải:
    • Đã bật/Đã bật;
    • IsTimer/IsTimerPresent;
    • Phương pháp:
      • Cho phép;
      • Tắt/Tắt;
      • ShowInStatusLine;
      • EnableTimer/StartTimer;
      • Tắt Hẹn giờ/Hẹn giờ dừng;
      • LoadPicture/LoadPicture;

Một sự kiện bên ngoài xảy ra trên bộ hẹn giờ, có thể được đăng ký từ mã 1C.

Được hướng dẫn bởi kiến ​​thức chúng ta có, chúng ta hãy xem xét thành phần này ngay từ đầu.

Thành phần đăng ký

Đối tượng của chúng tôi được triển khai dưới dạng một lớp C++ riêng biệt, trong trường hợp này là CDdInNative. Để 1C có thể xem được lớp của chúng ta thì thư viện dll phải xuất 3 hàm:

  • GetClassObject
  • Phá hủy đối tượng
  • Nhận tên lớp

Bạn có thể xem các bản xuất này trong tệp AddInNative.def trong cây dự án VisualStudio. Hãy xem mã của các chức năng này:

Cách đơn giản nhất - hàm GetClassNames - cho nền tảng 1C biết các lớp nào có trong thành phần của chúng ta. Hãy để các chuyên gia C++ sửa lỗi cho tôi, đối với tôi, có vẻ như ở đây nền tảng cần phản hồi bằng tên của các lớp C++ để có thể nhập chúng. Đây chính xác là mục đích sử dụng của mảng g_kClassNames, mảng có “khung” màu xanh lá cây. Tôi không kiểm tra cụ thể nó, nhưng nếu bạn chỉ cần làm cho thành phần đó hoạt động thì bạn nên để mọi thứ như trong ví dụ. Nó đã hoạt động rồi, không cần phải mày mò vào lúc này.

Vì vậy, GetClassNames trả về nền tảng một mảng tên lớp triển khai các đối tượng hữu ích của thành phần bên ngoài. Trong ví dụ của chúng tôi, thành phần sẽ trả về nền tảng một mảng gồm một phần tử có tên lớp CDdInNative.

Xin lưu ý rằng nền tảng sẽ nhận được giá trị loại WCHAR_T và tên lớp trong mảng g_kClassNames là loại wchar_t. Do đó, việc ép kiểu được thực hiện bằng cách sử dụng hàm trợ giúp đã thảo luận ở trên.

Hàm tiếp theo là GetClassObject. Được gọi khi chúng tôi viết “Mới” trong mã doanh nghiệp. Nền tảng này yêu cầu chúng ta tạo một phiên bản mới của lớp và trả về một con trỏ tới đối tượng mới.

Một lần nữa, hãy lưu ý rằng tham số đầu tiên mà nền tảng cho chúng ta biết là lớp nào sẽ được tạo (từ những lớp được cung cấp cho nó bằng phương thức GetClassNames). Vì chúng ta chỉ có một lớp nên tên này hoàn toàn không được kiểm tra ở đây, một đối tượng được tạo đơn giản thông qua new và được trả về thông qua tham số đầu ra pInterface.

Và hàm xuất được yêu cầu cuối cùng là DestroyObject. Tên nói cho chính nó. Khi một đối tượng không còn cần thiết trên nền tảng nữa thì nó cần phải bị xóa. Chúng ta được cấp một con trỏ tới đối tượng được tạo trước đó. Chúng tôi giải phóng nó bằng cách xóa và đặt lại các con trỏ không cần thiết.

Việc triển khai được mô tả là khá phổ biến. Nếu thành phần của chúng ta chỉ triển khai một lớp (như trong ví dụ), thì các hàm này chỉ cần được sao chép vào chính nó. Điều kiện duy nhất là tạo đúng lớp trong hàm GetClassObject, nếu tên của bạn không phải là CDdInObject mà là tên khác.

Khởi tạo/kết thúc một thành phần

Sau khi tạo một lớp triển khai một thành phần, nền tảng sẽ gọi các phương thức của lớp này. Trước khi bắt đầu công việc, nền tảng sẽ cho chúng ta biết một đối tượng là “chính nó”, mà chúng ta có thể gọi một số phương thức nhất định của chính nền tảng đó. Điều này xảy ra trong phương thức Init. Trong ví dụ này, đối tượng nền tảng được lưu trữ trong biến m_iConnect.

Một phương pháp quan trọng khác là setMemManager. Cho phép bạn phân bổ các khối bộ nhớ mà nền tảng sẽ giải phóng. Nó được thực hiện như sau:

Chúng tôi chỉ lưu trữ một con trỏ tới trình quản lý bộ nhớ mà nền tảng chuyển cho chúng tôi. Sau đó, với trình quản lý này, chúng tôi sẽ phân bổ bộ nhớ do chính nền tảng giải phóng.

Và một lần nữa, như trong trường hợp của các hàm xuất, các phương pháp khởi tạo khá phổ biến; bạn có thể chỉ cần sao chép chúng cho chính mình và không cần lo lắng về việc “hoàn thiện” chúng cho đến khi thực sự cần thiết.

Khối hàng. Các phương thức và thuộc tính của đối tượng thành phần

Sự đăng ký

Tất nhiên, chúng tôi tạo ra thành phần này không phải vì mục đích khởi tạo nó mà vì một số chức năng hữu ích. Đã đến lúc xem xét nó được thực hiện như thế nào.

Đầu tiên, chúng ta phải đăng ký một đối tượng có thể được tạo và gọi từ ngôn ngữ 1C. Đối tượng này được đăng ký trong phương thức RegisterExtensionAs.

Trong phương pháp này, chúng tôi thông báo cho nền tảng tên lớp của chúng tôi, vì nó sẽ hiển thị từ ngôn ngữ 1C. Với cái tên này, chúng tôi sẽ tạo ra nó thông qua “Mới”. Trong trường hợp này, việc tạo đối tượng sẽ được thực hiện với đoạn mã sau:

ConnectExternalComponent(Tệp, "MyComponent", InternalComponentType. Native);
ObjectComponents = Mới( "AddIn.MyComponent.AddInNativeExtension");

Theo tài liệu, bộ nhớ cho chuỗi có tên lớp được phân bổ bởi trình quản lý bộ nhớ và tên được ghi vào địa chỉ này - “AddInNativeExtension”. Ở đây bạn có thể viết tên của bạn một cách dễ dàng. Xin lưu ý rằng một lần nữa lại có sự chuyển đổi từ wchar_t sang nền tảng WCHAR_T.

Cách sử dụng

Như tôi đã viết ở trên, nền tảng truy vấn thành phần để tìm các tính năng ngôn ngữ khác nhau. Thuộc tính được chỉ định có tồn tại không, nó có thể ghi được không, tham số hàm có giá trị mặc định không, nó có giá trị trả về không, v.v. Nếu chúng ta lấy ví dụ mã được đưa ra trước đó:

OurObject = Mới( "AddIn.MyComponent.DataSender"); // DataSender là tên của hàm RegisterExtensionAs (được thảo luận bên dưới).
Đối tượng của chúng tôi. Người nhận địa chỉ = " [email được bảo vệ]" ;
Đối tượng của chúng tôi. Thực hiện tính toán (Số tiền thanh toán, "Vì tiện ích");

thì cuộc thăm dò sau đây sẽ được thực hiện:

  1. Có thuộc tính "Đích" không?
  2. Nó có hỗ trợ ghi âm không?
  3. Có phương pháp nào gọi là RunCalculation không?
  4. Nó có bao nhiêu thông số?
  5. Nó có giá trị trả về không
  6. Giá trị mặc định cho các tham số tùy chọn là gì (nếu có)

Ở đây sẽ hữu ích nhất khi xem một ví dụ và kiểm tra tài liệu. Việc thực hiện tất cả các cuộc khảo sát này khá đơn giản. Cả một tập hợp các phương pháp chịu trách nhiệm cho sự tương tác. Tôi sẽ không xem xét kỹ mọi thứ, chúng được ghi chép khá đầy đủ và bên cạnh đó, chúng rất dễ thực hiện. Chỉ những khoảnh khắc quan trọng nhất mới được xem xét, trong đó chúng ta, với tư cách là những kẻ ngu ngốc, sẽ cần chạm tay vào chúng :). Cách tiếp cận cơ bản như sau: lần đầu tiên một thuộc tính hoặc phương thức được đề cập, nền tảng sẽ yêu cầu chúng ta tìm kiếm nó theo tên. Chúng ta sẽ phải trả lời bằng số duy nhất của thuộc tính (phương thức) này. Tất cả các thông tin liên lạc tiếp theo sẽ chỉ diễn ra bằng số. Đây là nơi mà các bảng liệt kê lưu trữ những con số này sẽ hữu ích.

Của cải

Điều đầu tiên cần xem xét là cơ sở hạ tầng bất động sản. Nền tảng yêu cầu sự tồn tại của một thuộc tính bằng phương thức FindProp

Nền tảng chuyển cho chúng tôi tên của thuộc tính mà chúng tôi đang tìm kiếm ở dạng WCHAR_T. Nó được chuyển đổi thành wchar_t bằng phương pháp phụ trợ và văn bản này được tìm kiếm đầu tiên bằng thuật ngữ tiếng Anh, sau đó bằng tiếng Nga. Chúng tôi phải trả lại số tài sản. Lưu ý rằng chức năng trợ giúp findName có liên quan ở đây. Việc triển khai nó có trong ví dụ, nhưng thành phần này không có trong mẫu trống. Có vẻ thích hợp để kéo nó về phía bạn nếu bạn dự định có các thuật ngữ song ngữ trong thành phần của mình.

Tiếp theo, phương thức GetPropName thực hiện tác vụ ngược lại, lấy tên thuộc tính theo số của nó. Chuỗi tên cũng được phân bổ thông qua trình quản lý bộ nhớ doanh nghiệp. Tôi nghi ngờ rằng phương thức GetPropName cùng với GetNProps được sử dụng khi chúng tôi mở rộng thuộc tính của một đối tượng bằng dấu cộng trong trình gỡ lỗi. Sau đó, nền tảng sẽ nhận được tổng số thuộc tính và yêu cầu đặt tên cho từng thuộc tính đó.

Cặp phương thức tiếp theo là IsPropReadable/IsPropWritable. Ở đây mọi thứ đều đơn giản, đối với số thuộc tính được chỉ định, chúng ta phải nói liệu nó có thể được đọc/ghi hay không.

Việc nhận và ghi các giá trị được thực hiện bằng các phương thức GetPropVal/SetPropVal. Thật đáng để đi vào chi tiết hơn ở đây. Chúng tôi đang bắt đầu làm việc với các loại 1C:Enterprise, điều đó có nghĩa là Biến thể đang xuất hiện.

Mẫu thành phần xác định một tập hợp các macro phụ trợ để đơn giản hóa thao tác với Biến thể. Đầu tiên là kiểm tra loại giá trị. Ví dụ: macro TV_VT cho phép bạn kiểm tra/đặt loại giá trị. Các hằng số được đặt tên cũng được xác định cho từng loại được hỗ trợ. Các hằng số này và sự tương ứng của chúng với các loại 1C:Enterprise được liệt kê trong tài liệu.

Macro TV_BOOL nhận giá trị Boolean từ biến thể mà bạn có thể làm việc. Bằng cách tương tự, thu được các giá trị số nguyên (TV_INT), chuỗi (TV_WSTR) và các giá trị khác. Các giá trị chính xác có trong mã, bạn luôn có thể nhìn thấy chúng.

Một điểm quan trọng là việc gán một giá trị cho một biến thể là chưa đủ; bạn còn phải gán một kiểu thực. Hãy chú ý đến GetPropVal. Ngoài phép gán TV_BOOL = true, còn có một kiểu gán: TV_VT = VTYPE_BOOL. Nếu loại không được chỉ định, nền tảng sẽ không biết loại giá trị nào được trả về cho nó. Tất nhiên, bạn có thể nhầm lẫn và đặt sai loại. Điều này thường đi kèm với việc nền tảng bị rơi.

Hãy tóm tắt những điều trên:

Chúng tôi nhận được giá trị từ tùy chọn:

bool someVariable = TV_BOOL(pVariant);

Viết giá trị cho tùy chọn:

TV_VT(pVariant) = VTYPE_BOOL; // kiểu dữ liệu hợp lệ

TV_BOOL(pVariant) = someBooleanVariable; // tự đặt giá trị

Và bây giờ - phương pháp gù lưng!

Các phương thức phức tạp hơn một chút, nhưng nhìn chung chúng tương tự như các thuộc tính. Đầu tiên, có chức năng giống hệt như tìm kiếm một phương thức theo tên, lấy tổng số phương thức, lấy tên theo số. Tuy nhiên, ngoài những tính năng trên, còn có thêm các tính năng sau:

  • Nếu một phương thức có thể trả về một giá trị thì phương thức đó có thể được sử dụng trong “Tính toán” và được viết ở bên phải thao tác gán bằng ngôn ngữ 1C. Nếu không thì đó là một thủ tục và những thứ tương tự sẽ tạo ra ngoại lệ "Sử dụng thủ tục làm hàm"
  • Phương thức này có tham số. Nền tảng phải biết số của họ. Nếu cuộc gọi chỉ định nhiều đối số hơn mức được chỉ định trong chữ ký phương thức thì sẽ xảy ra lỗi “Quá nhiều tham số”.
  • Nếu không đủ đối số được truyền cho một phương thức thì một số trong số chúng có thể là tùy chọn và nếu không có tham số tùy chọn thì sẽ xảy ra lỗi "Không đủ tham số".
  • Khi được gọi, nếu là thủ tục thì không thể có giá trị trả về. Nếu là hàm thì sẽ có giá trị trả về. Nó cũng cần được xử lý.

Có một số phương pháp đơn giản, mục đích của chúng được nêu rõ trong tên và tài liệu. Chúng bao gồm HasRetVal, GetNParams, GetParamDefValue. Tôi đề nghị không xem xét chúng; một ví dụ là quá đủ. Mối quan tâm của chúng tôi sẽ hướng tới việc triển khai trực tiếp tải trọng. Nó được triển khai trong các phương thức CallAsProc và CallAsFunc. Cái đầu tiên chịu trách nhiệm gọi các thủ tục, cái thứ hai chịu trách nhiệm gọi các hàm. Chúng khác nhau ở chỗ CallAsFunc có một tham số đầu ra bổ sung, trong đó chúng ta sẽ chuyển giá trị trả về của hàm cho nền tảng.

Cuộc gọi được thực hiện như sau: nền tảng chuyển cho chúng ta số của phương thức được gọi, một mảng các tham số thực tế và số của chúng. Chúng ta phải phân tích số phương thức và cung cấp cho nó các tham số đã truyền. Trong trường hợp hàm, chúng ta cũng phải ghi nội dung nào đó vào giá trị trả về.

Trong ví dụ này, số phương thức được phân tích trong switch/case và tùy thuộc vào số đó, logic phương thức được thực thi. Đối với các phương thức Bật/Tắt, chỉ cần chọn một hộp kiểm. Phương thức ShowInStatusLine rất thú vị. Nó hiển thị những gì đã được chuyển đến nó trên thanh trạng thái của cửa sổ 1C:Enterprise. Để thực hiện việc này, chúng tôi sử dụng đối tượng kết nối nền tảng m_iConnect, đối tượng đã được “cấp” cho chúng tôi khi đăng ký thành phần. Danh sách đầy đủ các khả năng của nó được mô tả trong tài liệu.

Điểm thú vị. Ở đây, trong ví dụ, loại giá trị đến từ 1C không được chọn, nhưng SetStatusLine được gọi đơn giản với phần chuỗi Variant. Tôi nghi ngờ rằng nếu bạn gọi phương thức thành phần từ ngôn ngữ 1C, chuyển một số hoặc ngày vào đó (thay vì một chuỗi), thì sẽ không có gì hoạt động... Một lần nữa, hãy để các chuyên gia sửa lại, nhưng có vẻ như con trỏ pwstrVal sẽ trỏ có Chúa mới biết nếu nó đến từ doanh nghiệp thì hãy nói một con số chứ không phải một chuỗi trung thực. Khi gọi SetStatusLine, nền tảng sẽ cố đọc một dòng từ một địa chỉ không xác định và rất có thể sẽ gặp sự cố. Tốt hơn hết là luôn kiểm tra loại dự kiến. Bạn không bao giờ biết.

Hàm LoadImage trong ví dụ được triển khai theo cách thú vị hơn; nó xem xét khả năng trao đổi chuỗi và dữ liệu nhị phân với nền tảng.

Đầu tiên, số lượng tham số được truyền sẽ được kiểm tra ở đây. Nếu họ không có mặt thì cuộc gọi được coi là không thành công. Trả về sai, được nền tảng hiểu là lỗi cuộc gọi.

Tiếp theo, loại tham số đã truyền sẽ được kiểm tra ở đây. Nếu đây là một chuỗi hẹp (VTYPE_PSTR), thì phần char của biến thể sẽ được sử dụng. Ví dụ cho biết paParam->pstrVal, nhưng bạn có thể sử dụng macro TV_STR, nó sẽ giống nhau nhưng tính đồng nhất khi làm việc với tùy chọn cũng sẽ được duy trì.

Nếu đó là một chuỗi rộng (VTYPE_PWSTR), thì việc chuyển đổi được thực hiện trước tiên thành wchar_t và sau đó thành char. Thực tế là đường dẫn đến tệp được truyền từ ngôn ngữ 1C sang phương thức này, sau đó được sử dụng trong hàm fopen(char*). Hàm này yêu cầu loại char* làm đầu vào và WCHAR_T sẽ được gửi cho chúng tôi từ nền tảng. Để hoạt động chính xác, chuyển đổi chuỗi được thực hiện.

Và cuối cùng, nếu đây hoàn toàn không phải là một chuỗi thì cuộc gọi được coi là không thành công và trả về sai.

Chúng tôi phân bổ bộ nhớ cho dữ liệu nhị phân bằng trình quản lý bộ nhớ. Điều này là hợp lý; dữ liệu nhị phân sẽ trở thành một đối tượng chính thức trong nền tảng và nó phải được quản lý bởi nền tảng đó. Bộ nhớ được phân bổ cho biến thể pvarRetValue, là giá trị trả về của hàm thành phần bên ngoài.

Ngoài ra, toàn bộ tệp được đọc vào bộ đệm được phân bổ; nhất thiết kích thước byte được chỉ định trong thuộc tính tùy chọn strLen và kiểu dữ liệu của tùy chọn VTYPE_BLOB. Nếu bộ nhớ được cấp phát thành công thì chúng ta trả về true như một dấu hiệu của lệnh gọi thành công tới toàn bộ hàm.

Vì vậy, khi ở ngôn ngữ 1C nó được viết:

Dữ liệu nhị phân = Thành phần. Tải lênHình ảnh("C:\pic.jpg");

Phương thức CallAsFunc của đối tượng thành phần sẽ được gọi, truyền đường dẫn và trả về dữ liệu nhị phân như mô tả ở trên.

Nếu thành công, biến BinaryData sẽ chứa đối tượng ngôn ngữ 1C chính thức. Khi nó vượt quá phạm vi, tất cả bộ nhớ mà nó chiếm giữ sẽ được nền tảng giải phóng. Đó là lý do tại sao nó được cấp phát thông qua trình quản lý bộ nhớ.

Phần kết luận

Câu chuyện được viết bởi một ấm trà dành cho những người ngu ngốc, do đó, rất có thể, nó có rất nhiều điểm không chính xác về mặt thuật ngữ. Tuy nhiên, mục đích của bài viết này là giới thiệu nhanh về các thành phần bên ngoài. Nếu bạn cần nhanh chóng tạo ra một thành phần trong thời gian ngắn, không gặp rắc rối không cần thiết, không cần thảo luận dài dòng, thì tôi hy vọng rằng bài viết này sẽ giúp ích cho bạn. Nếu bất kỳ sai lầm nào của tôi khiến bạn cảm thấy tồi tệ, với tư cách là một bậc thầy về C++, vui lòng cho tôi biết trong phần nhận xét và chúng tôi sẽ sửa chúng.

Cám ơn vì sự quan tâm của bạn.

ansmirnov Ngày 22 tháng 8 năm 2013 lúc 2:12 chiều

Các thành phần bên ngoài trong 1C 8.2

  • Lập trình,
  • C++
  • Hướng dẫn

Giới thiệu

Bài viết này đưa ra ý tưởng về cách các thành phần bên ngoài hoạt động trong hệ thống 1C: Enterprise.
Quá trình phát triển thành phần bên ngoài cho hệ thống 1C: Enterprise phiên bản 8.2, chạy trên hệ điều hành Windows với chế độ hoạt động tệp sẽ được hiển thị. Tùy chọn này được sử dụng trong hầu hết các giải pháp được thiết kế cho doanh nghiệp nhỏ. VK sẽ được triển khai bằng ngôn ngữ lập trình C++.

Các thành phần bên ngoài "1C: Enterprise"

"1C: Enterprise" là một hệ thống có thể mở rộng. Để mở rộng chức năng của hệ thống, các thành phần bên ngoài (EC) được sử dụng. Theo quan điểm của nhà phát triển, VC là một đối tượng bên ngoài có các thuộc tính và phương thức, đồng thời cũng có thể tạo ra các sự kiện để hệ thống 1C: Enterprise xử lý.
Các thành phần bên ngoài có thể được sử dụng để giải quyết một nhóm vấn đề khó hoặc thậm chí không thể thực hiện được bằng ngôn ngữ lập trình được tích hợp trong 1C: Enterprise. Đặc biệt, lớp này bao gồm các tác vụ yêu cầu tương tác ở mức độ thấp với hệ điều hành, chẳng hạn như để làm việc với thiết bị cụ thể.
Hệ thống 1C: Enterprise sử dụng hai công nghệ để tạo các thành phần bên ngoài:
  • sử dụng API gốc
  • sử dụng công nghệ COM
Với những hạn chế nhất định, sự khác biệt giữa hai công nghệ nêu trên là không đáng kể, vì vậy chúng tôi sẽ xem xét việc phát triển trò chơi điện tử bằng API gốc. Nếu cần, những phát triển đã triển khai có thể được áp dụng để phát triển phần mềm máy tính sử dụng công nghệ COM, đồng thời, với những sửa đổi nhỏ, được áp dụng để sử dụng trong hệ thống 1C: Enterprise với các tùy chọn vận hành khác ngoài chế độ vận hành tệp.
Cấu trúc VK
Thành phần bên ngoài của hệ thống 1C: Enterprise được trình bày dưới dạng thư viện DLL. Mã thư viện mô tả lớp con IComponentBase. Lớp được tạo phải xác định các phương thức chịu trách nhiệm thực hiện các chức năng của thành phần bên ngoài. Các phương pháp được ghi đè sẽ được mô tả chi tiết hơn bên dưới khi tài liệu được trình bày.

Ra mắt bản demo VK

Nhiệm vụ:
  1. Lắp ráp một thành phần bên ngoài được cung cấp cùng với đăng ký ITS và nhằm mục đích chứng minh các khả năng chính của cơ chế thành phần bên ngoài trong 1C
  2. Kết nối thành phần demo với cấu hình 1C
  3. Đảm bảo các hàm được khai báo hoạt động chính xác
biên soạn
Bản demo VK nằm trên đĩa đăng ký ITS trong thư mục “/VNCOMP82/example/NativeAPI”.
Để xây dựng VC demo, chúng tôi sẽ sử dụng Microsoft Visual Studio 2008. Các phiên bản khác của sản phẩm này không hỗ trợ định dạng dự án Visual Studio được sử dụng.


Mở dự án AddInNative. Trong cài đặt dự án, chúng tôi bao gồm thư mục chứa các tệp tiêu đề cần thiết để xây dựng dự án. Theo mặc định, chúng nằm trên đĩa ITS trong thư mục /VNCOMP82/bao gồm.
Kết quả của việc xây dựng là tập tin /bind/AddInNative.dll. Đây là thư viện được biên dịch để kết nối với cấu hình 1C.
Kết nối cấu hình VK với 1C
Hãy tạo một cấu hình 1C trống.
Dưới đây là mã cho mô-đun ứng dụng được quản lý.
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...\bind\AddInNative.dll", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.AddInNativeExtension"); Kết thúcThủ tục
Nếu không có lỗi nào được báo cáo khi khởi động cấu hình 1C thì VK đã được kết nối thành công.
Kết quả của việc thực thi đoạn mã trên là một đối tượng xuất hiện trong chế độ hiển thị chung của cấu hình DemoComp, có các thuộc tính và phương thức được xác định trong mã của thành phần bên ngoài.
Trình diễn chức năng tích hợp
Hãy kiểm tra chức năng của bản demo VK. Để thực hiện việc này, hãy thử thiết lập và đọc một số thuộc tính, gọi một số phương thức VK, đồng thời nhận và xử lý tin nhắn VK.
Tài liệu được cung cấp trên đĩa ITS nêu rõ chức năng sau của VC demo:
  1. Quản lý trạng thái đối tượng thành phần
    Phương pháp: Bật, Tắt
    Của cải: Bao gồm
  2. Quản lý hẹn giờ
    Mỗi giây thành phần sẽ gửi tin nhắn đến 1C: Hệ thống doanh nghiệp có thông số Thành phần, hẹn giờ và một dòng đếm đồng hồ hệ thống.
    Phương pháp: bộ đếm thời gian bắt đầu, Dừng hẹn giờ
    Của cải: Có đồng hồ hẹn giờ
  3. Phương pháp Hiển thị trong dòng trạng thái, hiển thị trên dòng trạng thái văn bản được truyền cho phương thức dưới dạng tham số
  4. Phương pháp Tải lênHình ảnh. Tải hình ảnh từ tệp được chỉ định và chuyển nó sang hệ thống 1C: Enterprise dưới dạng dữ liệu nhị phân.
Hãy đảm bảo các chức năng này hoạt động. Để thực hiện việc này, hãy chạy đoạn mã sau:
biến DemoComp; Quy trình khi hệ thống khởi động() ConnectExternalComponent(...); DemoComp = Mới ("AddIn.DemoVK.AddInNativeExtension"); DemoComp.Disable(); Báo cáo(DemoComp.Enabled); DemoComp.Enable(); Báo cáo(DemoComp.Enabled); DemoComp.StartTimer(); Thủ tục Kết thúc Thủ tục Báo cáo Xử lý sự kiện bên ngoài(Nguồn, Sự kiện, Dữ liệu)(Nguồn + " " + Sự kiện + " " + Dữ liệu); Kết thúcThủ tục
Kết quả chạy cấu hình hiển thị trong hình


Bảng “Tin nhắn” hiển thị kết quả của các lệnh gọi phương thức DemoComp.Disable()Demo.Comp.Enable(). Các dòng tiếp theo trong cùng bảng chứa kết quả xử lý tin nhắn nhận được từ VK - Nguồn, Sự kiệnDữ liệu tương ứng.

Tên thành phần bên ngoài tùy chỉnh

Nhiệm vụ: Thay đổi tên của thành phần bên ngoài thành tên tùy ý.
Phần trước đã sử dụng mã định danh AddInNativeExtension, ý nghĩa của nó không được giải thích. Trong trường hợp này AddInNativeExtension- đây là tên của phần mở rộng.
Mã VK xác định một phương thức Đăng kýTiện ích mở rộngNhư, trả lại tên cho hệ thống 1C: Enterprise, cần thiết cho việc đăng ký VK trong hệ thống sau này. Nên chỉ định một mã định danh ở một mức độ nào đó tiết lộ bản chất của thành phần bên ngoài.
Đây là mã hoàn chỉnh của phương pháp Đăng kýTiện ích mở rộngNhư với tên tiện ích mở rộng đã thay đổi:
bool CAddInNative::RegisterExtensionAs(WCHAR_T** wsExtensionName) ( wchar_t *wsExtension = L"SomeName"; int iActualSize = ::wcslen(wsExtension) + 1; WCHAR_T* dest = 0; if (m_iMemory) ( if(m_iMemory->AllocMemory ((void**)wsExtensionName, iActualSize * sizeof(WCHAR_T))) ::convToShortWchar(wsExtensionName, iActualSize) trả về sai;
Trong ví dụ đã cho, tên VK được đổi thành Một số tên. Sau đó, khi kết nối VK, bạn phải chỉ định tên mới:
DemoComp = Mới ("AddIn.DemoVK.SomeName");

Mở rộng danh sách thuộc tính VK

Nhiệm vụ:
  1. Nghiên cứu triển khai thuộc tính VK
  2. Thêm thuộc tính đọc/ghi của loại chuỗi
  3. Thêm thuộc tính chuỗi đọc/ghi lưu trữ kiểu dữ liệu của tập thuộc tính cuối cùng. Không có hành động nào được thực hiện khi đặt giá trị thuộc tính

Để xác định các thuộc tính của thành phần đang được tạo, nhà phát triển phải triển khai các phương thức sau trong mã thư viện AddInNative.cpp:
GetNProps
Trả về số thuộc tính của phần mở rộng này, 0 nếu không có thuộc tính
TìmProp
Trả về số sê-ri của thuộc tính có tên được truyền trong tham số
GetPropName
Trả về tên của thuộc tính theo số sê-ri và mã định danh ngôn ngữ đã truyền
GetPropVal
Trả về giá trị của thuộc tính với số thứ tự được chỉ định
SetPropVal
Đặt giá trị của thuộc tính với số thứ tự được chỉ định
IsPropCó thể đọc được
Trả về cờ dễ đọc của thuộc tính với số thứ tự đã chỉ định
IsPropCó thể ghi
Trả về cờ có thể ghi của thuộc tính với số thứ tự đã chỉ định


Hãy xem xét việc thực hiện các phương thức lớp trên CDaddInBản địa.
Trong demo VC, 2 thuộc tính được xác định: Bao gồmCó đồng hồ hẹn giờ (Được kích hoạtIsTimerHiện tại).
Trong phạm vi toàn cầu của mã thư viện, hai mảng được xác định:
wchar_t tĩnh *g_PropNames = (L"IsEnabled", L"IsTimerPresent"); wchar_t tĩnh *g_PropNamesRu = (L"Đã bật", L"Có bộ hẹn giờ");
nơi lưu trữ tên thuộc tính tiếng Nga và tiếng Anh. Trong tập tin tiêu đề AddInNative.h bảng liệt kê được xác định:
đạo cụ enum ( ePropIsEnabled = 0, ePropIsTimerPresent, ePropLast // Luôn cuối cùng);
ePropIsEnabledePropIsTimerHiện tại, tương ứng có giá trị 0 và 1, được sử dụng để thay thế số sê-ri của thuộc tính bằng mã định danh có ý nghĩa. ePropLast, có giá trị là 2, được sử dụng để lấy số lượng thuộc tính (sử dụng phương pháp GetNProps). Những tên này chỉ được sử dụng trong mã thành phần và không có sẵn từ bên ngoài.
Các phương thức FindProp và GetPropName thực hiện tìm kiếm mảng g_PropNamesg_PropNamesRu.
Để lưu trữ giá trị của các trường trong mô-đun thư viện, lớp CAddInNative có các thuộc tính lưu trữ giá trị của thuộc tính thành phần. phương pháp GetPropValSetPropVal return và đặt giá trị của các thuộc tính này cho phù hợp.
phương pháp IsPropCó thể đọc đượcIsPropCó thể ghi và quay lại ĐÚNG VẬY hoặc SAI, tùy thuộc vào số thứ tự được truyền của thuộc tính phù hợp với logic ứng dụng.
Để thêm thuộc tính tùy chỉnh, bạn cần:

  1. Thêm tên thuộc tính đang được thêm vào mảng g_PropNamesg_PropNamesRu(tài liệu AddInNative.cpp)
  2. Liệt kê đạo cụ(tài liệu AddInNative.h) trước ePropLast thêm tên xác định duy nhất thuộc tính đang được thêm
  3. Sắp xếp bộ nhớ để lưu trữ các giá trị thuộc tính (tạo các trường thành phần mô-đun lưu trữ các giá trị tương ứng)
  4. Thực hiện thay đổi phương pháp GetPropValSetPropValđể tương tác với bộ nhớ được phân bổ ở bước trước
  5. Phù hợp với logic ứng dụng, thực hiện các thay đổi đối với các phương thức IsPropCó thể đọc đượcIsPropCó thể ghi
Điểm 1, 2, 5 không cần giải thích. Chi tiết thực hiện các bước này có thể được tìm thấy bằng cách nghiên cứu phần phụ lục của bài viết.
Hãy đặt tên cho các thuộc tính thử nghiệm Bài kiểm traKiểm tra loại tương ứng. Khi đó, theo kết quả của bước 1, chúng ta có:
wchar_t tĩnh *g_PropNames = (L"IsEnabled", L"IsTimerPresent", L"Test", L"TestType"); wchar_t tĩnh *g_PropNamesRu = (L"Đã bật", L"Có bộ hẹn giờ", L"Kiểm tra", L"Kiểm tra loại");
Chuyển khoản đạo cụ sẽ trông giống như:
Đạo cụ enum ( ePropIsEnabled = 0, ePropIsTimerPresent, ePropTest1, ePropTest2, ePropLast // Luôn tồn tại cuối cùng);
Để đơn giản hóa đáng kể mã, chúng tôi sẽ sử dụng STL C++. Đặc biệt, để làm việc với chuỗi WCHAR, hãy kết nối thư viện chuỗi.
Để lưu giá trị phương thức Bài kiểm tra, chúng ta định nghĩa trong lớp CDaddInBản địa trong phạm vi của một lĩnh vực tư nhân:
kiểm tra chuỗi1;
Để truyền các tham số chuỗi giữa 1C: Enterprise và các thành phần bên ngoài, trình quản lý bộ nhớ 1C: Enterprise được sử dụng. Chúng ta hãy xem xét kỹ hơn công việc của anh ấy. Các chức năng được sử dụng để cấp phát và giải phóng bộ nhớ tương ứng Phân bổ bộ nhớGiải phóng bộ nhớđược xác định trong tập tin ImemoryManager.h. Nếu cần truyền một tham số chuỗi cho hệ thống 1C: Enterprise, thành phần bên ngoài phải cấp phát bộ nhớ cho nó bằng cách gọi hàm Phân bổ bộ nhớ. Nguyên mẫu của nó trông như thế này:
bool ảo ADDIN_API AllocMemory (void** pMemory, ulCountByte dài không dấu) = 0;
Ở đâu pBộ nhớ- địa chỉ của con trỏ mà địa chỉ của vùng bộ nhớ được phân bổ sẽ được đặt vào đó,
ulCountByte- kích thước của vùng bộ nhớ được phân bổ.
Ví dụ về cấp phát bộ nhớ cho chuỗi:
WCHAR_T *t1 = NULL, *test = L"TEST_STRING"; int iActualSize = wcslen(test1)+1; m_iMemory->AllocMemory((void**)&t1, iActualSize * sizeof(WCHAR_T)); ::convToShortWchar(&t1, test1, iActualSize);
Để thuận tiện khi làm việc với các kiểu dữ liệu chuỗi, chúng tôi sẽ mô tả hàm wstring_to_p. Nó nhận một chuỗi wstring làm tham số. Kết quả của hàm là một cấu trúc được lấp đầy tBiến thể. Mã chức năng:
bool CAddInNative::wstring_to_p(std::wstring str, tVariant* val) ( char* t1; TV_VT(val) = VTYPE_PWSTR; m_iMemory->AllocMemory((void**)&t1, (str.length()+1) * sizeof(WCHAR_T)); memcpy(t1, str.c_str(), (str.length()+1) * sizeof(WCHAR_T)); val -> pstrVal = t1; trả về đúng)
Sau đó, phần trường hợp tương ứng của câu lệnh switch của phương thức GetPropVal sẽ có dạng:
trường hợp ePropTest1: wstring_to_p(test1, pvarPropVal); phá vỡ;
Phương pháp SetPropVal:
trường hợp ePropTest1: if (TV_VT(varPropVal) != VTYPE_PWSTR) trả về sai; test1 = std::wstring((wchar_t*)(varPropVal -> pstrVal)); phá vỡ;
Để triển khai thuộc tính thứ hai, chúng tôi xác định trường lớp CaddInBản Địa
uint8_t Last_type;
trong đó chúng ta sẽ lưu loại giá trị được chuyển cuối cùng. Để thực hiện việc này, hãy thêm lệnh vào phương thức CaddInNative::SetPropVal:
Last_type = TV_VT(varPropVal);
Bây giờ, khi yêu cầu đọc giá trị của thuộc tính thứ 2, chúng ta sẽ trả về giá trị loại cuối cùng, nhiệm vụ được chỉ định yêu cầu gì.
Hãy kiểm tra chức năng của những thay đổi được thực hiện.
Để làm điều này, chúng ta hãy trình bày diện mạo của cấu hình 1C như sau:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); DemoComp.TypeCheck = 1; Báo cáo (Chuỗi (DemoComp.TypeCheck)); DemoComp.Test = "Vasya"; Báo cáo (Chuỗi (DemoComp.Test)); DemoComp.Test = "Petya"; Báo cáo (Chuỗi (DemoComp.Test)); Báo cáo (Chuỗi (DemoComp.TypeCheck)); Kết thúcThủ tục
Sau khi ra mắt, chúng tôi sẽ nhận được một chuỗi thông báo:
3
Vasya
Peter
22

Thông báo thứ hai và thứ ba là kết quả của việc đọc thuộc tính được đặt ở bước trước. Tin nhắn đầu tiên và thứ hai chứa mã loại của tập thuộc tính cuối cùng. 3 tương ứng với một giá trị số nguyên, 22 tương ứng với một giá trị chuỗi. Sự tương ứng của các loại và mã của chúng được thiết lập trong tệp loại.h, nằm trên đĩa ITS.

Mở rộng danh sách các phương pháp

Nhiệm vụ:
  1. Mở rộng chức năng của thành phần bên ngoài với chức năng sau:
  2. Khám phá các cách để triển khai các phương thức thành phần bên ngoài
  3. Thêm một phương thức hàm Chức năng1, lấy hai chuỗi (“Parameter1” và “Parameter2”) làm tham số. Kết quả là một chuỗi như: “Đang kiểm tra. Tham số1, Tham số2"
  4. Đảm bảo những thay đổi bạn thực hiện có hiệu quả.

Để xác định các phương thức của thành phần đang được tạo, nhà phát triển phải triển khai các phương thức sau trong mã thư viện AddInNative:
GetNPhương thức, Phương thức tìm kiếm, GetMethodName
Được thiết kế để thu được số lượng phương thức tương ứng, tìm kiếm số lượng và tên của phương thức. Tương tự như các phương thức tương ứng cho thuộc tính
NhậnNParams
Trả về số tham số của phương thức với số thứ tự đã chỉ định; nếu phương thức có số này vắng mặt hoặc không có tham số, trả về 0
GetParamDefValue
Trả về giá trị mặc định của tham số đã chỉ định của phương thức đã chỉ định
HasRetVal
Trả về cờ xem phương thức có giá trị trả về thứ tự được chỉ định có giá trị trả về hay không: true cho các phương thức có giá trị trả về và SAI nếu không thì
CallAsProc
SAI, lỗi thời gian chạy sẽ xảy ra và quá trình thực thi mô-đun 1C: Enterprise bị chấm dứt. Bộ nhớ dành cho mảng tham số được phân bổ và giải phóng bởi 1C: Enterprise.
GọiAsFunc
Thực thi phương thức với số thứ tự được chỉ định. Nếu phương thức trả về SAI, lỗi thời gian chạy sẽ xảy ra và quá trình thực thi mô-đun 1C: Enterprise bị chấm dứt. Bộ nhớ dành cho mảng tham số được cấp phát bởi 1C: Enterprise. Nếu giá trị trả về là chuỗi hoặc kiểu dữ liệu nhị phân, thành phần sẽ cấp phát bộ nhớ bằng hàm Phân bổ bộ nhớ trình quản lý bộ nhớ, ghi dữ liệu vào đó và lưu trữ địa chỉ này vào trường tương ứng của cấu trúc. 1C: Doanh nghiệp sẽ giải phóng bộ nhớ này bằng cách gọi Giải phóng bộ nhớ.
Mô tả đầy đủ về các phương pháp, bao gồm danh sách các tham số, được mô tả chi tiết trong tài liệu được cung cấp trên đĩa ITS.
Chúng ta hãy xem việc thực hiện các phương pháp được mô tả ở trên.
Trong mã thành phần, hai mảng được xác định:
wchar_t tĩnh *g_MethodNames = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture"); wchar_t tĩnh *g_MethodNamesRu = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadImage");
và liệt kê:
Phương thức enum ( eMethEnable = 0, eMethDisable, eMethShowInStatusLine, eMethStartTimer, eMethStopTimer, eMethLoadPicture, eMethLast // Luôn tồn tại cuối cùng);
Chúng được sử dụng trong các chức năng GetNPhương thức, Phương thức tìm kiếmGetMethodName, bằng cách tương tự với việc mô tả các thuộc tính.
phương pháp NhậnNParams, GetParamDefValue, HasRetVal triển khai switch, tùy thuộc vào các tham số được truyền và logic ứng dụng, trả về giá trị được yêu cầu. Phương pháp HasRetVal trong mã của nó, nó có một danh sách các phương thức duy nhất có thể trả về kết quả. Vì họ anh trở lại ĐÚNG VẬY. Đối với tất cả các phương pháp thép trả về SAI.
phương pháp CallAsProcGọiAsFunc chứa mã thực thi trực tiếp của phương thức.
Để thêm một phương thức chỉ có thể được gọi dưới dạng hàm, bạn cần thực hiện những thay đổi sau đối với mã nguồn của thành phần bên ngoài:
  1. Thêm tên phương thức vào mảng g_Tên phương thứcg_MethodNamesRu(tài liệu AddInNative.cpp)
  2. Thêm một mã định danh phương thức có ý nghĩa vào bảng liệt kê Phương thức (tệp AddInNative.h)
  3. Thực hiện thay đổi mã chức năng NhậnNParams theo logic chương trình
  4. Nếu cần, hãy thay đổi mã phương thức GetParamDefValue, nếu bạn muốn sử dụng các giá trị mặc định của các tham số phương thức.
  5. Thực hiện thay đổi chức năng HasRetVal
  6. Thực hiện các thay đổi về logic của hàm CallAsProc hoặc GọiAsFunc, đặt mã thực thi trực tiếp của phương thức vào đó
Hãy trình bày các mảng g_Tên phương thứcg_MethodNamesRu, cũng như liệt kê phương phápđến dạng:
wchar_t tĩnh *g_MethodNames = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture", L"Test"); wchar_t tĩnh *g_MethodNamesRu = (L"Bật", L"Tắt", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture", L"Test");

Phương thức Enum ( eMethEnable = 0, eMethDisable, eMethShowInStatusLine, eMethStartTimer, eMethStopTimer, eMethLoadPicture, eMethTest, eMethLast // Luôn tồn tại );
Hãy chỉnh sửa chức năng GetNPropsđể nó trả về số lượng tham số của phương thức “Test”:
dài CAddInNative::GetNParams(const long lMethodNum) ( switch(lMethodNum) ( case eMethShowInStatusLine: return 1; case eMethLoadPicture: return 1; case eMethTest: return 2; mặc định: return 0; ) return 0; )
Hãy thực hiện các thay đổi đối với chức năng:
bool CAddInNative::GetParamDefValue(const long lMethodNum, const long lParamNum, tVariant *pvarParamDefValue) ( ​​​​ TV_VT(pvarParamDefValue)= VTYPE_EMPTY; switch(lMethodNum) ( case eMethEnable: case eMethDisable: case eMethShowInStatusLine: case eMethStartTimer : case eMethStopTimer: case eMethTest : // Mặc định không có giá trị tham số break;
Cảm ơn dòng được thêm vào
trường hợp eMethTest:
nếu thiếu một hoặc nhiều đối số, các tham số tương ứng sẽ có giá trị trống ( VTYPE_EMPTY). Nếu bạn cần một giá trị mặc định cho một tham số thì nên đặt nó trong phần eMethTest câu lệnh chuyển đổi chức năng CDdInNative::GetParamDefValue.
Vì phương thức Test có thể trả về một giá trị nên bạn cần thay đổi mã hàm HasRetVal:
bool CAddInNative::HasRetVal(const long lMethodNum) ( switch(lMethodNum) ( case eMethLoadPicture: case eMethTest: return true; mặc định: return false; ) return false; )
Và thêm mã thực thi của phương thức vào hàm GọiAsFunc:
bool CAddInNative::CallAsFunc(const long lMethodNum, tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray) ( ... std::wstring s1, s2; switch(lMethodNum) ( case eMethLoadPicture: ... break; case eMethTest: if (!lSizeArray || !paParams) trả về sai; s1 = (paParams) -> pwstrVal; s2 = (paParams+1) -> pwstring_to_p(std::wstring(s1+s2), pvarRetValue return ret ; ;
Hãy biên dịch thành phần và đưa mã cấu hình về dạng:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); Lane = DemoComp.Test("Xin chào," "Thế giới!"); Báo cáo(mỗi); Kết thúcThủ tục
Sau khi khởi chạy cấu hình, chúng ta sẽ nhận được thông báo: “Xin chào, Thế giới!”, Cho biết phương thức đã hoạt động thành công.

hẹn giờ

Nhiệm vụ:
  1. Nghiên cứu cách triển khai bộ đếm thời gian trong bản demo VK
  2. Sửa đổi phương thức “StartTimer” bằng cách thêm khả năng truyền tham số khoảng thời gian phản hồi của bộ hẹn giờ (tính bằng mili giây)
  3. Đảm bảo những thay đổi bạn thực hiện có hiệu quả.

Trong WinAPI, bạn có thể sử dụng tin nhắn để làm việc với thời gian WM_TIMER. Thông báo này sẽ được gửi đến chương trình của bạn theo khoảng thời gian mà bạn đã đặt khi tạo bộ hẹn giờ.
Để tạo bộ hẹn giờ, hãy sử dụng chức năng Bộ hẹn giờ:
UINT SetTimer(HWND hWnd, // bộ mô tả cửa sổ UINT nIDevent, // mã định danh bộ đếm thời gian (số) UINT nElapse, // độ trễ TIMERPROC lpTimerFunc); // con trỏ tới hàm
Hệ điều hành sẽ gửi tin nhắn WM_TIMER vào chương trình với khoảng thời gian được chỉ định trong đối số trôi qua(tính bằng mili giây). Trong tham số cuối cùng, bạn có thể chỉ định một chức năng sẽ được thực thi mỗi khi bộ hẹn giờ kích hoạt. Tiêu đề của hàm này sẽ trông như thế này (tên có thể là bất kỳ tên nào):
void __stdcall Hẹn giờProc (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
Hãy xem xét việc triển khai bộ hẹn giờ trong VC demo.
Vì chúng tôi đang xem xét quá trình phát triển một thành phần bên ngoài cho dòng hệ điều hành Windows nên chúng tôi sẽ không xem xét việc triển khai bộ hẹn giờ trong các hệ điều hành khác. Đặc biệt, đối với hệ điều hành GNU/Linux, việc triển khai sẽ khác nhau về cú pháp của hàm Bộ hẹn giờHẹn giờProc.
Mã thực thi gọi phương thức Bộ hẹn giờ, mà hàm được truyền tới MyTimerProc:
m_uiTimer = ::SetTimer(NULL,0,100,(TIMERPROC)MyTimerProc);
ID của bộ đếm thời gian đã tạo được đặt trong một biến m_uiTimerđể sau này có thể vô hiệu hóa nó.
Chức năng MyTimerProc như sau:
VOID CUỘC GỌI MyTimerProc(HWND hwnd, // tay cầm cửa sổ dành cho tin nhắn hẹn giờ UINT uMsg, // tin nhắn WM_TIMER UINT idEvent, // mã định danh bộ đếm thời gian DWORD dwTime // thời gian hệ thống hiện tại) ( if (!pAsyncEvent) return; wchar_t *who = L "ComponentNative", *what = L"Timer"; wchar_t *wstime = new wchar_t; if (wstime) ( wmemset(wstime, 0, TIME_LEN); ::_ultow(dwTime, wstime, 10); pAsyncEvent->ExternalEvent(ai , cái gì, wstime); xóa wstime;
Bản chất của hàm là phương thức này được gọi Sự kiện bên ngoài, gửi tin nhắn đến hệ thống 1C: Enterprise.
Để mở rộng chức năng của phương pháp bộ đếm thời gian bắt đầu Hãy làm như sau:
Sửa đổi mã phương thức NhậnNParams vì vậy nó dành cho phương pháp eMethStartTimer giá trị trả về 1:
trường hợp eMethStartTimer: return 1;
Đây là mã phương pháp CallAsProcđến dạng:
trường hợp eMethStartTimer: if (!lSizeArray || TV_VT(paParams) != VTYPE_I4 || TV_I4(paParams)<= 0) return false; pAsyncEvent = m_iConnect; #ifndef __linux__ m_uiTimer = ::SetTimer(NULL,0,TV_I4(paParams),(TIMERPROC)MyTimerProc); #else // код для GNU/Linux #endif break;
Bây giờ hãy kiểm tra chức năng. Để thực hiện việc này, chúng tôi sẽ viết mã trong mô-đun ứng dụng được quản lý của cấu hình:
biến DemoComp; Quy trình khi hệ thống khởi động() Kết nối thành phần bên ngoài("...", "DemoVK", Loại thành phần bên ngoài.Native); DemoComp = Mới ("AddIn.DemoVK.SomeName"); DemoComp.StartTimer(2000); Kết thúcThủ tục
Sau khi bắt đầu cấu hình, chương trình sẽ nhận được thông báo trong khoảng thời gian 2 giây, cho biết bộ hẹn giờ đang hoạt động chính xác.

Tương tác với 1C: Hệ thống doanh nghiệp

Để tương tác giữa thành phần bên ngoài và hệ thống 1C: Enterprise, các phương thức của lớp IAddInDefBase, được mô tả trong tệp AddInDefBase.h. Chúng tôi liệt kê những cái được sử dụng phổ biến nhất:
Tạo thông báo lỗi
bool ảo ADDIN_API AddError(wcode ngắn không dấu, nguồn const WCHAR_T*, const WCHAR_T* descr, scode dài)
mã wcode, mã hóa- mã lỗi (có thể tìm thấy danh sách mã lỗi kèm theo mô tả trên đĩa ITS)
nguồn- nguồn lỗi
mô tả- Mô tả lỗi
Gửi tin nhắn tới 1C: Hệ thống doanh nghiệp
bool ảo ADDIN_API InternalEvent(WCHAR_T* wszSource, WCHAR_T* wszMessage, WCHAR_T* wszData) = 0;
nguồn wsz- nguồn tin nhắn
wszMessage- Tin nhắn văn bản
wszData- dữ liệu được truyền đi
Việc chặn tin nhắn được thực hiện bằng thủ tục Xử lý sự kiện bên ngoài
Đăng ký thành phần bên ngoài trong 1C: Hệ thống doanh nghiệp
bool ảo ADDIN_API RegisterProfileAs(WCHAR_T* wszProfileName)
wszTên hồ sơ- Tên thành phần.
Những phương pháp này đủ để tương tác đầy đủ giữa VK và 1C. Để nhận dữ liệu bởi một thành phần bên ngoài từ hệ thống 1C: Enterprise và ngược lại, thành phần bên ngoài sẽ gửi một tin nhắn đặc biệt, tin nhắn này sẽ bị hệ thống 1C chặn và nếu cần, sẽ gọi các phương thức của thành phần bên ngoài để truyền dữ liệu trở lại .

Kiểu dữ liệu tVariant

Khi trao đổi dữ liệu giữa thành phần bên ngoài và hệ thống 1C: Enterprise, kiểu dữ liệu tVariant được sử dụng. Nó được mô tả trong tệp type.h, có thể tìm thấy trên đĩa ITS:
struct _tVariant ( _ANONYMOUS_UNION union ( int8_t i8Val; int16_t shortVal; int32_t lVal; int intVal; unsigned int uintVal; int64_t llVal; uint8_t ui8Val; uint16_t ushortVal; uint32_t ulVal; uint64_t ; t errCode; float fltVal; struct tm tmVal; void* pInterfaceVal; __VARIANT_NAME_2/*iface*/; pstrVal; uint32_t strLen ; //đếm byte ) __VARIANT_NAME_3/*str*/; _ANONYMOUS_STRUCT struct ( WCHAR_T* pwstrVal; uint32_t wstrLen; //đếm ký hiệu ) __VARIANT _NAME_1 mảng chiều trong pvarVal TYPEVAR vt);
Kiểu tBiến thể là một cấu trúc bao gồm:
  • hỗn hợp (kết hợp) dành trực tiếp cho việc lưu trữ dữ liệu
  • định danh kiểu dữ liệu
Nói chung, làm việc với các biến kiểu tBiến thể xảy ra theo thuật toán sau:
  1. Xác định loại dữ liệu hiện được lưu trữ trong một biến
  2. Truy cập trường hỗn hợp tương ứng để truy cập trực tiếp vào dữ liệu
Sử dụng loại tBiến thểđơn giản hóa đáng kể sự tương tác của 1C: Hệ thống doanh nghiệp và các thành phần bên ngoài

Ứng dụng

Thư mục “examples” chứa các ví dụ cho bài viết
ví dụ/1 - khởi chạy thành phần demo
ví dụ/2 - trình diễn việc mở rộng danh sách tài sản
ví dụ/3 - trình diễn mở rộng danh sách các phương thức
Mỗi thư mục chứa một dự án VS 2008 và cấu hình 1C được tạo sẵn.