Вернуть строку из неуправляемой DLL в C # - PullRequest
0 голосов
/ 11 февраля 2012

Извините, что спрашиваю об этом здесь, так как я уверен, что на него нужно ответить "там", но я застрял на этом уже несколько месяцев, и ни одно из найденных решений не помогло я.

У меня работает следующий код VB:

Declare Function DeviceSendRead Lib "unmanaged.dll" (ByVal sCommand As String, ByVal sReply As String, ByVal sError As String, ByVal Timeout As Double) As Integer

Dim err As Integer
Dim outstr As String
Dim readstr As String
Dim errstr As String

outstr = txtSend.Text
readstr = Space(4000)
errstr = Space(100)

Timeout = 10

err = DeviceSendRead(outstr, readstr, errstr, Timeout)

и я пытаюсь реализовать это в проекте C #. Лучший эквивалент, который мне удалось найти:

    [DllImport("unmanaged.dll")] public static extern int DeviceSendRead(String outstr, StringBuilder readstr, StringBuilder errstr, double Timeout);

    int err;
    StringBuilder readstr = new StringBuilder(4000);
    StringBuilder errstr = new StringBuilder(100);

    err = DeviceSendRead(txtSend.Text, readstr, errstr, 10);

Однако, когда я запускаю это, приложение зависает, и я должен принудительно завершить его. Экспериментируя с ref и out, мне иногда удавалось заставить его зависать, а не зависать, но единственный «прогресс», которого я достиг, - заменить вызов функции dll на:

    DeviceSendRead(txtSend.Text, null, null, 10);

Это предотвращает сбой, но, конечно, ничего не делает (что я могу обнаружить). Поэтому я предполагаю, что именно способ передачи двух возвращаемых строковых параметров вызывает проблему. Если кто-нибудь может подсказать, что я делаю неправильно, я был бы очень рад это услышать. Спасибо.

Ответы [ 5 ]

1 голос
/ 20 февраля 2012

Я получил ответ, который я запишу здесь для полноты, с благодарностью всем тем, кто указал мне правильное направление.

Согласно этому посту в другом месте, использование .NET Reflector в аналогичном коде VB предполагает необходимость использования типа string вместо моего StringBuilder, как предложено здесь Алексом Мендесом, JamieSee и Austin Salonen, вместе с явным маршалингом, предложенным Nanhydrin, но с использованием неуправляемого типа VBByRefStr вместо AnsiBStr. Последний ключ к загадке заключается в том, что параметр строки необходимо передать по ссылке, используя ключевое слово ref.

Я могу подтвердить, что это работает, и что мой последний рабочий код C #, таким образом:

    [DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
    public static extern short DeviceSendRead(
        [MarshalAs(UnmanagedType.VBByRefStr)] ref string sCommand,
        [MarshalAs(UnmanagedType.VBByRefStr)] ref string sReply,
        [MarshalAs(UnmanagedType.VBByRefStr)] ref string sError,
        double Timeout);

            short err;
            string outstr = txtSend.Text;
            string readstr = new string(' ', 4000);
            string errstr = new string(' ', 100);

            err = DeviceSendRead(ref outstr, ref readstr, ref errstr, 10);

Я надеюсь, что это полезно для других, сталкивающихся с подобной проблемой.

0 голосов
/ 12 февраля 2012

Маршаллинг по умолчанию для строк
Маршаллинг по умолчанию

Возможно, вам нужно быть более конкретным в объявлении dllimport и добавить некоторые атрибуты MarshalAs, еслиу вас есть более подробная информация о том, какой тип строк ожидает вызываемая функция (Ansi, Unicode, null terminated и т. д.), и это поможет.Фактически, ожидание того, что строки с нулевым символом в конце могут объяснить, почему он зависает, а не выдает ошибку.

[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]  
public static extern int DeviceSendRead(string outString, [MarshalAs(UnmanagedType.AnsiBStr)]string readString, string errorString, double timeout);

Вам также может понадобиться явно указать, что ваши параметры являются входными, выходными или и теми, и другими, используя атрибуты параметров [In, Out].

0 голосов
/ 11 февраля 2012

Попробуйте это:

[DllImport("unmanaged.dll")]
public static extern int DeviceSendRead(string outString, string readString, string errorString, double timeout);


int err;
string outstr;
string readstr;
string errstr =
outstr = txtSend.Text;
readstr = new string(' ', 4000);
errstr = new string(' ', 100);
double timeout = 10;
err = DeviceSendRead(outstr, readstr, errstr, timeout);
0 голосов
/ 11 февраля 2012
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);

Вы не можете собрать StringBuilder здесь.Есть несколько правил, которым нужно следовать для сортировки StringBuilder (см. CLR Inside Out: маршалинг между управляемым и неуправляемым кодом ):

StringBuilder и Marshaling

Маршалер CLRобладает встроенными знаниями о типе StringBuilder и рассматривает его иначе, чем другие типы.По умолчанию StringBuilder передается как [InAttribute, OutAttribute].StringBuilder является особенным, потому что у него есть свойство Capacity, которое может определять размер требуемого буфера во время выполнения, и его можно динамически изменять.Следовательно, во время процесса маршалинга CLR может закрепить StringBuilder, напрямую передать адрес внутреннего буфера, используемого в StringBuilder, и разрешить изменение содержимого этого буфера с помощью встроенного кода.

Чтобы использовать все преимуществаStringBuilder, вам нужно будет соблюдать все эти правила:

1.Не передавать StringBuilder по ссылке (используя out или ref).В противном случае CLR будет ожидать, что сигнатура этого аргумента будет wchar_t ** вместо wchar_t *, и не сможет закрепить внутренний буфер StringBuilder.Производительность будет значительно снижена.

2.Используйте StringBuilder, когда неуправляемый код использует Unicode.В противном случае CLR придется сделать копию строки и преобразовать ее между Unicode и ANSI, что приведет к снижению производительности.Обычно вы должны маршалировать StringBuilder как LPARRAY символов Unicode или как LPWSTR.

3. Всегда заранее указывайте емкость StringBuilder и убедитесь, что емкость достаточно велика для хранения буфера.Лучшая практика на стороне неуправляемого кода - принять размер строкового буфера в качестве аргумента, чтобы избежать переполнения буфера.В COM вы также можете использовать size_is в IDL для указания размера.

Правило 3, похоже, здесь не выполняется.

0 голосов
/ 11 февраля 2012

Попробуйте это как эквивалент:

string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);
...