استدعاء التعليمات البرمجية غير المُدارة من C# - إرجاع بنية تحتوي على صفائف

StackOverflow https://stackoverflow.com/questions/1650763

سؤال

[تحرير] لقد غيرت المصدر كما اقترح ستيفن مارتن (مظللة بالخط العريض).وأضفت كود مصدر C++ أيضًا.

أرغب في استدعاء وظيفة غير مُدارة في ملف C++ dll مكتوب ذاتيًا.تقرأ هذه المكتبة الذاكرة المشتركة للجهاز للحصول على معلومات الحالة الخاصة ببرنامج طرف ثالث.نظرًا لوجود قيمتين، أود إرجاع القيم في البنية.ومع ذلك، داخل الهيكل هناك char [] (صفائف شار ذات حجم ثابت).أحاول الآن تلقي هذه البنية من استدعاء dll مثل هذا:

[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_OUTPUT
{
    UInt16 ReadyForConnect;        

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    String VersionStr;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
    String NameOfFile;    
    // actually more of those
}

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(out SYSTEM_OUTPUT output);

    public Form1()
    {
        InitializeComponent();           

    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        try
        {
            label1.Text = getStatus(out output).ToString();
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
    }
}

سأقوم بنشر التعليمات البرمجية من C++ dll أيضًا، وأنا متأكد من أن هناك المزيد لمطاردته.الهيكل الأصلي STATUS_DATA يحتوي على مجموعة من أربع مثيلات للبنية SYSTEM_CHARACTERISTICS وداخل هذا الهيكل هناك char[]s، التي لم يتم ملؤها (حتى الآن)، مما يؤدي إلى ظهور مؤشر سيئ.لهذا السبب أحاول استخراج مجموعة فرعية من الأولى SYSTEM_CHARACTERISTICS البند في STATUS_DATA.

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#if defined(_MSC_VER)
#include <windows.h>
#define DLL extern "C" __declspec(dllexport)
#else
#define DLL
#endif

using namespace std;

enum { SYSID_LEN = 1024, VERS_LEN = 128, SCENE_LEN = 1024 };
enum { MAX_ENGINES = 4 };

struct SYSTEM_CHARACTERISTICS
{
    unsigned short  ReadyForConnect;
    char            VizVersionStr[VERS_LEN];
    char            NameOfFile[SCENE_LEN];

    char            Unimplemented[SCENE_LEN]; // not implemented yet, resulting to bad pointer, which I want to exclude (reason to have SYSTEM_OUTPUT)
};

struct SYSTEM_OUTPUT
{
    unsigned short  ReadyForConnect;        
    char            VizVersionStr[VERS_LEN];
    char            NameOfFile[SCENE_LEN];
};

struct STATUS_DATA
{
    SYSTEM_CHARACTERISTICS engine[MAX_ENGINES];
};


TCHAR szName[]=TEXT("E_STATUS");


DLL int getStatus(SYSTEM_OUTPUT* output)
{
    HANDLE hMapFile;
    STATUS_DATA* pBuf;

    hMapFile = OpenFileMapping(
        FILE_MAP_READ,          // read access
        FALSE,                  // do not inherit the name
        szName);                // name of mapping object 

    if (hMapFile == NULL) 
    { 
        _tprintf(TEXT("Could not open file mapping object (%d).\n"), 
            GetLastError());
        return -2;

    } 

    pBuf = (STATUS_DATA*) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);                                          

    if (pBuf == NULL) 
    { 
        _tprintf(TEXT("Could not map view of file (%d).\n"), 
            GetLastError()); 

        CloseHandle(hMapFile);  
        return -1;

    }

    output->ReadyForConnect = pBuf->engine[0].ReadyForConnect;              
    memcpy(output->VizVersionStr, pBuf->engine[0].VizVersionStr, sizeof(pBuf->engine[0].VizVersionStr));
    memcpy(output->NameOfFile, pBuf->engine[0].NameOfFile, sizeof(pBuf->engine[0].NameOfFile));

    CloseHandle(hMapFile);
    UnmapViewOfFile(pBuf);  

    return 0;
}

الآن أحصل على فارغة output البنية وقيمة الإرجاع ليست 0 على النحو المنشود.إنه بالأحرى رقم متغير مكون من سبعة أرقام، مما يتركني في حيرة...هل أخطأت في ملف DLL؟إذا قمت بجعل التعليمات البرمجية غير المُدارة قابلة للتنفيذ وقمت بتصحيحها، فيمكنني رؤية ذلك output يتم ملؤها بالقيم المناسبة.

هل كانت مفيدة؟

المحلول

عند عودته المعلومات في البنية الأسلوب القياسي هو لتمرير مؤشر إلى بنية كمعلمة هذه الطريقة. يملأ الأسلوب في أعضاء البنية ثم إرجاع رمز الحالة (أو منطقية) من نوع ما. لذلك ربما كنت ترغب في تغيير الخاص بك C ++ طريقة لاتخاذ SYSTEM_OUTPUT * والعودة 0 من أجل نجاح أو بعض رمز الخطأ:

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(out SYSTEM_OUTPUT output);

    public Form1()
    {
        InitializeComponent();           
    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        try
        {
            if(getStatus(out output) != 0)
            {
                //Do something about error.
            }
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
    }
}

