2010-07-06 19 views
5

Giả sử tôi có một lớp học thực hiện hai hay nhiều giao diện COM (chính xác như here):Có sử dụng chuyển đổi ngầm cho một upcast thay vì QueryInterface() hợp pháp với nhiều thừa kế?

class CMyClass : public IInterface1, public IInterface2 { 
}; 

QueryInterface() phải trả lại con trỏ tương tự cho mỗi yêu cầu của cùng một giao diện (nó cần một sự liệng lên rõ ràng cho điều chỉnh con trỏ thích hợp) :

if(iid == __uuidof(IUnknown)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface1)) { 
    *ppv = static_cast<IInterface1*>(this); 
    //call Addref(), return S_OK 
} else if(iid == __uuidof(IInterface2)) { 
    *ppv = static_cast<IInterface2*>(this); 
    //call Addref(), return S_OK 
} else { 
    *ppv = 0; 
    return E_NOINTERFACE; 
} 

tại đang có hai IUnknown s trong đối tượng - là một trong những cơ sở của IInterface1 và người kia là cơ sở của IInterface2. Và .

Hãy giả vờ tôi gọi là QueryInterface() cho IInterface2 - con trỏ được trả lại sẽ khác với con trỏ được trả về khi tôi gọi QueryInterface() cho IUnknown. Càng xa càng tốt. Sau đó, tôi có thể vượt qua các truy xuất IInterface2* vào bất kỳ chức năng chấp nhận IUnknown* và nhờ C++ chuyển đổi tiềm ẩn con trỏ sẽ được chấp nhận, nhưng nó sẽ không cùng một con trỏ mà QueryInterface() cho IUnknown* sẽ lấy. Thực tế, nếu hàm đó gọi QueryInterface() cho IUnknown ngay sau khi được gọi, nó sẽ lấy một con trỏ khác.

Điều này có hợp pháp về mặt COM không? Làm thế nào để xử lý các tình huống khi tôi có một con trỏ đến một đối tượng thừa kế nhân và tôi cho phép một upcast ngầm định?

+0

Thực tế, tôi chưa bao giờ thấy bất kỳ mã nào sử dụng quy tắc nhận dạng COM. Điều đó nói rằng, khác IUnknown * là không hợp pháp - bạn phải chọn một để trở về từ QueryInterface. Về mặt của bạn, nội bộ, C++ sử dụng đối tượng COM thực hiện đối tượng - nếu bạn đang đúc bạn không làm COM anyway, vì vậy bất cứ điều gì bạn đang làm là hợp pháp C++, nhưng không hợp pháp COM. –

+0

@Chris Becke: Tôi đoán danh tính là bắt buộc để triển khai một số chức năng giống như bộ nhớ cache. – sharptooth

Trả lời

3

COM không có quy tắc liên quan đến nhận dạng giao diện, chỉ có nhận dạng đối tượng. Quy tắc đầu tiên của QI nói rằng một QI trên IID_Unknown trên hai con trỏ giao diện phải trả về cùng một con trỏ nếu chúng được thực hiện bởi cùng một đối tượng . Việc triển khai QI của bạn thực hiện điều này một cách chính xác.

Nếu không có sự đảm bảo về nhận dạng giao diện, phương thức COM không thể giả định rằng nó nhận được cùng con trỏ IUnknown mà nó sẽ truy xuất khi nó gọi QI trên con trỏ đó. Vì vậy, nếu nhận dạng đối tượng cần được chứng minh thì cần có một QI riêng biệt.

2

Dường như có một sự hiểu lầm nhỏ. Giao diện IInterface1IInterface2 là hình ảnh trừu tượng. Không có riêng biệt QueryInterface() cho IInterface1IInterface2. Chỉ có một tuyên bố, lớp CMyClass sẽ thực hiện tất cả các phương thức từ IInterface1IInterface2 (tập hợp các phương thức CMyClass là tập hợp các phương thức IInterface1IInterface1 cùng nhau).

Vì vậy, bạn thực hiện trong lớp CMyClassmộtQueryInterface(), mộtAddRef()mộtRelease() phương pháp. Trong các QueryInterface() bạn đúc con trỏ đến ví dụ của lớp CMyClass đến static_cast<IUnknown*>.

CẬP NHẬT: Xin chào! Tôi phải lái xe ngay lập tức sau khi viết câu trả lời của tôi. Chỉ bây giờ tôi có thể đọc tất cả các câu trả lời khác và có thể thêm một cái gì đó vào câu trả lời của tôi.

OK. Bạn nói rằng nếu bạn truyền IInterface1 đến IUnknown và nếu bạn truyền IInterface2 đến IUnknown bạn sẽ nhận được hai con trỏ khác nhau. Bạn đúng rồi! Nhưng tuy nhiên nó không quan trọng. Nếu bạn so sánh , chứa cả hai con trỏ (địa chỉ có QueryInterface(), AddRef()Release() trong cả hai trường hợp), bạn sẽ thấy rằng cả hai con trỏ đều có cùng chứa. Vì vậy, tôi cũng đúng!

Có rất nhiều ví dụ hay về cách triển khai COM trong pure C.Trong trường hợp này, bạn cần xác định một cấu trúc tĩnh với con trỏ tới các hàm ảo như QueryInterface(), AddRef()Release() và cung cấp cho con trỏ của cấu trúc như vậy trở lại như là kết quả của QueryInterface(). Nó cho thấy một lần nữa, rằng chỉ có chứa mà bạn cung cấp lại là quan trọng cho COM và không phải là một con trỏ (nó không phải là quan trọng mà vtable bạn trả lại).

