пятница, декабря 07, 2012

Немножко про unit tests


"А по-моему, они одинаковы"

понедельник, декабря 03, 2012

Что, мальчики, не получается?

Несчастные вы бедолажки в Эпле... Ну, кое-как портировали уродца своего на Windows, ладно. Он, правда, почти год после этого не понимал русский язык, ну, тоже спишем на вашу экстраординарную нетрадиционность. Но вы же всё-таки инженеры, нет? Я там не говорю, что все как один состоите в IETF, но хотя бы общие представления о платформе, на которую делает порт, надо иметь!

Safari это поганое: DDE не поддерживает, с COM не знакомо, требованиям Windows по проектированию оконных интерфейсов не удовлетворяет.

Что же так слабо? Не умеете? Не получается? Ай-яй-яй. Да... это вам не подсветочку вокруг кнопки рисовать. Срамота!

Простой пример DDE и C

Потребовалось получить URL из Firefox. FF вообще написан чудным образом, весьма далёким от того, как надо писать программы для Windows. Поэтому пройти по иерархии окон и получить текст окна - невозможно. Зато возможно получить нужное через DDE. О командах, которые можно использовать в DDE для получения информации из браузера, примерно можно почитать тут: http://support.microsoft.com/kb/160957 Не знаю, какие именно из них поддерживает FF, но получить URL таким образом можно. Для этого нужно использовать команду WWW_GetWindowInfo.

Несмотря на то, что DDE уже старенькая технология (в этом году ей исполнилось 22 года), она по-прежнему работает и неплохо. Её суть примерно в следующем.
  1. Клиент-серверная архитектура DDE подразумевает, что одна сторона (клиент) будет слать запросы, а другая (сервер) - их выполнять. Используется парадигма "разговора", а именно: вопрос-ответ.
  2. Клиент может задавать темы "разговора" (не знаю, для чего это нужно). Выглядит, в переводе на человеческий язык это так. Клиент говорит: "Эй, ты, сервер по имени Firefox, хочу с тобой потолковать насчёт WWW_GetWindowInfo. Ты как, в настроении?". Сервер отвечает: "Ну, давай. Дескриптор этого разговора равен 1232134. Если дальше будешь спрашивать про своё - не забывай мне напоминать, о чём толкуем, подставляя это число как идентификатор. А то много вас тут лезет поговорить, не уследишь по-другому"
  3. Если сервер согласился поддержать "разговор" (выдав HCONV не равный NULL), дальше можно задавать ему вопросы. Для каждой темы, на которую можно клиент может потолковать с сервером есть допустимый "список вопросов". В нашем случае для темы WWW_GetWindowInfo браузер предполагает, что дальше его будут спрашивать про URL и WindowText. Задать вопросы про прогресс закачки файла в разговоре, посвящённом WWW_GetWindowInfo, не выйдет.
  4. Задавание вопроса заключается в последовательном вызове 3х функций:
    1. DdeCreateStringHandle - создаёт DDE-описатель для "вопроса", такого как "URL"
    2. DdeClientTransaction - начинает транзакцию передачи данных. Можно это себе представлять как фразу "Эй, сервер, хочу тебя спросить... Ну, вот мы говорим, про WWW_GetWindowInfo. Так что там с URL?"
    3. DdeGetData - это вызывается для получения ответа на свой вопрос. В ответ на этот вызов DDE скопирует данные в переданный вами буфер и в нём будет искомый ответ.
Таким образом, для получения URL из FF надо:
  1. Инициализировать DDE, чтобы можно было дальше вызывать его функции
  2. Установить соединение с сервером DDE, каковым является FF. При установке соединения задать тему разговора WWW_GetWindowInfo
  3. Спросить FF про URL
 
Подключаемся к FF (никакой обработки ошибок и дурацкие имена для понятности)

TCHAR ddeServer[] = L"Firefox";
TCHAR conversationTopic[] = L"WWW_GetWindowInfo";
TCHAR topicSpecificCommand[] = L"URL";

DWORD idInst=0;
DdeInitialize(&idInst, (PFNCALLBACK)DdeCallback, APPCLASS_STANDARD | APPCMD_CLIENTONLY, 0 );

HSZ appStringHandle, topicStringHandle;
HCONV conversationHandle;
appStringHandle = DdeCreateStringHandle(idInst, ddeServer, 0);
topicStringHandle = DdeCreateStringHandle(idInst, conversationTopic, 0);

conversationHandle = DdeConnect(idInst, appStringHandle, topicStringHandle, NULL);
DdeFreeStringHandle(idInst, appStringHandle);
DdeFreeStringHandle(idInst, topicStringHandle);
Спрашиваем у FF, какой URL у его активной закладки активного экземпляра

HSZ commandStringHandle = DdeCreateStringHandle(idInst, topicSpecificCommand, 0);
HDDEDATA hData = DdeClientTransaction(NULL,0,conversationHandle,commandStringHandle, CF_TEXT, XTYP_REQUEST, 5000, NULL); // CF_TEXT для получения однобайтовой строки
// CF_UNICODETEXT - для юникодной

