2015-02-26 4 views
9

TLDR: मैं .NET COM dll से डेल्फी क्लाइंट .exe से async कॉलबैक कॉल करने का प्रयास कर रहा हूं, लेकिन ये पंजीकरण-मुक्त COM में ठीक से काम नहीं कर रहा है, जबकि सिंक्रोनस कॉलबैक काम करते हैं, और एसिंक कॉलबैक काम कर रहे हैं जब यह एक reg-free COM नहीं है।पंजीकरण-मुक्त (साइड-बाय-साइड) में .NET COM dll से डेल्फी क्लाइंट से कॉलबैक COM


मेरा वैश्विक मामला यह है कि मेरे पास एक विदेशी बंद स्रोत है। नेट डेल जो कुछ सार्वजनिक घटनाओं को उजागर करता है। मुझे इन घटनाओं को डेल्फी ऐप में पास करने की आवश्यकता है। तो मैंने एक इंटरमीडिएट। डीएल बनाने का फैसला किया जो मेरे ऐप और एक और डीएल के बीच एक COM पुल के रूप में काम करेगा। यह ठीक काम करता है जब मेरा डीएल शासन के माध्यम से पंजीकृत होता है, लेकिन जब मैं reg-free COM पर स्विच करता हूं तो चीजें बदतर हो रही हैं। मैंने अपने मामले को छोटे प्रतिलिपि बनाने वाले उदाहरण में छोटा कर दिया जो अन्य डीएल पर निर्भर नहीं है, इसलिए मैं इसे नीचे पोस्ट कर दूंगा। तब

namespace ComDllNet 
{ 
    [ComVisible(true)] 
    [Guid("B6597243-2CC4-475B-BF78-427BEFE77346")] 
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
    public interface ICallbackHandler 
    { 
     void Callback(int value); 
    } 

    [ComVisible(true)] 
    [Guid("E218BA19-C11A-4303-9788-5A124EAAB750")] 
    public interface IComServer 
    { 
     void SetHandler(ICallbackHandler handler); 
     void SyncCall(); 
     void AsyncCall(); 
    } 

    [ComVisible(true)] 
    [Guid("F25C66E7-E9EF-4214-90A6-3653304606D2")] 
    [ClassInterface(ClassInterfaceType.None)] 
    public sealed class ComServer : IComServer 
    { 
     private ICallbackHandler handler; 
     public void SetHandler(ICallbackHandler handler) { this.handler = handler; } 

     private int GetThreadInfo() 
     { 
      return Thread.CurrentThread.ManagedThreadId; 
     } 

     public void SyncCall() 
     { 
      this.handler.Callback(GetThreadInfo()); 
     } 

     public void AsyncCall() 
     { 
      this.handler.Callback(GetThreadInfo()); 
      Task.Run(() => { 
       for (int i = 0; i < 5; ++i) 
       { 
        Thread.Sleep(500); 
        this.handler.Callback(GetThreadInfo()); 
       } 
      }); 
     } 
    } 
} 

, मैं dll के लिए एक मजबूत नाम दिया, और Regasm.exe के माध्यम से इसे पंजीकृत:

पर this answer आधार पर मैं एक सार्वजनिक इंटरफ़ेस ICallbackHandler जो मैं डेल्फी ग्राहक app से प्राप्त करने की उम्मीद कर दिया।

अब मैं डेल्फी क्लाइंट में बदल गया। मैं tlb आवरण जो मुझे

ICallbackHandler = interface(IUnknown) 
    ['{B6597243-2CC4-475B-BF78-427BEFE77346}'] 
    function Callback(value: Integer): HResult; stdcall; 
    end; 
    IComServer = interface(IDispatch) 
    ['{E218BA19-C11A-4303-9788-5A124EAAB750}'] 
    procedure SetHandler(const handler: ICallbackHandler); safecall; 
    procedure SyncCall; safecall; 
    procedure AsyncCall; safecall; 
    end; 
    IComServerDisp = dispinterface 
    ['{E218BA19-C11A-4303-9788-5A124EAAB750}'] 
    procedure SetHandler(const handler: ICallbackHandler); dispid 1610743808; 
    procedure SyncCall; dispid 1610743809; 
    procedure AsyncCall; dispid 1610743810; 
    end; 

दिया और एक हैंडलर और दो बटन के साथ किसी न किसी रूप और ज्ञापन बातें परीक्षण करने के लिए बनाया Component > Import Component > Import a Type Library का उपयोग कर कोड बनाने के लिए:

