मैं यहां FUZxxl की पोस्ट पर अपनी टिप्पणी पर विस्तृत जानकारी दूंगा।
आपके द्वारा पोस्ट किए गए उदाहरण FFI
का उपयोग कर सभी संभव हैं। एक बार जब आप एफएफआई का उपयोग करके अपने कार्यों को निर्यात कर लेते हैं तो आप पहले से ही प्रोग्राम को डीएलएल में संकलित कर सकते हैं।
.NET को सी, सी ++, COM, आदि के साथ आसानी से इंटरफ़ेस करने में सक्षम होने के इरादे से डिज़ाइन किया गया था। इसका अर्थ यह है कि एक बार जब आप अपने कार्यों को एक डीएलएल में संकलित करने में सक्षम होते हैं, तो आप इसे (अपेक्षाकृत) आसान कह सकते हैं .NET से जैसा कि मैंने आपके द्वारा पोस्ट की गई दूसरी पोस्ट में पहले उल्लेख किया है, ध्यान रखें कि आपके कार्यों को निर्यात करते समय आप कौन सा कॉलिंग कॉन्फ़्रेंस निर्दिष्ट करते हैं। .NET में मानक stdcall
है, जबकि ccall
का उपयोग करते हुए हास्केल FFI
निर्यात के अधिकांश (उदाहरण) उदाहरण हैं।
अब तक केवल एकमात्र सीमा जिसे मैंने एफएफआई द्वारा निर्यात किया जा सकता है, polymorphic types
है, या ऐसे प्रकार जो पूरी तरह लागू नहीं हैं। जैसे दयालु *
के अलावा कुछ भी (आप Maybe
निर्यात नहीं कर सकते हैं लेकिन उदाहरण के लिए आप Maybe Int
निर्यात कर सकते हैं)।
मैंने एक उपकरण Hs2lib लिखा है जो आपके उदाहरण में आपके किसी भी कार्य को स्वचालित रूप से कवर और निर्यात करेगा। इसमें unsafe
सी # कोड उत्पन्न करने का विकल्प भी है जो इसे बहुत अधिक "प्लग और प्ले" बनाता है। असुरक्षित कोड चुनने का कारण यह है कि पॉइंटर्स को संभालना आसान है, जिससे बदले में डेटास्ट्रक्चर के लिए मार्शलिंग करना आसान हो जाता है।
पूरा होने के लिए मैं विस्तार से बताऊंगा कि टूल आपके उदाहरणों को कैसे प्रबंधित करता है और मैं पॉलिमॉर्फिक प्रकारों को संभालने की योजना कैसे बना सकता हूं।
जब उच्च आदेश कार्यों के निर्यात, समारोह हल्के से बदल दिया जाना चाहिए। उच्च-आदेश तर्कों को FunPtr के तत्व बनने की आवश्यकता है। असल में उन्हें स्पष्ट फ़ंक्शन पॉइंटर्स (या सी # में प्रतिनिधियों) के रूप में माना जाता है, जो कि अनिवार्य भाषाओं में आमतौर पर उच्च आदेश दिया जाता है।
मान लिया जाये कि हम CInt
में Int
परिवर्तित डबल के प्रकार
FunPtr (CInt -> CInt) -> CInt -> IO CInt
इन प्रकार के एक आवरण समारोह (doubleA
इस मामले में) के लिए उत्पन्न कर रहे हैं जो double
के बजाय निर्यात किया जाता है में
(Int -> Int) -> Int -> Int
से तब्दील हो जाता है अपने आप। रैपर निर्यातित मानों और मूल फ़ंक्शन के लिए अपेक्षित इनपुट मानों के बीच मानचित्र बनाता है। आईओ की आवश्यकता है क्योंकि FunPtr
का निर्माण एक शुद्ध संचालन नहीं है।
याद रखने की एक बात यह है कि FunPtr
का निर्माण या हटाना करने का एकमात्र तरीका स्थिर रूप से आयात बना रहा है जो जीएचसी को इसके लिए स्टब्स बनाने का निर्देश देता है।
foreign import stdcall "wrapper" mkFunPtr :: (Cint -> CInt) -> IO (FunPtr (CInt -> CInt))
foreign import stdcall "dynamic" dynFunPtr :: FunPtr (CInt -> CInt) -> CInt -> CInt
"आवरण" समारोह हमें बनाने के लिए एक FunPtr
और "डायनामिक"FunPtr
एक के बाद एक सम्मान करने की अनुमति देता है।
सी # में हम एक IntPtr
के रूप में इनपुट की घोषणा और उसके बाद एक समारोह सूचक है कि हम फोन कर सकते हैं, या एक समारोह सूचक से एक IntPtr
बनाने के लिए उलटा समारोह बनाने के लिए Marshaller
सहायक समारोह Marshal.GetDelegateForFunctionPointer का उपयोग करें।
यह भी याद रखें कि फ़ंक्शन के कॉलिंग सम्मेलन को FunPtr के लिए तर्क के रूप में पारित किया जाना चाहिए, उस कार्य के कॉलिंग सम्मेलन से मेल खाना चाहिए जिस पर तर्क पारित किया जा रहा है। दूसरे शब्दों में, &foo
से bar
को foo
और bar
को उसी कॉलिंग सम्मेलन के लिए पास करने की आवश्यकता है।
एक उपयोगकर्ता डेटाप्रकार निर्यात वास्तव में काफी सीधे आगे है। प्रत्येक डेटाटाइप के लिए जिसे Storable निर्यात करने की आवश्यकता है, इस प्रकार के लिए उदाहरण बनाना होगा। इस उदाहरण में इस प्रकार के निर्यात/आयात करने में सक्षम होने के लिए जीएचसी की मार्शलिंग जानकारी निर्दिष्ट होती है। अन्य चीजों के अलावा आपको size
और alignment
प्रकार को परिभाषित करने की आवश्यकता होगी, साथ ही किसी सूचक को प्रकार के मानों को पढ़ने/लिखने के तरीके के साथ। मैं इस कार्य के लिए आंशिक रूप से Hsc2hs का उपयोग करता हूं (इसलिए फ़ाइल में सी मैक्रोज़)।
newtypes
या datatypes
बस एक कन्स्ट्रक्टर आसान है। ये एक सपाट संरचना बन जाते हैं क्योंकि इन प्रकारों का निर्माण/विनाश करते समय केवल एक ही संभावित विकल्प होता है। एकाधिक कन्स्ट्रक्टर वाले प्रकार एक संघ बन जाते हैं (Layout
विशेषता वाला एक स्ट्रक्चर Explicit
पर सी #) पर सेट है। हालांकि हमें यह जानने के लिए एक enum शामिल करने की आवश्यकता है कि किस निर्माण का उपयोग किया जा रहा है।
सामान्य रूप में
, डेटाप्रकार Single
रूप
data Single = Single { sint :: Int
, schar :: Char
}
परिभाषित बनाता Storable
उदाहरण
instance Storable Single where
sizeOf _ = 8
alignment _ = #alignment Single_t
poke ptr (Single a1 a2) = do
a1x <- toNative a1 :: IO CInt
(#poke Single_t, sint) ptr a1x
a2x <- toNative a2 :: IO CWchar
(#poke Single_t, schar) ptr a2x
peek ptr = do
a1' <- (#peek Single_t, sint) ptr :: IO CInt
a2' <- (#peek Single_t, schar) ptr :: IO CWchar
x1 <- fromNative a1' :: IO Int
x2 <- fromNative a2' :: IO Char
return $ Single x1 x2
और सी struct
typedef struct Single Single_t;
struct Single {
int sint;
wchar_t schar;
} ;
समारोह foo :: Int -> Single
foo :: CInt -> Ptr Single
के रूप में निर्यात किया जाएगा निम्नलिखित जबकि कई निर्माता के साथ एक डेटाप्रकार
data Multi = Demi { mints :: [Int]
, mstring :: String
}
| Semi { semi :: [Single]
}
निम्नलिखित सी कोड उत्पन्न करता है:
enum ListMulti {cMultiDemi, cMultiSemi};
typedef struct Multi Multi_t;
typedef struct Demi Demi_t;
typedef struct Semi Semi_t;
struct Multi {
enum ListMulti tag;
union MultiUnion* elt;
} ;
struct Demi {
int* mints;
int mints_Size;
wchar_t* mstring;
} ;
struct Semi {
Single_t** semi;
int semi_Size;
} ;
union MultiUnion {
struct Demi var_Demi;
struct Semi var_Semi;
} ;
Storable
उदाहरण अपेक्षाकृत सीधे आगे है और सी struct परिभाषा से आसान पालन करना चाहिए।
मेरे निर्भरता ट्रेसर के लिए प्रकार Maybe Int
दोनों प्रकार Int
और Maybe
पर निर्भरता के लिए के लिए फेंकना होगा। इसका मतलब यह है, कि जब Maybe Int
के लिए Storable
उदाहरण पैदा सिर की तरह
instance Storable Int => Storable (Maybe Int) where
यही है, वहाँ के रूप में आवेदन प्रकार खुद भी निर्यात किया जा सकता के तर्कों के लिए एक Storable उदाहरण है aslong लग रहा है।
चूंकि Maybe a
को पॉलीमॉर्फिक तर्क Just a
के रूप में परिभाषित किया गया है, तो structs बनाते समय, कुछ प्रकार की जानकारी खो जाती है। Structs में void*
तर्क होगा, जिसे आपको मैन्युअल रूप से सही प्रकार में परिवर्तित करना होगा। विकल्प मेरी राय में बहुत बोझिल था, जो विशेष संरचनाओं को भी बनाना था। जैसे संरचना शायद INT। लेकिन सामान्य मॉड्यूल से उत्पन्न विशेष संरचनाओं की मात्रा जल्दी से इस तरह से विस्फोट कर सकती है। (इसे बाद में ध्वज के रूप में जोड़ सकते हैं)।
जानकारी के इस नुकसान को कम करने के लिए मेरा उपकरण किसी भी Haddock
फ़ंक्शन के लिए मिले दस्तावेज को जेनरेट की गई टिप्पणियों के रूप में निर्यात करेगा। यह टिप्पणी में मूल हास्केल प्रकार हस्ताक्षर भी रखेगा। एक आईडीई तब इन्हें इंटेलिसेंस (कोड कॉम्प्लेशन) के हिस्से के रूप में प्रस्तुत करेगा।
इन उदाहरणों के साथ है मैं चीजों की नेट पक्ष के लिए कोड ommited है, तो आप आप सिर्फ Hs2lib के उत्पादन में देख सकते हैं कि में रुचि रखते हैं।
कुछ अन्य प्रकार हैं जिन्हें विशेष उपचार की आवश्यकता है। विशेष रूप से Lists
और Tuples
।
- सूचियों को सरणी के आकार को पारित करने की आवश्यकता है, जिससे हम मार्शल से हैं, क्योंकि हम अप्रबंधित भाषाओं के साथ इंटरफेस कर रहे हैं जहां सरणी का आकार स्पष्ट रूप से ज्ञात नहीं है। इसके विपरीत जब हम एक सूची वापस करते हैं, तो हमें सूची के आकार को वापस करने की भी आवश्यकता होती है।
टुपल्स प्रकारों में विशेष निर्माण होते हैं, उन्हें निर्यात करने के लिए, हमें पहले उन्हें "सामान्य" डेटाटाइप पर मैप करना होगा, और उनको निर्यात करना होगा। उपकरण में यह 8-टुपल्स तक किया जाता है।
समस्या बहुरूपी प्रकार e.g. map :: (a -> b) -> [a] -> [b]
साथ कि a
की size
और b
पता नहीं कर रहे हैं। यही है, तर्कों और वापसी मूल्य के लिए जगह आरक्षित करने का कोई तरीका नहीं है क्योंकि हम नहीं जानते कि वे क्या हैं। मैं आपको a
और b
के लिए संभावित मान निर्दिष्ट करने और इन प्रकारों के लिए विशेष रैपर फ़ंक्शन बनाने की अनुमति देकर इसका समर्थन करने की योजना बना रहा हूं। दूसरे आकार पर, अनिवार्य भाषा में मैं उपयोगकर्ता को चुने गए प्रकारों को प्रस्तुत करने के लिए overloading
का उपयोग करूंगा।
कक्षाओं के लिए, हास्केल की खुली दुनिया धारणा आमतौर पर एक समस्या होती है (उदाहरण के लिए एक उदाहरण किसी भी समय जोड़ा जा सकता है)। हालांकि संकलन के समय केवल उदाहरणों की एक सांख्यिकीय रूप से ज्ञात सूची उपलब्ध है। मैं एक विकल्प पेश करना चाहता हूं जो इन सूचीओं का उपयोग करके जितना संभव हो उतना विशेष उदाहरण निर्यात करेगा। जैसे निर्यात (+)
संकलन समय पर सभी ज्ञात Num
उदाहरणों के लिए एक विशेष कार्य निर्यात करता है (उदा। Int
, Double
, आदि)।
उपकरण भी भरोसा है। चूंकि मैं वास्तव में शुद्धता के लिए कोड का निरीक्षण नहीं कर सकता, इसलिए मुझे हमेशा भरोसा है कि प्रोग्रामर ईमानदार है। जैसे आप ऐसे फ़ंक्शन को पास नहीं करते हैं जिस पर एक फ़ंक्शन पर दुष्प्रभाव होते हैं जो शुद्ध फ़ंक्शन की अपेक्षा करता है। ईमानदार रहें और उच्च-आदेशित तर्क को समस्याओं से बचने के लिए अशुद्ध होने के रूप में चिह्नित करें।
मुझे आशा है कि इससे मदद मिलेगी, और मुझे उम्मीद है कि यह बहुत लंबा नहीं था।
अद्यतन: कुछ हद तक एक बड़ा गॉचाचा है जिसे मैंने हाल ही में खोजा है। हमें याद रखना होगा कि .NET में स्ट्रिंग प्रकार अपरिवर्तनीय है। तो जब मार्शलर इसे हास्केल कोड से बाहर भेजता है, तो सीडब्लूस्ट्रिंग हम मूल की एक प्रति प्राप्त करते हैं। हम में इसे मुक्त करने के लिए है। जब सी # में जीसी किया जाता है तो यह सीडब्लूस्ट्रिंग को प्रभावित नहीं करेगा, जो एक प्रति है।
समस्या यह है कि जब हम इसे हास्केल कोड में मुक्त करते हैं तो हम freeCWString का उपयोग नहीं कर सकते हैं। सूचक को सी (msvcrt.dll) के आवंटन के साथ आवंटित नहीं किया गया था। इसे हल करने के तीन तरीके हैं (जिन्हें मैं जानता हूं)।
- हास्केल फ़ंक्शन को कॉल करते समय स्ट्रिंग के बजाय अपने सी # कोड में char * का उपयोग करें। जब आप रिटर्न कॉल करते हैं तो आपके पास पॉइंटर मुक्त होता है, या fixed का उपयोग करके फ़ंक्शन प्रारंभ करना होता है।
- आयात CoTaskMemFree हास्केल में आयात करें और हास्केल
- में सूचक को स्ट्रिंग के बजाय स्ट्रिंगबिल्डर का उपयोग करें। मैं इस बारे में पूरी तरह से निश्चित नहीं हूं, लेकिन विचार यह है कि चूंकि स्ट्रिंगबिल्डर को मूल सूचक के रूप में लागू किया जाता है, इसलिए मार्शलर इस सूचक को आपके हास्केल कोड (जो इसे बीटीडब्ल्यू भी अपडेट कर सकता है) में पास करता है। जब कॉल रिटर्न के बाद जीसी किया जाता है, तो स्ट्रिंगबिल्डर को मुक्त किया जाना चाहिए।
मुझे लगता है कि काफी कुछ सब कुछ शामिल है। मैं तुमसे प्यार करता हूँ दोस्तों/gals! :) –
आपका स्वागत है :) यदि आपके पास कोई और प्रश्न पूछने के लिए स्वतंत्र महसूस है :) – Phyx