char res[255] = {0};
DdeGetData(hData, (unsigned char*)res, 255, 0);
printf("%s\n", szResult);
Всё. В res лежит URL закладки, с которой пользователь работал последней в экземпляре FF с которым пользователь работал последним.

P.S. Само собой, не забывайте проверять ошибки после вызовов. А то всякое может быть. Например, нету у вас запущенного экземляра FireFox, вы не знаете и стремитесь с ним поговорить. Не выйдет! :)

P.P.S. Удаляйте всё, что создаёте: DdeCreateStringHandle -> DdeFreeStringHandle, DdeCreateDataHandle -> DdeFreeDataHandle

P.P.P.S. То же самое работает и для Opera, только содержимое ddeServer должно быть "Opera"

суббота, октября 13, 2012

Вот и случилось

Microsoft утратила технологическое лидерство. Да, миллиард десктопов, да, сотни миллионов копий Windows, но эта эпоха начала заканчиваться.

Увы.

понедельник, сентября 10, 2012

Автоматы по продаже билетов на аэроэкспресс придумал и установил идиот. Потому что:
1. Нет надписей о принимаемых номиналах
2. Купюры, которые (по непонятным причинам) не могут быть приняты - выбрасываются на пол. Нет ни тормозящих механизмов, ни лотка-ограничителя.
3. Способ покупки нескольких билетов настолько непонятен, что им воспользоваться обычному человеку невозможно.

И я вспоминаю свои впечатления от первого полёта на Боинге. После него у меня не осталось вопросов, почему советские ТУ-154 и прочие канули в Лету. Они внутри сделаны не для пассажиров. Двигатели, планер, авионика - меня они не интересуют в той степени в какой ширина кресел, форма подголовников и удобство раскрытия столика.

Хотя, если не летать на нём, то мне очень нравится и силуэт, и компоновка ТУ-154.

вторник, августа 14, 2012

The task category text is missing in event viewer in Vista+

I bet you see the text because you can't see your category string for your event that defined inside EXE file. Calm down. I know how to resolve the issue. There are a few steps to go:

1. Run regedit, open your event source at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application\SRC_NAME
2. Right click it and select Permissions.
3. Give READ access to Everyone. This will allow system to determine where the strings are located exactly. Without this action you won't see messages for events if running your program in Vista+
4. Start the Windows Explorer up and locate your EXE
5. Again, right click it, then Properties->Security and allow Everyone to READ the exe. This allows event subsystem to read category definitions to be added into Event Viewer

So, you need only 2 additional permissions to see both message text and task category name for the event:
- READ permission for Event Source in the Registry
- READ permission for your executable containing task category definitions

понедельник, августа 13, 2012

Совместимость поддержана на отлично!

Дано. Windows 7. По всем правилам написанный файл сообщений .mc. Полученные .res-файлы корректно встраиваются в итоговый файл. Источник событий корректно инициализирован в реестре. А вот при попытке вызвать ReportEvent получаем в журнале Windows вот такое:

"the message resource is present but the message is not found in the string/message table"

Почему? Ответ, как оказалось, в Windows 7. Если приложение не запущено от имени администратора и не имеет прав читать реестр, то источник сообщений (пусть он хоть сто раз описан в реестре) будет проигнорирован. Вот так-то.

Настоящие молодцы. Прахрамисты!

понедельник, июня 04, 2012

Зачем убрали кнопку "Menu" в Android

Была аппаратная кнопка menu. Ей очень удобно пользоваться: нажал и получил контекстные команды. Вызвал команды и опять экран не содержит мусора. Теперь нам "рекомендуют" (в терминах: шевелитесь, а то скоро вообще исчезнет, физически) использовать actionbar. Ну смысл-то какой? У меня E730. 800x480. Ну, давайте займём сверху 100 точек под actionbar. Потом ещё 100 под subactionbar. Будет очень унифицированно. А пользоваться-то как? Мобильные устройства должны иметь минимум управляющих элементов на экране. Для этого должны применяться gestures и, как максимум, полупрозрачные кнопки. Забивать экран статическими элементами, которые кто-то то ли вызовет, то ли нет - это кретинизм. Это глумление над пользователем. И это даром не проходит.

Вот что бывает, когда концепцию развития интерфейса доверяют идиотам. Видно, что кто-то там в этом гугле в отделе UI был с мозгами, а потом ушёл. Ну, что ж: присаживайтесь поудобнее: дурачьё хочет показать нам Word 2.0 for Windows, только для Android. Но не в 1993м году, а в 2012м. Больше! Больше! Больше грузных кнопок! Давайте загадим уже этому дураку-покупателю весь экран!

четверг, мая 31, 2012

Ускорение работы WCF-сервиса

Вчера были тестовые запуски сервиса, хостящегося (теперь) на IIS. Один вызов занимал 30 секунд (или около того). От великого огорчения стал внимательно читать документацию.

Вычитал, что WCF при отсутствии частно-определённой привязки сначала лезет исследовать прокси-серверы в сети. Никаких таких устройств у нас нет, потому до таймаута сервис просто "ждал".