unit Unit1; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, ComDllNet_TLB, StdCtrls; 

type 
    THandler = class(TObject, IUnknown, ICallbackHandler) 
    private 
    FRefCount: Integer; 
    protected 
    function Callback(value: Integer): HResult; stdcall; 

    function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall; 
    function _AddRef: Integer; stdcall; 
    function _Release: Integer; stdcall; 
    public 
    property RefCount: Integer read FRefCount; 
    end; 

type 
    TForm1 = class(TForm) 
    Memo1: TMemo; 
    syncButton: TButton; 
    asyncButton: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure syncButtonClick(Sender: TObject); 
    procedure asyncButtonClick(Sender: TObject); 
    private 
    { Private declarations } 
    handler : THandler; 
    server : IComServer; 
    public 
    { Public declarations } 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

function THandler._AddRef: Integer; 
begin 
    Inc(FRefCount); 
    Result := FRefCount; 
end; 

function THandler._Release: Integer; 
begin 
    Dec(FRefCount); 
    if FRefCount = 0 then 
    begin 
    Destroy; 
    Result := 0; 
    Exit; 
    end; 
    Result := FRefCount; 
end; 

function THandler.QueryInterface(const IID: TGUID; out Obj): HRESULT; 
const 
    E_NOINTERFACE = HRESULT($80004002); 
begin 
    if GetInterface(IID, Obj) then 
    Result := 0 
    else 
    Result := E_NOINTERFACE; 
end; 

function THandler.Callback(value: Integer): HRESULT; 
begin 
    Form1.Memo1.Lines.Add(IntToStr(value)); 
    Result := 0; 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    handler := THandler.Create(); 
    server := CoComServer.Create(); 
    server.SetHandler(handler); 
end; 

procedure TForm1.syncButtonClick(Sender: TObject); 
begin 
    Form1.Memo1.Lines.Add('Begin sync call'); 
    server.SyncCall(); 
    Form1.Memo1.Lines.Add('End sync call'); 
end; 

procedure TForm1.asyncButtonClick(Sender: TObject); 
begin 
    Form1.Memo1.Lines.Add('Begin async call'); 
    server.AsyncCall(); 
    Form1.Memo1.Lines.Add('End async call'); 
end; 

end. 

तो, मैं चलाने यह, दबाया 'सिंक 'और' async 'बटन और सब कुछ अपेक्षित के रूप में काम किया। ध्यान दें कि कैसे एक टास्क के धागे आईडी (यह भी Thread.Sleep की वजह से कुछ देरी के साथ) 'समाप्ति async कॉल' पंक्ति के बाद आता है:

all works via registration-COM

भाग एक का अंत। अब मैं पंजीकरण-मुक्त (साइड-बाय-साइड) COM का उपयोग करने के लिए स्विच किया गया। पर this answer आधार पर मैं अपने डेल्फी अनुप्रयोग प्रकट करने के लिए dependentAssembly हिस्सा कहा:

<dependency> 
    <dependentAssembly> 
     <assemblyIdentity name="ComDllNet" version="1.0.0.0" publicKeyToken="f31be709fd58b5ba" processorArchitecture="x86"/> 
    </dependentAssembly> 
</dependency> 

mt.exe tool का उपयोग करते हुए मैं अपने dll के लिए एक प्रकट उत्पन्न:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
    <assemblyIdentity name="ComDllNet" version="1.0.0.0" publicKeyToken="f31be709fd58b5ba" processorArchitecture="x86"/> 
    <clrClass clsid="{F25C66E7-E9EF-4214-90A6-3653304606D2}" progid="ComDllNet.ComServer" threadingModel="Both" name="ComDllNet.ComServer" runtimeVersion="v4.0.30319"/> 
    <file name="ComDllNet.dll" hashalg="SHA1"/> 
</assembly> 

तब मैं dll अपंजीकृत और एप्लिकेशन को चलाने के। और मैंने पाया कि कॉलबैक का केवल तुल्यकालिक भागों काम कर रहे हैं:

enter image description here

संपादित करें: नोट आप /tlb विकल्प के साथ पंजीकरण रद्द करना है, अन्यथा यह स्थानीय मशीन पर काम कर रहा है, जैसे कि dll था जारी रहेगा अभी भी पंजीकृत (see)।