نصائح أخرى

  1. تأكد من عدم ملء حقل ReadyForConnect حتى 4 بايت.في مشروعي، تبين أن جميع الحقول القصيرة (2 بايت) مملوءة بالبايتات الوهمية إلى 4 بايت في مكتبة الارتباط الحيوي (DLL) غير المُدارة.إذا كانت هذه هي المشكلة، فيجب عليك تنظيم البنية بهذه الطريقة:
    [StructLayout(LayoutKind.Sequential)] 
    public struct SYSTEM_OUTPUT 
    {     
       [MarshalAs(UnmanagedType.I2)] 
       UInt16 ReadyForConnect;
       [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I1, SizeConst=2)]
       byte[] aligment;          // 2 byte aligment up to 4 bytes margin
       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]    
       String VersionStr;    
       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]    
       String NameOfFile;        // ...
    }
  1. إذا كانت السلاسل عبارة عن سلاسل منتهية بقيمة ANSI خالية، فيمكنك التعليق عليها على النحو التالي:
  [MarshalAs(UnmanagedType.LPStr)]                   public String VersionStr;

وأنت لم تقم فعلا التعبئة أية بيانات على جانب إدارتها. عندما تقوم بتعريف output على الجانب المدارة، انها القيمة الافتراضية هي null. ثم، وعلى الجانب غير المدارة، يمكنك أبدا تخصيص أي ذاكرة للoutput. يجب عليك تخصيص بعض الذاكرة غير المدارة، وتمرير مؤشر إلى أن الذاكرة إلى وظيفة دلل الخاص بك، ثم حشد مؤشر لتلك الذاكرة إلى البنية الخاصة بك:

[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct SYSTEM_OUTPUT
{
    UInt16 ReadyForConnect;        

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    String VersionStr;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
    String NameOfFile;    
    // actually more of those
}

public partial class Form1 : Form
{
    public SYSTEM_OUTPUT output;

    [DllImport("testeshm.dll", EntryPoint="getStatus")]
    public extern static int getStatus(IntPtr output);

    public Form1()
    {
        InitializeComponent();           

    }

    private void ReadSharedMem_Click(object sender, EventArgs e)
    {
        IntPtr ptr;
        try
        {
            ptr = Marshall.AllocHGlobal(Marshall.SizeOf(typeof(SYSTEM_OUTPUT)));
            int ret = getStatus(ptr);

            if(ret == 0)
            {
                output = (SYSTEM_OUTPUT)Marshal.PtrToStructure(ptr, typeof(SYSTEM_OUTPUT));
            }

        //do something with output

            label1.Text = ret;
        }
        catch (AccessViolationException ave)
        {
            label1.Text = ave.Message;
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);  //make sure to free the memory
        }
    }
}

وتحرير:

ويمكن أن تكون مشكلتك مشكلة مع الفرق بين <لأ href = "http://msdn.microsoft.com/en-us/library/aa366769٪28VS.85٪29.aspx" يختلط = "نوفولو noreferrer" > استراتيجيات التعبئة . لقد تحديث تعريف البنية.

وتحرير: أنا إعادة كتابة هذا الحل الكامل

.

وأخذت كل من كل من C ++ و C # رمز، أسقطته في حل وتجلى ذلك - ويعمل كل شيء بالنسبة لي. لم يكن لديك محددة الاشياء تعيين ذاكرة لذلك أنا محاكاة ذلك عن طريق ملء pBuf مع بعض البيانات وهمية، وكل شيء يجعل من العودة بشكل جيد؛ كل من قيمة الإرجاع والبنية الإخراج هي الصحيحة.

ويمكن أن يكون شيئا خاطئا مع إعدادات مشروعك؟ هذا يبدو سخيفا، ولكن ذكرتم تشغيل وتصحيح رمز unamnaged. كنت بناء الحق DLL؟

ما أنت تحاول أن تفعله هو ممكن، ولكن أعتقد أنك حل مشكلة خاطئة.

لماذا لا يقرأ ذاكرة ملف معين مباشرة من C #؟ نلقي نظرة على Winterdom.IO.FileMap

ولقد استخدمت وانها تعمل بشكل جيد.

MemoryMappedFile file = MemoryMappedFile.Open(FileMapRead, name);
using (Stream stream = memoryMappedFile.MapView(MapAccess.FileMapAllAccess, 0, length))
{
    // here read the information that you need   
}

مع أنه لا تنتهي - لا يزال لديك لتحويل منطقة عازلة بايت إلى البنية، ولكن كنت فقط على الجانب المدارة وسيكون من الأسهل

والذي تخصيص الذاكرة للبنية؟ لا يمكنك حذف ذاكرة الأم من كومة إدارتها. عموما DLL الأصلي ينبغي أن تخصص على كومة COM إذا كان يتوقع المتصل لتحرير الذاكرة، أو العودة واجهة رد مثل IMalloc لتحرير الذاكرة العودة. وهذا يعني أنك في حاجة الى الحصول على عنوان الذاكرة النتيجة كما IntPtr واستخدام System.Runtime.InteropServices.Marshal لنسخ البيانات من مواطن إلى كومة تمكن (قد يكون لهيكل) قبل تحرير الذاكرة.

وتحرير للتوقيع وظيفة تحديث: استخدام العام ثابت خارجي كثافة العمليات getStatus (الناتج SYSTEM_OUTPUT المرجع)؛ لم يتم تخصيص على كومة COM في وظيفة الأم، وذلك انطلاقا لا لزوم لها.

هل تعتبر إضافة التجمع C ++ / CLI لمشروعك؟ هذا هو وسيلة سهلة للغاية وقوية لردم الهوة بين التعليمات البرمجية المدارة وغير المدارة. أنا استخدامها الكثير جدا نفسي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top