Но стоило определить свою привязку как дело пошло. 4 строчки в конфиге дали ускорение работы в несколько сотен тысяч раз.

А всё из-за этого: usedefaultwebproxy="false"

понедельник, мая 28, 2012

Как передать массив из Android в WCF?

Ну, то что из Android практически невозможно передать сложный тип веб-сервису на WCF, понятно. Ведь кроме Microsoft для Microsoft ничего не существует. Тут даже и вопросов никаких не возникает: "так надо". А вот почему нужны такие пляски с бубнами для передачи простого массива из приложения на Android в WCF-сервис, непонятно. Этот ваш любимый ksoap2 тоже не особо-то помогает в виде "из коробки". Приходится смотреть логи, сравнивать сообщения и подстраиваться. А что, замечательный способ разработки. Если некуда спешить.

Короче.

Полностью завершённый код C#, делающий то, что нужно
class Program
{
 static void Main(string[] args)
 {
  ServiceReference1.ServiceClient sr = new ServiceReference1.ServiceClien();
  sr.GetSomething(new []{10, 20, 30});
  sr.Close();
 }
}
Вызывает и работает.

Теперь то же самое из Android. Просто так передать массив не получится, его надо передавать изощрённо (извращённо?). Потому что в противном случае WCF не сможет десериализовать сообщение.

Делай раз
1. Сделать вид, что передаём не какой-то там паршивый int[], но настоящий Объект! настоящего Класса! Со всеми атрибутами такого. А именно
2. Полный код "взрослого класса"
public class MyIds extends Vector implements KvmSerializable {
    private static final long serialVersionUID = -1143242342342342265L;

    @Override
    public Object getProperty(int i) {
        return this.get(i);
    }
    @Override
    public int getPropertyCount() {
        return this.size();
    }
    @Override
    public void setProperty(int i, Object o) {
        this.add(Integer.parseInt(o.toString()));
    }
    @Override
    public void getPropertyInfo(int i, Hashtable hashtable, PropertyInfo propertyInfo) {
        propertyInfo.name = "int";
        propertyInfo.namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";        propertyInfo.type = PropertyInfo.INTEGER_CLASS;
    }
}

Самый цимес в жирной строчке. Хотите неприятно провести время - уберите её.

После этого вызов метода GetSomething в Android становится очень простым
3. public static int getSomething(int[] args)
    {
        final String MethodName = "GetSomething";
        final String SoapAction = "http://mydomain.ru/MyService/IInterface/GetSomething";
        SoapObject req = new SoapObject(Namespace, MethodName);

        MyIds myIds = new MyIds();
        for(Integer i : args)
            myIds.add(i);
       
        PropertyInfo pi = new PropertyInfo();
        pi.setName("WCFParam");
        pi.setValue(myIds);
        pi.setType(myIds.getClass());
        req.addProperty(pi);
        SoapSerializationEnvelope se = getEnvelope(req);
        SoapPrimitive p = initializeTransportAndCall(SoapAction, se);

        if (null == p)
            return 0;

        return Integer.parseInt(p.toString());
    }
Всё. WCF SOAP WS принимает массив целых чисел. Ну и дальше дело техники.

среда, февраля 08, 2012

MVC, Facebook и McDonald's

Будучи погружён в пучины анонимных типов, вызовов ajax, разделение ответственности, оптимизацию модели, linq и многопоточность Windows одновременно, не могу, тем не менее, не заметить, что Facebook выходит на IPO.

Ну, выходит и выходит, казалось, бы. Но интересно замечание одного главного редактора одного русского журнала: "Вот вы знаете, сейчас FaceBook выходит на IPO и капитализация компании оценивается в 100 миллиардов долларов. Я замечу, у нас вот об этом материал Романа Карачинского в номере, который там открыл стартап в Кремниевой долине, так вот капитализация Макдональдса – 101 миллиард. Вот что такое интеллект, понимаете? И они ищут, конечно, поддержки, там, основателей FaceBook, Twitter’а и так далее. Это я понимаю, это люди, которые создают фантастический интеллектуальный продукт."

Что я хотел бы сказать. Несмотря на наличие у меня там аккаунта, я всё равно не очень-то понимаю, в чём изюм фейсбука. Что там может стоить хотя бы сто миллионов. Поясню. Из чего составлена стоимость акций Бигмачечной - мне прекрасно понятно. Основные средства и гарантия гастрономического удовлетворения клиентов. А Фейсбук? Что лежит в основе столь оптимистических оценок? Всё та же тупая модель: "у нас много хомяков, мы будем им показывать рекламу - вот вам и кэшфлоу"? Остроумно. Было в 99м году, с Гуглом. Но сейчас как бы уже не того...

Хорошо, что Альбац в курсе что там творится, но называть Фейсбук "фантастическим интеллектуальным продуктом" и считать нормальным 100 миллиардов за это - это уж чересчур. Гомеопатическое интернет-средство от неврозов должно стоить подешевле.