मैंने पहले से ही कई चीजें थक चुकी हैं, और मुझे यकीन नहीं है कि आगे क्या करना है। मैं इस बात पर संदेह कर रहा हूं कि प्रारंभिक दृष्टिकोण बिल्कुल काम नहीं करना चाहिए और मुझे डेल्फी ऐप की ओर कुछ थ्रेडिंग लागू करने की आवश्यकता है। लेकिन मुझे यकीन नहीं है कि क्या और कैसे। किसी भी सहायता की सराहना की जाएगी!

उत्तर

6

आपको ICallbackHandler इंटरफ़ेस पंजीकृत करना होगा।तो, एक ही फाइल में आप clrClass तत्व है जहां, लेकिन file तत्वों का एक भाई के रूप में, जोड़ें:

<comInterfaceExternalProxyStub iid="{B6597243-2CC4-475B-BF78-427BEFE77346}" 
            name="ICallbackHandler" 
            tlbid="{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" 
            proxyStubClsid32="{00020424-0000-0000-C000-000000000046}"/> 

यह COM बताता है एक बाहरी प्रॉक्सी/ठूंठ, प्रकार लायब्रेरी marshaler ({00020424- उपयोग करने के लिए 0000-0000-C000-000000000046}), और यह टाइप लाइब्रेरी मार्शलर को आपकी टाइप लाइब्रेरी ({XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}) देखने के लिए बताता है। यह GUID आपकी असेंबली की GUID है, जो आपके प्रोजेक्ट के गुणों में मिलती है (AssemblyInfo.cs जांचें)।

आपको इस प्रकार की लाइब्रेरी जेनरेट करने की आवश्यकता है। चूंकि आप पंजीकरण-मुक्त COM चाहते हैं, मुझे लगता है कि TLBEXP.EXE बिल को पूरी तरह फिट करता है, आप इसे एक पोस्ट बिल्ड इवेंट के रूप में सेट कर सकते हैं।

अंत में, आप एक अलग प्रकार की लाइब्रेरी फ़ाइल रख सकते हैं या आप इसे अपनी असेंबली में एम्बेड कर सकते हैं। मैं सलाह देता हूं कि आप इसे अलग रखें, और भी अधिक यदि आपकी असेंबली बड़ी है।

किसी भी तरह से, आपको इसे मैनिफेस्ट में रखना होगा।

<file name="ComDllNet.tlb"> 
     <typelib tlbid="{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" 
       version="1.0" 
       helpdir="." 
       flags=""/> 
    </file> 

आप प्रकार लायब्रेरी एम्बेड करते हैं, निम्नलिखित जोड़ें <file name="ComDLLNet.dll"/> तत्व के एक बच्चे के रूप:

 <typelib tlbid="{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" 
       version="1.0" 
       helpdir="." 
       flags=""/> 
+1

प्रॉक्सी को पंजीकृत करना सही बात है, लेकिन @ मिखाइल को भी 'mandler.Callback (GetThreadInfo()) को कॉल नहीं करना चाहिए, सीधे पूल पूल से, COM marshaling के बिना। जब तक कि उनकी डेल्फी पक्ष यादृच्छिक धागे पर कॉलबैक प्राप्त करने की अपेक्षा न करे। – Noseratio

+0

मैंने 'comInterfaceExternalProxyStub' तत्व जोड़ने की कोशिश की लेकिन डीडी तत्व को संदर्भित किया। अब मैंने आपके प्रस्ताव को .tlb के लिए sepearate 'file' तत्व के साथ करने की कोशिश की है और इसमें कुछ भी नहीं बदला है, लेकिन अधिक पर्याप्त लगता है, इसलिए मैं इसे रखना चाहूंगा। – Mikhail

+0

@ नोसेरेटियो: यह वास्तव में मामला प्रतीत होता है। क्या आप उचित मार्शलिंग कार्यान्वयन पर मुझे मार्गदर्शन कर सकते हैं? – Mikhail

1

इसमें काफ़ी समय एक टिप्पणी हो रहा है यहाँ एक अलग .tlb फ़ाइल का उपयोग कर एक उदाहरण है , इसलिए इसे एक उत्तर के रूप में पोस्ट करना।

एक संकेतक COM इंटरफेस के लिए उचित प्रमुखता बिना एक अलग COM फ्लैट से कभी नहीं पहुँचा जा चाहिए। इस मामले में, this.handler एक डेल्फी के एसटीए थ्रेड पर बनाई गई एक एसटीए COM वस्तु है (सबसे अधिक संभावना है)। फिर इसे सीधे किसी भी प्रकार के COM मार्शलिंग के बिना Task.Run के अंदर एक .NET MTA पूल थ्रेड थ्रेड से कॉल किया जाता है। यह COM हार्ड नियमों का उल्लंघन है, जो INFO: Descriptions and Workings of OLE Threading Models पर उल्लिखित है।

