2

アンマネージ C++ dll を呼び出す C# プロジェクトがあります。ラッパーとほとんどの呼び出しは正常に機能しているので、すべてがうまく結合する方法の基本的な構造があることはわかっていますが、適合する特定の呼び出しが 1 つあります。API 呼び出しには、構成データのリストを含む構造体へのポインターが必要です。

呼び出しは次のとおりです。

m_status = m_XXXXBox.SetConfig(m_channelId, ref SCONFIG_LIST);

SCONFIG_LIST は、データを含む構造体です...

この問題は特に SCONFIG_LIST に関連しています

この API の仕様から直接引用したドキュメントを次に示します。

Points to the structure SCONFIG_LIST, which is defined as follows:
typedef struct
{
unsigned long NumOfParams; /* number of SCONFIG elements */
SCONFIG *ConfigPtr; /* array of SCONFIG */
} SCONFIG_LIST
where:
NumOfParms is an INPUT, which contains the number of SCONFIG elements in the array
pointed to by ConfigPtr.
ConfigPtr is a pointer to an array of SCONFIG structures.
The structure SCONFIG is defined as follows:
typedef struct
{
unsigned long Parameter; /* name of parameter */
unsigned long Value; /* value of the parameter */
} SCONFIG

C# で定義した 2 つの構造は次のとおりです。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig
{
    public int Parameter;
    public int Value;
}



[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
    public int NumOfParams;
    // public List<SConfig> sconfig = new List<SConfig>();  // This throws compile time error
    public List<SConfig> sconfig;
}

構造体にフィールド初期化子を含めることができないことは知っていますが、構造体の sconfig を外部で初期化する方法を理解できないようです...呼び出し元のメソッドのスニペットを次に示します。

      SConfig_List myConfig = new SConfig_List();
      SConfig configData = new SConfig();

      configData.Parameter = 0x04;
      configData.Value = 0x10;
      myConfig.NumOfParams = 1;
      myConfig.sconfig.Add(configData);

これにより、実行時に「オブジェクト参照がオブジェクトのインスタンスに設定されていません」というエラーがスローされます.sconfigが初期化されていないため、このエラーを理解しています.

したがって、私の次の考えは、これを回避することでした。このように SCONFIG_LIST 構造体を作成するだけです (内部にリストはありません)。これの理由は、オブジェクトを初期化する必要がなくなり、 NumOfParams > 1 ではなく、NumOfParams = 1 で dll を作成し、構造体データを介して dll をループさせます。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SConfig_List
{
    public int NumOfParams;
    public SConfig sconfig;
}

そして、これが私がメソッドを呼び出した方法です

configData.Parameter = 0x04;
configData.Value = 0x10;
myConfig.NumOfParams = 1;
myConfig.sconfig.Parameter = configData.Parameter;
myConfig.sconfig.Value = configData.Value;

m_status = m_XXXXBox.SetConfig(m_channelId, ref myConfig);

これにより、この時点までのエラーが解消され、dll を呼び出す実際のメソッドに戻りました。

public XXXXErr SetConfig(int channelId, ref SConfig_List config)
{
    unsafe
    {
        IntPtr output = IntPtr.Zero;
        IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
        Marshal.StructureToPtr(config, input, true);

        XXXXErr returnVal = (XXXXErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
        return returnVal;
    } 
}

これはエラーなしですべての初期セットアップを通過しますが、実際にdllを呼び出そうとすると、エラーが発生します:保護されたメモリを読み書きしようとしました. これは多くの場合、他のメモリが破損していることを示しています。

この投稿には複数の問題があると確信しているため、何を質問すればよいか正確にはわかりませんが、正しい軌道に乗せるためのアイデアはありますか?

この時点で非常に多くのことを試しましたが、途方に暮れており、方向性が必要です。私は「私のためにこれを行う」タイプの回答を探しているのではなく、説明と、これを実行するためのいくつかの指針を探しています。すべてのものと同様に、タスクを達成する方法は複数あると確信しています。おそらく機能する方法ですが、良い形式ではなく、「より良い練習」になる可能性のあるより複雑な方法です。

すべての提案/コメントは大歓迎です。このなぞなぞを解決するのに必要な関連データを除外した場合は、私に知らせてください。できる限り提供します。


これまでの回答に感謝したい。私はこれを自分で解決しようとあらゆる組み合わせを試してきましたが、これまでのところ運がありません. ただし、機能しない方法がかなり見つかりました:-)

「安全でない」「MarshalAs」、「StructLayout」、および Web で見つけた他のいくつかのもののさまざまな組み合わせを試しましたが、今は慈悲を求めています。

このアンマネージ dll への他のいくつかの呼び出しを正常に実装しましたが、それらはすべて単純な整数ポインターなどを使用しています。別の構造体の配列を含む構造体にポインターを渡すことが問題です。私の元の質問の一番上を見ると、dll のドキュメントと、それがどのように構造化されているかを見ることができます。戻り値はありません。この dll を介して構成設定をデバイスに渡そうとしているだけです。