Một nhận xét nhỏ hơn nữa. Trong một số nhận xét, bạn viết về khả năng có nhiều triển khai phương pháp QueryInterface(), AddRef()Release(). Tôi không đồng ý ở đây. Lý do là các giao diện là các lớp trừu tượng thuần túy và nếu bạn định nghĩa một lớp thực hiện một số giao diện bạn không có lớp thừa kế điển hình. Bạn chỉ có một lớp với một triển khai tất cả các chức năng từ tất cả các giao diện. Nếu bạn làm điều này trong C++, thì trình biên dịch tạo và điền vtables tĩnh với các con trỏ tương ứng với việc thực hiện duy nhất các hàm QueryInterface(), AddRef(), Release() và vân vân, nhưng tất cả vtables trỏ đến cùng chức năng.

Để giảm số lượng vtables Microsoft đã giới thiệu __declspec(novtable) hoặc ATL_NO_VTABLE, nhưng nó không phải là một phần của câu hỏi của bạn.

+0

Vâng, tôi biết điều đó. Nhưng có một vấn đề chính thức. Khi tôi hoàn toàn upcast từ 'IInterface2' để' IUnknown' tôi nhận được một con trỏ khác với một trong tôi nhận được từ 'QI()'. Toàn bộ câu hỏi là về một điều: là "IUnknown'" hợp pháp "về COM? – sharptooth

+0

Có nhiều triển khai của các phương pháp này, ngay cả khi tất cả, nhưng một chuyển tiếp cuộc gọi đến phiên bản duy nhất thực hiện nó. –

2

Hans chỉ ra, việc triển khai QueryInterface của bạn là chính xác.

Đó là trách nhiệm của người dùng đối tượng COM để sử dụng QueryInterface mọi lúc. Lý do chính xác là ngăn chặn kịch bản mà bạn đã chỉ ra: rằng việc đưa con trỏ IInterface1 * hoặc IInterface2 * vào con trỏ IUnknown * sẽ mang lại các giá trị vật lý khác nhau.

Trong C++, không thể cho người triển khai triển khai để ngăn người dùng làm sai.

Cho dù điều đó sẽ gây ra lỗi trong ứng dụng phụ thuộc vào việc ứng dụng quan tâm về việc so sánh các đối tượng COM cho danh tính.

MSDN: The Rules of the Component Object Model

sắc Object. Đó là yêu cầu mà bất kỳ cuộc gọi đến QueryInterface trên giao diện bất kỳ cho một trường hợp đối tượng nhất định cho giao diện cụ thể IUnknown phải luôn luôn trả về cùng một giá trị con trỏ vật lý . Này cho phép gọi QueryInterface (IID_IUnknown, ...) trên hai giao diện bất kỳ và so sánh kết quả để xác định xem họ điểm đến cùng một ví dụ của một đối tượng (giống nhận dạng đối tượng COM).

Oleg chỉ ra, sự thất bại của nhận dạng đối tượng sẽ có tác động khá hạn chế - mỗi mục nhập bảng ảo sẽ trỏ đến cùng địa chỉ hàm .

Tất cả việc triển khai con trỏ thông minh COM sử dụng QueryInterface khi truyền sang giao diện khác hoặc khi giao diện hiện tại không rõ ràng. Các toán tử so sánh của họ tự động sử dụng QueryInterface(IID_IUnknown, ...) trên mỗi con trỏ đầu vào để nhận các con trỏ IUnknown * vật lý có thể được so sánh trực tiếp. Sự thất bại của nhận dạng đối tượng sẽ bắt đầu ảnh hưởng đến ứng dụng của bạn nếu bạn chọn sử dụng con trỏ thô trong suốt ứng dụng của mình.

Một trường hợp đặc biệt mà sự cố không thể biểu hiện là khi lớp không có bất kỳ sự thừa kế kim cương nào. Tuy nhiên, việc đúc ngầm luôn luôn là bất hợp pháp trong COM, bất kể nó có treo ứng dụng hay không.

+0

Tôi đã đọc Quy tắc, nhưng ... Họ nói rằng 'QueryInterface()', không phải bất kỳ điều gì khác, phải tuân theo yêu cầu nhận dạng. Làm thế nào điều đó thực sự có nghĩa là một upcast ngầm là bất hợp pháp? 'QI()' là 'QI()', chuyển đổi C++ là các chuyển đổi C++. – sharptooth

+0

Tôi không đồng ý. Báo giá nói về * nhận diện đối tượng *, không giống với bản sắc giao diện. –

+1

@sharptooth: Khi suy nghĩ trong COM, hãy cố gắng tránh xa suy nghĩ mà IInterface1 thừa kế từ IUnknown. Một cách chính xác hơn là để nói: IInterface1 chứa các thành viên AddRef, Release và QueryInterface cũng như các chức năng riêng của nó. – rwong

0

Nếu bạn có interface IInterface1 : IDispatchinterface IInterface2 : IDispatch sau đó QI cho IUnknown trên IInterface1IInterface2 phải trả lại con trỏ cùng mỗi quy tắc nhận dạng đối tượng

nhưng ...

QI cho IDispatch trên IInterface1 có thể trả về một thực hiện khác nhau so với đến QI cho IDispatch trên IInterface2.

Vì vậy, câu trả lời là (một lần nữa) nó phụ thuộc. Phủ định cho upcasting để IUnknown, có thể là tích cực cho upcasting cho bất cứ điều gì khác.

Các vấn đề liên quan