यह एक प्रबंधित आरसीडब्ल्यू प्रॉक्सी .NET पक्ष पर एक COM इंटरफ़ेस लपेटने के बारे में भी सच है। आरसीडब्ल्यू केवल प्रबंधित कॉल से प्रबंधित कोड से मार्शल करेगा, लेकिन यह COM marshaling के बारे में कुछ भी नहीं करेगा।

इससे सभी तरह के आश्चर्यजनक आश्चर्य हो सकते हैं, खासकर यदि ओपी handler.Callback के अंदर डेल्फी ऐप के यूआई तक पहुंचता है।

अब, यह संभव है कि handler ऑब्जेक्ट फ्री थ्रेडेड मार्शलर को समेकित करता है (इसमें इसका अपना rules to follow होगा, और मुझे संदेह है कि यह ओपी कोड के मामले में है)। ऐसा होने दें, सूचकांक handler ऑब्जेक्ट को वास्तव में एफआईएमएम द्वारा 0 सूचकांक पर अनमोल किया जाएगा। हालांकि, सर्वर कोड जो किसी अन्य थ्रेड से ऑब्जेक्ट को कॉल करता है (यानी, Task.Run(() => { ... this.handler.Callback(GetThreadInfo() ...}) कभी यह नहीं मानना ​​चाहिए कि COM ऑब्जेक्ट फ्री-थ्रेडेड है, और इसे अभी भी सही मार्शलिंग करना चाहिए। यदि भाग्यशाली है, तो अनधिकृत होने पर प्रत्यक्ष सूचक वापस दिया जाएगा।

वहाँ तरीकों में से एक गुच्छा marshaling करना है:।।।

  • CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream
  • CoMarshalInterface/CoUnmarshalInterface
  • Global Interface Table (GIT)
  • CreateObjrefMoniker/BindMoniker
  • आदि
बेशक

, ऊपर marshaling तरीकों से काम करने के लिए, सही COM प्रॉक्सी/ठूंठ कक्षाएं पंजीकृत किया जाना चाहिए या एक साइड-बाई-साइड प्रकट के माध्यम से प्रावधान किया, के रूप में Paulo Madeira's answer बताते हैं।

वैकल्पिक रूप से, एक कस्टम dispinterface का उपयोग किया जा सकता है (जिस स्थिति में सभी कॉल IDispatch ओएलई ऑटोमेशन मार्शलर के साथ) या मानक COM मार्शलर को ज्ञात किसी अन्य मानक COM इंटरफ़ेस के माध्यम से जायेंगे। मैं आमतौर पर सरल कॉलबैक के लिए IOleCommandTarget का उपयोग करता हूं, इसे किसी भी पंजीकृत होने की आवश्यकता नहीं होती है।

+0

क्या आपने वास्तव में एक अपार्टमेंट में आरसीडब्ल्यू (वास्तविक वस्तु या प्रॉक्सी/स्टब) प्राप्त करने और इसे किसी अन्य में उपयोग करने का प्रयास किया है? मैंने इसके बारे में कभी भी चिंतित नहीं किया है, और [यह ब्लॉग पोस्ट] (http://blogs.msdn.com/b/mbend/archive/2007/04/18/the-mapping-between-interface-pointers-and-runtime -केलेबल-रैपर-आरसीयूएस.एएसपीएक्स) आरसीडब्ल्यू के कामकाज की कितनी मात्रा में काम करता है, इस बारे में काफी स्पष्ट है। – acelent

+0

@PauloMadeira, मेरे पास कई बार है। अपार्टमेंट में एक ही आरसीडब्ल्यू पर एक विधि को कॉल करना अपार्टमेंट में अंतर्निहित कच्चे COM इंटरफेस पॉइंटर पर एक विधि को कॉल करने जैसा अच्छा है। यानी, आरसीडब्ल्यू उस सम्मान में पारदर्शी है, और शेष वास्तव में विशेष COM वस्तु पर निर्भर करता है। आप * ठीक हो सकते हैं यदि आप निश्चित रूप से जानते हैं कि ऑब्जेक्ट को फ्री-थ्रेडेड के रूप में सही ढंग से कार्यान्वित किया गया है (जो मुझे यकीन है कि यहां 'हैंडलर' के बारे में मामला नहीं है)। – Noseratio

संबंधित मुद्दे