プロジェクト全体のフレームワークを投稿して、このプロセスを通じて誰かに手を握ってもらい、将来、この種の問題を解決しようとしている他の人を助けることができるようにします.

Wrapperのスケルトンはこちら(全ての機能が表示されているわけではありません)

using System;
using System.Runtime.InteropServices;

namespace My_Project
{
    internal static class NativeMethods
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);
    }

    internal class APIDllWrapper
    {
        private IntPtr m_pDll;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate int APIIoctl(int channelId, int ioctlID, IntPtr input, IntPtr output);
        public APIIoctl Ioctl;
        //extern “C” long WINAPI APIIoctl
        //(
        //unsigned long ChannelID,
        //unsigned long IoctlID,
        //void *pInput,
        //void *pOutput
        //)

        public bool LoadAPILibrary(string path)
        {
            m_pDll = NativeMethods.LoadLibrary(path);

            if (m_pDll == IntPtr.Zero)
                return false;

            pAddressOfFunctionToCall = NativeMethods.GetProcAddress(m_pDll, "APIIoctl");
            if (pAddressOfFunctionToCall != IntPtr.Zero)
                Ioctl = (APIIoctl)Marshal.GetDelegateForFunctionPointer(
                                                                                        pAddressOfFunctionToCall,
                                                                                        typeof(APIIoctl));
            return true;
        }

        public bool FreeLibrary()
        {
            return NativeMethods.FreeLibrary(m_pDll);
        }
    }
}


And Here is the class that defines the hardware I am trying to communicate with
    namespace My_Project
{
    public class APIDevice
    {
        public string Vendor { get; set; }
        public string Name { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }
}


Interface
    using System.Collections.Generic;

namespace My_Project
{
    public interface I_API
    {
        APIErr SetConfig(int channelId, ref SConfig_List config);
    }
}

API コードを含む実際のクラス - これがエラーの場所です。現在 IntPtrs を持っている方法は正しくありません - しかし、これは私がやろうとしていることを示しています

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace My_Project
{
    public class API : I_API
    {
        private APIDevice m_device;
        private APIDllWrapper m_wrapper;

        public APIErr SetConfig(int channelId, ref SConfig_List config)
        {
            IntPtr output = IntPtr.Zero;
            IntPtr input = Marshal.AllocHGlobal(Marshal.SizeOf(config));
            Marshal.StructureToPtr(config, input, true);

            APIErr returnVal = (APIErr)m_wrapper.Ioctl(channelId, (int)Ioctl.SET_CONFIG, input, output);
            return returnVal;             
        }       
    }
}

これは、私が使用している構造体の定義を含むクラスです

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace My_Project
{
    public enum APIErr
    {
        STATUS_NOERROR = 0x00,
        ERR_BUFFER_EMPTY = 0x10,
        ERR_BUFFER_FULL = 0x11,
        ERR_BUFFER_OVERFLOW = 0x12
    }

    public struct SConfig
    {
        public int Parameter;
        public int Value;
    }

    public struct SConfig_List
    {
        public int NumOfParams;
        public SConfig[] sconfig;

        public SConfig_List(List<SConfig> param)
        {
            this.NumOfParams = param.Count;
            this.sconfig = new SConfig[param.Count];
            param.CopyTo(this.sconfig);
        }
    }
}

そして最後に、ラッパーを介して dll を呼び出す実際のアプリケーション

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using My_Project;

namespace Test_API
{
    public class Comm
    {
        private I_API m_APIBox;
        APIErr m_status;
        int m_channelId;
        bool m_isConnected;

        public Comm(I_API apiInterface)
        {
            m_APIBox = apiInterface;
            m_isConnected = false;
            m_status = APIErr.STATUS_NOERROR;
        }

        public bool ConfigureDevice()
        {
            SConfig tempConfig = new SConfig();

            tempConfig.Parameter = 0x04;
            tempConfig.Value = 0x10;
            SConfig_List setConfig = new SConfig_List(tempConfig);

            m_status = m_APIBox.SetConfig(m_channelId, ref setConfig);
            if (m_status != APIErr.STATUS_NOERROR)
            {
                m_APIBox.Disconnect(m_channelId);
                return false;
            }
            return true;
        }
    }
}
4

3 に答える 3

0

リストを初期化するには、次の行を追加するだけです:

myConfig.sconfig = new List<SConfig>()

要素を追加する前に。

于 2013-10-25T17:37:37.027 に答える
-1

経験不足で間違った質問をしていたので、別のスレッドを立てました

実用的なソリューションはこちら

構造体と IntPtr のマーシャリング配列

助けてくれてありがとう

-リー

于 2013-11-04T04:18:27.850 に答える